- [Topic]
- MIDI
Common Music supports reading and writing all types of MIDI messages to MIDI files and to the Midishare real time operating system. Two levels of MIDI support are provided:
- High level support consists of Lisp classes and methods for generating music to MIDI score files and to MidiShare in non-real time mode.
- Low level support provides an interface to the Midishare real time MIDI system for "interactive" work (OpenMCL/OS X only).
High level MIDI support
High level MIDI support consists of class definitions of MIDI scores and events and functions that operate on them in non-real time. Note that non-real does not imply slower than real time -- in most cases the high level interface generates output many times faster than real time. High level support is documented by main entries in the Common Music dictionary, the following topic list provides links to this documentation:
MIDI event classes
midimidi-note-onmidi-note-offmidi-key-pressuremidi-control-changemidi-program-changemidi-channel-pressuremidi-pitch-bendmidi-system-eventmidi-sequence-numbermidi-text-eventmidi-eotmidi-tempo-changemidi-smpte-offsetmidi-time-signaturemidi-key-signaturemidi-sequencer-event
MIDI score classes
MIDI functions
Low level MIDI support
The low level MIDI interface provides the ability to read and write MIDI messages in real time on:
- OS X MidiShare using OpenMCL (release 0.14.1-p1 or higher.)
Opening and closing the MIDI port
In order to work with Midishare in real time a connection between CM and MidiShare must be established. This connection is made using an object called the midi port, which must first be opened before any MIDI events can be sent or received.
- [Function]
(midi-open)
Opens connection to MidiShare.
- [Function]
(midi-close)
Closes connection to MidiShare.
- [Function]
(midi-open?)
Returns the midi port object if MidiShare is open, otherwise false.
- [Variable]
*mp*
CM's reference number in MidiShare when the MIDI port is open, otherwise false.
Example 1. Opening the MIDI port.
(midi-open?) ⇒ nil (midi-open) ⇒ #<midi-port "midi.port"> (midi-open?) ⇒ #<midi-port "midi.port"> *mp* ⇒ 1
Midishare's MidiEv struct
Once a connection between CM and MidiShare has been established, MIDI messages can be sent to and from MidiShare ports in real time. MidiShare implements MIDI messages using a C struct called a MidiEv. MidiEvs are foreign objects in Lisp:
- they are allocated and deallocated outside of Lisp's memory partition
- they are not part of the Lisp data type system
- they are not managed by Lisp's garbage collection facility
- they are not trapped and handled by Lisp's error system
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 Lisp interface for information about how to create, read, write and deallocate MidiEv structs using Midishare's API.
Common Music adds two functions to Midishare's API:
ms:new, a high level MidiEv constructor, and
ms:MidiPrintEv, a printer
for MidiEv objects. Wit the addition of these two functions low-level
MidiEvs can be manipulated in manner consistent with the high-level
objects defined in CM. Note that to reference any function in the
MidiShare API you must include the package
prefix midishare: or ms: in the function
name.
- [Function]
(ms:newtype {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:
:portinteger- The reference number of the MidiShare port to send the event to. Defaults to 0.
:chaninteger- The channel number to send the event to. Defaults to 0.
:dateinteger- The time (in milliseconds) of the event. Defaults to 0.
typeNote
(0), typeKeyOn (1), typeKeyOff (2):
:pitchinteger- An integer key number 0-127. Defaults to 60.
:velinteger- An integer velocity 0-127. Defaults to 60.
:durinteger- Duration in milliseconds, defaults to 500. Only available
for
typeNote.
typeKeyPress (3):
:pitchinteger- An integer key number 0-127. Defaults to 60.
:pressureinteger- An integer pressure 0-127. Defaults to 0.
typeCtrlChange (4):
:controllerinteger- An integer controller 0-127. Defaults to 0.
:changeinteger- An integer change 0-127. Defaults to 0.
typeProgChange (5):
:programinteger- An integer program 0-127. Defaults to 0.
typeChanPress (6):
:pressureinteger- An integer pressure 0-127. Defaults to 0.
typePitchBend (7),
typePitchWheel (7):
:bendinteger- An integer bend value -8192 to 8191. Defaults to 0 (no bend).
typeSongPos (8):
:lsbinteger- An integer 0-127. Defaults to 0.
:msbinteger- An integer 0-127. Defaults to 0.
typeSongSel (9):
:songinteger- 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):
:datalist- 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):
:numberinteger- A sequence integer 0-127.
typeTextual (135),
typeCopyright (136),
typeSeqName (137),
typeInstrName (138),
typeLyric (139),
typeMarker (140),
typeCuePoint (141):
:textstring- A text string, defaults to "".
typeChannelPrefix (142):
:prefixinteger- A prefix integer 0-127, defaults to 0.
typeEndTrack (143):
None
typeTempo (144):
:tempointeger- Tempo in quarter notes per minute, defaults to 120.
typeSMPTEOffset (145):
:offsetlist- A list of SMPTE integer offsets (hr min sec frame subframe).
typeTimeSign (146):
:numeratorinteger- The upper number of the time signature, defaults to 4.
:denominatorinteger- The lower number of the time signature, defaults to 4.
:clocksinteger- Clocks per quarter, defaults to 24.
:32ndsinteger- Thirty-seconds per quarter, defaults to 8.
typeKeySign (147):
:signinteger- 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:MidiPrintEvev [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 below. Consult the MidiShare documentation for more information.
(ms:MidiNewEvtypenum)(ms:MidiCopyEvev)(ms:MidiFreeEvev)(ms:evtypeev [val])(ms:portev [val])(ms:chanev [val])(ms:dateev [val])(ms:pitchev [val])(ms:durev [val])(ms:velev [val])(ms:bendev [val])(ms:pgmev [val])(ms:ctlev [val])(ms:valev [val])(ms:fieldev pos [val])
Example 3. Copying and transposing an event by one octave.
(define ev2 (ms:MidiCopyEv ev)) (ms:pitch ev2 (+ (ms:pitch ev2) 12)) ⇒ 72 (ms:MidiPrintEv ev2) #<MidiEv Note [0/3 0ms] 72 64 2000ms>
Real time MIDI output
Real time MIDI output and musical process scheduling is initiated by
calling output,
sprout and
now outside the dynamic
scope of a call to events.
When used interactively these functions behave as if they were defined
slightly differently than during non-real time event scheduling:
- [Function]
(now)
Returns Midishare's current clock time in milliseconds.
- [Function]
(outputev [ahead])
Sends ev to Midishare immediately or ahead milliseconds later than when the function is called. No value is returned.
- [Function]
(sproutfn [ahead])
Schedules the process function fn to run as a real time Midishare task immediately or ahead milliseconds later than when the function is called. No value is returned.
Example 4. Using now, output and sprout interactively.
(define (rankeys reps rhy dur lb ub) (process repeat reps output (ms:new typeNote :dur dur :pitch (between lb ub)) wait rhy)) (now) ⇒ 274601 (output (ms:new typeNote :pitch 80 :dur 1000)) (sprout (rankeys 30 200 250 60 90) 2000)
As can be seen in the preceding example, the principle differences between real time and non-real time process definitions are:
- The
nowfunction is not needed to establish start times. - MidiEv structs are created using
ms:newrather thannew. - MidiEv fields are set to integer values only.
- Time is specified in integer milliseconds.
Real time MIDI input
Use the receive function to establish or remove a MIDI
input hook.
- [Function]
-
(receive[hook])
If called with no argument receive removes the
current MIDI input hook. Otherwise, hook is a function of
one argument that will be funcalled with every MIDI input event as
soon as MidiShare receives it. The receive function does
not return a value.
Example 5. Setting and clearing a MIDI receive hook.
(define (play9th ev) ;; if ev is NoteOn or NoteOff then play it ;; as a minor 9th, otherwise deallocate ev. (if (member (ms:evType ev) '(1 2)) (let ((ev2 (ms:MidiCopyEv ev))) (ms:pitch ev2 (+ 13 (ms:pitch ev))) (output ev) (output ev2)) (ms:MidiFreeEv ev))) (receive #'play9th) ;; Play some notes on the keyboard, then stop. (receive)