Doppelt gemoppelt hält besser oder wie man zwei Computer miteinander verbindet

Jetzt, wo die Megas so richtig in die heimischen Computerstuben Einzug gehalten haben, finden sich sicherlich bei vielen ST-Freunden zwei STs.

Aber nicht nur Besitzer zweier STs sollen angesprochen werden, sondern auch alle diejenigen, die mal zwei Rechner miteinander verbinden möchten.

Das hier vorgestellte Programm benutzt den MIDI-Ein- und Ausgang. Außerdem ist das Programm erweiterungsfähig, es lassen sich also gegebenenfalls noch viele Routinen implementieren.

Zu den Hardware-Voraussetzungen zählen - wie zu erwarten - zwei ATARI STs und zwei Stereo-Verbindungskabel. Das Programm wurde mit Megamax Modula 2 geschrieben und mit einem Mega ST 2 (mit TOS vom 22.4.’87) und einem 520 ST+ (mit TOS vom 6.2.’86) getestet, wobei sich dort keine großen Schwierigkeiten zeigten.

Das Programm - oder besser Accessory - “MiKom” hat nur eine Funktion, und die ist auch noch vergleichsweise bescheiden. Es kann eine kleine Nachricht von einem ST zum anderen und umgekehrt übermittelt werden.

Bevor wir überhaupt etwas ausprobieren können, muß das Programm abgetippt und übersetzt sein. Das Accessory und das dazugehörige Resourcefile werden auf die Bootdisketten der beiden STs kopiert. Die beiden Rechner werden abgeschaltet und jeweils MIDI-Out des einen mit MIDI-In des anderen über je ein Stereo-Übertragungskabel verbunden. Nun die Rechner wieder einschalten und mit den neuen Bootdisketten starten. Zu diesem Zeitpunkt stehen die beiden Rechner kommunikationsbereit vor uns (s.o.).

Um die Kommunikation aufzunehmen, wird der Accessory-Eintrag “MiKom” bei einem der beiden STs angeklickt (Bild 2), worauf eine Dialogbox erscheint. Hier kann man auswählen, was man machen möchte. Neben dem lebenswichtigen “Quit” kann eine Nachricht gesendet oder empfangen werden. Zuerst senden wir natürlich. Als nächstes erscheint eine zweite Dialogbox mit einer edierbaren Zeile und den Buttons “Quit” und Nachricht senden. Da wir senden wollen, geben wir zuerst eine passende Nachricht ein und klicken dann “Nachricht senden“ an. Auf unserem Bildschirm verschwindet die Dialogbox, und der Monitor sieht aus, als wäre nichts geschehen.

Bild 2: So wird MiKom aufgerufen

Entweder passiert nichts, und nach fünf Sekunden erscheint die Warnmeldung “Timeout“, oder auf dem anderen Rechner wird eine Alertbox dargestellt, die darüber informiert, daß eine Nachricht angekommen ist. Möchte man sie sogleich betrachten, so muß “ja“ angeklickt werden, sonst “nein“. Für den Fall, daß “ja“ gewählt wurde, erscheint die bekannte Sende-Dialogbox mit der Nachrichtenzeile des anderen ST!!! Natürlich kann sofort eine Nachricht zurückgeschickt werden, sonst sollte “Quit“ gewählt werden. Im Fall “nein“ verschwindet die Alertbox einfach wieder. Die Nachricht kann man sich jederzeit ansehen, wenn man den Accessory-Eintrag auswählt und “Nachricht senden/empfangen“ anklickt.

Zur Bedienung war das schon alles. Vor der Zerstörung des Bildschirmaufbaus braucht man keine Angst zu haben, denn in der Dialog-Routine ist eine eigenständige Restauration vorgesehen.

Nun zu dem, was für den Programmierer interessant ist. Das Modul “DialogHandler“ (Listing 2a und 2b) soll nicht ausführlich erklärt werden, denn “DoDialog“ stammt ursprünglich aus einem anderen Listing, welches zum Zeitpunkt der Veröffentlichung dieses Programms schon erschienen sein sollte. “DoDialog“ wurde lediglich in ein Extra-Modul ausgelagert und minimal verändert.

Die prinzipielle Funktionsweise soll jedoch trotzdem kurz erläutert werden. Normalerweise wird für eine Dialogbox ein Bildschirmbereich reserviert, die Dialogbox ausgegeben, der Dialog durchgeführt und anschließend der reservierte Bildschirmbereich wieder freigegeben. Dazu dienen die AES-Routinen FORM_DIAL, FORM_DO und OBJC_DRAW. FORM_DIAL ist dazu da, den Bildschirmbereich zu reservieren und wieder freizugeben. Bei Freigabe wird jedoch an das Programm, dessen Bildschirmbereich zerstört wurde, eine Meldung geschickt. Eine selbständige Restauration findet nicht statt.

