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

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:

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 Midi Related Functions @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.