midi

A MIDI file parser and writer in MoonBit

midi
music
audio
parser
writer
moon add CAIMEOX/midi@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
10 months ago
Downloads
40
README

#MIDI

A lightweight Standard MIDI File (SMF) parser and writer for the MoonBit language.

#Features

  • Parse MThd / MTrk chunks into strongly-typed structures (MidiFile, Track, Event)
  • Support for channel voice messages, meta events, SysEx, and common realtime messages
  • Proper handling of variable-length quantities & running status (both parse & serialize)
  • Serialize in‑memory structures back to a valid .mid byte stream
  • Human‑readable printing (all core types implement Show)

Status: work in progress (API surface may change). Contributions welcome.

#Installation

moon add CAIMEOX/midi

#Data Model

MidiFile { format: UInt16, division: UInt16, tracks: Array[Track] } Track { events: Array[Event] } Event => NoteOn(delta, channel, note, velocity) | NoteOff(delta, channel, note, velocity) | ControlChange(delta, channel, controller, value) | ProgramChange(delta, channel, program) | Aftertouch(delta, channel, value) | PolyTouch(delta, channel, note, value) | PitchWheel(delta, channel, pitch) // signed 14-bit centered at 0 | Tempo(delta~ : UInt, microseconds~ : UInt) // 0x51, in microseconds | TimeSignature(delta~ : UInt, numerator~ : Byte, denominator~ : Byte, clocks_per_beat~ : Byte, thirty_seconds_per_quarter~ : Byte) // 0x58, denominator is log(2)D | KeySignature(delta~ : UInt, sf~ : Byte, mi~ : Byte) // 0x59, sharp/flat and major/minor | Meta(delta, meta_type, data[]) // raw meta payload | SysEx(delta, data[]) // payload (without trailing F7) | Clock | Start | Continue | Stop | ActiveSensing | Reset

#Parsing

pub fn parse(bytes: Bytes) -> MidiFile raise ParseError

Guarantees / checks:

  • Validates header chunk tag & size (MThd, size 6)
  • Reads declared number of tracks
  • Enforces track byte length boundaries
  • Handles running status (channel messages may omit repeated status byte)

#Possible Errors (ParseError)

  • InvalidHeader
  • InvalidTrack
  • UnexpectedEOF
  • UnknownEventType
  • InvalidData(String)

#Serialization

pub fn serialize(file: MidiFile) -> Bytes

Behavior:

  • Emits canonical header (MThd, size 6)
  • Encodes delta times as variable-length quantities
  • Reuses running status for size optimization
  • Restores 14-bit pitch wheel (adds 8192 offset internally)
  • Writes SysEx with starting F0, length, payload, terminating F7

#Quick Usage Examples

#Parse & Inspect

fn demo(bytes: Bytes) raise @midi.ParseError {
let mf = @midi.parse(bytes)
println(mf.to_string()) // Uses Show impl to pretty print
}

#Roadmap Ideas

  • Tick-to-time utilities
  • Event builder DSL
  • Track manipulation helpers (merge, filter, quantize)
  • Event iteration utilities (by type, by channel, by time range)

#Contributing

PRs & issues welcome. Please include tests for parsing / serialization changes.

#License

Apache-2.0 © CAIMEOX Apache-2.0 © Elevonic611

#
ParseError

pub suberror ParseError {
InvalidHeader
InvalidTrack
UnexpectedEOF
UnknownEventType
InvalidData(String)
}

Parse errors

#
Event

pub(all) enum Event {
NoteOn(UInt, Byte, Byte, Byte)
NoteOff(UInt, Byte, Byte, Byte)
ControlChange(UInt, Byte, Byte, Byte)
ProgramChange(UInt, Byte, Byte)
Aftertouch(UInt, Byte, Byte)
PolyTouch(UInt, Byte, Byte, Byte)
PitchWheel(UInt, Byte, Int)
Tempo(UInt, UInt)
TimeSignature(UInt, Byte, Byte, Byte, Byte)
KeySignature(UInt, Byte, Byte)
Meta(UInt, Byte, Array[Byte])
SysEx(UInt, Array[Byte])
Clock(UInt)
Start(UInt)
Continue(UInt)
Stop(UInt)
ActiveSensing(UInt)
Reset(UInt)
}

MIDI Event types

#
MidiFile

pub(all) struct MidiFile {
format : UInt16
division : UInt16
tracks : Array[Track]
}

MIDI File structure
impl Show for MidiFile

#
Track

pub(all) struct Track {
events : Array[Event]
}

Track containing MIDI events

#
microseconds_to_bpm

fn microseconds_to_bpm(microseconds : UInt) -> Double

#
note_to_pitch

fn note_to_pitch(note : Byte) -> String

#
parse

fn parse(bytes : Bytes) -> MidiFile raise ParseError

Main MIDI file parser

#
program_to_instrument

fn program_to_instrument(program : Byte) -> String

#
serialize

fn serialize(midi_file : MidiFile) -> Bytes

Main MIDI file serializer