17.13. SoundFont 2 per-voice runtime: envelope, LFO, modulators, biquad
Module strudel_sf2_voice
17.13.1. Enumerations
- 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.
17.13.2. Structures
- SF2Envelope
- Fields:
stage : 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.
- 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.
- 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< SF2Modulator> - All modulators (zone + unoverridden defaults) targeting initialAttenuation, collected once.
17.13.3. Envelope
- sf2_env_start(env: SF2Envelope; sample_rate: float)
def sf2_env_start (var env: SF2Envelope; sample_rate: float)
- Arguments:
env : SF2Envelope
sample_rate : float
- 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 : SF2Envelope
released : bool
sample_rate : float
17.13.4. LFO
- sf2_lfo_tick(lfo: SF2LFO; dt: float): float
def sf2_lfo_tick (var lfo: SF2LFO; dt: float) : float
- Arguments:
lfo : SF2LFO
dt : float
17.13.5. Modulators
- 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:
- 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 : SF2Zone
velocity : int
key : int
cc_values : int[128]
gen_offsets : float[64]
- sf2_collect_atten_modulators(izone: SF2Zone; global_izone: SF2Zone const?; pzone: SF2Zone const?; global_pzone: SF2Zone const?): array<SF2Modulator>
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.
- 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]
- 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 : SF2Modulator
velocity : int
key : int
cc_values : int[128]
- 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 : SF2Modulator
b : SF2Modulator
- 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 : SF2VoiceAttenState
cc_values : int[128]
- 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
17.13.6. Voice creation
- 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 : SF2File
inst_idx : int
izone_idx : int
pzone : SF2Zone?
global_pzone : SF2Zone?
note : int
velocity : int
channel : int
cc_values : int[128]
out_voice : ma_sf2_voice
out_atten_state : SF2VoiceAttenState
- 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].
- 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).
- 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].