- [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
midi
midi-note-on
midi-note-off
midi-key-pressure
midi-control-change
midi-program-change
midi-channel-pressure
midi-pitch-bend
midi-system-event
midi-sequence-number
midi-text-event
midi-eot
midi-tempo-change
midi-smpte-offset
midi-time-signature
midi-key-signature
midi-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: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.
:mode
integer- 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 below. Consult the MidiShare documentation for more information.
(
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])
(
ms:field
ev 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]
(
output
ev [ahead])
Sends ev to Midishare immediately or ahead milliseconds later than when the function is called. No value is returned.
- [Function]
(
sprout
fn [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
now
function is not needed to establish start times. - MidiEv structs are created using
ms:new
rather 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)