Serpent and PortMidi

Roger B. Danennberg

Midi and Time Functions

Serpent has an interface to PortMidi, a cross-platform low-level MIDI interface library for MIDI I/O and millisecond-accuracy time. See also the Proc functions that implement periodic timer callbacks, and Windows Shell File Operations that include a local_time() function. See "Notes on MIDI" for help using MIDI under Serpent. Here is the API as seen through Serpent functions (See portmidi.h in PortMidi for detailed documentation):

midi_create()
Create an unopened MIDI stream. Returns an object of type Portmidi. (This is known to Serpent as an "external type".)
midi_in_default()
Return the integer device number for the default MIDI input device.
midi_out_default()
Return the integer device number for the default MIDI output device.
midi_count_devices()
Return the integer number of MIDI devices known to PortMidi.
midi_get_device_info(devno)
Return information about the device with the (integer) device number given by devno. The result is an array containing ["interface_name", "device_name", boolean_is_an_input, boolean_is_an_output].
midi_open_input(midi, devno, buffer_size)
Open midi, a MIDI stream created with midi_create(), for input. The input device number is given by devno, and the buffer size is buffer_size. Returns the PortMidi error code (0 for success). Use midi_close() to close midi when you are finished reading from it with midi_read().
midi_success
A pre-defined global variable equal to zero, the PortMidi success return code.
midi_open_output(midi, devno, buffer_size, latency)
Open midi, MIDI stream created with midi_create(), for output. The output device number is given by devno, and the buffer size is buffer_size. The output latency in milliseconds is given by the integer latency. Returns the PortMidi error code (0 for success). Use midi_close() to close midi when you are finished writing to it with midi_write().
midi_create_virtual_input(midi, name, interface, buffer_size)
Create a virtual input (it will appear as a MIDI output device to other applications) named name, and using interface (use nil for default; currently supported interfaces are "CoreMIDI" and "ALSA" (nothing for Windows). Then, open the virtual device for input as midi, a MIDI stream created with midi_create(). The buffer size is buffer_size. Returns the PortMidi error code (0 for success). Use midi_close() to close midi when you are finished.
midi_create_virtual_output(midi, name, interface, buffer_size, latency)
Create a virtual output (it will appear as a MIDI input device to other applications) named name, and using interface (use nil for default; currently supported interfaces are "CoreMIDI" and "ALSA" (nothing for Windows). Then, open the virtual device for output as midi, a MIDI stream created with midi_create(). The buffer size is buffer_size and latency is latency as with midi_open_output(). Returns the PortMidi error code (0 for success). Use midi_close() to close midi when you are finished.
midi_read(midi)
Read a message from midi, a MIDI stream created with midi_create() and opened with midi_open_input() . If no messages are available, returns nil. Otherwise, an array is returned with two elements. The first is the PortMidi timestamp (integer milliseconds). The second is an Integer containing the message. For short MIDI messages, the low-order bits will be the status byte. Returns the PortMidi error/success code.
midi_write(midi, time, msg)
Send a message to midi, a MIDI stream created wtih midi_create() and opened with midi_open_output(), with timestamp time and message msg, an Integer (see midi_read, above). Returns the PortMidi error/success code. To create a message, you typically use status + (data1 << 8) + (data2 << 16) to make this parameter
midi_close(midi)
Close a PortMidi stream. Returns the PortMidi error/success code.
midi_abort(midi)
Stop output on stream midi.
midi_poll(midi)
Poll for messages. Returns the number of messages available on stream midi without reading any.
time_start(resolution)
Start PortTime timer with the indicated resolution (in milliseconds). Returns nil.
time_get()
Return a double indicating time in seconds. Note that PortTime returns integer milliseconds, but the Serpent API converts the value to a floating-point number in units of seconds.
time_sleep(duration)
Sleep for duration seconds (a floating-point number).

Notes on MIDI

Serpent can query the system to find MIDI devices and their properties, send and receive MIDI data, and use timestamps for accurate input and output timing. However, it is up to the user/programmer to install MIDI device drivers, devices, and to configure devices for use by Serpent (via the PortMidi library). This section contains some suggestions for getting started. Please email the author (rbd at cs.cmu.edu) if you have problems, suggestions, or code contributions.

MIDI input on all systems normally requires a hardware interface to receive MIDI from a keyboard or other MIDI controller. If you do not have MIDI hardware plugged into your computer, do not expect to receive MIDI input data. With no input devices, attempts to open MIDI input will probably fail. Check those error codes!

Windows

Windows is probably the simplest system to set up and use. Windows will normally be configured with its own MIDI mapper virtual MIDI device that routes MIDI messages to some other MIDI handler. Normally, there will be one or more software synthesizers, including one that comes with Windows. You probably also have some sort of synthesis hardware on your PC. The Windows MIDI Mapper is the default output device for PortMidi, so you should be ready to go.

If you have problems getting output, make sure that

Macintosh

The Mac does not have built-in MIDI synthesis, but it is easy to add:
Now, when you run Serpent (initializing its embedded PortMidi library), PortMidi will look for MIDI devices. By default it searches the IAC Bus first, and so "IAC Driver Bus 1" will be the first output device. The first device found is the default output device, so this is probably where you will send MIDI. You have configured SimpleSynth to receive from "IAC Driver Bus 1."

Make sure your audio is enabled and volume is up.

Linux

MIDI support on Linux varies. I use a Planet CCRMA Linux installation that has been configured for music work. There is support for USB MIDI devices. I can also use a software synthesizer, Timidity++. Timidity was originally designed to convert standard MIDI files to audio files, but it can also be configured to listen for MIDI in real time and synthesize audio. Since this is probably the most generic way to get MIDI output using Linux, I will provide details for this method. The "Linux Audio Users Guide - TiMidity Howto" is very useful if you have problems.
Now, see if TiMidity is working by going to portmidi/pm_test and runing ./test (user input is bold).
> ./test
Latency in ms: 1
begin portMidi test...
enter your choice...
1: test input
2: test input (fail w/assert)
3: test input (fail w/NULL assign)
4: test output
5: test both
6: stream test
4
0: ALSA, Midi Through Port-0 (output)
2: ALSA, TiMidity port 0 (output)
3: ALSA, TiMidity port 1 (output)
4: ALSA, TiMidity port 2 (output)
5: ALSA, TiMidity port 3 (output)
Type output number: 2
Midi Output opened with 1 ms latency.
ready to send program 1 change... (type RETURN):
ready to note-on... (type RETURN):
ready to note-off... (type RETURN):
At this point you should have heard a note. Continue typing RETURNs to prompts until the test program quits.

Note in this example that the portmidi output device number was 2. The default output device number is 0 (the first output), so to send MIDI to TiMidity, you will have to open device number 2 rather than the default device.