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
- The volume is turned up (test by playing an audio file).
- PCs have built-in audio mixers that allow you to adjust the
volume separately for synthesizers, audio (wave) out, CD players, etc.,
so if in doubt, turn up the volume on at least the wave out (in case
you are using a software synthesizer) and synthesizer out (in case you
are getting sound from hardware synthesis).
- If you've played with MIDI on your computer, it's possible that
you have configured your MIDI Mapper to output to certain channels or
devices. You may have to check the configuration.
Macintosh
The Mac does not have built-in MIDI synthesis, but it is easy to add:
- Under Applications > Audio Midi Setup (or Applications >
Utilities > Audio Midi Setup), you can select MIDI Devices and open
IAC Driver. Then under, Ports, configure a port named "Bus 1" and
configure one input and one output connector for that port.
- Download and install SimpleSynth from http://notahat.com/simplesynth/
- Find the newly installed program: Applications > SimpleSynth
and run it.
- For the MIDI Source, select "IAC Driver Bus 1"
- Do not exit SimpleSynth,
but you can select the yellow button to minimise its window.
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.
- Download Timidity++ sources
from SourceForge (I already had a TiMidity, but it was not configured
to receive real-time MIDI, so I built a new version from these sources.)
- After expanding the source archive, go to the top-level TiMidity
directory and type
./configure
--enable-audio=alsa --enable-alsaseq --enable-gtk;make
- If all goes well, install
by becoming root (su) and running this:
make install
- TiMidity will complain about a configuration file:
timidity: Can't read any configuration file.
Please check /usr/local/share/timidity/timidity.cfg
Since I had a previous installation, I used locate timidity.cfg
to find the file (in /usr/share/timidity/) and copy it to
/usr/local/share/timidity. If you do not have timidity.cfg,
see
the "Linux
Audio Users
Guide - TiMidity Howto" about installing sound fonts.
- Run TiMidity as follows:
timidity -iA -B2,8 -Os -EFreverb=0
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.