"DoDialog" führt den vollständigen Dialog durch, rettet jedoch den Bildschirmausschnitt vorher und übernimmt auch die Restauration. Wofür nun der ganze Aufwand? Es gibt ein paar Programme, die keine Accessories zulassen, aber trotzdem Events verwenden. Auch bei solchen Programmen kann eine Meldung von MiKom erscheinen. Da MiKom Dialogboxen benutzt, könnte es passieren, daß graue Felder auf dem Bildschirm bleiben. Da jedoch "DoDialog" den Bildschirm wiederherstellt, werden die grauen Felder vermieden.

Die beiden anderen Routinen setzen bzw. lesen einen String aus einer Object-Struktur. Sie werden benötigt, um den Text der Nachrichtenzeile zu setzen und zu ermitteln.

Viel wichtiger ist der “MIDIHandler” (Listing 3a und 3b) und “MiKom” (Listing 1a, 1b und 1c).

Das Grundprinzip ist in den beiden C-Listings dargestellt (Listing 0a und 0b). Ein Rechner sendet 1-Byte-Zeichen zu dem anderen, der diese weiterverarbeitet. Die beiden Programme arbeiten natürlich noch nicht ganz so, wie man es sich wünschen würde, denn jede andere Arbeit wird dabei unterbrochen.

Die im MIDIHandler implementierten Routinen helfen schon eher weiter. WriteStringToMIDI und ReadStringFromMIDI senden bzw. empfangen Zeichenketten über die MIDI-Schnittstelle, die wir im folgenden kurz MIDI nennen. Analog arbeiten WriteToMIDI und ReadFromMIDI nur diesmal für einen einzelnen Character. MIDIReceived gibt TRUE zurück, falls mindestens ein Zeichen im MIDI-Empfangspuffer ist, ansonsten FALSE.

Bild 4: So muß die Dialogbox "Auswahl” konstruiert werden
Bild 3: Daten sind auf dem Rechner empfangen worden

Schließlich die Routinen SetTimeOut und GetTimeOut, die die TimeOut-Zeit setzen beziehungsweise abfragen. Die Zeit wird in Sekunden angegeben. Eine Timeout-Warnmeldung wird gegeben, falls ein Zeichen gesendet oder empfangen werden soll, aber innerhalb der Timeout-Zeit eben nicht gesendet oder empfangen werden kann. Die umfangreichsten Routinen aus diesem Modul sind WriteString-ToMIDI und ReadStringFromMIDI. Bevor wir jedoch zu deren Erklärung kommen, noch etwas zu den benutzten BIOS-Routinen.

    /** MIDI-Empfangsprogramm **/ 
    /* Entwickelt mit Turbo C. */

    #include <tos.h>
    #include <ext.h>

    main ()
    {
        char c;

        for (;;)
        {
            c=Bconin(3); 
            putch(c);
        }
    }

Listing 0a

    /**** MIDI-Sendeprogramm ****/

    /* Entwickelt mit Turbo C. */

    #include <tos.h>
    #include <ext.h>

    main ()
    {
        char c;

        for (;;)
        {
            c=getch(); 
            putch(c);
            Bconout(3,c);
        }
    }

Listing 0b

Die BIOS-Routinen teilen sich in Status- und I/O-Routinen auf. Status-Routinen sind Bconstat (Bios 1) und Bcostat (Bios 8). Bconstat stellt fest, ob ein Zeichen verfügbar ist. Für den MIDI-Port muß die Gerätenummer 3 als einziger Parameter übergeben werden. Falls ein Zeichen verfügbar ist, so erhalten wir eine -1 (TRUE) zurück, sonst eine 0 (FALSE). Etwas eigenartiger ist Bcostat. Hier wird zwar auch der MIDI-Port abgefragt, aber bei den Probeläufen mit MiKom zeigte diese Routine keinerlei nennenswerte Wirkung. Sie ist aber trotzdem in dem Modul MIDIHandler deklariert. Normalerweise sollte Bcostat auch die Gerätenummer 3 für MIDI übergeben werden können, aber die Gerätenummer soll mit der Nummer 4-Tastatur-Prozessor IKBD - vertauscht sein. Naja!

(*******************************************************************) 
(* MiKom - das MIDI-Kommunikationspr.für zwei Atari ST Version 0.0 *)
(* ----------------------------------------------------------------*)
(* Autor: Dietmar Rabich, Dövelingsweg 2, D-4408 Dülmen 27/09/88 *)
(* Entwickelt mit Megamax Modula 2 von Application Systems Heidelberg.*)
(*******************************************************************) 
(* H a r d w a r e - V o r a u s s e t z u n g e n :               *)
(* 2 ATARI ST und zwei Stereo-Verbindungskabel (DIN-Stecker)       *)
(*******************************************************************)

MODULE MiKom;

(*****************************************************)
(* * * * *      I  M  P  O  R  T  E          * * * * *)
(*****************************************************)

(* Resourcefile *)
                    IMPORT Mikomd;

