[Topic]
Midishare

Common Music supports reading and writing MIDI data in real time and non-real time using Grame's Midishare. CM supports Midishare on OS X and Linux in OpenMCL and CMUCL. The support consists of the stream classes midistream-stream and player-stream that manage IO connections and a handful of auxiliary functions for opening Midishare streams and working with Midishare's MidiEv foreign object. This low-level object is most appropriate for interactive, real-time work using rts and set-receiver!. It is more convenient to use midi objects for non-real time output with the events function.

The Midishare stream

[Class]
midishare-stream

A subclass of midi-stream that implements direct-to-driver MIDI io. This class is automatically chosen when you specify a stream with a ".ms" extension. The name of the stream will become the "client name" used by Midishare. The convenience functions midishare-open and midishare-close are provided for the typical case of a single input/output pair using "Common Music" as the client name.

midishare-stream supports the following slot initializations:

:connections list
A list of two strings specifying the names of the input and output Midishare client applications to connect to. If a connections list is not specified it will default to the value of the global variable *midi-connections*.

Midishare stream functions

A connection between CM and Midishare must first be established before reading or writing any MIDI data. The easiest way to make this connection is to use the following convenience functions.

[Function]
(midishare-open [name])

Opens a connection to a Midishare stream. If name is not provided it defaults to "midi-port.ms" with "Common Music" set as the client application name.

[Function]
(midishare-close [stream])

Closes a connection to optional stream, which defaults to the stream named "midi-port.ms" if it is not provided.

[Function]
(midishare-open? [stream])

Returns one of :in, :out or :inout if the stream is open, otherwise false. If stream is not provided it defaults to the stream named "midi-port.ms".

The Player stream

[Class]
player-stream

A subclass of midishare-stream that implements input and output to a Midishare Player multi-track sequencer application. A player-stream is automatically created when you specify a file with a .mp extension. The name of the player stream will become the player's application name used by Midishare.

player-stream supports the following slot initializations:

:track integer
Events generated to a player are placed in the track number specified by :track according to the value of :seq-mode. If :seq-mode is :replace (the default) then the the track contents at :track are replaced. If :seq-mode is :add then new tracks are created starting at :track and incrementing by 1 each time the events function outputs to the player.
:seq-mode {:replace | :add}
Determines if events sent to the player either replace the current sequence or are added as a new track in the sequence. The default value is :replace.
:play boolean
If true then the player application is automatically started once output to the player has concluded. If :play is false then the player is not automatically started. In either case a player-stream can be controlled interactively in the Lisp interpreter using the functions player-start, player-stop, player-pause and player-cont. The default value of :play is true.
:tempo bmp
The initial tempo of the player, specified in beats per minute. The default value is tempo 60.

Player stream functions

[Function]
(player-cont stream)

Continues playing the Player application associated with the player-stream stream.

[Function]
(player-load-midifile stream file)

Loads the MIDI file file into the Player application associated with the player-stream stream.

[Function]
(player-mute stream track)

Mutes track number in the Player application associated with the player-stream stream.

[Function]
(player-pause stream)

Pauses playing the Player application associated with the player-stream stream.

[Function]
(player-save-midifile stream file)

Saves the sequence in the Player application associated with the player-stream stream to the MIDI file file into

[Function]
(player-set-tempo stream tempo)

Sets the tempo of the Player application associated with the player-stream stream to tempo, in beats per minute. The default tempo is 60.

[Function]
(player-solo stream track)

Solos track number in the Player application associated with the player-stream stream.

[Function]
(player-start stream)

Starts playing the Player application associated with the player-stream stream.

[Function]
(player-stop stream)

Stops playing the Player application associated with the player-stream stream.

[Function]
(player-unmute stream track)

Unmutes track number in the Player application associated with the player-stream stream.

[Function]
(player-unsolo stream track)

Unsolos track number in the Player application associated with the player-stream stream.

The MidiEv foreign object

Once a connection between CM and Midishare has been established, MIDI data can be sent to and from Midishare ports in real time. For real time work it is best to work directly with the low-level MIDI objects that Midishare itself uses. These foreign objects are called MidiEvs.. Special care should be taken working with them because:

You are completely responsible for properly managing the MidiEv's you allocate and use. In some cases this may include explicit deallocation after a MidiEv has been sent or received. Be sure to consult the Midishare manual and the Midishare FFI for information about how to create, read, write and deallocate MidiEv structs.

Common Music adds two functions to Midishare's API: ms:new, a high level MidiEv constructor, and ms:MidiPrintEv, a printer for MidiEv objects. These two functions allow low-level MidiEvs to be manipulated in manner consistent with CLOS objects defined in CM. Note that to reference functions in the Midishare API you must include the package prefix ms: in the function name.

[Function]
(ms:new type {keyword value}*)

Allocates, initializes and returns a foreign Midishare event. Every type of MidiEv is identified by a unique integer type id, a Lisp constant (symbol) with an integer value. This value is followed by zero or more keyword parameters as appropriate for the type of MidiEv returned:

Keyword arguments applicable to all types of MidiEvs:

:port integer
The reference number of the Midishare port to send the event to. Defaults to 0.
:chan integer
The channel number to send the event to. Defaults to 0.
:date integer
The time (in milliseconds) of the event. Defaults to 0.

typeNote (0), typeKeyOn (1), typeKeyOff (2):

:pitch integer
An integer key number 0-127. Defaults to 60.
:vel integer
An integer velocity 0-127. Defaults to 60.
:dur integer
Duration in milliseconds, defaults to 500. Only available for typeNote.

typeKeyPress (3):

