MIDI Player
Plays Standard MIDI Files (Format 0/1) through the Juno synth engine using AudioContext-timed scheduling for sample-accurate playback.
Files
static/js/synth/midi-player.js
MidiPlayer API
Constructor
var player = new MidiPlayer(audioContext);
Methods
| Method | Args | Returns | Description |
|---|---|---|---|
load(arrayBuffer) |
Raw MIDI file data | { duration, noteCount, tempo } |
Parses SMF and returns info |
play(startTime, scheduleVoiceFn, releaseVoiceFn) |
startTime — AudioContext time, scheduleVoiceFn(freq, time, duration) — called for each note, releaseVoiceFn(voice) — called for cleanup |
— | Starts incremental scheduling |
stop(releaseVoiceFn) |
releaseVoiceFn(voice) — called for each active voice |
— | Stops playback, releases all voices |
onProgress(fn) |
fn(elapsed, total) |
— | Progress callback during playback |
onStop(fn) |
fn() |
— | Callback when playback finishes naturally |
SMF Parser (_parse)
Handles:
- Header chunk — format (0/1), number of tracks, division (metrical or SMPTE)
- Track chunks — delta-time variable-length quantity, running status
- Note on/off — matched by note number + track, durations computed from note-off times
- Tempo meta events (0xFF 0x51) — microseconds per quarter note, updates tick-to-second conversion mid-stream
- End of track (0xFF 0x2F) — terminates track parsing
- Running status correctly scoped to channel events only (0x80–0xEF), meta events (0xFF) do not set running status
Scheduling Strategy
Uses incremental lookahead scheduling to avoid creating all voices upfront:
- On
play(startTime), ascheduleBatchfunction runs viasetTimeout - Each batch looks ahead 1 second from
audioCtx.currentTime - All note events within the lookahead window are scheduled on the AudioContext timeline
- After scheduling, another batch is queued 50 ms later
- Cleanup timers (
setTimeout) release voices after note duration + release
This keeps the number of active voices bounded (typically 20–40) regardless of song length.
Integration in init-juno.js
The Juno page creates a MidiPlayer instance and wires it to three functions:
| Function | Purpose |
|---|---|
scheduleVoice(freq, startTime, duration) |
Creates voice nodes scheduled at startTime with ADSR envelope ending at startTime + duration + release |
midiReleaseVoice(voice) |
Stops oscillators and disconnects gain (does NOT modify the already-scheduled envelope) |
loadMidiFile(url, label) |
Fetches a MIDI file from the server and parses it |
The scheduleVoice function mirrors createVoice (keyboard playback) but all
node scheduling uses the startTime parameter instead of audioCtx.currentTime.
Transpose
MIDI notes are transposed by the keyboard's current octave setting (octave):
baseFreq = midiFreq * Math.pow(2, octave - 4) * rangeMultiplier
Built-in MIDI Files
MIDI files placed in /static/midi/ are listed via /static/midi/list.json
and displayed as a clickable list in the Juno page sidebar. Users can also
load files from their local filesystem via the File... button.