- [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
.*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)