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.
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*midi-connections*.
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.
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.
Closes a connection to optional stream, which defaults to the stream named "midi-port.ms" if it is not provided.
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".
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: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}:replace.
:play boolean: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 bmpFunction
(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.
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.
(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:chan
integer:date
integertypeNote
(0), typeKeyOn (1), typeKeyOff (2):
:pitch integer:vel integer:dur integertypeNote.
typeKeyPress (3):
:pitch integer:pressure integertypeCtrlChange (4):
:controller integer:change integertypeProgChange (5):
:program integertypeChanPress (6):
:pressure integertypePitchBend (7),
typePitchWheel (7):
:bend integertypeSongPos (8):
:lsb integer:msb integertypeSongSel (9):
:song integertypeClock (10),
typeStart (11),
typeContinue (12),
typeStop (13),
typeTune (14),
typeActiveSens (15),
typeReset (16):
None
typeSysEx (17):
:data listtypeSeqNum (134):
:number integertypeTextual (135),
typeCopyright (136),
typeSeqName (137),
typeInstrName (138),
typeLyric (139),
typeMarker (140),
typeCuePoint (141):
:text stringtypeChannelPrefix (142):
:prefix integertypeEndTrack (143):
None
typeTempo (144):
:tempo integertypeSMPTEOffset (145):
:offset listtypeTimeSign (146):
:numerator integer:denominator integer:clocks integer:32nds integertypeKeySign (147):
:sign integer:modeintegerThe MIDI Meta message types 134-147 can appear in MIDI files but cannot be sent to an external synthesizer.
(ms:MidiPrintEv ev [stream])Formats the message contents of ev to stream, which defaults to the standard output.
;; 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>
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.
(ms:MidiNewEv typenum)
(ms:MidiCopyEv ev)
(ms:MidiFreeEv ev)
(ms:evtype ev [val])
(ms:port ev [val])
(ms:chan ev [val])
(ms:date ev [val])
(ms:pitch ev [val])
(ms:dur ev [val])
(ms:vel ev [val])
(ms:bend ev [val])
(ms:pgm ev [val])
(ms:ctl ev [val])
(ms:val ev [val])
Function
(ms:fieldev pos [val])
;; 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 ;; 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 ) ;; 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*) ;; 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*)
events [Function]rts [Function]set-receiver! [Function]