:pitch integer
An integer key number 0-127. Defaults to 60.
:pressure integer
An integer pressure 0-127. Defaults to 0.

typeCtrlChange (4):

:controller integer
An integer controller 0-127. Defaults to 0.
:change integer
An integer change 0-127. Defaults to 0.

typeProgChange (5):

:program integer
An integer program 0-127. Defaults to 0.

typeChanPress (6):

:pressure integer
An integer pressure 0-127. Defaults to 0.

typePitchBend (7), typePitchWheel (7):

:bend integer
An integer bend value -8192 to 8191. Defaults to 0 (no bend).

typeSongPos (8):

:lsb integer
An integer 0-127. Defaults to 0.
:msb integer
An integer 0-127. Defaults to 0.

typeSongSel (9):

:song integer
An integer song 0-127. Defaults to 0.

typeClock (10), typeStart (11), typeContinue (12), typeStop (13), typeTune (14), typeActiveSens (15), typeReset (16):

None

typeSysEx (17):

:data list
A list of data bytes. Do not include a leading #xF0 or tailing #xF7 in the list; these markers are added automatically by Midishare.

typeSeqNum (134):

:number integer
A sequence integer 0-127.

typeTextual (135), typeCopyright (136), typeSeqName (137), typeInstrName (138), typeLyric (139), typeMarker (140), typeCuePoint (141):

:text string
A text string, defaults to "".

typeChannelPrefix (142):

:prefix integer
A prefix integer 0-127, defaults to 0.

typeEndTrack (143):

None

typeTempo (144):

:tempo integer
Tempo in quarter notes per minute, defaults to 120.

typeSMPTEOffset (145):

:offset list
A list of SMPTE integer offsets (hr min sec frame subframe).

typeTimeSign (146):

:numerator integer
The upper number of the time signature, defaults to 4.
:denominator integer
The lower number of the time signature, defaults to 4.
:clocks integer
Clocks per quarter, defaults to 24.
:32nds integer
Thirty-seconds per quarter, defaults to 8.

typeKeySign (147):

:sign integer
The number of flats or sharps in the key signature -7 to 7, defaults to 0.
:modeinteger
An integer 0 or 1 where 0 means major and 1 means minor, defaults to 0.

The MIDI Meta message types 134-147 can appear in MIDI files but cannot be sent to an external synthesizer.

[Function]
(ms:MidiPrintEv ev [stream])

Formats the message contents of ev to stream, which defaults to the standard output.

Example 2. Creating and printing a MidiEv.

(define ev (ms:new typeNote :chan 3 :dur 2000))

(ms:MidiPrintEv ev)
#<MidiEv Note [0/3 0ms] 60 64 2000ms>

Accessing and modifying MidiEvs

To access values or set the fields of a MidiEv struct you use the functions provided by the MidiShare API. The more important constructors and accessors are listed here.

[Function]
(ms:MidiNewEv typenum)
[Function]
(ms:MidiCopyEv ev)
[Function]
(ms:MidiFreeEv ev)
[Function]
(ms:evtype ev [val])
[Function]
(ms:port ev [val])
[Function]
(ms:chan ev [val])
[Function]
(ms:date ev [val])
[Function]
(ms:pitch ev [val])
[Function]
(ms:dur ev [val])
[Function]
(ms:vel ev [val])
[Function]
(ms:bend ev [val])
[Function]
(ms:pgm ev [val])
[Function]
(ms:ctl ev [val])
[Function]
(ms:val ev [val])
[Function]
(ms:field ev pos [val])

Example 1. Non-realtime Midishare output using midi objects.

(defun simp (len lb ub rhy dur amp)
  (process repeat len
           output (new midi :time (now)
                       :keynum (between lb ub)
                       :duration dur
                       :amplitude amp)
           wait rhy))

(events (simp 20 60 80 .1 .15 .6) "midi-port.ms")
#<midishare-stream "midi-port.ms">

(midishare-open?)
 :inout

Example 2. Real-time output using MidiEv objects.

(defparameter *ms* (midishare-open ))

(defun zzz (len knum wai vel)
  ;; a process that creates ms:typeNote objects
  (process for i below len
           for k = knum then (between knum (+ knum 12))
           ;; IMPORTANT: the 'at (now)' subclause in the output statement
           ;; is required because MidiEv is a foreign type and has no
           ;; object-time CLOS method.
           output (ms:new typeNote
                    :dur 100
                    :pitch k
                    :vel (round (interp i 0 vel (- len 1) (* vel .3))))
               at (now)
           wait wai))

;;; start the realtime scheduler...

(rts nil *ms*)

;;; ...eval this sprout multiple times, plays in real time...

(sprout (zzz 20 (between 40 80) .1 (between 60 80)) )

;;; ...then stop the rts

(rts-stop )

Example 3. Setting and clearing a Midishare receiver.

;;; Set a receiver that prints out incoming events

(set-receiver! (lambda (ev) (ms:MidiPrintEv ev)) *ms*)

;;; play the keyboard, then clear the receiver

(remove-receiver! *ms*)

Example 4. Receiving and real-time scheduling together.

(defun trigger (ev)
  ;; triggers zzz process in real time
  ;; if ev is NoteOn otherwise deallocate event
  (if (= (ms:evType ev) 1)
      (sprout (zzz 20 (ms:pitch ev) .1 (ms:vel ev)))
    (ms:MidiFreeEv ev)))

;;; start rts and receiver running

(rts nil *ms*)

(set-receiver! #'trigger *ms*)

;;; ...now play the keyboard to sprout processes...
                 
;;; ...then stop rts and clear receiver

(rts-stop)
(remove-receiver! *ms*)

See also: