Skip to content

midi

stable

Build 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, …}
20 functions Audio & Media
/ filter jk navigate Esc clear
Functions (20)
  1. note_on Build a Note On MIDI message bytes
  2. note_off Build a Note Off MIDI message bytes
  3. control_change Build a Control Change MIDI message bytes
  4. program_change Build a Program Change MIDI message bytes
  5. pitch_bend Build a Pitch Bend MIDI message bytes
  6. channel_pressure Build a Channel Pressure (aftertouch) message
  7. poly_pressure Build a Polyphonic Key Pressure message
  8. all_notes_off Build an All Notes Off CC message
  9. all_sound_off Build an All Sound Off CC message
  10. reset_controllers Build a Reset All Controllers CC message
  11. parse_message Parse raw MIDI bytes into a typed table
  12. note_name Convert MIDI note number to name string
  13. note_number Convert note name string to MIDI number
  14. frequency Convert MIDI note number to Hz frequency
  15. tempo_to_bpm Convert microseconds-per-beat to BPM
  16. bpm_to_tempo Convert BPM to microseconds-per-beat
  17. encode_variable_length Encode integer as MIDI variable-length quantity
  18. smf_header Build a Standard MIDI File MThd header
  19. tempo_event Build a tempo meta event (FF 51 03)
  20. 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"]}")
enespt-br