midi
stableBuild and parse MIDI messages, convert between note names and numbers, and assemble Standard MIDI File (SMF) structures for music sequencing applications.
use plugin midi::{note_on, note_off, control_change, …} Functions (20)
- note_on Build a Note On MIDI message bytes
- note_off Build a Note Off MIDI message bytes
- control_change Build a Control Change MIDI message bytes
- program_change Build a Program Change MIDI message bytes
- pitch_bend Build a Pitch Bend MIDI message bytes
- channel_pressure Build a Channel Pressure (aftertouch) message
- poly_pressure Build a Polyphonic Key Pressure message
- all_notes_off Build an All Notes Off CC message
- all_sound_off Build an All Sound Off CC message
- reset_controllers Build a Reset All Controllers CC message
- parse_message Parse raw MIDI bytes into a typed table
- note_name Convert MIDI note number to name string
- note_number Convert note name string to MIDI number
- frequency Convert MIDI note number to Hz frequency
- tempo_to_bpm Convert microseconds-per-beat to BPM
- bpm_to_tempo Convert BPM to microseconds-per-beat
- encode_variable_length Encode integer as MIDI variable-length quantity
- smf_header Build a Standard MIDI File MThd header
- tempo_event Build a tempo meta event (FF 51 03)
- build_sequence Sort and delta-encode a list of timed events
Overview
midi is a dependency-free toolkit for building, parsing, and sequencing MIDI
data as raw byte buffers. Channel-voice messages (note on/off, control change,
program change, pitch bend, aftertouch) are produced as bytes you can send to
any MIDI output, and incoming bytes can be decomposed with parse_message. The
plugin is stateless: there is no connection or device handle to manage — every
function is a pure transform from its arguments to a value, so you assemble and
inspect messages freely.
Beyond live messages, it also covers the music-theory and file-format layer:
convert between note names, note numbers, and frequencies; translate tempo
between BPM and microseconds-per-beat; and assemble Standard MIDI File (SMF)
structures with smf_header, tempo_event, encode_variable_length, and
build_sequence. Reach for it whenever you need to drive a synth, log MIDI
traffic, or write a .mid file by hand.
Common patterns
Play a note: build a Note On, then the matching Note Off.
use plugin midi::{note_on, note_off, note_name}
let n = 60
print("playing {note_name(n)}")
let on = note_on(0, n, 100)
let off = note_off(0, n, 0)
Inspect raw MIDI bytes by parsing them back into a typed table:
use plugin midi::{control_change, parse_message}
let raw = control_change(0, 7, 90)
let msg = parse_message(raw)
print("{msg["type"]} ch={msg["channel"]} cc={msg["data1"]} val={msg["data2"]}")
Assemble a delta-time encoded track for a Standard MIDI File:
use plugin midi::{smf_header, tempo_event, bpm_to_tempo, build_sequence, note_on, note_off}
let header = smf_header(0, 1, 480)
let tempo = tempo_event(bpm_to_tempo(120.0))
let seq = build_sequence([
#{"tick": 0, "message": note_on(0, 60, 100)},
#{"tick": 480, "message": note_off(0, 60, 0)}
])
print("events: {seq[1]["delta"]}")
Build a Note On MIDI message bytes
Builds a 3-byte MIDI Note On message. channel is 0–15, note is 0–127, velocity is 0–127. Returns raw bytes suitable for sending to a MIDI output.
use plugin midi::{note_on, note_off}
let msg_on = note_on(0, 60, 100)
let msg_off = note_off(0, 60, 64)
print("Note On bytes: {msg_on}")
Build a Note Off MIDI message bytes
Builds a 3-byte MIDI Note Off message (status byte 0x80 | channel).
use plugin midi::{note_on, note_off}
let on = note_on(0, 69, 127)
let off = note_off(0, 69, 0)
Build a Control Change MIDI message bytes
Builds a 3-byte Control Change message. Common controllers: 7 = volume, 10 = pan, 64 = sustain pedal.
use plugin midi::{control_change}
let sustain_on = control_change(0, 64, 127)
let sustain_off = control_change(0, 64, 0)
Set channel volume (CC 7) and pan (CC 10) in one go:
use plugin midi::{control_change}
let volume = control_change(0, 7, 100)
let pan_left = control_change(0, 10, 0)
let pan_right = control_change(0, 10, 127)
Build a Program Change MIDI message bytes
Builds a 2-byte Program Change message to select a MIDI instrument patch (0–127).
use plugin midi::{program_change}
let piano = program_change(0, 0)
let strings = program_change(1, 48)
Build a Pitch Bend MIDI message bytes
Builds a 3-byte Pitch Bend message. value is a 14-bit unsigned integer (0–16383); 8192 is center (no bend).
use plugin midi::{pitch_bend}
let center = pitch_bend(0, 8192)
let up = pitch_bend(0, 16383)
let down = pitch_bend(0, 0)
Build a Channel Pressure (aftertouch) message
Builds a 2-byte Channel Pressure (monophonic aftertouch) message. pressure is 0–127.
use plugin midi::{channel_pressure}
let msg = channel_pressure(0, 80)
Build a Polyphonic Key Pressure message
Builds a 3-byte Polyphonic Key Pressure message, applying aftertouch to a specific note.
use plugin midi::{poly_pressure}
let msg = poly_pressure(0, 60, 64)
Build an All Notes Off CC message
Sends CC 123 with value 0 on the specified channel, signaling all playing notes to stop. Use for emergency note cutoff or when switching patches.
use plugin midi::{all_notes_off}
let msg = all_notes_off(0)
Build an All Sound Off CC message
Sends CC 120 with value 0, cutting all audio immediately on the channel (harder than all_notes_off).
use plugin midi::{all_sound_off}
let msg = all_sound_off(0)
Build a Reset All Controllers CC message
Sends CC 121 with value 0 to reset all controllers to their default values on the channel.
use plugin midi::{reset_controllers}
let msg = reset_controllers(0)
Parse raw MIDI bytes into a typed table
Parses a raw 2- or 3-byte MIDI message into {type, channel, data1, data2}. Handles note_on, note_off (including velocity-zero note_on), aftertouch, control_change, program_change, channel_pressure, pitch_bend, and system messages.
use plugin midi::{note_on, parse_message}
let raw = note_on(0, 60, 100)
let msg = parse_message(raw)
print("{msg["type"]} ch={msg["channel"]} note={msg["data1"]} vel={msg["data2"]}")
A Note On with velocity 0 is reported as note_off, matching the running-status convention:
use plugin midi::{note_on, parse_message}
let silent = note_on(0, 60, 0)
let msg = parse_message(silent)
print(msg["type"])
Convert MIDI note number to name string
Converts a MIDI note number (0–127) to its name string such as "C4", "D#5", or "A-1".
use plugin midi::{note_name, note_number}
print(note_name(60))
print(note_name(69))
print(note_number("C4"))
print(note_number("A4"))
Convert note name string to MIDI number
Converts a note name string like "C4" or "D#5" back to a MIDI note number.
use plugin midi::{note_number}
let n = note_number("A4")
print(n)
It is the inverse of note_name, so the two round-trip cleanly:
use plugin midi::{note_name, note_number}
let name = note_name(72)
print("{name} = {note_number(name)}")
Convert MIDI note number to Hz frequency
Calculates the frequency in Hz for a MIDI note number using the formula 440 * 2^((n-69)/12).
use plugin midi::{frequency, note_number}
let hz = frequency(69.0)
print("A4 = {hz} Hz")
let c4 = frequency(note_number("C4"))
print("C4 = {c4} Hz")
Convert microseconds-per-beat to BPM
Converts a MIDI tempo value (microseconds per quarter note) to beats per minute.
use plugin midi::{tempo_to_bpm, bpm_to_tempo}
let bpm = tempo_to_bpm(500000.0)
print("{bpm} BPM")
let usec = bpm_to_tempo(120.0)
print("{usec} µs/beat")
Convert BPM to microseconds-per-beat
Converts beats per minute to microseconds per quarter note, suitable for use in a MIDI tempo meta event.
use plugin midi::{bpm_to_tempo}
let usec = bpm_to_tempo(140.0)
print(usec)
Encode integer as MIDI variable-length quantity
Encodes a non-negative integer as a MIDI variable-length quantity (VLQ), used for delta times in Standard MIDI Files.
use plugin midi::{encode_variable_length}
let vlq = encode_variable_length(0)
let vlq2 = encode_variable_length(300)
Build a Standard MIDI File MThd header
Builds a 14-byte Standard MIDI File MThd header chunk. format is 0 (single track), 1 (multi-track synchronous), or 2 (multi-track sequential).
use plugin midi::{smf_header}
let header = smf_header(0, 1, 480)
print("Header bytes: {header}")
Build a tempo meta event (FF 51 03)
Builds a MIDI tempo meta event (FF 51 03 tt tt tt), used inside a Standard MIDI File track.
use plugin midi::{tempo_event, bpm_to_tempo}
let usec = bpm_to_tempo(120.0)
let evt = tempo_event(usec)
Sort and delta-encode a list of timed events
Takes a table of {tick, message} events, sorts them by tick, and returns a new table with {delta, tick, message} entries where delta is the time since the previous event. Use this to assemble delta-time encoded track data for SMF.
use plugin midi::{build_sequence, note_on, note_off}
let events = [
#{"tick": 0, "message": note_on(0, 60, 100)},
#{"tick": 480, "message": note_off(0, 60, 0)},
#{"tick": 480, "message": note_on(0, 64, 100)},
#{"tick": 960, "message": note_off(0, 64, 0)}
]
let seq = build_sequence(events)
let first = seq[1]
print("delta={first["delta"]} tick={first["tick"]}")