(* AES-Routinen *)
FROM AESEvents      IMPORT MultiEvent,MessageBuffer,accOpen,EventSet,Event,
                           RectEnterMode,TimerEvent;
FROM AESForms       IMPORT FormAlert;
FROM AESMenus       IMPORT RegisterAcc;
FROM AESResources   IMPORT LoadResource,ResourceAddr,ResourcePart; 
FROM DialogHandler  IMPORT setTextString,getTextString,DoDialog;

(* allgemeine GEM-Routinen *)
FROM GEMEnv         IMPORT RC,InitGem,DeviceHandle,GemError;
FROM GEMGlobals     IMPORT PtrObjTree,PtrMaxStr,MButtonSet,GemChar,SpecialKeySet;

(* Graphik *)
FROM GrafBase       IMPORT Point,Rect;

(* MIDI-Routinen *)
FROM MIDIHandler    IMPORT WriteStringToMIDI,ReadString FromMIDI,
                           WriteToMIDI,ReadFromMIDI,MIDIReceived;

(* sonstige Routinen *)

FROM SYSTEM         IMPORT ADR;
FROM Strings        IMPORT String;
FROM FastStrings    IMPORT Length;

(*****************************************************)
(* * * * *     V  A  R  I  A  B  L  E  N     * * * * *)
(*****************************************************)

VAR GEMDevice       : DeviceHandle; (* Gerätekennung     *)
    ReturnButton,                   (* für Dialog        *)
    VoidC,                          (* diverse Zwecke    *)
    AccID           : CARDINAL;     (* Accessory-Kennung *)
    DialogBox,
    AuswahlBox      : PtrObjTree;   (* für Dialog        *)
    MessageArrive   : PtrMaxStr;
    InitOK          : BOOLEAN;      (* für Initialisierung *)
    AccName         : ARRAY [0..15] OF CHAR; (* Accessory-Name *)
    TypeOfImport    : CHAR;         (* Art der MIDI-Daten *)

(*****************************************************)
(* * * * *     R  O  U  T  I  N  E  N        * * * * *)
(*****************************************************)

(****** sendet Nachricht an den anderen Computer *****)
PROCEDURE SendeNachricht;

    VAR InfoString : String;

    BEGIN
        ReturnButton:=DoDialog(GEMDevice,DialogBox);(*Dialog durchführen*)

        (* Nachricht senden... *)
        IF ReturnButton=Okbutton THEN 
            getTextString(DialogBox,Infoline,InfoString);
            IF InfoString[0]#0C THEN 
                WriteToMIDI(1C);          (* Code senden       *)
                WriteStringToMIDI(InfoString) (* String senden *)
            END
        END
END SendeNachricht; 

Die IO-Routinen sind Bconin (Bios 2) für die Eingabe und Bconout (BIOS 3) für die Ausgabe via MIDI. Bconin wird die bekannte Gerätenummer 3 für MIDI als Parameter übergeben, dafür erhält man einen Long-Wert(!!!) zurück. Ein Long-Wert besteht bekanntlich aus 4 Bytes, und daher sollte man sich fragen, woher die anderen 3 Bytes kommen, wenn wir nur einen 1-Byte-Wert senden. Die Bits 16 bis 23 werden für den Scancode genutzt, falls das Eingabegerät Konsole Verwendung findet, und die Bits 24 bis 31 liefern den Wert der Bios-Routine Kbshift, falls das passende Bit in der Systemvariablen conterm gesetzt ist. Wofür die Bits 8 bis 15 da sind, ist etwas unklar, aber wir benötigen sowieso nur die Bits 0 bis 7, die unser Zeichen enthalten. Die Ausgaberoutine Bconout ist einfacher aufgebaut, denn ihr wird neben der MIDI-Gerätenummer nur der Wert unseres Characters als Parameter übergeben. Mit diesen vier Routinen wird all das, was wir so brauchen, bewerkstelligt.

Zum Senden und Empfangen sind die Routinen WriteToMidi und ReadFromMidi da, die jedoch nur intern benutzt werden. WriteToMidi versucht, ein Zeichen über MIDI zu schreiben. Dazu wird in einer Schleife abgewartet, ob der Puffer bereit ist. Ist er es innerhalb der Timeout-Zeit nicht, so wird eine Warnmeldung ausgegeben und kein Zeichen gesendet. Ist der Puffer bereit, so wird das Zeichen abgeschickt. ReadFromMidi arbeitet analog, nur empfängt diese Routine, anstatt zu senden.

WriteStringToMIDI sendet einen String über MIDI. Damit der andere Rechner weiß, wieviele Bytes geschickt werden, wird zuerst die Länge übermittelt und danach der String, dieser natürlich auch wieder zeichenweise. Sollte sich mal ein Zeichen nicht senden lassen, bricht die Routine ihre Arbeit einfach ab. Ist alles in Ordnung, wird auf eine Bestätigung vom anderen Rechner gewartet, der ein Zeichen zurücksendet, wenn alles korrekt übermittelt wurde. Analog dazu arbeitet wieder ReadStringFromMIDI. Hier wird eine Zeichenkette gelesen und im Anschluß daran das Bestätigungszeichen geschickt.

