[Topic]
Portmidi

Common Music supports reading and writing MIDI messages to and from Portmidi, an open-source, cross-platform MIDI device library. CM supports Portmidi on OS X and Linux in OpenMCL, SBCL and CMUCL. The support consists of a portmidi-stream class to manage different Portmidi input/output devices and a handful of auxiliary functions for querying Portmidi about its current configuration and for accessing its millisecond timer. To work with Portmidi you must compile the Portmidi and Porttime libraries as shared libs (libport*.dylib on OS X and libport*.so on Linux) and install in /usr/local/lib. Note also that on Linux you may have to load the snd-seq-* kernel modules before starting CM with Portmidi. These modules are not loaded by default, perhaps something like this will work:

$ modprobe snd_seq_midi snd_seq_oss snd_seq_midi_emul

The Portmidi stream

[Class]
portmidi-stream

Manages resources and connections between CM and Portmidi input and output devices. This class is automatically chosen when you specify a stream with a .pm extension. The convenience functions portmidi-open and portmidi-close are provided for the typical case of a single input/output pair. Opening a portmidi-stream initializes the library and starts the millisecond timer if they have not been initialized.

portmidi-stream supports the following slot initializations:

:input {string | integer | boolean}
The name or integer identifier of a Portmidi input device to open or boolean true or false. The function pm:GetDeviceInfo can be used to determine the names and ids of available Portmidi devices. If the value is boolean true then Portmidi's default input device is opened, as determined by pm:GetDefaultInputDeviceID. If the value is false then no input device is opened. If no value is specified the input device defaults to the global variable *portmidi-default-input*.
:output {string | integer | boolean}
The name or integer identifier of a Portmidi output device to open or boolean true or false. The function pm:GetDeviceInfo can be used to determine the names and ids of available Portmidi devices. If the value is boolean true then Portmidi's default output device is opened, as determined by pm:GetDefaultOutputDeviceID. If the value is false then no output device is opened. If no value is specified the output device defaults to the global variable *portmidi-default-output*.
:latency integer
A millisecond value that determines how Portmidi treats MIDI message timestamps. If the value is 0 then Portmidi operates in "realtime" mode and MIDI messages are sent to the destination device as soon as Portmidi receives them, regardless of message timestamps. If the latency value is greater than zero then Portmidi delays future messages until timestamp+latency. The default value is 100 milliseconds. If no value is specified the latency defaults to the global variable *portmidi-default-latency*.
:receive-mode {:message | :raw}
Determines what values are passed to a Portmidi receiver hook established by set-receiver!. If the value is :message then the receiver hook is passed two values: the incoming MIDI message its a millisecond timestamp. If the value is :raw then the hook is passed the Portmidi event buffer (a pointer to a foreign array) and the number of events read. See the EventBuffer functions in portmidi.lisp for more information about reading raw events from Portmidi.
:inbuf-size integer
The size of the event buffer that will be allocated to hold incoming midi data when the input device is read. If no value is specified the latency defaults to the global variable *portmidi-default-inbuf-size*.
:outbuf-size integer
The size of the event buffer Portmidi allocates to queue future messages. If no value is specified the latency defaults to the global variable *portmidi-default-outbuf-size*.
:filter integer
An optional filter value. Filtered messages will be ignored by the open Portmidi devices. The value can be a logical OR (logior) of the following constants:

pm:filt-active pm:filt-sysex pm:filt-clock pm:filt-play pm:filt-f9 pm:filt-fd pm:filt-reset pm:filt-note pm:filt-channel-aftertouch pm:filt-poly-aftertouch pm:filt-program pm:filt-control pm:filt-pitchbend pm:filt-mtc filt-song-position pm:filt-song-select pm:filt-tune pm:filt-tick pm:filt-undefined pm:filt-realtime pm:filt-aftertouch pm:filt-systemcommon.

If no value is specified the filter defaults to the global variable *portmidi-default-filter*.
:mask integer
An optional bit mask where each 1-bit enables a channel in the open device. For example the mask value #b1101 will enable messages only on channels 1, 3 and 4. By default all channels are enabled. If no value is specified the mask defaults to the global variable *portmidi-default-mask*.

Auxiliary Functions and Variables

CM provides some a few functions and variables to facilitate working with Portmidi. Some of the functions are exported from the Portmidi package and should be referenced using the pm: package prefix.
[Function]
(portmidi-open . args )

Opens a portmidi-stream called "midi-port.pm" according to the keyword initialization args passed to the stream. The function pm:GetDeviceInfo can be used to determine the names and ids of available Portmidi devices.

[Function]
(portmidi-open?)

Tests to see if "midi-port.pm" has already been opened, returns one of four possible values: :in, :out, :inout or false.

[Function]
(portmidi-close)

Closes the portmidi-stream called "midi-port.pm" if it is open, otherwise has no effect.

[Function]
(portmidi-record! seq [stream])

