Midi

[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-file-stream`
- `midishare-stream`
- `player-stream`

### MIDI functions

- `events`
- `import-events`
- `midi-file-print`
- `osx-play-midi-file`
- `win-play-midi-file`
- `set-midi-output-hook!`

## 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 than `new`.
- 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)

### 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

- Garbage collection (GC) is not disabled.
- Regardless of GC status you should should keep consing to a minimum during real time work. The most common Lisp activities that cons are:
  - Creating objects, arrays, strings and lists.
  - Floating point or fractional arithmetic.
- A rule of thumb: integers are good, everything else is suspect. Analyze your programs to determine when and where you use floats or ratios and see if there is an integer alternative.
- Do your consing ahead of time, for example, allocate all consing structure (patterns, envelopes, etc) before calling `sprout`.
- Real time processes are managed in a "process table" and are automatically removed from the table when they stop. By default the size of this table is 64, if 64 concurrent processes are not enough use `make-proctable` to increase the size of the table:

  >     (set! *proctable* (make-proctable 200))

- If you trigger an error during real time process scheduling and abort to toplevel Lisp you should reset the proctable back to its initial state before calling `sprout` again:

  >     (reset-proctable *proctable*))