(*-------------------------------------------------------------------------*)
(*                                                                         *)
(*  Amiga Oberon Interface Module: Midi                   Date: 06-Jan-91  *)
(*                                                                         *)
(*  © 1991 by Peter Fröhlich of Nephthys Software.                         *)
(*                                                                         *)
(*  Translated to OBERON from the original Assembler and C include files   *)
(*  from AmigaLibDisk227.                                                  *)
(*                                                                         *)
(*  This module is freely distributable as long as you don't make money    *)
(*  with it's distribution and leave this header in. Use of this module    *)
(*  in a commercial product is subject to my permission.                   *)
(*                                                                         *)
(*  Exceptions:                                                            *)
(*                                                                         *)
(*  1. Commercial Oberon programs accessing the midi.library through this  *)
(*     module but not supplying the module itself are OK. It would be nice *)
(*     if you could find a place in the documentation that mentions my     *)
(*     name and send me a registrated copy of your software.               *)
(*                                                                         *)
(*  2. Mr. Fridtjof Siebert is granted the liscence to include this module *)
(*     in the next release of his Oberon compiler.                         *)
(*                                                                         *)
(*  Please send modifications and bug reports to me at the following       *)
(*  address: Z-NET:P.FROEHLICH@MUSIKBOX.ZER.                               *)
(*                                                                         *)
(*  NOTE: This interface module is probably buggy in many places. This is  *)
(*        because I'm quit a newcomer to Oberon and high-level languages   *)
(*        in general. In assembler I don't need to worry about types and   *)
(*        stuff.                                                           *)
(*                                                                         *)
(*  NOTE: For easier debugging I left the C structure definitions in this  *)
(*        file. I'll clean up that mess if the module runs stable.         *)
(*        The MidiWord macro is not implemented. If someone has an idea ?  *)                                                                 *)
(*                                                                         *)
(*-------------------------------------------------------------------------*)

MODULE Midi;

IMPORT
  e: Exec,
  g: Graphics,
  i: Intuition,
  s: SYSTEM;

(*-------------------------------------------------------------------------*)
(*                                                                         *)
(*    Definitions pertaining to MIDI are derived form MIDI 1.0 Detailed    *)
(*    Specification v4.0 (published by the Internation MIDI Association)   *)
(*    and is current as of June, 1988.                                     *)
(*                                                                         *)
(*    v2.0 - 23-Oct-88                                                     *)
(*                                                                         *)
(*-------------------------------------------------------------------------*)

CONST
  MidiName = "midi.library";
  MidiVersion = 7;

(*--- Routes --------------------------------------------------------------*)
CONST
  RIMMAXCOUNT = 3;

TYPE
  RIMatch = STRUCT
    flags : SHORTINT;
    match : ARRAY RIMMAXCOUNT OF SHORTINT;
  END;

(*
struct RIMatch {
    UBYTE Flags;                /* flag bits defined below */
    UBYTE Match[RIM_MAXCOUNT];
};
*)

CONST
  RIMFCOUNTBITS = 03H; (* mask for # of match values (0 for match all) *)
  RIMFEXTID = 40H;     (* indicates all 3 bytes of match[] == one 3 byte manuf. id (not valid for CtrlMatch) *)
  RIMFEXCLUDE = 80H;   (* reverses logic of RIMatch so that all except those specified pass *)

TYPE
  MRouteInfoPtr = POINTER TO MRouteInfo;

  MRouteInfo* = STRUCT
    msgFlags* : SET;
    chanFlags* : SET;
    chanOffset* : SHORTINT;
    noteOffset* : SHORTINT;
    sysExMatch* : RIMatch;
    ctrlMatch* : RIMatch;
  END;

(*
struct MRouteInfo {
    UWORD MsgFlags;             /* flags enabling message types defined below (MMF_) (msg filters) */
    UWORD ChanFlags;            /* incoming channel enable flags (LSB = chan 1, MSB = chan 16) (channel filters) */
    BYTE  ChanOffset;           /* signed offset applied to channels (simple channelizing) */
    BYTE  NoteOffset;           /* signed offset applied to note numbers (transposition) */
    struct RIMatch SysExMatch;  /* Sys/Ex manufacturer id filtering */
    struct RIMatch CtrlMatch;   /* Controller number filtering */
};
*)

(*--- Msg Flags for MRouteInfo structure and returned by MidiMsgType ------*)
CONST
  MMFCHAN = 00FFH;
  MMFNOTEOFF = 0001H;
  MMFNOTEON = 0002H;
  MMFPOLYPRESS = 0004H;
  MMFCTRL = 0008H;
  MMFPROG = 0010H;
  MMFCHANPRESS = 0020H;
  MMFPITCHBEND = 0040H;
  MMFMODE = 0080H;

  MMFSYSCOM = 0100H;
  MMFSYSRT = 0200H;
  MMFSYSEX = 0400H;

  MMFALL = 07FFH;

TYPE
  MRoutePtr* = POINTER TO MRoute;   (* this is just a pointer, don't mix it up with MroutePtr below [phf] *)
  MSourcePtr* = POINTER TO MSource;
  MDestPtr* = POINTER TO MDest;

TYPE
  MroutePtr* = STRUCT               (* Can someone tell me what this is for ? [phf] *)
    node* : e.MinNode;
    route* : MRoutePtr;
  END;

(*
struct MRoutePtr {
    struct MinNode node;
    struct MRoute *Route;
};
*)


TYPE
  MRoute* = STRUCT
    source* : MSourcePtr;
    dest* : MDestPtr;
    sRoutePtr*, dRoutePtr* : MRoutePtr;
    routeInfo* : MRouteInfo;
  END;

(*
struct MRoute {
    struct MSource *Source;
    struct MDest *Dest;
    struct MRoutePtr SRoutePtr, DRoutePtr;
    struct MRouteInfo RouteInfo;
};
*)



(*--- Nodes ---------------------------------------------------------------*)
TYPE
  MSource = STRUCT
    node : e.Node;
    image : i.ImagePtr;
    rPList : e.MinList;
    userData : e.ADDRESS;   (* user data extension *)

    (* new stuff for v2.0 *)
    routeMsgFlags : SET;    (* mask of all route->MsgFlags for this MSource *)
    routeChanFlags : SET;   (* mask of all route->ChanFlags for this MSource *)
  END;

(*
struct MSource {
    struct Node Node;
    struct Image *Image;
    struct MinList RPList;
    APTR UserData;              /* user data extension */

    /* new stuff for v2.0 */
    UWORD RouteMsgFlags;        /* mask of all route->MsgFlags for this MSource */
    UWORD RouteChanFlags;       /* mask of all route->ChanFlags for this MSource */
};
*)

(* node types for Source *)
CONST
  mSource = 20H;
  resMSource = 21H;

TYPE
  MDest = STRUCT
    node : e.Node;
    image : i.ImagePtr;
    rPList : e.MinList;
    destPort : e.MsgPortPtr;
    userData : e.ADDRESS;          (* user data extension *)

    (* new stuff for v2.0 *)
    defaultRouteInfo : MRouteInfo; (* used when Routing function doesn't supply a RouteInfo *)
  END;

(*
struct MDest {
    struct Node Node;
    struct Image *Image;
    struct MinList RPList;
    struct MsgPort *DestPort;
    APTR UserData;              /* user data extension */

    /* new stuff for v2.0 */
    struct MRouteInfo DefaultRouteInfo;     /* used when Routing function doesn't supply a RouteInfo */
};
*)


(* node types for Dest *)
CONST
  mDest = 22H;
  resMDest = 23H;

(* MIDI Packet (new for v2.0) *)
TYPE
  MidiPacketPtr = POINTER TO MidiPacket;

  MidiPacket = STRUCT              (* returned by GetMidiPacket() *)
    execMsg : e.Message;
    type : INTEGER;                (* MMF bit for this message (as returned by MidiMsgType()) *)
    length : INTEGER;              (* length of msg in bytes (as returned by MidiMsgLength()) *)
    reserved : LONGINT;            (* reserved for future expansion *)
    midiMsg : ARRAY 4 OF SHORTINT; (* actual MIDI message (real length of this array is Length, always at least this much memory allocated) *)
  END;

(*
struct MidiPacket {         /* returned by GetMidiPacket() */
    struct Message ExecMsg;
    UWORD Type;             /* MMF_ bit for this message (as returned by MidiMsgType()) */
    UWORD Length;           /* length of msg in bytes (as returned by MidiMsgLength()) */
    ULONG reserved;         /* reserved for future expansion */
    UBYTE MidiMsg[4];       /* actual MIDI message (real length of this array is Length, always at least this much memory allocated) */
};
*)

(* Public List Change Signal *)
TYPE
  MListSignalPtr = POINTER TO MListSignal;

  MListSignal = STRUCT
    node : e.MinNode;
    sigTask : e.TaskPtr;    (* task to signal *)
    sigBit : SHORTINT;      (* signal bit to use *)
    flags : SHORTSET;       (* flags, see below *)
  END;

(*
struct MListSignal {
    struct MinNode Node;
    struct Task *SigTask;       /* task to signal */
    UBYTE SigBit;               /* signal bit to use */
    UBYTE Flags;                /* flags, see below */
};
*)

(* user flags *)
CONST
  MLSFSOURCE = 0;   (* causes signal when SourceList changes *)
  MLSFDEST = 1;     (* causes signal when DestList changes *)


(*--- MIDI message defininition ---*)

(* Status Bytes *)
CONST
  (* Channel Voice Messages (1sssnnnn) (OR with channel number) *)
  MSNOTEOFF    = 80H;
  MSNOTEON     = 90H;
  MSPOLYPRESS  = 0A0H;
  MSCTRL       = 0B0H;
  MSMODE       = 0B0H;
  MSPROG       = 0C0H;
  MSCHANPRESS  = 0D0H;
  MSPITCHBEND  = 0E0H;

  (* System Common Messages (11110sss) *)
  MSSYSEX      = 0F0H;
  MSQTRFRAME   = 0F1H;
  MSSONGPOS    = 0F2H;
  MSSONGSELECT = 0F3H;
  MSTUNEREQ    = 0F6H;
  MSEOX        = 0F7H;

  (* System Real Time Messages (11111sss) *)
  MSCLOCK      = 0F8H;
  MSSTART      = 0FAH;
  MSCONTINUE   = 0FBH;
  MSSTOP       = 0FCH;
  MSACTVSENSE  = 0FEH;
  MSRESET      = 0FFH;

  (* Miscellaneous *)
  MIDDLEC =        60;      (* middle C note value *)
  DEFAULTVELOCITY= 64;      (* default Note On or Off velocity *)
  PITCHBENDCENTER = 2000H;  (* pitch bend center position as a 14 bit word *)
  MCLKSPERQTR  =   24;      (* MIDI clocks per qtr-note *)
  MCLKSPERSP   =   6;       (* MIDI clocks per song position index *)
  MCCENTER     =   64;      (* center value for controllers like Pan and Balance *)


(* Standard Controllers *)

  (* continuous 14 bit - MSB: 0-1f, LSB: 20-3f *)
  MCMODWHEEL  = 01H;
  MCBREATH    = 02H;
  MCFOOT      = 04H;
  MCPORTATIME = 05H;
  MCDATAENTRY = 06H;
  MCVOLUME    = 07H;
  MCBALANCE   = 08H;
  MCPAN       = 0AH;
  MCEXPRESSION = 0BH;
  MCGENERAL1  = 10H;
  MCGENERAL2  = 11H;
  MCGENERAL3  = 12H;
  MCGENERAL4  = 13H;

  (* continuous 7 bit (switches: 0-3f=off, 40-7f=on) *)
  MCSUSTAIN   = 40H;
  MCPORTA     = 41H;
  MCSUSTENUTO = 42H;
  MCSOFTPEDAL = 43H;
  MCHOLD2     = 45H;
  MCGENERAL5  = 50H;
  MCGENERAL6  = 51H;
  MCGENERAL7  = 52H;
  MCGENERAL8  = 53H;
  MCEXTDEPTH     = 5BH;
  MCTREMOLODEPTH = 5CH;
  MCCHORUSDEPTH  = 5DH;
  MCCELESTEDEPTH = 5EH;
  MCPHASERDEPTH  = 5FH;

  (* parameters *)
  MCDATAINCR  = 60H;
  MCDATADECR  = 61H;
  MCNRPNL     = 62H;
  MCNRPNH     = 63H;
  MCRPNL      = 64H;
  MCRPNH      = 65H;

  MCMAX       = 78H;       (* max controller value *)




(* Channel Modes *)

  MMMIN       = 79H;       (* min mode value *)

  MMRESETCTRL = 79H;
  MMLOCAL     = 7AH;
  MMALLOFF    = 7BH;
  MMOMNIOFF   = 7CH;
  MMOMNION    = 7DH;
  MMMONO      = 7EH;
  MMPOLY      = 7FH;


(* Registered Parameter Numbers *)
(*-------------------------------------------------------------------------*)
(*  These are 16 bit values that need to be separated into two bytes for   *)
(*  use with the MC_RPNH & MC_RPNL messages using 8 bit math (hi = MRP     *)
(*  >> 8, lo = MRP & FFH) as opposed to 7 bit math.  This is done          *)
(*  so that the defines match the numbers from the MMA.  See MIDI 1.0      *)
(*  Detailed Spec v4.0 pp 12, 23 for more info.                            *)
(*-------------------------------------------------------------------------*)

  MRPPBSENS      = 0000H;
  MRPFINETUNE    = 0001H;
  MRPCOURSETUNE  = 0002H;


(* MTC Quarter Frame messages *)
(*-------------------------------------------------------------------------*)
(*  Qtr Frame message is F1 0nnndddd where                                 *)
(*                                                                         *)
(*      nnn is a message type defined below                                *)
(*      dddd is 4 bit data nibble for those message types                  *)
(*                                                                         *)
(*  Each pair of nibbles is combined by the receiver into a single byte.   *)
(*  There are masks and type values defined for some of these data bytes   *)
(*  below.                                                                 *)
(*-------------------------------------------------------------------------*)

  (* message types *)
  MTCQFRAMEL = 00H;
  MTCQFRAMEH = 10H;
  MTCQSECL   = 20H;
  MTCQSECH   = 30H;
  MTCQMINL   = 40H;
  MTCQMINH   = 50H;
  MTCQHOURL  = 60H;
  MTCQHOURH  = 70H;      (* also contains time code type *)

  (* message masks *)
  MTCQTYPEMASK = 70H;    (* mask for type bits in message *)
  MTCQDATAMASK = 0FH;    (* mask for data bits in message *)

  (* hour byte *)
  MTCHTYPEMASK = 60H;    (* mask for time code type *)
  MTCHHOURMASK = 1FH;    (* hours mask (range 0-23) *)

  (* time code type values for hour byte *)
  MTCT24FPS          = 00H;
  MTCT25FPS          = 20H;
  MTCT30FPSDROP     = 40H;
  MTCT30FPSNONDROP  = 60H;




(*--- Sys/Ex ID numbers ---*)
(*-------------------------------------------------------------------------*)
(*    Now includes 3 byte extension for the American Group.  This new      *)
(*    format uses a 00H as the sys/ex id followed by two additional bytes  *)
(*    that actually identify the manufacturer.  These new extended id      *)
(*    constants are 32 bit values (24 significant bits) that can be        *)
(*    managed using SPLIT_MIDX() and MAKE_MIDX() macros defined below.     *)
(*                                                                         *)
(*    You can match or filter off one of the extended id's when using the  *)
(*    RIMFEXTID bit described above.                                       *)
(*                                                                         *)
(*    example RIMatch                                                      *)
(*        {                                                                *)
(*            RIMF_EXTID | 1,         extend id, match one manufacturer    *)
(*            SPLIT_MIDX (MIDX_IOTA)  splits id into 3 bytes               *)
(*        }                                                                *)
(*-------------------------------------------------------------------------*)

(*--- American Group ---*)
CONST
  MIDXAMERICA = 00H;
  MIDSEQUENTIAL = 01H;
  MIDIDP = 02H;
  MIDOCTAVEPLATEAU = 03H;
  MIDMOOG = 04H;
  MIDPASSPORT = 05H;
  MIDLEXICON = 06H;
  MIDKURZWEIL = 07H;
  MIDFENDER = 08H;
  MIDAKG = 0AH;
  MIDVOYCE = 0BH;
  MIDWAVEFRAME = 0CH;
  MIDADA = 0DH;
  MIDGARFIELD = 0EH;
  MIDENSONIQ = 0FH;
  MIDOBERHEIM = 10H;
  MIDAPPLE = 11H;
  MIDGREYMATTER = 12H;
  MIDPALMTREE = 14H;
  MIDJLCOOPER = 15H;
  MIDLOWREY = 16H;
  MIDADAMSSMITH = 17H;
  MIDEMU = 18H;
  MIDHARMONY = 19H;
  MIDART = 1AH;
  MIDBALDWIN = 1BH;
  MIDEVENTIDE = 1CH;
  MIDINVENTRONICS = 1DH;
  MIDCLARITY = 1FH;

  (* ATTENTION: The following are 3 byte constants, could be difficult to *)
  (*            write them if they pass maxint [phf]                      *)
  MIDXDIGITALMUSIC = 000007H;
  MIDXIOTA = 000008H;
  MIDXIVL = 00000BH;
  MIDXSOUTHERNMUSIC = 00000CH;
  MIDXLAKEBUTLER = 00000DH;
  MIDXDOD = 000010H;
  MIDXPERFECTFRET = 000014H;
  MIDXOPCODE = 000016H;
  MIDXSPATIALSOUND = 000018H;
  MIDXKMX = 000019H;
  MIDXAXXES = 000020H;

(*--- European Group ---*)
  MIDPASSAC = 20H;
  MIDSIEL = 21H;
  MIDSYNTHAXE = 22H;
  MIDHOHNER = 24H;
  MIDTWISTER = 25H;
  MIDSOLTON = 26H;
  MIDJELLINGHAUS = 27H;
  MIDSOUTHWORTH = 28H;
  MIDPPG = 29H;
  MIDJEN = 2AH;
  MIDSSL = 2BH;
  MIDAUDIOVERITRIEB = 2CH;
  MIDELKA  = 2FH;
  MIDDYNACORD = 30H;

(*--- Japanese Group ---*)
  MIDKAWAI = 40H;
  MIDROLAND = 41H;
  MIDKORG = 42H;
  MIDYAMAHA = 43H;
  MIDCASIO = 44H;
  MIDMORIDAIRA = 45H;
  MIDKAMIYA = 46H;
  MIDAKAI = 47H;
  MIDJAPANVICTOR = 48H;
  MIDMEISOSHA = 49H;
  MIDHOSHINOGAKKI = 4AH;
  MIDFUJITSU = 4BH;
  MIDSONY = 4CH;
  MIDNISSHINONPA = 4DH;
  MIDSYSTEMPRODUCT = 4FH;

(*--- Universal ID Numbers ---*)
  MIDUNC = 7DH;
  MIDUNRT = 7EH;
  MIDURT = 7FH;

(*-------------------------------------------------------------------------*)

VAR
  midi* : e.LibraryPtr;

(*--- midi.library: -------------------------------------------------------*)
(* $OvflChk- $RangeChk- $StackChk- $NilChk- $ReturnChk- $CaseChk- *)

(*--- locking ---*)
PROCEDURE LockMidiBase* {midi,-30} ();
PROCEDURE UnlockMidiBase* {midi,-36} ();

(*--- source ---*)
PROCEDURE CreateMSource* {midi,-42} (name{8}: ARRAY OF CHAR; image{9}: i.ImagePtr): MDestPtr;
PROCEDURE DeleteMSource* {midi,-48} (source{8}: MSourcePtr);
PROCEDURE FindMSource* {midi,-54} (name{8}: ARRAY OF CHAR): MSourcePtr;

(*--- dest ---*)
PROCEDURE CreateMDest* {midi,-60} (name{8}: ARRAY OF CHAR; image{9}: i.ImagePtr): MDestPtr;
PROCEDURE DeleteMDest* {midi,-66} (dest{8}: MDestPtr);
PROCEDURE FindMDest* {midi,-72} (name{8}: ARRAY OF CHAR): MDestPtr;

(*--- route ---*)
PROCEDURE CreateMRoute* {midi,-78} (source{8}: MSourcePtr;
                                    dest{9}: MDestPtr;
                                    routeinfo{10}: MRouteInfoPtr): MRoutePtr;
PROCEDURE ModifyMRoute* {midi,-84} (route{8}: MRoutePtr;
                                     newrouteinfo{9}: MRouteInfoPtr);
PROCEDURE DeleteMRoute* {midi,-90} (route{8}: MRoutePtr);
PROCEDURE MRouteSource* {midi,-96} (source{8}: MSourcePtr;
                                    destname{9}: ARRAY OF CHAR;
                                    routeinfo{10}: MRouteInfoPtr): MRoutePtr;
PROCEDURE MRouteDest* {midi,-102} (sourcename{8}: ARRAY OF CHAR;
                                   dest{9}: MDestPtr;
                                   routeinfo{10}: MRouteInfoPtr): MRoutePtr;
PROCEDURE MRoutePublic* {midi,-108} (sourcename{8}: ARRAY OF CHAR;
                                     destname{9}: ARRAY OF CHAR;
                                     routeinfo{10}: MRouteInfoPtr): MRoutePtr;

(*--- msg ---*)
PROCEDURE GetMidiMsg* {midi,-114} (dest{8}: MDestPtr): LONGINT; (*ARRAY OF CHAR;*)
PROCEDURE PutMidiMsg* {midi,-120} (source{8}: MSourcePtr; msg{9}: ARRAY OF CHAR);
PROCEDURE FreeMidiMsg* {midi,-126} (msg{8}: ARRAY OF CHAR);
PROCEDURE MidiMsgType* {midi,-132} (msg{8}: ARRAY OF CHAR): INTEGER;
PROCEDURE MidiMsgLength* {midi,-138} (msg{8}: ARRAY OF CHAR): LONGINT;

(* PROCEDURE PutMidiStream* {midi,-144} (source,fillbuffer,buf,bufsize,cursize)(A0/A1/A2,D0/D1) *)

(*--- v1.2 routines ---*)
PROCEDURE LockMRoutes* {midi,-150} ();
PROCEDURE UnlockMRoutes* {midi,-156} ();
PROCEDURE FlushMDest* {midi,-162} (dest{8}: MDestPtr);

(*--- v1.6 routines ---*)
PROCEDURE GetMidiPacket* {midi,-168} (dest{8}: MDestPtr): MidiPacketPtr;
PROCEDURE FreeMidiPacket* {midi,-174} (packet{8}: MidiPacketPtr);
PROCEDURE SetDefaultMRouteInfo* {midi,-180} (dest{8}: MDestPtr;
                                             routeinfo{9}: MRouteInfoPtr);
PROCEDURE CreateMListSignal* {midi,-186} (flags{0}: LONGSET): MListSignalPtr;
PROCEDURE DeleteMListSignal* {midi,-192} (signal{8}: MListSignalPtr);



(*--- Handy macros --------------------------------------------------------*)
(* I translated the tricky bit-shifting to simpler procedures using arrays *)
(* as buffers. I think the "macros" are more readable this way, if slower. *)
(*                                                                 - [phf] *)

(* pack high/low bytes of a word into midi format (7/14 bit math) *)
PROCEDURE MidiHiByte* (w: INTEGER): SHORTINT;
BEGIN
  RETURN SHORT(s.VAL(INTEGER,(s.VAL(SET,s.LSH(w,-7))*{0..6})));
END MidiHiByte;
(* #define MIDI_HIBYTE(word) ( (word) >> 7 & 0x7f ) *)

PROCEDURE MidiLoByte* (w: INTEGER): SHORTINT;
BEGIN
  RETURN SHORT(s.VAL(INTEGER,s.VAL(SET,w)*{0..6}));
END MidiLoByte;
(* #define MIDI_LOBYTE(word) ( (word) & 0x7f ) *)

(* unpack 2 midi bytes into a word (7/14 bit math) *)
PROCEDURE MidiWord* (hi,lo: SHORTINT): INTEGER;
BEGIN
  RETURN (0);
END MidiWord;
(* #define MIDI_WORD(hi,lo) ( (hi & 0x7f) << 7 | (lo & 0x7f) ) *)

(* unpack a 3 byte sys/ex id into single bytes for argument lists and RIMatch initializers *)
PROCEDURE SplitMIDX(id: LONGINT; VAR id0,id1,id2 : BYTE);
TYPE
  TBUF = ARRAY 4 OF SHORTINT;
VAR
  BUF : TBUF;
BEGIN
  BUF := s.VAL(TBUF,id);
  id0 := BUF[1];
  id1 := BUF[2];
  id2 := BUF[3];
END SplitMIDX;
(* #define SPLIT_MIDX(id)  UBYTE)((id)>>16), (UBYTE)((id)>>8), (UBYTE)(id) *)

(* make a 3 byte sys/ex id from single bytes (MAKE_MIDX(msg[1],msg[2],msg[3]) *)
PROCEDURE MakeMIDX(id0,id1,id2: SHORTINT): LONGINT;
TYPE
  TBUF = ARRAY 4 OF CHAR;
VAR
  BUF : TBUF;
BEGIN
  BUF[0] := CHR(0);          (* MSB is always zero *)
  BUF[1] := CHR(id0);
  BUF[2] := CHR(id1);
  BUF[3] := CHR(id2);
  RETURN s.VAL(LONGINT,BUF);
END MakeMIDX;
(*#define MAKE_MIDX(id0,id1,id2) ((ULONG)((id0) & 0xff)<<16 | (ULONG)((id1) & 0xff)<<8 | (ULONG)((id2) & 0xff))*)

(*-------------------------------------------------------------------------*)

BEGIN
  midi := e.OpenLibrary(MidiName,MidiVersion);
  IF (midi = NIL) THEN HALT(0); END;
CLOSE
  IF (midi # NIL) THEN e.CloseLibrary(midi); END;
END Midi.

