.. _stdlib_strudel_sf2_voice: ================================================================ SoundFont 2 per-voice runtime: envelope, LFO, modulators, biquad ================================================================ .. das:module:: strudel_sf2_voice Module strudel_sf2_voice ++++++++++++ Enumerations ++++++++++++ .. _enum-strudel_sf2_voice-SF2EnvStage: .. das:attribute:: SF2EnvStage :Values: * **env_delay** = 0 - Silent delay before the envelope starts. * **env_attack** = 1 - Rising ramp from 0.0 to 1.0. * **env_hold** = 2 - Hold at peak level before decay begins. * **env_decay** = 3 - Falling ramp from peak toward sustain level. * **env_sustain** = 4 - Held at sustain level until note-off. * **env_release** = 5 - Falling ramp from current level toward zero after note-off. * **env_finished** = 6 - Envelope has fully decayed and voice can be freed. ++++++++++ Structures ++++++++++ .. _struct-strudel_sf2_voice-SF2Envelope: .. das:attribute:: SF2Envelope :Fields: * **stage** : :ref:`SF2EnvStage ` = strudel_sf2_voice::SF2EnvStage.env_delay - Current envelope stage. * **time_in_stage** : float = 0f - Countdown samples remaining in the current stage (delay/hold). * **level** : float = 0f - Current envelope output level [0..1]. * **release_level** : float = 1f - Level captured at note-off; release ramps down from here. * **slope** : float = 0f - Per-sample slope (linear delta or exponential multiplier depending on stage). * **is_exponential** : bool = false - True when the current segment uses an exponential curve instead of linear. * **is_amp_env** : bool = false - True for the amplitude envelope (uses exponential decay/release and velocity-scaled attack). * **midi_velocity** : int = 127 - MIDI velocity used to scale the attack segment length on amplitude envelopes. * **delay_sec** : float = 0f - Delay stage duration in seconds. * **attack_sec** : float = 0.001f - Attack stage duration in seconds. * **hold_sec** : float = 0f - Hold stage duration in seconds. * **decay_sec** : float = 0.001f - Decay stage duration in seconds. * **sustain_level** : float = 1f - Sustain level in [0..1] as a linear fraction (1.0 = peak, 0.0 = silence). * **release_sec** : float = 0.001f - Release stage duration in seconds. .. _struct-strudel_sf2_voice-SF2LFO: .. das:attribute:: SF2LFO :Fields: * **delay_sec** : float = 0f - Silent delay before the LFO begins oscillating, in seconds. * **freq** : float = 8.176f - LFO frequency in Hz (SF2 default ~8.176 Hz = 0 cents from middle C). * **phase** : float = 0f - Current phase in [0..1]. * **elapsed** : float = 0f - Elapsed time since note-on, used to test against `delay_sec`. .. _struct-strudel_sf2_voice-SF2VoiceAttenState: .. das:attribute:: SF2VoiceAttenState :Fields: * **base_atten_cb** : int = 0 - Generator initialAttenuation value in centibels, before modulators. * **velocity** : int = 127 - Note-on velocity captured at voice creation. * **key** : int = 60 - MIDI key number captured at voice creation. * **atten_mods** : array< :ref:`SF2Modulator `> - All modulators (zone + unoverridden defaults) targeting initialAttenuation, collected once. ++++++++ Envelope ++++++++ * :ref:`sf2_env_start (var env: SF2Envelope; sample_rate: float) ` * :ref:`sf2_env_tick (var env: SF2Envelope; released: bool; sample_rate: float) : float ` .. _function-strudel_sf2_voice_sf2_env_start_SF2Envelope_float: .. das:function:: sf2_env_start(env: SF2Envelope; sample_rate: float) def sf2_env_start (var env: SF2Envelope; sample_rate: float) :Arguments: * **env** : :ref:`SF2Envelope ` * **sample_rate** : float .. _function-strudel_sf2_voice_sf2_env_tick_SF2Envelope_bool_float: .. das:function:: sf2_env_tick(env: SF2Envelope; released: bool; sample_rate: float) : float def sf2_env_tick (var env: SF2Envelope; released: bool; sample_rate: float) : float :Arguments: * **env** : :ref:`SF2Envelope ` * **released** : bool * **sample_rate** : float +++ LFO +++ * :ref:`sf2_lfo_tick (var lfo: SF2LFO; dt: float) : float ` .. _function-strudel_sf2_voice_sf2_lfo_tick_SF2LFO_float: .. das:function:: sf2_lfo_tick(lfo: SF2LFO; dt: float) : float def sf2_lfo_tick (var lfo: SF2LFO; dt: float) : float :Arguments: * **lfo** : :ref:`SF2LFO ` * **dt** : float ++++++++++ Modulators ++++++++++ * :ref:`sf2_apply_default_modulators (izone: SF2Zone; global_izone: SF2Zone const?; pzone: SF2Zone const?; global_pzone: SF2Zone const?; velocity: int; key: int; cc_values: int const[128]; var gen_offsets: float[64]) ` * :ref:`sf2_apply_modulators (zone: SF2Zone; velocity: int; key: int; cc_values: int const[128]; var gen_offsets: float[64]) ` * :ref:`sf2_collect_atten_modulators (izone: SF2Zone; global_izone: SF2Zone const?; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : array\ ` * :ref:`sf2_default_cc_values (var cc: int[128]) ` * :ref:`sf2_mod_get_value (m: SF2Modulator; velocity: int; key: int; cc_values: int const[128]) : float ` * :ref:`sf2_mod_identity_match (a: SF2Modulator; b: SF2Modulator) : bool ` * :ref:`sf2_recalc_atten (atten_state: SF2VoiceAttenState; cc_values: int const[128]) : float ` * :ref:`sf2_velocity_attenuation (velocity: int) : float ` .. _function-strudel_sf2_voice_sf2_apply_default_modulators_SF2Zone_SF2Zone_const_q__SF2Zone_const_q__SF2Zone_const_q__int_int_int_const_lb_128_rb__float_lb_64_rb_: .. das:function:: sf2_apply_default_modulators(izone: SF2Zone; global_izone: SF2Zone const?; pzone: SF2Zone const?; global_pzone: SF2Zone const?; velocity: int; key: int; cc_values: int const[128]; gen_offsets: float[64]) Apply the 10 SF2 spec default modulators (8.4.1-8.4.10) that are not overridden by any zone modulator. Must be called AFTER zone modulators have already been accumulated into `gen_offsets`. :Arguments: * **izone** : :ref:`SF2Zone ` * **global_izone** : :ref:`SF2Zone `? * **pzone** : :ref:`SF2Zone `? * **global_pzone** : :ref:`SF2Zone `? * **velocity** : int * **key** : int * **cc_values** : int\ [128] * **gen_offsets** : float\ [64] .. _function-strudel_sf2_voice_sf2_apply_modulators_SF2Zone_int_int_int_const_lb_128_rb__float_lb_64_rb_: .. das:function:: sf2_apply_modulators(zone: SF2Zone; velocity: int; key: int; cc_values: int const[128]; gen_offsets: float[64]) Accumulate all modulator outputs from a zone into `gen_offsets` indexed by destination generator. :Arguments: * **zone** : :ref:`SF2Zone ` * **velocity** : int * **key** : int * **cc_values** : int\ [128] * **gen_offsets** : float\ [64] .. _function-strudel_sf2_voice_sf2_collect_atten_modulators_SF2Zone_SF2Zone_const_q__SF2Zone_const_q__SF2Zone_const_q_: .. das:function:: sf2_collect_atten_modulators(izone: SF2Zone; global_izone: SF2Zone const?; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : array Collect every modulator targeting initialAttenuation across all zone layers plus unoverridden defaults. Called once at voice creation; results are cached in SF2VoiceAttenState for fast CC-driven re-evaluation. :Arguments: * **izone** : :ref:`SF2Zone ` * **global_izone** : :ref:`SF2Zone `? * **pzone** : :ref:`SF2Zone `? * **global_pzone** : :ref:`SF2Zone `? .. _function-strudel_sf2_voice_sf2_default_cc_values_int_lb_128_rb_: .. das:function:: sf2_default_cc_values(cc: int[128]) Initialise a CC array to General MIDI defaults (CC7=100, CC10=64, CC11=127, pitchwheel slot=8192). Used by MidiChannelState and standalone voice creation; slot 127 holds the pitch wheel value for SF2 modulator input. :Arguments: * **cc** : int\ [128] .. _function-strudel_sf2_voice_sf2_mod_get_value_SF2Modulator_int_int_int_const_lb_128_rb_: .. das:function:: sf2_mod_get_value(m: SF2Modulator; velocity: int; key: int; cc_values: int const[128]) : float Evaluate a single SF2 modulator: amount * transform(src1) * transform(src2). Applies the optional absolute-value transform when `m.transform == 2`. :Arguments: * **m** : :ref:`SF2Modulator ` * **velocity** : int * **key** : int * **cc_values** : int\ [128] .. _function-strudel_sf2_voice_sf2_mod_identity_match_SF2Modulator_SF2Modulator: .. das:function:: sf2_mod_identity_match(a: SF2Modulator; b: SF2Modulator) : bool Two SF2 modulators share identity when src1, src2, and destination all match. Used to detect when a zone modulator overrides a default modulator. :Arguments: * **a** : :ref:`SF2Modulator ` * **b** : :ref:`SF2Modulator ` .. _function-strudel_sf2_voice_sf2_recalc_atten_SF2VoiceAttenState_int_const_lb_128_rb_: .. das:function:: sf2_recalc_atten(atten_state: SF2VoiceAttenState; cc_values: int const[128]) : float Recalculate voice attenuation (linear gain) from cached modulators and current CC values. Called at voice creation and whenever CC7/CC11/etc change on the channel. :Arguments: * **atten_state** : :ref:`SF2VoiceAttenState ` * **cc_values** : int\ [128] .. _function-strudel_sf2_voice_sf2_velocity_attenuation_int: .. das:function:: sf2_velocity_attenuation(velocity: int) : float SF2 default velocity-to-attenuation curve (concave, 960 cB range). Implements default modulator #1 from SF2 spec section 8.4.2; returns a linear gain. :Arguments: * **velocity** : int ++++++++++++++ Voice creation ++++++++++++++ * :ref:`sf2_create_c_voice (sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?; note: int; velocity: int; channel: int; cc_values: int const[128]; var out_voice: ma_sf2_voice; var out_atten_state: SF2VoiceAttenState) : bool ` * :ref:`sf2_get_chorus_send (sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : float ` * :ref:`sf2_get_exclusive_class (sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : int ` * :ref:`sf2_get_reverb_send (sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : float ` .. _function-strudel_sf2_voice_sf2_create_c_voice_SF2File_int_int_SF2Zone_const_q__SF2Zone_const_q__int_int_int_int_const_lb_128_rb__ma_sf2_voice_SF2VoiceAttenState: .. das:function:: sf2_create_c_voice(sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?; note: int; velocity: int; channel: int; cc_values: int const[128]; out_voice: ma_sf2_voice; out_atten_state: SF2VoiceAttenState) : bool Resolve all SF2 generators/modulators for a note and populate the C `ma_sf2_voice` runtime state. Sets up sample addressing, pitch, envelopes, LFOs, filter, pan and attenuation; returns false if the sample is invalid. :Arguments: * **sf2** : :ref:`SF2File ` * **inst_idx** : int * **izone_idx** : int * **pzone** : :ref:`SF2Zone `? * **global_pzone** : :ref:`SF2Zone `? * **note** : int * **velocity** : int * **channel** : int * **cc_values** : int\ [128] * **out_voice** : :ref:`ma_sf2_voice ` * **out_atten_state** : :ref:`SF2VoiceAttenState ` .. _function-strudel_sf2_voice_sf2_get_chorus_send_SF2File_int_int_SF2Zone_const_q__SF2Zone_const_q_: .. das:function:: sf2_get_chorus_send(sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : float Resolve the chorus effects-send amount across zone layers, normalised to [0..1]. :Arguments: * **sf2** : :ref:`SF2File ` * **inst_idx** : int * **izone_idx** : int * **pzone** : :ref:`SF2Zone `? * **global_pzone** : :ref:`SF2Zone `? .. _function-strudel_sf2_voice_sf2_get_exclusive_class_SF2File_int_int_SF2Zone_const_q__SF2Zone_const_q_: .. das:function:: sf2_get_exclusive_class(sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : int Resolve the exclusiveClass generator across all zone layers. Non-zero classes cut off earlier notes in the same class (GM drum open/closed hi-hat behaviour). :Arguments: * **sf2** : :ref:`SF2File ` * **inst_idx** : int * **izone_idx** : int * **pzone** : :ref:`SF2Zone `? * **global_pzone** : :ref:`SF2Zone `? .. _function-strudel_sf2_voice_sf2_get_reverb_send_SF2File_int_int_SF2Zone_const_q__SF2Zone_const_q_: .. das:function:: sf2_get_reverb_send(sf2: SF2File; inst_idx: int; izone_idx: int; pzone: SF2Zone const?; global_pzone: SF2Zone const?) : float Resolve the reverb effects-send amount across zone layers, normalised to [0..1]. :Arguments: * **sf2** : :ref:`SF2File ` * **inst_idx** : int * **izone_idx** : int * **pzone** : :ref:`SF2Zone `? * **global_pzone** : :ref:`SF2Zone `?