.. _stdlib_strudel_midi_player: ========================================= MIDI file playback with GM preset mapping ========================================= .. das:module:: strudel_midi_player Module strudel_midi_player ++++++++++ Structures ++++++++++ .. _struct-strudel_midi_player-GmPreset: .. das:attribute:: GmPreset :Fields: * **osc** : :ref:`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. .. _struct-strudel_midi_player-MidiVoice: .. das:attribute:: MidiVoice struct MidiVoice .. _struct-strudel_midi_player-MidiChannelState: .. das:attribute:: MidiChannelState struct MidiChannelState .. _struct-strudel_midi_player-MidiPlaybackState: .. das:attribute:: MidiPlaybackState :Fields: * **name** : string - Track name supplied by the caller (also used by ``midi_stop`` / ``midi_set_volume``). * **events** : array< :ref:`MidiEvent `> - All MIDI events across tracks, merged and sorted by absolute tick. * **channels** : :ref:`MidiChannelState `\ [16] - Per-channel state (program, CCs, pitch bend, sustain pedal). * **voices** : array< :ref:`MidiVoice `?> - Active oscillator/sample voices (legacy path; unused when SF2 is active). * **csf2_voices** : array< :ref:`ma_sf2_voice `> - Active SF2 voices rendered by the C runtime. * **bank** : :ref:`SampleBank `? - Optional sample bank pointer (owned by the main context). * **sf2** : :ref:`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** : :ref:`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`. +++++++++ Lifecycle +++++++++ * :ref:`midi_init () ` * :ref:`midi_init_reverb () ` * :ref:`midi_shutdown () ` .. _function-strudel_midi_player_midi_init: .. das:function:: 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. .. _function-strudel_midi_player_midi_init_reverb: .. das:function:: midi_init_reverb() Initialise the module-global reverb and chorus effects for offline rendering (calling `midi_tick` without running the thread). .. _function-strudel_midi_player_midi_shutdown: .. das:function:: midi_shutdown() Stop the playback thread and release its channels; blocks until the audio thread confirms shutdown. ++++++++++++++++ Playback control ++++++++++++++++ * :ref:`midi_is_playing () : bool ` * :ref:`midi_play (name: string; path: string; gain: float = 1f; looping: bool = false; bank: SampleBank const? = null; use_sf2: bool = true) : bool ` * :ref:`midi_play_data (name: string; data: array\; gain: float = 1f; looping: bool = false; bank: SampleBank const? = null; use_sf2: bool = true) : bool ` * :ref:`midi_set_pause (paused: bool) ` * :ref:`midi_stop (name: string = "") ` * :ref:`midi_tick (var state: MidiPlaybackState; chunk_seconds: float) : array\ ` .. _function-strudel_midi_player_midi_is_playing: .. das:function:: midi_is_playing() : bool Return true while the MIDI playback thread is running. .. _function-strudel_midi_player_midi_play_string_string_float_bool_SampleBank_const_q__bool: .. das:function:: 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** : :ref:`SampleBank `? * **use_sf2** : bool .. _function-strudel_midi_player_midi_play_data_string_array_ls_uint8_gr__float_bool_SampleBank_const_q__bool: .. das:function:: midi_play_data(name: string; data: array; 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 * **gain** : float * **looping** : bool * **bank** : :ref:`SampleBank `? * **use_sf2** : bool .. _function-strudel_midi_player_midi_set_pause_bool: .. das:function:: midi_set_pause(paused: bool) Pause or resume all MIDI playback (the underlying audio stream continues with silence while paused). :Arguments: * **paused** : bool .. _function-strudel_midi_player_midi_stop_string: .. das:function:: midi_stop(name: string = "") Stop a single track by name, or shut down the whole playback thread when `name` is empty. :Arguments: * **name** : string .. _function-strudel_midi_player_midi_tick_MidiPlaybackState_float: .. das:function:: midi_tick(state: MidiPlaybackState; chunk_seconds: float) : array 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** : :ref:`MidiPlaybackState ` * **chunk_seconds** : float +++++++++++ Audio setup +++++++++++ * :ref:`midi_set_audio_vis_channel (var ch: Channel?) ` * :ref:`midi_set_volume (name: string; volume: float; fade: float = 0f) ` .. _function-strudel_midi_player_midi_set_audio_vis_channel_Channel_q_: .. das:function:: midi_set_audio_vis_channel(ch: Channel?) Register a channel that will receive rendered audio chunks for visualisation; call before `midi_init`. :Arguments: * **ch** : :ref:`Channel `? .. _function-strudel_midi_player_midi_set_volume_string_float_float: .. das:function:: 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 +++++++++++++ Asset loading +++++++++++++ * :ref:`midi_load_samples (media_path: string) ` * :ref:`midi_load_sf2 (path: string) : bool ` .. _function-strudel_midi_player_midi_load_samples_string: .. das:function:: 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 .. _function-strudel_midi_player_midi_load_sf2_string: .. das:function:: midi_load_sf2(path: string) : bool Load an SF2 soundfont from disk into the module-global slot; returns true on success. :Arguments: * **path** : string ++++++++++++++++ Event processing ++++++++++++++++ * :ref:`init_playback (var state: MidiPlaybackState; midi: MidiFile const?; name: string; gain: float; looping: bool; bank: SampleBank const? = null; sf2: SF2File const? = null) ` * :ref:`process_events (var state: MidiPlaybackState) ` .. _function-strudel_midi_player_init_playback_MidiPlaybackState_MidiFile_const_q__string_float_bool_SampleBank_const_q__SF2File_const_q_: .. das:function:: 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** : :ref:`MidiPlaybackState ` * **midi** : :ref:`MidiFile `? * **name** : string * **gain** : float * **looping** : bool * **bank** : :ref:`SampleBank `? * **sf2** : :ref:`SF2File `? .. _function-strudel_midi_player_process_events_MidiPlaybackState: .. das:function:: 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** : :ref:`MidiPlaybackState ` ++++++++++++++++++++++++++++ Drum and GM preset rendering ++++++++++++++++++++++++++++ * :ref:`gm_preset (program: int) : GmPreset ` * :ref:`midi_adsr (elapsed: float; note_off_elapsed: float; attack: float; decay: float; sustain: float; release: float) : float ` * :ref:`render_drum_hit (note: int; velocity: float) : array\ ` .. _function-strudel_midi_player_gm_preset_int: .. das:function:: 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 .. _function-strudel_midi_player_midi_adsr_float_float_float_float_float_float: .. das:function:: 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 .. _function-strudel_midi_player_render_drum_hit_int_float: .. das:function:: render_drum_hit(note: int; velocity: float) : array 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