After you've done the above once, then you can do your REXX GUI message loop, calling GuiGetMsg(). When a MIDI message arrives at your input port, Reginald will call that window's WM_EXTRA event handler, passing it whatever message number you specified to MidiIoParams. (If you do not specify a message number, it defaults to 1124). The second arg passed return with RXWIND set to your window's ID, RXID = 'MIDI', and RXSUBID set to an "event ID" that you can pass to MidiIoInput() in order to retrieve the actual data bytes for that MIDI message. If MidiIoInput successfully retrieves the data bytes for you, it will return an empty string. Otherwise, it returns an error message.
Here then is a skeleton for setting up a REXX GUI window for MIDI input, opening the default MIDI In port, and then detecting whenever a MIDI message arrives at that port:
/* GUIBEGIN WINDOW , 54, 263, 257, 54, POPUP | CAPTION | SYSMENU | MINBOX | MAXBOX | THICK, , MIDI Input example FONT 8, 400, MS Shell Dlg TEXT 6, 7, 72, 8, , , , , Received MIDI event: TEXT 5, 45, 247, 8, , , InputEvent PUSH 140, 24, 40, 14, DEFAULT | TABSTOP, , RecordButton, ALT "R", &Record DEND GUIEND */ OPTIONS 'C_CALL' NUMERIC DIGITS 10 /* Load and register functions in REXXGUI.DLL and RXMIDIIO.DLL. */ LIBRARY rexxgui, rxmidiio GuiErr = 'SYNTAX' GuiHeading = 1 MidiErr = 'SYNTAX' MidiHeading = 1 GuiCreateWindow('NORMAL') DO /* Associate our window with MIDI Input by passing the handle to * MidiIoParams(). If an error, SYNTAX is raised. */ MidiIoParams(GuiWindow) /* Open the default MIDI In port. */ MidiIoOpenPort(0, 'IN') CATCH SYNTAX CONDITION('M') RETURN END /* We're recording. */ Recording = 'START' Again: DO FOREVER GuiGetMsg() IF EXISTS('GuiObject') == 0 THEN DO IF EXISTS('GuiSignal') THEN DO NOP END END /* We have no Child Window Layout scripts, so we can skip any further checking * of GuiSignal and GuiObject. */ CATCH HALT CATCH SYNTAX CONDITION('M') SIGNAL Again FINALLY /* Close the MIDI In port. */ MidiIoClosePort('I') GuiDestroyWindow() END RETURN /* Called by Reginald when the user clicks on the "Record" button. */ WM_CLICK_RecordButton: /* If we're currently recording, then stop. Otherwise, start. */ IF Recording == 'STOP' THEN Temp = 'START' ELSE Temp = 'STOP' MidiIoRecord(Temp) /* Save the new mode. NOTE: If an error, SYNTAX was raised, and we've * jumped to the CATCH SYNTAX before we get here. */ Recording = Temp RETURN /* Called by Reginald when our window receives an event that REXX GUI itself * doesn't know about. REXX GUI does not know about MIDI events, * so it calls our "EXTRA" event handler. It passes two args, which are the * data for the event. The third arg is the message number. For an RxMidiIo * event, the default message number is 1124. The second arg is an event * ID that we pass to MidiIoInput to get the data bytes for the event. */ WM_EXTRA: /* Is it a message from RxMidiIo? */ IF ARG(3) == 1124 THEN DO /* Get the actual data bytes in the stem variable 'MyStem'. * MyStem.0 will be a count of how many data bytes there are, * and MyStem.1 to myStem.xxx will be those data bytes. Here, * we just display them. NOTE: We pass the event ID. */ MidiIoInput(ARG(2), 'MyStem') /* Display the data bytes. */ DO i = 1 TO MyStem.0 SAY MyStem.i END END /* Don't let Rexx Gui process this event. */ RETURN ""
MIDIEvent variable
As you can see from the above, MidiIoInput() is used to retrieve the actual MIDI data bytes of a single MIDI message. If you supply a stem variable name, then MidiIoInput() stores each byte in its own compound variable using that stem, and sets a count of how many bytes have been stored. For example, if you supply a stem variable name of MyStem, then MyStem.1 will be the first data byte (ie, the MIDI status byte), MyStem.2 will be the second data byte (if there is a second byte), MyStem.3 will be the third data byte, etc. MyStem.0 will be a count of how many bytes have been stored (ie, how many compound variables have been set).
If preferred, you can omit passing any variable name to MidiIoInput(). In this case, MidiIoInput() will automatically use the stem variable MIDIEvent, just like it is used with RxMidi's MIDIGetEvent(). That is, the event number will be stored in MIDIEvent.!Type. The MIDI channel will be stored in MIDIEvent.!Channel. (This will be an empty string if the event has no channel). And the remaining data bytes will be stored in MIDIEvent.!Data1 and perhaps MIDIEvent.!Data2 according to the Types of events chart.
Here then is an alternate way to retrieve and display an incoming MIDI message:
WM_EXTRA: /* Is it a message from RxMidiIo? */ IF ARG(3) == 1124 THEN DO /* Get the actual data bytes in the stem variable 'MIDIEvent'. */ i = MidiIoInput(ARG(2)) /* Display the bytes. */ IF i \== "" THEN DO SAY "Type of event =" MIDIEvent.!Type /* For system exclusive, the data bytes are stored verbatim into MIDIEvent.Data1. * It's like using MIDISysex to retrieve the bytes, using the 'A' option. To * get each byte into REXX numeric format suitable for performing math on it, * or testing/displaying its value, we need to break off that byte and use * C2D() to convert it to REXX numeric format. */ IF MIDIEvent.!Type == 240 THEN DO p = 1 DO i /* Do all the data bytes. */ byte = C2D(SUBSTR(MIDIEvent.!Data1, p, 1)) /* Break off and convert next byte. */ SAY 'Data' p '=' byte p = p + 1 END /* LENGTH of sysex. */ END /* All other types except SysEx may have a channel, and Data2, and are already * stored in a suitable format for REXX. */ ELSE DO IF MIDIEvent.!Channel \== "" THEN SAY "MIDI Channel =" MIDIEvent.!Channel SAY "MIDI Data 1 =" MIDIEvent.!Data1 IF MIDIEvent.!Data2 \== "" THEN SAY "MIDI Data 2 =" MIDIEvent.!Data2 END END /* Display the bytes. */ END /* Don't let Rexx Gui process this event. */ RETURN ""
Inputting System Exclusive
In order to allow System Exclusive messages to be input, you must pass a second argument to MidiIoParams -- the desired size of a buffer to hold incoming messages. This must be at least as big as the largest single message you expect to receive. But, if a device will be sending Sysex messages faster than you can process them, it's advisable to specify a buffer size twice the size of the largest message (or even larger if MidiIoInput reports an error return about a buffer overrun).
For example, here we set a buffer size of 1024:
MidiIoParams(GuiWindow, 1024)
Filtering events
Normally, all incoming MIDI messages are received, except for Active Sense. But you can choose which messages you wish to be received. You must pass a third argument to MidiIoParams, which is a list of which types of events you wish to be received. Each event number is separated by a space. For example, here I receive only Note On (event number 90) and Note Off (event number 80):
MidiIoParams(GuiWindow, , '90 80')You can also filter by channel. You must pass a fourth argument to MidiIoParams, which is a list of which channels you wish to be received. Each channel number is separated by a space. Here I receive only events on MIDI channels 1, 2, and 16:
MidiIoParams(GuiWindow, , , '1 2 16')And you can combine the two filtering options. Here I receive only program change messages on channel 10:
MidiIoParams(GuiWindow, , '192', 10)Rather than specifying which event types or channels to receive, you can specify which not to receive. Simply specify a \ as the first character. For example, here we choose to receive all types of events except Active Sense, MIDI Clock, and System Exclusive:
error = MidiIoParams(GuiWindow, , '\ 254 248 240')
Echoing events
If you desire, you can have certain received MIDI messages echoed (ie, retransmitted) to the computer's MIDI Out. This is useful if you are receiving messages from a controller, and wish to echo them to some MIDI sound module attached to MIDI Out. You must pass a fifth argument to MidiIoParams, which is a list of which types of events you wish to be echoed. Each event number is separated by a space. For example, here I echo only Note On (event number 90) and Note Off (event number 80):
error = MidiIoParams(GuiWindow, , , , '90 0')Note that you may echo events that you have nevertheless set to be filtered (with the exception of System Exclusive. SysEx must not be filtered in order to be echoed).
So too, you can set which MIDI channels you wish to be echoed. You must pass a sixth argument to MidiIoParams, which is a list of which channels you wish to be echoed. Here we echo all events on channel 10.
error = MidiIoParams(GuiWindow, , , , , 10)
Start/Stop input
To temporarily stop the input of all MIDI messages, you can call MidiIoRecord(), passing either no arguments, or an argument of 'STOP'.
To subsequently resume input, call MidiIoRecord() with an argument of 'START' as so:
error = MidiIoRecord('START')