[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 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 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:

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:

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.
: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 below. Consult the MidiShare documentation for more information.

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:

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)

Example: receiving and scheduling in tandem

The following example demonstrates real time processes and a receive hook used together. The wiggle process adds a "chromatic wiggle" to a specified keynum knum. Each wiggle lasts between 10 and 20 notes; each note is produced from a heap pattern (random shuffle) of keynums centered around knum not exceeding a minor third on either side. The rate (speed) of each wiggle is a random choice between 100, 150 and 200 milliseconds between notes. All wiggles make a decrescendo over their lifetime.

The receive hook dowiggle first outputs the current input note and then 75% of the time it adds a wiggle to it. Half of these wiggles are at the input key number, otherwise they appear one octave up or down from the input note.

Example 6. Receiving, outputting and scheduling used together.

(define (wiggle knum)
  ;; return a wiggle process centered on specified knum
  (let ((n (between 10 20))
        (r (pick 100 150 200))
        (h (new heap :of '(0 -1 1 -2 2 -3 3))))
    (process for i below n
             output (ms:new typeNote
                       :dur 250
                       :pitch (+ knum (next h))
                       :vel (round (interp i 0 90 (- n 1) 20)))
             wait r)))

(define (dowiggle ev)
  ;; a receive hook to sprout wiggles
  (output ev)
  (if (and (= (ms:evType ev) typeKeyOn)
           (> (ms:vel ev) 0)
           (odds .75))
      (sprout (wiggle (+ (ms:pitch ev)
                         (pick 0 0 12 -12)))
              0)))

(or (midi-open?) (midi-open))
 #<midi-port "midi.port">

(sprout (wiggle 60)) ; wiggle once

(sprout (wiggle 40) 1000) ; wiggle twice

;;; wiggle till the cows come home!

(receive #'dowiggle)

(receive)

Real time processing issues