@midi_read(string, map)

This function read a midi file whose pathname is given by the first argument (a string), and returns a tab representing a list of tracks. Each tracks is a list of timestamped midi messages. And a midi message si a tab of integer, each integer representing the corresponding byte of the encoded midi message:

    @midi_read(string)    [ track₀, track₁, ...]
    trackᵢ = [ [timestamp₀, [byte₀₀, byte₀₁, byte₀₂ ...]],
               [timestamp₁, [byte₁₀, byte₁₁, byte₁₂ ...]],
               [timestamp₂, [byte₂₀, byte₂₁, byte₂₂ ...]],

If there is only one track, the returned tab is directly the list of timestamped midi messages.

Timestamps interpretation

The timestamps are expressed in seconds and represent a duration starting from the previous midi message (delta times in midi). The timestamps are computed from the data in the midifile, taking into account the changes of tempo.

This is true irrespectively of the time representation used in the original midi file (tick or seconds, relative or absolute).

Parameterizing the track extraction

The second optional argument of the function is a map that can be used to specify several options by associating a value to a key characterizing the option:

  • the entry "jointracks" defines a boolean to true if all the tracks of the midi file must be joined beore producing the resulting tab. If this option is specified, there is only one track and the returned value is directly the list of timestamped midi messages.

  • the entry "tracks" defines a tab enumerating the tracks to extract from the midi file. This option can be used independently of the previous option (then the results is a list of tracks, except if there is only one extracted track).

  • the entry "noteonly" defines a boolean specifying if only NoteOn and NoteOff midi messages are extracted.

  • the entry "start" is used to specify a date (in second, relatively to the start of the midi file) from which the midi event must be extracted. If not specified the extract start with the first midi message in the midi file.

  • the entry "end" is used to specify a date (in second, relatively to the start of the midi file) from which the midi event should not be considered anymore. If not specified, the extraction goes until the last midi message in the midi file.

Example of the specification of some options:

     @midi_read("bolero.mid", MAP{ ("jointracks", true),
                                   ("tracks", [0, 2]),
                                   ("end", 10) })

will extract the midi messages of the first and third tracks of the first 10 seconds of the midi file `:::antescofo "bolero.mid").

Encoding of Midi Message in Antescofo

Midi messages are encoded as a tab of integers. Element i in this tab encodes the value of the byte i in the binary representation of the message (i.e. a positive value between 0 and 256). This encoding makes easy to send the relevant data to a MAX midioutobject`, cf. the example given below.

Midi messages have a variable length. This link provide a short but complete description of the Standard midi file format:

  • The first byte in the MIDI message is expected to be a command byte, which is a byte in the range from 0x80 to 0xff (128-255 decimal):

    • There are seven midi commands from 0x80 to 0xe0 command that specify a midi channel (the command is specified by the top four bits of the byte. The bottom four bits of the byte indicates the midi channel involved by the command. Midi channels range from 0x0 to 0xf hex (0 to 15 decimal).

    • There are 16 miscellaneous commands starting with a 0xf0 nibble that don't refer to midi channels in the bottom bits.

  • Each command has an expected number of parameter bytes after it, which are in the range from 0x00 to 0x7f hexadecimal (0 to 127 decimal). Here is a table summarizing the seven main midi commands and their required parameter count:

Command | Command name | #param | Parameter meaning |:-----:|:--------------:|:--------:|:-------------------:| 0x80 | Note Off | 2 | key, off velocity 0x90 | Note On | 2 | key, on velocity 0xA0 | Aftertouch | 2 | key, pressure 0xB0 | Controller | 2 | controller number, controller value 0xC0 | Patch change | 1 | instument number 0xD0 | Channel Pressure | 2 | key, off velocity 0xE0 | Pitch-bend | 2 | LSB, MSB

Various predicate and observers takes the tab encoding a midi message and extract relevant information, see Handling Midi @hz2midi    @hz2midicent    @hz2symb    @midi_getChannel    @midi_getCommandByte    @midi_getCommand    @midi_getMetaType    @midi_isAftertouch    @midi_isController    @midi_isEndOfTrack    @midi_isMeta    @midi_isNoteOff    @midi_isNoteOn    @midi_isNote    @midi_isPatchChange    @midi_isPitchbend    @midi_isPressure    @midi_isTempo    @midi_read    @midi_track2ascii    @midi2hz    @midicent2hz    @symb2midicent   

The function @midi_track2ascii takes a list of timestamped midi messages, as returned by @midi_read, and produces a tab where the seven previous commands are given in a human readable way, with the channel is uncoupled from the command name. This function can be used for debugging purposes.

A simple midi player

The following code fragment implement a very simple midi player, by parsing a midifile with @midi_read and using the results to send the data to a midiout max oject (through the receiver maxmidi). The sending of the midi messages are implement with a loop that uses the timestamp as periods:

         $filename := "/Users/giavitto/UTopIa/Antescofo/Work/TEST/Bolero-1.mid"
         $option := MAP{ ("end", 35) }
         $midi := @midi_read($filename, $option)

         // player
         $index := 0
         $period := $midi[$index, 0]

         Loop $period
              print "midi message: " ($midi[$index, 1])
              ForAll $e in $midi[$index, 1]
                  maxmidi $e
              $index := $index + 1
              if ($index < @size($midi)) { $period := $midi[$index, 0] }

         } while ($index < @size($midi))

Because timestamps are interpreted here as relative time (the period specifies a relative time, not an absolute time, even if the computed timestamps corresponds initially to seconds in the midfile), it is possible to modulate the tempo of the playback, using a computed tempo @tempo or to snchronize the playback with the play of the musician. Note that the playback at the original speed is achieved with a tempo of 60.

Nota Bene: because the way timestamps are computed by Antescofo, there is no further need to send the meta message relatively to the tempo. So the midi event sent to the midiout object in MAX must be restricted to note-on and note-off event, which can be achieved using the noteonly option when reading the midi file.