WriteToMIDI und ReadFromMIDI arbeiten wie die entsprechenden String-Routinen, nur eben für einen einzelnen Character.

Damit ist das Geheimnis des MIDIHandlers gelüftet. Kommen wir nun zu dem eigentlichen Accessory: MiKom.

(*** empfängt Nachricht von dem anderen Computer ***) 
PROCEDURE EmpfangeNachricht;

    VAR InfoString : String;

    BEGIN
        ReadStringFromMIDI(InfoString); (* Daten holen *)
        setTextString(DialogBox,Infoline,InfoString); (* String in Dialogbox setzen *) 
        FormAlert(1,MessageArrive^,VoidC);
        IF VoidC=1 THEN         (* Nachricht ansehen? *)
            SendeNachricht      (* ggf. Nachr. zurücksenden *)
        END
END EmpfangeNachricht;

(***************** Hauptroutine **********************) 
PROCEDURE Ablauf;

    VAR Message     : MessageBuffer;    (* Puffer für Message *)
        Events      : EventSet;         (* Set für Events     *)
        dummyPoint  : Point;            (* Dummy-Variable     *)
        dummyBut    : MButtonSet;       (*      "             *)
        dummyChar   : GemChar;          (*      "             *)
        specialKeys : SpecialKeySet;    (*      "             *)

    BEGIN
        REPEAT
            MultiEvent (EventSet{message,timer}, (* Event abwarten *)
                        0,MButtonSet{},MButtonSet{}, 
                        lookForEntry,Rect(0,0,0,0), 
                        lookForEntry,Rect(0,0,0,0),
                        Message,10L,
                        dummyPoint,dummyBut,specialKeys,dummyChar, 
                        VoidC,Events);

            (* Falls unser Accessory angewählt wurde... *)
            IF (message IN Events) AND (Message.msgType=accOpen) THEN

                (* Auswahldialogbox darstellen *)
                ReturnButton:=DoDialog(GEMDevice,AuswahlBox);

                (* Auswertung *)
                CASE ReturnButton OF 
                    Sendmsg : SendeNachricht|

                (* Hier können weitere Aktionen eingefügt werden. *)

                END

            END;

            (* Falls vom anderen ST etwas via Midi kam... *)

            IF (timer IN Events) AND MIDIReceived() THEN

                (* Code ermitteln *)
                ReadFromMIDI(TypeOfImport);

                (* Auswertung *)
                CASE TypeOfImport OF

                    (* Es ist eine Nachricht angekommen... *)
                    1C : EmpfangeNachricht|

                    (* Auch hier ist noch Platz für weitere Aktionen                *)
                    (* Das erste Byte bestimmt, was gesendet wurde                  *)
                    (* und was damit bewirkt werden soll.                           *)
                    (*   "1" heißt somit: Nachricht lesen und                       *)
                    (* ausgeben.                                                    *)
                    (* Beispielsweise könnte man mit "2" ganze Dateien übertragen.  *)
                    (* Dazu ist reicht es, hier eine weitere Aktion an zufügen.     *)
                    (* Die Auswahldialogbox sollte dann auch                        *)
                    (* um die weiteren Möglichkeiten erweitert werden.              *)

                END

            END

        UNTIL FALSE 
END Ablauf;

(******************************************************) 
(* * * * *      H a u p t p r o g r a m m     * * * * *)
(******************************************************)

BEGIN
    InitGem(RC,GEMDevice,InitOK);   (* anmelden *)
    IF InitOK THEN 
        LoadResource('MIKOM.RSC');  (* Resourcefile laden *)
        IF GemError() THEN          (* OK? *)
            FormAlert(1,'[0][Das Resourcefile fehlt!][ OK ]',VoidC);
            REPEAT 
                TimerEvent(10000L)
            UNTIL FALSE 
        ELSE
            AccName:='  MiKom      ';   (* Accessory-Name setzen *)
            RegisterAcc(ADR(AccName),AccID,InitOK);(* Accessory anmelden *) 
            IF InitOK THEN              (* OK? *)
                DialogBox :=ResourceAddr(treeRsrc,Dialog);  (* Dialogbox-Adresse *)
                AuswahlBox:=ResourceAddr(treeRsrc,Auswahl); 
                setTextString(DialogBox,Infoline,'');       (* Leerstring setzen *)
                MessageArrive:=ResourceAddr(textstring,Arrive); (* Alertbox-Adresse *)
                Ablauf                  (* Accessory-Ablauf *)
            ELSE
                FormAlert (1,'[0][Accessory MiKom|nicht installiert.][ OK ]',VoidC);
                REPEAT 
                    TimerEvent(10000L)
                UNTIL FALSE 
            END 
        END
    END 
END MiKom.

(* * * * *      E n d e   d e s   M o d u l s    * * * * *)

Listing 1a

DEFINITION MODULE Mikomd;

EXPORT
    Dialog, Infoline, Exbutton, Okbutton, Auswahl, Sendmsg,
    Auswexit, Arrive;


CONST
    Dialog   = 0; (* Formular/Dialog *)
    Infoline = 7; (* FTEXT in Baum DIALOG *)
    Exbutton = 8; (* BUTTON in Baum DIALOG *)
    Okbutton = 9; (* BUTTON in Baum DIALOG *)
    Auswahl  = 1; (* Formular/Dialog *)
    Sendmsg  = 8; (* BOXTEXT in Baum AUSWAHL *)
    Auswexit = 9; (* BOXTEXT in Baum AUSWAHL *)
    Arrive   = 0; (* Meldung-String Index *)

END Mikomd.

(* Listing 1b *)

Listing 1b

IMPLEMENTATION MODULE Mikomd;
(*$N+,M-*)
END Mikomd.

(* Listing 1c *)

Listing 1c

Bevor das Listing zu MiKom angefertigt wird, sollte ein Resourcefile erstellt werden. Dieses Resourcefile muß zwei Dialogboxen und eine Alertbox enthalten. Ein Muster ist auf den Bildern 3 bis 5 Zusehen. Das Resource-Construction-Programm legt auch die beiden Module an, die als Listing 1b und 1c aufgeführt sind. Das Resourcefile wurde übrigens unter dem Namen Mikomd angelegt, um eine eventuelle Verwirrung zu vermeiden. Es ist vor der Benutzung entsprechend umzubenennen.

Das Modul MiKom umfaßt neben dem Hauptprogramm lediglich drei Routinen. SendeNachricht sendet eine Nachricht. Damit aber MiKom erweiterungsfähig bleibt, wird vor der Nachricht eine Kennung übermittelt. Sie besteht hier aus einer 1. Wird sie geschickt, weiß der andere Rechner gleich Bescheid, daß eine Nachricht kommt und nicht etwa eine Datei oder sonst etwas. EmpfangeNachricht, die zweite Routine, arbeitet ähnlich, nur taucht hier die Kennung nicht auf.

Die gesuchte Kennung steckt in der Routine Ablauf. Sie enthält eine Endlosschleife, in der auf einen Message- bzw. Timer-Event gewartet wird. Tritt also ein Timer-Event ein, wird abgefragt, ob ein Zeichen am MIDI-Port anliegt. Falls ja, wird es gelesen. Dieses Zeichen ist die Kennung, die der andere Rechner geschickt hat! Also kann an dieser Stelle entschieden werden, wie weiterverfahren werden soll. Da MiKom aber in der aktuellen Fassung nur eine Nachricht empfangen kann, wird auch nichts anderes als Alternative aufgeführt. Die auf den ersten Blick unsinnige CASE-Anweisung hat einen besonderen Sinn. Sollen weitere Möglichkeiten angefügt werden, können diese durch CASE einfacher eingefügt werden. Falls ein Message-Event eingetreten ist, kann auch eine bestimmte Routine ausgewählt werden. Aber auch hier ist bisher nur das Senden einer Nachricht möglich. CASE ist aus den gleichen Gründen, die gerade schon angeführt wurden, benutzt worden.

An den beiden genannten Stellen lassen sich also leicht weitere Routinen einfügen. Es ist dann aber unbedingt auch die Auswahl-Dialogbox entsprechend zu erweitern. Auf Erweiterungen kommen wir aber gleich noch wieder zurück.

Das Hauptprogramm hat lediglich die ehrenvolle Aufgabe, das Accessory zu installieren und entsprechend vorzubereiten. Zu den Vorbereitungen gehört das Laden des Resourcefiles und das Festlegen der Adressen, aber auch das Anmelden des Accessorys.

Nun sind wir am Ende der Erklärungen angelangt. Was kann man an Erweiterungen einbringen? Tja, eine gute Frage. Hier können nur einige wenige Vorschläge gemacht werden. Denkbar wäre beispielsweise das Übermitteln von Dateien. Dabei könnte die Möglichkeit berücksichtigt werden, daß MiKom selbständig das Inhaltsverzeichnis einer Diskette, die im Laufwerk des anderen ST liegt, ausliest. Es könnten aber auch direkt Hardcopies übertragen werden. Möchte man mehr als zwei STs benutzen, muß ein etwas größerer Aufwand betrieben werden. Neben der Kennung dessen, was übertragen wird, müßte ein Byte eingeführt werden, das den Adressaten bestimmt. Gleichzeitig sollte man ein Freizeichen senden, beispielsweise eine 0. Lediglich der ST, der gerade die 0 hat, kann etwas absenden. Damit hätte man dann schon so ein richtiges kleines Netz. Nachteilig ist natürlich, daß immer alle Rechner angeschaltet sein müssen und dabei nur GEM-Programme verwenden dürfen. Bei TOS-Programmen, die keine Events nutzen, steht ja auch MiKom still.

Die MIDI-Schnittstelle kann noch auf vielerlei Art und Weise genutzt werden. Ich hoffe, daß dieser Artikel Anreiz genug bietet, viele eigene Versuche zu starten.

Literatur:

[1] Atari ST Profibuch
H.-D. Jankowski/ J.F. Reschke/ D. Rabich Sybex, 5. Auflage 1988

[2] Auf der Schwelle zum Licht - Zeichenorientierte Geräte
A. Esser
ST Computer 10/88

Bild 5: So muß die Dialogbox "Dialog" konstruiert werden
(************************************************************ )
(*Definitionsmodul für Dialog-Routinen Version 0.0,19/09/88  *)
(*Autor: Dietmar Rabich, Dülmen.Entw. mit Megamax Modula-2   *) 
(*************************************************************)
(*Erläuterungen befinden sich im Implementationsmodul        *)
(*************************************************************)

DEFINITION MODULE DialogHandler;

FROM GEMEnv     IMPORT DeviceHandle;
FROM GEMGlobals IMPORT PtrObjTree;

PROCEDURE getTextString (tree    : PtrObjTree;
                         obj     : CARDINAL;
                         VAR str : ARRAY OF CHAR);

PROCEDURE setTextString (tree    : PtrObjTree;
                         obj     : CARDINAL;
                         str     : ARRAY OF CHAR);

PROCEDURE DoDialog (GEMDevice    : DeviceHandle;
                         tree    : PtrObjTree):CARDINAL;

END DialogHandler.

(* Listing 2a *)

Listing 2a

(*********************************************************************) 
(* Implementationsmodul für Dialog-Routinen Version 0.0, 19/09/88    *)
(* Autor: Dietmar Rabich, Dülmen. Entw. mit Megamax Modula 2.        *)
(*********************************************************************) 
(* setTextString und getTextString sind Routinen aus MShell nachempf.*) 
(* DoDialog stammt von D. Rabich und wurde in der ST Computer veröf. *) 
(*********************************************************************) 

IMPLEMENTATION MODULE DialogHandler;

(* AES-Routinen *)
FROM AESForms     IMPORT FormDo,FormDial,FormCenter,FormDialMode;
FROM AESGraphics  IMPORT MouseForm,GrafMouse;
FROM AESObjects   IMPORT ChangeObjState,DrawObject;ObjectOffset;
FROM AESWindows   IMPORT UpdateWindow;
FROM ObjHandler   IMPORT ObjectState,ObjectSpace,ObjTreeError,
                         GetTextStrings,SetCurrObjTree, AssignTextStrings,
                         SetPtrChoice;

(* allgemeine GEM-Routinen *)
FROM GEMEnv       IMPORT DeviceHandle;
FROM GEMGlobals   IMPORT PtrObjTree,ObjState,OStateSet,Root,MaxDepth;

(* Graphik *)
FROM GrafBase     IMPORT Rectangle,Reet,TransRect,Pnt,MemFormDef,
                         GetLogMemForm,BitOperation;

(* VDI-Routinen *)
FROM VDIRasters   IMPORT CopyOpaque;

(* sonstige Routinen *)
FROM Strings      IMPORT String;
FROM Storage      IMPORT ALLOCATE,DEALLOCATE;
FROM SYSTEM       IMPORT ADR,ADDRESS;

(* Zeichenkette in tree ermitteln, die bei obj steht*)
PROCEDURE getTextString (tree    : PtrObjTree;
                         obj     : CARDINAL;
                         VAR str : ARRAY OF CHAR);

 VAR voidStr : String;

 BEGIN
    SetCurrObjTree(tree,FALSE);
    GetTextStrings(obj,str,voidStr,voidStr);
    IF ObjTreeError() THEN 
        str[0]:=0C 
    END
 END getTextString;

(* Zeichenkette in tree bei obj setzen *)
PROCEDURE setTextString (tree   : PtrObjTree;
                         obj    : CARDINAL;
                         str    : ARRAY OF CHAR);

 BEGIN
    SetCurrObjTree(tree,FALSE);
    AssignTextStrings(obj,setOnly,str,noChange,'',noChange,'')
 END setTextString;

(* führt vollständigen Dialog aus *)
PROCEDURE DoDialog (GEMDevice : DeviceHandle; tree : PtrObjTree) : CARDINAL;

 VAR space      : Rectangle;    (* Dialogbox-Größe *)
     Status     : OStateSet;    (* Object-Status *)
     button     : CARDINAL;     (* ausgewähltes Object*)
     MakeBits   : BOOLEAN;      (* Flag für reserv. Speicher *)
     Picture    : POINTER TO ARRAY [0..32511] OF CHAR;
     MemFSource,MemFDest : MemFormDef; (* FDB *) 

 BEGIN
    UpdateWindow(TRUE);         (* keine Fenster-ausgaben mehr *)
    SetCurrObjTree(tree,FALSE);
    space:=FormCenter(tree);    (* Dialog zentrieren *)
    ALLOCATE(Picture,SIZE(Pictured);(* Platz für Bild *)
    MakeBits:=Picture#NIL;      (* Platz OK? *)
    IF MakeBits THEN 
        GetLogMemForm(MemFSource);      (* FDB holen *)
        MemFDest:=MemFSource;           (* Ziel-FDB *)
        MemFDest.start:=(* Adresse auf volle 512 Byte*) 
            ADDRESS(LONGCARD(Picture)+512L-(LONGCARD(Picture) MOD 512L));
        GrafMouse(mouseOff,NIL);        (* Maus aus *)
        CopyOpaque(GEMDevice, (* Hintergrund sichern *) 
                   ADR(MemFSource),ADR(MemFDest), 
                   space,space,onlyS);
        GrafMouse(mouseOn,NIL)          (* Maus wieder an *)
    ELSE
        FormDial(reserveForm,Rect(0,0,0,0),space)   (* Bild ber. reserv. *)
    END;
    DrawObject(tree,Root,MaxDepth,space); (* Dialog zeichnen *)
    FormDo(tree,Root,button);             (*Dialog durchführen *) 
    IF MakeBits THEN 
        GrafMouse(mouseOff,NIL);          (* Maus aus *)
        CopyOpaque(GEMDevice, (* Hintergrund zurück *) 
                   ADR(MemFDest),ADR(MemFSource), 
                   space,space,onlyS);
        GrafMouse(mouseOn,NIL); (* und wieder Maus an*) 
        DEALLOCATE(Picture,SIZE(Picture*))(* Speicher freigeben *)
    ELSE
        FormDial(freeForm,Rect(0,0,0,0),space);(* Bild.ber.freigeben *)
    END;
    Status:=ObjectState(button); (* Status angeklicktes Object *) 
    EXCL(Status,selectObj); (* SELECTED entfernen*)
    ChangeObjState(tree,button, (* neuen Status setzen *)
                   TransRect(ObjectSpace(button), 
                   ObjectOffset(tree,button)), 
                   Status,FALSE);
    UpdateWindow(FALSE); (* Fensterausgabe freigeben *)
    RETURN button 
 END DoDialog

END DialogHandler.

(* Listing 2b *)

Listing 2b

(************************************************************) 
(* Definitionsmodul für MIDI-Routinen Version 0.0, 19/09/88 *)
(*==========================================================*)
(* Autor: Dietmar Rabich, Dülmen.Entw.mit Megamax Modula 2. *)
(************************************************************) 
(* Erläuterungen befinden sich im Implementationsmodul.     *)
(************************************************************) 

DEFINITION MODULE MIDIHandler;

PROCEDURE SetTimeOut (t : CARDINAL);

PROCEDURE GetTimeOut : CARDINAL;

PROCEDURE MIDIReceived : BOOLEAN;

PROCEDURE WriteStringToMIDI (s : ARRAY OF CHAR);

PROCEDURE ReadStringFromMIDI(VAR s : ARRAY OF CHAR);

PROCEDURE WriteToMIDI (c : CHAR);

PROCEDURE ReadFromMIDI (VAR c: CHAR);

END MIDIHandler.

(* Listing 3a *)

Listing 3a

(***************************************************************)
(* Implementationsmodul für MIDI-Routinen Version 0.1, 27/09/88*)
(*============================================================ *)
(* Autor: Dietmar Rabich, Dülmen. Entw. mit Megamax Modula 2.  *)
(***************************************************************)

IMPLEMENTATION MODULE MIDIHandler;

(* BIOS-Routinen *)
FROM BIOS           IMPORT BConOut,BConIn,BConStat,BCosStat,Device;

(* String-Routinen *)
FROM FastStrings    IMPORT Length;

(* AES-Routinen *)
FROM AESEvents      IMPORT TimerEvent;
FROM AESForms       IMPORT FormAlert;

(* TimeOut-Zeit in 1/100 s *)
VAR Timeout : CARDINAL;

(* setzt Timeout in s *)
PROCEDURE SetTimeOut (t : CARDINAL);

 BEGIN 
    Timeout:=t*100
 END SetTimeOut;

(* gibt Timeout in s zurück *)
PROCEDURE GetTimeOut : CARDINAL;

 BEGIN
    RETURN Timeout DIV 100 
 END GetTimeOut;

(* Schreibt ein Zeichen über MIDI. (interne Routine)        *) 
(* Das Zeichen wird nur dannweitergegeben, wenn der         *) 
(* Puffer nicht gänzlich gefüllt ist. Kann nach             *) 
(* 5 s(=500*10 ms)noch immer kein Zeichen in den            *) 
(* Puffer geschrieben werden, so gibt wird eine             *) 
(* Timeout-Meldung ausgegeben. Verläuft die                 *)
(* Übertragung korrekt, so ist die Rückgabe von             *)
(* WriteToMidi TRUE, sonst FALSE.                           *)
(* 5 s ist die Standard-Vorgabe, die verändert werden kann. *)

PROCEDURE WriteToMidi (c : CHAR) : BOOLEAN;

 VAR TimeCounter,VoidC : CARDINAL;

 BEGIN 
    TimeCounter:=0;
    WHILE ~BCosStat(HSS) AND (TimeCounter<500) DO 
        TimerEvent(10L);
        INC(TimeCounter)
    END;
    IF TimeCounter<TimeOut THEN 
        BConOut(HSS,c)
    ELSE
        FormAlert(1,'[0][MIDI - Timeout| ][ OK ]',VoidC)
    END;
    RETURN TimeCounter<TimeOut 
 END WriteToMidi;

(* Analog zur WriteToMidi, allerdings werden die Zeichen gelesen. *)
(* (interne Routine) *)

PROCEDURE ReadFromMidi (VAR c : CHAR) : BOOLEAN;

 VAR TimeCounter,VoidC : CARDINAL;

 BEGIN 
    TimeCounter:=0;
    WHILE ~BConStat(HSS) AND (TimeCounter<500) DO 
        TimerEvent(10L);
        INC(TimeCounter)
    END;
    IF TimeCounter<TimeOut THEN 
        c:=CHR(SHORT(BConIn(HSS) MOD $100L))
    ELSE
        FormAlert(1,'[0][MIDI - Timeout| ][ OK ]',VoidC)
    END;
    RETURN TimeCounter<TimeOut 
 END ReadFromMidi;

(* Status des MIDI-Eingangs                   *)
(* TRUE, falls Zeichen verfügbar, FALSE sonst *) 
PROCEDURE MIDIReceived : BOOLEAN;

 BEGIN 
    RETURN BConStat(HSS)
 END MIDIReceived;

(* schreibt einen String über MIDI an den zweiten Rechner *)
PROCEDURE WriteStringToMIDI (s : ARRAY OF CHAR);

 VAR i,LengthOfMessage  : CARDINAL;
     WriteFlag          : BOOLEAN;
     VoidCh             : CHAR;

 BEGIN
    IF s[0]#0C THEN 
        LengthOfMessage:=Length(s); (* Anzahl der Buchstaben *)
        WriteFlag:=WriteToMidi(CHR(LengthOfMessage)); (* Länge übermitteln *)
        i:=0;
        WHILE (i<LengthOfMessage) AND WriteFlag DO (* String schreiben, *)
            WriteFlag:=WriteToMidi(s[i]);          (* falls bisher kein *)
            INC(i)                                 (* Timeout           *)
        END;
        IF WriteFlag THEN                          (* Antwort abwarten  *)
            WriteFlag:=ReadFromMidi(VoidCh)
        END
    END
 END WriteStringToMIDI;

(*liest einen String über MIDI vom zweiten Rechner*) 
PROCEDURE ReadStringFromMIDI (VAR s : ARRAY OF CHAR);

 VAR Anzahl,i : CARDINAL;
     ReadFlag : BOOLEAN;
     Ch       : CHAR;

 BEGIN
    ReadFlag:=ReadFromMidi(Ch);
    Anzahl :=0;
    IF ReadFlag THEN 
        Anzahl:=ORD(Ch);    (* Anzahl der Buchstaben *)
        i:=0;
        WHILE (i<Anzahl) AND ReadFlag DO (* String lesen,falls *) 
            ReadFlag:=ReadFromMidi(s[i]); (* kein Timeout *)
            INC(i)
        END;
        IF ReadFlag THEN    (* Antwort senden *)
            ReadFlag:=WriteToMidi(0C)
        END
    END;
    s[Anzahl]:=0C           (* ASCII 0 anfügen *)
 END ReadStringFromMIDI;

(* schreibt einen Character über MIDI *)
PROCEDURE WriteToMIDI (c : CHAR);

 VAR VoidB  : BOOLEAN;
     VoidCh : CHAR;

 BEGIN
    IF WriteToMidi(c) THEN (* Character schreiben*) 
        VoidB:=ReadFromMidi(VoidCh)(* Antwort abwarten *)
    END
 END WriteToMIDI;

(* liest einen Character über MIDI *)
PROCEDURE ReadFromMIDI (VAR c: CHAR);

 VAR VoidB : BOOLEAN;

 BEGIN
    IF ReadFromMidi(c) THEN     (* Character empfangen*) 
        VoidB:=WriteToMidi(0C)  (* Antwort senden *)
    END
 END ReadFromMIDI;

BEGIN
    Timeout:=500                (* Voreinstellung *)
END MIDIHandler.

(* Listing 3b *)

Listing 3b


Dietmar Rabich
Aus: ST-Computer 01 / 1989, Seite 102

Links

Copyright-Bestimmungen: siehe Über diese Seite