17.11. MIDI file playback with GM preset mapping
Module strudel_midi_player
17.11.1. Structures
- GmPreset
- Fields:
osc : OscType = strudel_synth::OscType.osc_sine - Oscillator waveform used to synthesise the instrument.
att : float = 0.005f - Envelope attack time in seconds.
dec : float = 0.1f - Envelope decay time in seconds.
sus : float = 0.5f - Envelope sustain level in [0, 1].
rel : float = 0.15f - Envelope release time in seconds.
- MidiVoice
struct MidiVoice
- MidiChannelState
struct MidiChannelState
- MidiPlaybackState
- Fields:
name : string - Track name supplied by the caller (also used by
midi_stop/midi_set_volume).events : array< MidiEvent> - All MIDI events across tracks, merged and sorted by absolute tick.
channels : MidiChannelState[16] - Per-channel state (program, CCs, pitch bend, sustain pedal).
voices : array< MidiVoice?> - Active oscillator/sample voices (legacy path; unused when SF2 is active).
csf2_voices : array< ma_sf2_voice> - Active SF2 voices rendered by the C runtime.
bank : SampleBank? - Optional sample bank pointer (owned by the main context).
sf2 : SF2File? - Optional SF2 soundfont pointer (owned by the main context).
ticks_per_qn : int = 480 - Ticks per quarter note, taken from the MIDI file header.
event_index : int = 0 - Index of the next event in events to process.
current_tick : double = 0lf - Current playback position in ticks.
ticks_per_sec : double = 0lf - Conversion factor from seconds to ticks (depends on current tempo).
tempo_usec : int = 500000 - Current tempo in microseconds per quarter note (500000 = 120 BPM).
playback_time : double = 0lf - Elapsed playback time in seconds.
looping : bool = false - If true, playback restarts from the beginning when the track ends.
gain : float = 1f - Current linear gain applied to the track mix.
target_gain : float = 1f - Fade target for gain; used by midi_set_volume to implement fades.
fade_speed : float = 0f - Fade rate per second; 0 means instantaneous.
remove_when_silent : bool = false - If true, the track is removed once it fades to zero (used by
midi_stop).done : bool = false - Set to true when playback has finished and no voices remain.
mixer : ma_volume_mixer - Volume/pan mixer used for drum and sample voices.
mixer_ready : bool = false - True after ma_volume_mixer_init has been called on mixer.
17.11.2. Lifecycle
- midi_init()
Start the MIDI playback thread and attach it to the running audio stream. No-op if already running; panics if the audio system has not been initialised first.
- midi_init_reverb()
Initialise the module-global reverb and chorus effects for offline rendering (calling midi_tick without running the thread).
- midi_shutdown()
Stop the playback thread and release its channels; blocks until the audio thread confirms shutdown.
17.11.3. Playback control
- midi_is_playing(): bool
Return true while the MIDI playback thread is running.
- midi_play(name: string; path: string; gain: float = 1f; looping: bool = false; bank: SampleBank const? = null; use_sf2: bool = true): bool
Parse a .mid file from disk and submit it for playback under name. Auto-starts the playback thread if needed; falls back to the oscillator/sample path when no SF2 is loaded or use_sf2 is false.
- Arguments:
name : string
path : string
gain : float
looping : bool
bank : SampleBank?
use_sf2 : bool
- midi_play_data(name: string; data: array<uint8>; gain: float = 1f; looping: bool = false; bank: SampleBank const? = null; use_sf2: bool = true): bool
Parse a .mid file from an in-memory byte array and submit it for playback under name. Same semantics as midi_play but sourced from memory instead of disk.
- Arguments:
name : string
data : array<uint8>
gain : float
looping : bool
bank : SampleBank?
use_sf2 : bool
- midi_set_pause(paused: bool)
Pause or resume all MIDI playback (the underlying audio stream continues with silence while paused).
- Arguments:
paused : bool
- midi_stop(name: string = "")
Stop a single track by name, or shut down the whole playback thread when name is empty.
- Arguments:
name : string
- midi_tick(state: MidiPlaybackState; chunk_seconds: float): array<float>
Advance playback by chunk_seconds, process pending events, render all active voices and return an interleaved stereo PCM chunk. Handles oscillator voices, drum/sample playback, SF2 voices, and the shared reverb/chorus send buses.
- Arguments:
state : MidiPlaybackState
chunk_seconds : float
17.11.4. Audio setup
- midi_set_audio_vis_channel(ch: Channel?)
Register a channel that will receive rendered audio chunks for visualisation; call before midi_init.
- Arguments:
ch : Channel?
- midi_set_volume(name: string; volume: float; fade: float = 0f)
Set the linear gain of a named track, optionally fading over fade seconds (0 = instantaneous).
- Arguments:
name : string
volume : float
fade : float
17.11.5. Asset loading
- midi_load_samples(media_path: string)
Populate the module-global sample bank with piano samples (note_{n}) and GM drum samples under media_path.
- Arguments:
media_path : string
- midi_load_sf2(path: string): bool
Load an SF2 soundfont from disk into the module-global slot; returns true on success.
- Arguments:
path : string
17.11.6. Event processing
- init_playback(state: MidiPlaybackState; midi: MidiFile const?; name: string; gain: float; looping: bool; bank: SampleBank const? = null; sf2: SF2File const? = null)
Initialise a MidiPlaybackState from a parsed MidiFile, merging its tracks and resetting per-channel state to GM defaults.
- Arguments:
state : MidiPlaybackState
midi : MidiFile?
name : string
gain : float
looping : bool
bank : SampleBank?
sf2 : SF2File?
- process_events(state: MidiPlaybackState)
Dispatch all MIDI events up to the current playback tick: note-on/off, program changes, CCs, pitch bend, tempo. Routes note events either to the SF2 voice allocator or to the legacy oscillator/sample path depending on state.sf2.
- Arguments:
state : MidiPlaybackState
17.11.7. Drum and GM preset rendering
- gm_preset(program: int): GmPreset
Map a General MIDI program number (0-127) to a GmPreset used by the oscillator path. Groups programs by GM family (piano, strings, brass, synth lead/pad, …) and returns a reasonable default envelope per family.
- Arguments:
program : int
- midi_adsr(elapsed: float; note_off_elapsed: float; attack: float; decay: float; sustain: float; release: float): float
Evaluate the MIDI-style ADSR at elapsed seconds since note-on and note_off_elapsed seconds since note-off. note_off_elapsed < 0 means the note is still held. The sustain phase also decays exponentially so long-held notes fade.
- Arguments:
elapsed : float
note_off_elapsed : float
attack : float
decay : float
sustain : float
release : float
- render_drum_hit(note: int; velocity: float): array<float>
Render a GM channel-10 drum hit for note at the given velocity as a mono PCM buffer. Dispatches to the appropriate render_* drum voice from strudel_synth; falls back to a short hi-hat burst for unmapped notes.
- Arguments:
note : int
velocity : float