Zbex MIDI instructions
Previous chapter Graphics |
Zbex Manual |
Next Chapter None |
MIDI stands for Musical Instrument Digital Interface. MIDI is the dominant method for sending data to and receiving data from a music keyboard. In order for Dmuse running on Linux to interface with a MIDI keyboard, your system needs two things.
Linux MIDI preliminaries
Getting MIDI working in Linux is highly dependent on what version of Linux you have and what is currently installed or needs to be installed. Here are some basic guidelines for Fedora 6:
(1) You need to have the ALSA (Advanced Linux Sound Architecture) Driver installed on your system. You can determine whether the ALSA system is installed on your computer by typing the command:
cat /proc/asound/version
which should return something like:
Advanced Linux Sound Architecture Driver Version 1.0.17
If you do not get positive response to the command above, then you will need to go on line and download the Alsa system. The web site for the ALSA project is www.alsa-project.org You will need to download the alsa-lib and the alsa-utils and you will need a makefile, which looks something like this:
all: unpack compile download: Tab ⇆wget -i source.txt unpack: Tab ⇆tar xvjf alsa-lib-1.0.20.tar.bz2 Tab ⇆tar xvjf alsa-utils-1.0.20.tar.bz2 compile: Tab ⇆(cd alsa-lib-1.0.20; ./configure) Tab ⇆(cd alsa-lib-1.0.20; make install)
(2) You need to have hardware and drivers that connect your computer (probably via a usb port) to a keyboard. I am using a MidiMan 1x1 usb MIDI device.
To get and install the drivers, following the instructions on
http://usb-midi-fw.sourceforge.net
(1) Download fxload RPM package:
wget ftp://rpmfind.net/linux/fedora/updates/7/i386/fxload-2002_04_11-6.fc7.i386.rpm
(2) Install fxload rpm:
rpm --install fxload-2002_04_11-6.fc7.i386.rpm
(3) Download midisport firmware package:
wget http://softlayer.dl.sourceforge.net/sourceforge/usb-midi-fw/midisport-firmware-1.2.tar.gz
--> (4) tar xvzf midisport-firmware-1.2.tar.gz
--> (5) cd midisport-firmware-1.2
--> (6) ./configure
--> (7) make
(8) As root, type:
--> make install
(9) Now, when you plug in a MidiSport 1x1 USB device on the computer the green LED near the USB output should start pulsing, which indicates that it is activated properly and ready to use with the ALSA system. You can check that you have ALSA installed on your system by running this command:
cat /proc/alsa/version
Which will reply something like this:
Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2007 UTC).
When you have done these things, you need to give Dmuse the information it needs to talk to the MIDI port. This information is transmitted in your INIT file in Record 48. When Dmuse is first installed, this record has an "N" in column 17. This must be changed to a "Y", indicating that MIDI has been installed. There must be a space in column 18. This is followed by the port name and the name of the hardware you are using. The length of the hardware name need only be long enough to identify the hardware uniquely. The port name and the hardware name must be separated by a semi-colon, and must be in double quotes. Dmuse also needs to know the path location of the "midi_in" program. On my system, Record 48 of the INIT file looks like this:
MIDI Installed: Y "hw:1,0,0;MidiSport" "/usr/local/apps/disp"
Craig Stuart Sapp has written a program called alsarawportlist which should be in the same path location as the program midi_in. If you run this program, and if the ALSA system is installed on your computer, You will get a list of MIDI devices and hardware names on your computer, from which you can construct Record 48 in your INIT file.
If your MIDI interface has been installed properly, you can communicate with a music keyboard using the following five Zbex MIDI instructions:
midistart midistop midiget str midiput str-exp miditime int
Before explaining these instructions, it should be pointed out that Dmuse can communicate with MIDI though only one Zbex program at a time. If another Zbex program (running in another window) has any midi instructions whatsoever, executing or not, a subsequent Zbex program containing any midi instructions will compile, but will not run. Instead, it will exit with the message:
Unable to run program. Midi in use by another zbex program.
Apple OSX MIDI preliminaries
CoreMIDI should be already installed on your Mac OS X computer.
midistart
Format: midistart stands alone and takes no arguments.
Operation:
Midistart is the command that sets up the operation of the MIDI interface. No other midi commands will execute unless and until the midistart command has been issued. Midistart starts the MIDI timer and clears the MIDI I/O buffers and opens the snd_rawmidi write interface.
Midistart checks to see if midi is "plugged in." If you are using a usb MidiSport, for example, you might have everything installed on your system, but the MidiSport might be unplugged. In this case, midistart will exit with the message:
Unable to open the midi interface at line = xxx. Check to see if the midi interface is connected. Wait 10 seconds after connecting.
Midistart actually has two modes of execution depending on whether or not the Zbex program contains the midiget instruction. Without the midiget instruction, MIDI operation is relatively simple. MIDI data is sent out to the interface whenever your program executes a midiput instruction, and you can select when to do this by checking the MIDI timer with the miditime instruction. With midiget, however, things get more complicated. Dmuse needs to fork, creating a child process, which executes a separate program called "midi_in." When started, midi_in simply "listens" for input from the music keyboard (or other MIDI device) and passes time-stamped data back to Dmuse through a FIFO pipe. If your MIDI interface becomes disconnected, or midi-in from the keyboard stops working for any reason, it becomes impossible for Dmuse to talk to the child process (actually, the child process stops listening for commands from Dmuse). Under these conditions, if Dmuse tries to stop the child (with a midistop instruction), this will freeze the entire Dmuse environment (as presently coded). This is why we check first to see that the MIDI interface is plugged in before we startmidi.
Note: If you are an experienced Linux user, you can unfreeze Dmuse by using the ps command to identify the process number of the child process running "midi_in." You can then use the kill command to terminate "midi_in," which will cause a "pipe-closed" signal to be sent to Dmuse and allow Dmuse to complete the midistop procedure.
midistop
Format and Operation:
The midistop command takes no argument. Operation depends on the current running state of the Zbex program. If the MIDI interface is currently active, the midistop command begins by closing the snd_rawmidi write interface. If the Zbex program does not contain the midiget instruction (i.e., was not opened for midiget), this completes the stop operation. If Zbex was open for midiget, then these addtional steps must be completed.
- Close the FIFO pipe Dmuse -> midi_in. This tells the midi_in program (running in the child process) that MIDI operations are to be stopped.
- Wait for midi_in to acknowledge this message. (Midi_in does this by closing its end of the midi_in -> Dmuse pipe.) This step also insures that the child process (midi-in) is terminated.
- Close the FIFO pipe (read end of) midi_in -> Dmuse.
- Remove the process id for the child process.
Note: this sequence can be fouled up if the MIDI_IN connection from the MIDI device to the computer is broken. A broken connection will cause the midi_in program (running in the child process) to get stuck in the loop which looks for input from the MIDI device. Since it is stuck there, it cannot check for a close-pipe signal from Dmuse. Consequently, Dmuse will hang on step (2) of the above sequence. If Dmuse simply skips step (2) after a timeout, then the child process is left spinning and is never terminated, and step (4) creates a "zombie."
midistop does not generate error messages.
midiget
Format: midiget takes one argument: an string variable (without subscripts)
Operation:
midiget is used to read MIDI data that has come in from the MIDI interface. The data is actually read by the independent program midi_in, running as a child process. Midi_in reads MIDI data as it comes in and appends a time stamp to each midi packet (which consists of a midi command and the proper number of data bytes).
If the MIDI interface is not currently activated for midiget (with the midistart instruction), the Zbex program is terminated with an error message. Otherwise Zbex attempts to read "atomic" events from the (circular) pipe buffer. An atomic event consists of a four byte time stamp, plus a complete midi packet. Since midi_in "write events" to the FIFO pipe are asynchronous with the Dmuse Zbex interpreter, we cannot be certain that the data present in the pipe buffer at any particular time is complete. Therefore a read (and removal) from the pipe buffer is done only when a complete "atomic" event is found.
If no complete atomic events are found, midiget returns the null string. Otherwise, midiget will return one or more complete atomic events in the output string, provided there is space available in the string. If adding an atomic event to the output string would cause its maximum length to be exceeded, the atomic event is not read (not removed from the circular buffer).
An atomic event should always start with an 0xff byte. If this is not the case, a flag is set, and all further data is assumed to be corrupted. In this event, all further calls to midiget will simply return the null string. The data_integrety_flag is reset when MIDI is stopped.
The time stamp consists of four bytes: 0xff + a 3 byte number. (maximum value = 16,777,215) The midi_in "timer" is started when the midistart command is executed. Timer units are in milliseconds. (16,777,215 milliseconds is approximately 4 and 2/3 hours). From the standpoint of collecting data, it is the difference in time stamps that matters, not the absolute value. The 4 hour upper limit on time stamp should not present a logical problem to any running Zbex program.
A midi packet consists of a midi command (one byte) and from 0 to 2 data bytes, depending on the command. Midiget will read as many atomic events as permitted by the size of the input string, and if possible all atomic events in the (circular) pipe buffer.
midiput
Format: midiput takes one argument: a string expression (subscripts allowed)
Operation:
midiput is used to send MIDI data directly to the MIDI interface with no time delay.
If the MIDI interface is not currently activated (with the midistart instruction), the Zbex program is terminated with an error message. Otherwise, contents of the string is transferred to the (non-circular) MIDI-out buffer, space permitting. Cycling of the Zbex program is then stopped and control is returned to the Dmuse main module, which "writes" the contents of the MIDI-out buffer to the MIDI interface and resets the buffer pointers.
miditime
Format: miditime takes one integer variable as vehicle for input.
Operation:
One of the things midistart does when it is first called is to read the time-of-day. A call to miditime also reads the time-of-day. A difference is calculated and the number of transpired milliseconds is computed. This number is returned in the integer variable.
If miditime is called without first calling midistart, the Zbex program is terminated with an error message.
Use of miditime:
Miditime is used in conjunction with midiput to send music (MIDI commands) out to the midi interface.
A MIDI file consists of a series of MIDI commands, each one preceded by a time delay (which can be zero). A the top of a MIDI file are two numbers: the number of divisions per quarter note (e.g. 240), and the tempo number (measured as the number of u-secs per quarter note). The time delay numbers are measured in divisions. Typically, the number of divisions per quarter note does not change in a MIDI file. The tempo number, however, can be changed. This is how tempos are changed in a MIDI performance.
In order for a Zbex program to send out MIDI commands at the proper time, there needs to be a delay procedure which can "spin" for a specified length of time (i.e., a specified interval called a division or a "tick." And what is a "tick" in terms of milliseconds? Well this depends on the tempo.
If the tempo is 60 quarters per minute, which is one quarter per 1,000 milli-secs, and there are 240 divisions in a quarter, then one division is 1,000 / 240 = 4.16667 milli-secs. Since this unit contains a significant rounding error (to the nearest integer), we cannot use it directly, but must work with a cumulative counter. We define the procedure tick as follows:
procedure tick int i,j,k ++midi_ticks k = midi_ticks * 2000 / tempo_mult loop miditime i if i > k return end repeat return
where tempo_mult is set as follows, assuming the string temp{1,6} contains the tempo setting MIDI command, and divspq = number of divisions per quarter note:
if temp{1} = chr(255) and temp{2} = chr(0x51) s4 = temp{3,4} <-- n-sec. per quarter usec_pq = ors(s4) h = 2000000 * divspq / usec_pq putc ~h divisions per (2 seconds) tempo_mult = h temp = temp{7..} end
Note that this code resolves the tempo_mult variable to the number of divisions per (2 second), which gives us some extra resolution in terms of tempo variances.
In this scheme, the midi_ticks "clock" keeps increasing, so fractions parts of the fundamental unit (division) also accumulate. And since most time intervals that we can detect are in the range of 10 or more divisions, the resolution error goes undetected. The "spin" portion of the procedure simply makes repeated calls to miditime until the "timer" exceeds the target. And the tick procedure is called in the main program for the exact number divisions that are requested as a time delay.
Note: This code only works when the tempo is constant. A change in the tempo number will change the tempo_mult variable and completely screw-up the value of k in the procedure tick. In order to fix this problem, the value of k would need to be the sum of a "total elapsed time at the point where a new tempo is set" plus "the increment since that setting." You write the code!
Previous chapter Graphics |
Zbex Manual |
Next Chapter None |