Records MIDI data from the open input stream into seq. Returns no values. If stream is not supplied it defaults to "midi-port.pm". If seq already contains objects when the recording starts the new objects will be inserted into the existing list. The first recorded object is always placed at time 0.0 in seq. To stop recording call portmidi-record! with false for seq.

[Function]
(pm:CountDevices)

Returns the number of available Portmidi devices.

[Function]
(pm:GetDeviceInfo . id)

If id is not specified, returns a list of the device ids (integer), names (string) device types (keyword) and open status (boolean) of each available Portmidi device. If id is specifed only information for that device is returned. Calling this function initializes the Portmidi library if it has not already been initialized. Note that MIDI devices added after the Portmidi library has been initializd will not be reflected in the list of descriptions.

[Function]
(pm:GetDefaultInputDeviceID)

Returns the id (integer) of Portmidi's default input device.

[Function]
(pm:GetDefaultOutputDeviceID)

Returns the id (integer) of Portmidi's default output device.

[Function]
(pm:Time)

Returns the current millisecond time from Porttime. This function should not be called unless a Portmidi stream has already been opened or you have called the pm:TimeStart function to initialize Porttime first.

[Variable]
*portmidi-default-input*

The input device to open if none specified to portmidi-open. The default value is true. The variable can be respecified in a .cminit.lisp file.

[Variable]
*portmidi-default-output*

The output device to open if none specified to portmidi-open. The default value is true. The variable can be respecified in a .cminit.lisp file.

[Variable]
*portmidi-default-latency*

The portmidi latency if none specified to portmidi-open. The default value is 100 milliseconds. The variable can be respecified in a .cminit.lisp file.

[Variable]
*portmidi-default-outbuf-size*

The input buffer size if none specified to portmidi-open. The default value is 64. The variable can be respecified in a .cminit.lisp file.

[Variable]
*portmidi-default-outbuf-size*

The output buffer size if none specified to portmidi-open. The default value is 256. The variable can be respecified in a .cminit.lisp file.

[Variable]
*portmidi-default-filter*

The message filter specification if none specified to portmidi-open. The default value is (). The variable can be respecified in a .cminit.lisp file.

[Variable]
*portmidi-default-mask*

The channel mask specification if none specified to portmidi-open. The default value is 0. The variable can be respecified in a .cminit.lisp file.

Examples:

Example 1. Accessing Portmidi information and opening a Portmidi stream.

(pm:CountDevices)
 4
(pm:GetDeviceInfo)
 ((:id 0 :name "IAC Driver: IAC Bus 1" :type :input :open #f)
    (:id 1 :name "1x1: Port 1" :type :input :open #f)
    (:id 2 :name "IAC Driver: IAC Bus 1" :type :output :open #f)
    (:id 3 :name "1x1: Port 1" :type :output :open #f))
(pm:GetDefaultInputDeviceID)
 0
(pm:GetDefaultOutputDeviceID)
 2
(portmidi-open :output 3 :input #f)
 #<portmidi-stream "midi-port.pm" (out:3)>
(portmidi-open?)
 :out
(pm:Time)
 36993
(portmidi-close)
 #t

Example 2. Writing MIDI events.

;; midi output should be open with latency>0
(portmidi-open :output 3 :input #f :latency 500)
 #<portmidi-stream "midi-port.pm" (out:3)>
(define (playsome len lb ub)
  (process repeat len
           for d = (pick .4 .2 .2)
           output (new midi :time (now) 
                       :duration (* d 1.5)
                       :keynum (between lb ub)
                       :amplitude (odds .25 .8 .4))
           wait d))

(events (playsome 20 60 90) "midi-port.pm")
 #<portmidi-stream "midi-port.pm" (out:3)>
(portmidi-close)
 #t

Example 3. Recording messages.

(define *pm* (portmidi-open :latency 0 :input 1 :output 3))

(define myseq (new seq))

(portmidi-record! myseq)

;; stop recording

(portmidi-record! #f)

(list-objects myseq)

0. #i(midi time 0.0 keynum 60 duration 0.159 amplitude 0.46456692 channel 0)
1. #i(midi time 0.495 keynum 64 duration 0.147 amplitude 0.6771653 channel 0)
2. #i(midi time 0.834 keynum 67 duration 0.181 amplitude 0.61417323 channel 0)
3. #i(midi time 1.108 keynum 65 duration 0.144 amplitude 0.6062992 channel 0)
4. #i(midi time 1.47 keynum 62 duration 0.214 amplitude 0.61417323 channel 0)
5. #i(midi time 2.848 keynum 60 duration 0.155 amplitude 0.62204725 channel 0)
6. #i(midi time 2.837 keynum 64 duration 0.179 amplitude 0.72440946 channel 0)
7. #i(midi time 2.847 keynum 67 duration 0.191 amplitude 0.56692916 channel 0)

See also: