diff --git a/fedac/native/src/gm_synth.c b/fedac/native/src/gm_synth.c index 10b16a3811..053bc81ebd 100644 --- a/fedac/native/src/gm_synth.c +++ b/fedac/native/src/gm_synth.c @@ -213,6 +213,55 @@ typedef struct { double wg_trem_hz; // section/tremolo amplitude LFO rate (0 = none) double wg_trem_depth; // amplitude LFO depth double wg_bore_mult; // bore-length multiplier (1=string, 2=brass half-wave) + // -- Additive Hammond drawbar / free-reed organ (ORGAN) -- + double org_amps[9]; // drawbar registration (footage ratios are fixed) + int org_freereed; // 1 = saw/pulse reed banks (accordion/harmonica) + int org_reed_sq[9]; // free-reed: per-bank 1 = pulse, 0 = saw + double org_drive; // rock-organ overdrive + double org_perc_amp; // percussive ping amplitude (0 = none) + double org_perc_ms; // percussive ping decay + double org_perc_ratio; // percussion harmonic ratio (4=2nd, 6=3rd) + double org_click_amp; // key-click burst amplitude + double org_click_ms; // key-click decay + double org_leslie_hz; // Leslie rotary rate (0 = none) + double org_leslie_depth; // Leslie amp+pitch depth + double org_breath; // free-reed breath noise + double org_lp_hz; // free-reed output LPF cutoff (0 = bypass) + double org_detune_cents; // free-reed musette detune spread + // -- Detuned multi-osc subtractive (SUPERSAW): ensemble / lead / pad -- + int ss_nosc; // oscillator count (1..7); 7 = full Szabo supersaw + double ss_detune_x; // Szabo detune knob 0..1 (drives 7-saw spread) + double ss_mix_m; // Szabo mix knob 0..1 (center/side gain law) + double ss_spread_cents; // simple per-osc detune when ss_nosc<7 (lead/pad) + int ss_square; // 1 = pulse/square oscillators (else saw) + double ss_pwm_hz, ss_pwm_depth; // PWM LFO on square width + double ss_cut0; // base LPF cutoff (Hz) + double ss_cut_env; // attack filter-envelope amount (Hz added at onset) + double ss_cut_env_ms; // filter-envelope decay + double ss_res; // SVF resonance (0..0.98) + double ss_sweep_hz, ss_sweep_oct; // slow cutoff LFO rate + depth (octaves) + double ss_attack_ms; // amplitude attack ramp (slow for pads) + double ss_vib_hz, ss_vib_depth; // collective vibrato + double ss_chorus_hz, ss_chorus_depth, ss_chorus_mix; // ensemble chorus + double ss_drive; // charang/overdrive + double ss_sub_mix; int ss_sub_sq; // bass+lead sub-octave + double ss_fifth_gain; // fifths lead: +7 semitone (1.5x) layer gain + double ss_chiff_amt, ss_chiff_ms, ss_chiff_bp; // chiff/transient burst + double ss_trem_hz, ss_trem_depth; // halo amplitude tremolo + double ss_body_ms; // orchestra-hit body decay (0 = sustained) + double ss_pitch_blip; // orchestra-hit downward pitch blip (cents) + double ss_out_scale; // output gain + // -- Vocal formant bank (FORMANT): choir / voice / synth-voice -- + int fmt_nsrc; // source-osc count (1=solo voice, 3=choir) + int fmt_src_sq; // 1 = pulse source, 0 = saw + double fmt_detune_cents; // per-source choir detune + double fmt_f[3]; // formant frequencies F1..F3 (Hz, absolute) + double fmt_bw[3]; // formant bandwidths (Hz) + double fmt_gain_db[3]; // formant gains (dB) + double fmt_attack_ms; // slow vocal onset + double fmt_breath; // aspiration noise + double fmt_vib_hz, fmt_vib_depth; // decorrelated vibrato + double fmt_out_scale; // output gain } GMProgramParams; #define GM_PIANO_PROGRAM_COUNT 8 @@ -681,6 +730,225 @@ static const GMProgramParams gm_pipe_programs[8] = { .wg_out_scale = 1.5 }, }; +// ============================================================ +// Batch-4 families: additive organs, supersaw ensemble/lead/pad, vocal formant. +// Organ 16-23, Ensemble 48-55, Synth Lead 80-87, Synth Pad 88-95. +// Three new engines: ORGAN (Hammond drawbars + free-reed), SUPERSAW (detuned +// multi-osc subtractive + SVF), FORMANT (source-filter vocal bank). Variants are +// DATA; the generators are generic. See dossiers 01 (organ), 02 (ensemble/choir), +// 03 (synth lead / synth pad), 00 (per-family stochasticism mul). +// ============================================================ + +// ── Organ (GM 17-24 / 0-based 16-23) ── +// Hammond drawbar footage ratios (fixed): 16′ 5⅓′ 8′ 4′ 2⅔′ 2′ 1⅗′ 1⅓′ 1′. +static const double GM_ORGAN_RATIOS[9] = + { 0.5, 1.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0 }; +#define GM_ORGAN_FIRST 16 +static const GMProgramParams gm_organ_programs[8] = { + // 17 Drawbar — full registration 888 800 000, slow Leslie + faint click. + { .engine = GM_ENGINE_ORGAN, + .org_amps = {0.8,0.8,0.8,0.8,0.0,0.0,0.0,0.0,0.0}, + .org_click_amp = 0.04, .org_click_ms = 3.0, + .org_leslie_hz = 0.8, .org_leslie_depth = 0.10 }, + // 18 Percussive — 888 000 000 + 2nd-harmonic ping (4′) fast decay. + { .engine = GM_ENGINE_ORGAN, + .org_amps = {0.7,0.0,0.9,0.6,0.0,0.0,0.0,0.0,0.0}, + .org_perc_amp = 0.5, .org_perc_ms = 180.0, .org_perc_ratio = 2.0, + .org_click_amp = 0.06, .org_click_ms = 2.5, + .org_leslie_hz = 0.8, .org_leslie_depth = 0.10 }, + // 19 Rock — full bright drawbars + overdrive + fast Leslie. + { .engine = GM_ENGINE_ORGAN, + .org_amps = {0.9,0.7,0.9,0.8,0.5,0.7,0.4,0.3,0.6}, + .org_drive = 0.55, .org_click_amp = 0.05, .org_click_ms = 2.0, + .org_leslie_hz = 6.9, .org_leslie_depth = 0.18 }, + // 20 Church (Pipe) — octave-spaced ranks 8+4+2+mixtures, slow chiff, no Leslie. + { .engine = GM_ENGINE_ORGAN, + .org_amps = {0.4,0.3,0.9,0.7,0.4,0.6,0.3,0.0,0.4}, + .org_click_amp = 0.03, .org_click_ms = 8.0, + .org_detune_cents = 4.0 }, + // 21 Reed Organ — free-reed, sawtooth-ish + gentle LP + reed beating. + { .engine = GM_ENGINE_ORGAN, .org_freereed = 1, + .org_amps = {0.0,0.0,1.0,0.5,0.0,0.0,0.0,0.0,0.0}, + .org_detune_cents = 8.0, .org_breath = 0.04, .org_lp_hz = 3500.0, + .org_click_amp = 0.02, .org_click_ms = 6.0 }, + // 22 Accordion — free-reed, 3 detuned banks (musette), buzzy bright. + { .engine = GM_ENGINE_ORGAN, .org_freereed = 1, + .org_amps = {0.0,0.0,1.0,0.7,0.0,0.5,0.0,0.0,0.0}, + .org_detune_cents = 18.0, .org_breath = 0.05, .org_lp_hz = 5000.0, + .org_click_amp = 0.03, .org_click_ms = 4.0, + .org_leslie_hz = 4.5, .org_leslie_depth = 0.06 }, + // 23 Harmonica — free-reed, breath-driven, strong air + tremolo. + { .engine = GM_ENGINE_ORGAN, .org_freereed = 1, + .org_amps = {0.0,0.0,1.0,0.6,0.0,0.4,0.0,0.0,0.0}, + .org_reed_sq = {0,0,1,0,0,0,0,0,0}, .org_detune_cents = 6.0, + .org_breath = 0.14, .org_lp_hz = 4500.0, + .org_leslie_hz = 5.5, .org_leslie_depth = 0.10, + .org_click_amp = 0.02, .org_click_ms = 3.0 }, + // 24 Tango Accordion (Bandoneon) — drier/sharper, dual-reed octave, more buzz. + { .engine = GM_ENGINE_ORGAN, .org_freereed = 1, + .org_amps = {0.0,0.0,1.0,0.8,0.0,0.6,0.0,0.0,0.3}, + .org_reed_sq = {0,0,0,1,0,1,0,0,0}, .org_detune_cents = 9.0, + .org_breath = 0.05, .org_lp_hz = 6000.0, + .org_click_amp = 0.05, .org_click_ms = 3.0 }, +}; + +// ── Ensemble (GM 49-56 / 0-based 48-55) ── +// 48-51 supersaw string/synth-string pads; 52-54 vocal formant (init routes to +// FORMANT); 55 Orchestra Hit = detuned cluster + transient + fast decay. +#define GM_ENSEMBLE_FIRST 48 +static const GMProgramParams gm_ensemble_programs[5] = { + // 49 String Ensemble 1 — warm, slow attack, lower LPF, heavy chorus. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 7, .ss_detune_x = 0.30, .ss_mix_m = 0.70, + .ss_cut0 = 4500.0, .ss_res = 0.20, .ss_attack_ms = 160.0, + .ss_vib_hz = 5.0, .ss_vib_depth = 0.004, + .ss_chorus_hz = 0.45, .ss_chorus_depth = 0.004, .ss_chorus_mix = 0.5, + .ss_out_scale = 0.5 }, + // 50 String Ensemble 2 — wider detune, brighter, slower swell, more chorus. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 7, .ss_detune_x = 0.45, .ss_mix_m = 0.78, + .ss_cut0 = 7000.0, .ss_res = 0.18, .ss_attack_ms = 250.0, + .ss_vib_hz = 4.8, .ss_vib_depth = 0.005, + .ss_chorus_hz = 0.4, .ss_chorus_depth = 0.006, .ss_chorus_mix = 0.6, + .ss_out_scale = 0.5 }, + // 51 SynthStrings 1 — overtly synthetic; filter-env sweep + heavy chorus. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 7, .ss_detune_x = 0.30, .ss_mix_m = 0.60, + .ss_cut0 = 1400.0, .ss_cut_env = 4000.0, .ss_cut_env_ms = 200.0, .ss_res = 0.35, + .ss_attack_ms = 90.0, .ss_chorus_hz = 0.5, .ss_chorus_depth = 0.007, + .ss_chorus_mix = 0.7, .ss_out_scale = 0.5 }, + // 52 SynthStrings 2 — more resonance, square layer, touch faster. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 7, .ss_detune_x = 0.40, .ss_mix_m = 0.65, + .ss_cut0 = 1800.0, .ss_cut_env = 4500.0, .ss_cut_env_ms = 160.0, .ss_res = 0.55, + .ss_attack_ms = 70.0, .ss_pwm_hz = 0.6, .ss_pwm_depth = 0.25, + .ss_chorus_hz = 0.5, .ss_chorus_depth = 0.006, .ss_chorus_mix = 0.6, + .ss_out_scale = 0.5 }, + // 56 Orchestra Hit — dense detuned cluster + chiff transient + fast decay + blip. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 7, .ss_detune_x = 0.55, .ss_mix_m = 0.80, + .ss_cut0 = 6000.0, .ss_res = 0.25, .ss_attack_ms = 3.0, + .ss_fifth_gain = 0.5, .ss_chiff_amt = 0.5, .ss_chiff_ms = 18.0, .ss_chiff_bp = 8.0, + .ss_body_ms = 320.0, .ss_pitch_blip = 100.0, .ss_out_scale = 0.45 }, +}; + +// ── Choir / Voice (GM 53-55 / 0-based 52-54) — vocal formant bank ── +// /a/ "Aah" (bass row) and /u/ "Ooh" (bass row), dossier 02 Csound tables. +static const GMProgramParams gm_choir_program = { // 53 Choir Aahs — /a/, 3 voices + .engine = GM_ENGINE_FORMANT, .fmt_nsrc = 3, .fmt_src_sq = 0, .fmt_detune_cents = 7.0, + .fmt_f = {600.0, 1040.0, 2250.0}, .fmt_bw = {60.0, 70.0, 110.0}, + .fmt_gain_db = {0.0, -7.0, -9.0}, .fmt_attack_ms = 120.0, .fmt_breath = 0.05, + .fmt_vib_hz = 5.5, .fmt_vib_depth = 0.012, .fmt_out_scale = 1.6, +}; +static const GMProgramParams gm_voiceoohs_program = { // 54 Voice Oohs — /u/, 3 voices + .engine = GM_ENGINE_FORMANT, .fmt_nsrc = 3, .fmt_src_sq = 0, .fmt_detune_cents = 7.0, + .fmt_f = {350.0, 600.0, 2400.0}, .fmt_bw = {40.0, 80.0, 100.0}, + .fmt_gain_db = {0.0, -20.0, -32.0}, .fmt_attack_ms = 140.0, .fmt_breath = 0.04, + .fmt_vib_hz = 5.2, .fmt_vib_depth = 0.012, .fmt_out_scale = 1.8, +}; +static const GMProgramParams gm_synthvoice_program = { // 55 Synth Voice — solo, neutral + .engine = GM_ENGINE_FORMANT, .fmt_nsrc = 1, .fmt_src_sq = 0, .fmt_detune_cents = 0.0, + .fmt_f = {500.0, 900.0, 2500.0}, .fmt_bw = {60.0, 90.0, 120.0}, + .fmt_gain_db = {0.0, -8.0, -14.0}, .fmt_attack_ms = 40.0, .fmt_breath = 0.02, + .fmt_vib_hz = 5.0, .fmt_vib_depth = 0.008, .fmt_out_scale = 1.5, +}; + +// ── Synth Lead (GM 81-88 / 0-based 80-87) — subtractive named waveforms ── +// 86 Voice routes to FORMANT (init); the rest are SUPERSAW (1-2 osc subtractive). +#define GM_LEAD_FIRST 80 +static const GMProgramParams gm_lead_programs[8] = { + // 81 Square — 1 square + PWM, light static LPF, hollow odd-harmonic. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 1, .ss_square = 1, + .ss_pwm_hz = 0.4, .ss_pwm_depth = 0.35, + .ss_cut0 = 6000.0, .ss_res = 0.20, .ss_attack_ms = 6.0, .ss_out_scale = 0.7 }, + // 82 Sawtooth — 2 detuned saws + LPF env sweep, bright buzzy lead. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 2, .ss_spread_cents = 7.0, + .ss_cut0 = 2500.0, .ss_cut_env = 4500.0, .ss_cut_env_ms = 120.0, .ss_res = 0.30, + .ss_attack_ms = 5.0, .ss_out_scale = 0.7 }, + // 83 Calliope — triangle-ish (filtered saw) + soft saw, slow attack, breath. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 2, .ss_spread_cents = 4.0, + .ss_cut0 = 2000.0, .ss_res = 0.10, .ss_attack_ms = 60.0, + .ss_vib_hz = 5.0, .ss_vib_depth = 0.005, .ss_chiff_amt = 0.06, .ss_chiff_ms = 30.0, + .ss_chiff_bp = 6.0, .ss_out_scale = 0.8 }, + // 84 Chiff — saw + noise burst on attack, fast env, breathy chiff transient. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 2, .ss_spread_cents = 6.0, + .ss_cut0 = 3000.0, .ss_res = 0.25, .ss_attack_ms = 8.0, + .ss_chiff_amt = 0.45, .ss_chiff_ms = 40.0, .ss_chiff_bp = 5.0, .ss_out_scale = 0.7 }, + // 85 Charang — 2 hard-detuned saws + drive + resonant LPF, guitar-ish. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 2, .ss_spread_cents = 12.0, + .ss_cut0 = 3500.0, .ss_res = 0.45, .ss_attack_ms = 5.0, .ss_drive = 0.6, + .ss_out_scale = 0.65 }, + // 86 Voice — formant "aah" lead (routes to FORMANT in init; row unused). + { .engine = GM_ENGINE_FORMANT }, + // 87 Fifths — lead osc + perfect-fifth (1.5x) layer, LPF, organum. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 1, .ss_fifth_gain = 0.9, + .ss_cut0 = 3500.0, .ss_res = 0.25, .ss_attack_ms = 6.0, .ss_out_scale = 0.6 }, + // 88 Bass+Lead — saw + sub-octave square, keytracked LPF, fat split. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 2, .ss_spread_cents = 6.0, + .ss_sub_mix = 0.7, .ss_sub_sq = 1, + .ss_cut0 = 3000.0, .ss_res = 0.30, .ss_attack_ms = 6.0, .ss_out_scale = 0.6 }, +}; + +// ── Synth Pad (GM 89-96 / 0-based 88-95) — detuned multi-osc + slow sweep ── +// 92 Choir + 95 Halo route to FORMANT (init); the rest SUPERSAW with slow attacks. +#define GM_PAD_FIRST 88 +static const GMProgramParams gm_pad_programs[8] = { + // 89 New Age — detuned saws, slow LPF sweep, shimmery slow swell. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 5, .ss_spread_cents = 10.0, + .ss_cut0 = 2200.0, .ss_res = 0.30, .ss_sweep_hz = 0.15, .ss_sweep_oct = 1.0, + .ss_attack_ms = 500.0, .ss_chorus_hz = 0.3, .ss_chorus_depth = 0.005, + .ss_chorus_mix = 0.5, .ss_out_scale = 0.5 }, + // 90 Warm — 3 detuned saws, low cutoff, soft attack, rounded. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 3, .ss_spread_cents = 8.0, + .ss_cut0 = 1100.0, .ss_res = 0.20, .ss_attack_ms = 600.0, + .ss_chorus_hz = 0.25, .ss_chorus_depth = 0.005, .ss_chorus_mix = 0.5, + .ss_out_scale = 0.55 }, + // 91 Polysynth — bright detuned saws + PWM, medium attack. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 5, .ss_spread_cents = 12.0, + .ss_pwm_hz = 0.4, .ss_pwm_depth = 0.2, + .ss_cut0 = 4500.0, .ss_res = 0.25, .ss_attack_ms = 200.0, + .ss_chorus_hz = 0.35, .ss_chorus_depth = 0.004, .ss_chorus_mix = 0.5, + .ss_out_scale = 0.5 }, + // 92 Choir — vocal-formant pad (routes to FORMANT in init; row unused). + { .engine = GM_ENGINE_FORMANT }, + // 93 Bowed Glass — saw + slow noisy attack + high-Q body resonance swell. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 3, .ss_spread_cents = 6.0, + .ss_cut0 = 1800.0, .ss_res = 0.55, .ss_attack_ms = 700.0, + .ss_chiff_amt = 0.10, .ss_chiff_ms = 400.0, .ss_chiff_bp = 4.0, + .ss_chorus_hz = 0.2, .ss_chorus_depth = 0.004, .ss_chorus_mix = 0.4, + .ss_out_scale = 0.5 }, + // 94 Metallic — FM inharmonic (ratio 1.4) bell-like + slow attack. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 3, .ss_spread_cents = 5.0, + .ss_square = 1, .ss_pwm_hz = 0.3, .ss_pwm_depth = 0.4, + .ss_cut0 = 5000.0, .ss_res = 0.40, .ss_attack_ms = 400.0, + .ss_sweep_hz = 0.18, .ss_sweep_oct = 1.2, + .ss_out_scale = 0.5 }, + // 95 Halo — bright formant choir pad + slow tremolo (routes to FORMANT). + { .engine = GM_ENGINE_FORMANT }, + // 96 Sweep — detuned saws + dramatic resonant LPF sweep LFO. + { .engine = GM_ENGINE_SUPERSAW, .ss_nosc = 5, .ss_spread_cents = 12.0, + .ss_cut0 = 1500.0, .ss_res = 0.70, .ss_sweep_hz = 0.22, .ss_sweep_oct = 2.5, + .ss_attack_ms = 300.0, .ss_chorus_hz = 0.3, .ss_chorus_depth = 0.005, + .ss_chorus_mix = 0.5, .ss_out_scale = 0.45 }, +}; +// Halo = bright formant pad: /a/ bright (alto row) + slow amplitude tremolo. +static const GMProgramParams gm_halo_program = { + .engine = GM_ENGINE_FORMANT, .fmt_nsrc = 3, .fmt_src_sq = 0, .fmt_detune_cents = 9.0, + .fmt_f = {800.0, 1150.0, 2800.0}, .fmt_bw = {50.0, 60.0, 170.0}, + .fmt_gain_db = {0.0, -4.0, -16.0}, .fmt_attack_ms = 500.0, .fmt_breath = 0.05, + .fmt_vib_hz = 5.0, .fmt_vib_depth = 0.012, .fmt_out_scale = 1.5, + .ss_trem_hz = 4.0, .ss_trem_depth = 0.25, +}; +// Choir pad (92) = "ooh/aah" SoS formant + chorus + slow attack (alto /o/-ish). +static const GMProgramParams gm_choirpad_program = { + .engine = GM_ENGINE_FORMANT, .fmt_nsrc = 3, .fmt_src_sq = 0, .fmt_detune_cents = 8.0, + .fmt_f = {450.0, 800.0, 2830.0}, .fmt_bw = {70.0, 80.0, 100.0}, + .fmt_gain_db = {0.0, -9.0, -16.0}, .fmt_attack_ms = 450.0, .fmt_breath = 0.05, + .fmt_vib_hz = 5.0, .fmt_vib_depth = 0.012, .fmt_out_scale = 1.6, +}; +// Synth-lead Voice (86) = formant "aah" lead, solo, fast attack. +static const GMProgramParams gm_leadvoice_program = { + .engine = GM_ENGINE_FORMANT, .fmt_nsrc = 1, .fmt_src_sq = 0, .fmt_detune_cents = 0.0, + .fmt_f = {660.0, 1700.0, 2400.0}, .fmt_bw = {80.0, 90.0, 120.0}, + .fmt_gain_db = {0.0, -8.0, -12.0}, .fmt_attack_ms = 14.0, .fmt_breath = 0.02, + .fmt_vib_hz = 5.5, .fmt_vib_depth = 0.010, .fmt_out_scale = 1.4, +}; + // ── Batch-2 note-on helpers ── // Set up up-to-3 parallel body-resonance band-pass biquads. @@ -984,6 +1252,346 @@ static void gm_waveguide_init(GMVoice *v, const GMProgramParams *p, double f0, } } +// ── Batch-4 note-on helpers (ORGAN / SUPERSAW / FORMANT) ── + +// One-pole LPF coefficient for a cutoff in Hz (g = 1 - exp(-2π·fc/sr)). +static inline double gm_onepole_g(double fc, double sr) { + if (fc <= 0.0) return 0.0; + if (fc > sr * 0.45) fc = sr * 0.45; + return 1.0 - exp(-2.0 * M_PI * fc / sr); +} + +// Additive Hammond drawbar / free-reed organ note-on. Organs are the MOST +// consistent GM family (dossier 00 §7: mul 0.3 for Hammond, 0.5 for free-reed) — +// the electromechanical machine. Variation is mostly per-bar phase (lever c, free) +// + a hair of tonewheel detune + key-click seed. +static void gm_organ_init(GMVoice *v, const GMProgramParams *p, double f0, + double sr) { + const double mul = p->org_freereed ? 0.5 : 0.3; + v->org_freereed = p->org_freereed; + v->org_drive = p->org_drive; + int nb = 0; + for (int d = 0; d < 9; d++) { + if (p->org_amps[d] <= 0.0) continue; + double ratio = GM_ORGAN_RATIOS[d]; + double fk = f0 * ratio; + // Free-reed musette detune is intrinsic to the patch; Hammond gets only a + // hair of tonewheel detune (lever a, hard-capped ±6c regardless of mul). + double spread = p->org_freereed ? p->org_detune_cents : (p->org_detune_cents + 1.0); + fk = voice_detune(v, fk, spread, mul); + if (fk > sr * 0.45) fk = sr * 0.45; + v->org_inc[nb] = fk / sr; + v->org_phase[nb] = voice_rand_phase(v); // lever c (free) + v->org_amp[nb] = voice_jitter(v, p->org_amps[d], 0.10, mul); // lever b + v->org_bar_square[nb] = p->org_reed_sq[d]; + nb++; + } + if (nb < 1) { // never silent: guarantee at least the 8′ fundamental + v->org_inc[0] = voice_detune(v, f0, 2.0, mul) / sr; + v->org_phase[0] = voice_rand_phase(v); + v->org_amp[0] = 1.0; + v->org_bar_square[0] = 0; + nb = 1; + } + v->org_nbars = nb; + // Normalize so the summed registration stays bounded. + double sum = 0.0; + for (int i = 0; i < nb; i++) sum += fabs(v->org_amp[i]); + if (sum < 1e-6) sum = 1.0; + double norm = 1.0 / sum; + for (int i = 0; i < nb; i++) v->org_amp[i] *= norm; + // Percussive-organ 2nd/3rd harmonic ping (fast decay, B3 percussion). + if (p->org_perc_amp > 0.0) { + double pr = p->org_perc_ratio > 0.0 ? p->org_perc_ratio : 2.0; + double pf = voice_detune(v, f0 * pr, 2.0, mul); + if (pf > sr * 0.45) pf = sr * 0.45; + v->org_perc_inc = pf / sr; + v->org_perc_phase = voice_rand_phase(v); + v->org_perc_amp = voice_jitter(v, p->org_perc_amp, 0.10, mul); + v->org_perc_env = 1.0; + double pms = p->org_perc_ms * 0.001; if (pms < 0.001) pms = 0.001; + v->org_perc_dec = exp(-1.0 / (pms * sr)); + } else { + v->org_perc_amp = 0.0; v->org_perc_env = 0.0; v->org_perc_dec = 0.0; + v->org_perc_inc = 0.0; v->org_perc_phase = 0.0; + } + // Key-click: short HF noise burst at note-on (contact bounce, seed varies, g). + if (p->org_click_amp > 0.0) { + v->org_click_amp = voice_jitter(v, p->org_click_amp, 0.20, mul); + v->org_click_env = 1.0; + double cms = voice_jitter(v, p->org_click_ms, 0.20, mul) * 0.001; + if (cms < 0.0003) cms = 0.0003; + v->org_click_dec = exp(-1.0 / (cms * sr)); + v->org_click_lp = 0.0; + } else { + v->org_click_amp = 0.0; v->org_click_env = 0.0; v->org_click_dec = 0.0; + } + // Leslie rotary: amplitude + slight pitch LFO; rate jittered, random phase. + if (p->org_leslie_hz > 0.0) { + double lhz = voice_jitter(v, p->org_leslie_hz, 0.10, mul); + v->org_leslie_inc = lhz / sr; + v->org_leslie_depth = p->org_leslie_depth; + v->org_leslie_phase = voice_rand_phase(v); + } else { + v->org_leslie_inc = 0.0; v->org_leslie_depth = 0.0; v->org_leslie_phase = 0.0; + } + v->org_breath_amt = p->org_breath; + v->org_breath_lp = 0.0; + v->org_lp_g = gm_onepole_g(p->org_lp_hz, sr); + v->org_lp = 0.0; +} + +// 11th-order Szabo polynomial mapping the detune knob x∈[0,1] → spread d. +static double gm_szabo_detune(double x) { + return 10028.7312891634*pow(x,11) - 50818.8652045924*pow(x,10) + + 111363.4808729368*pow(x,9) - 138150.6761080548*pow(x,8) + + 106649.6679158292*pow(x,7) - 53046.9642751875*pow(x,6) + + 17019.9518580080*pow(x,5) - 3425.0836591318*pow(x,4) + + 404.2703938388*pow(x,3) - 24.1878824391*pow(x,2) + + 0.6717417634*x + 0.0030115596; +} + +// Detuned multi-osc subtractive note-on: supersaw ensemble / synth lead / pad. +// Per-family mul: ensemble 0.6, lead 0.6, pad 0.5 (dossier 00 §7). Supersaw is +// built from detune; organic add is per-note phase (c) + small detune wobble (a) +// + filter-cutoff drift (j). +static void gm_supersaw_init(GMVoice *v, const GMProgramParams *p, double f0, + double sr) { + double mul = 0.6; + if (p->ss_attack_ms > 250.0) mul = 0.5; // pad-ish → tighter + double fc = voice_detune(v, f0, 6.0, mul); + int n = p->ss_nosc; if (n < 1) n = 1; if (n > 7) n = 7; + v->ss_nosc = n; + + if (n == 7 && p->ss_detune_x > 0.0) { + // Szabo 7-saw supersaw: fixed relative offsets × poly-fitted spread. + static const double off[7] = { + -0.11002313, -0.06288439, -0.01952356, 0.0, + 0.01991221, 0.06216538, 0.10745242 }; + double d = gm_szabo_detune(clampd(p->ss_detune_x, 0.0, 1.0)); + double m = clampd(p->ss_mix_m, 0.0, 1.0); + double centerGain = -0.55366*m + 0.99785; + double sideGain = -0.73764*m*m + 1.2841*m + 0.044372; + for (int i = 0; i < 7; i++) { + // Per-osc detune wobble (lever a) on top of the fixed Szabo spread. + double jit = 1.0 + 0.0008 * mul * voice_rand_bipolar(v); + double fi = fc * (1.0 + off[i] * d) * jit; + if (fi > sr * 0.45) fi = sr * 0.45; + v->ss_inc[i] = fi / sr; + v->ss_phase[i] = voice_rand_phase(v); // lever c + v->ss_gain[i] = (i == 3) ? centerGain : sideGain; + v->ss_square[i] = p->ss_square; + } + } else { + // Simple detuned stack (lead/pad): symmetric spread around center. + for (int i = 0; i < n; i++) { + double frac = (n > 1) ? ((double)i / (double)(n - 1) - 0.5) * 2.0 : 0.0; + double cents = frac * p->ss_spread_cents; + cents += 0.5 * mul * voice_rand_bipolar(v); // lever a wobble + double fi = fc * cents_to_ratio(cents); + if (i == 0 && p->ss_fifth_gain > 0.0) {} // base stays center + if (fi > sr * 0.45) fi = sr * 0.45; + v->ss_inc[i] = fi / sr; + v->ss_phase[i] = voice_rand_phase(v); + v->ss_gain[i] = 1.0 / sqrt((double)n); + v->ss_square[i] = p->ss_square; + } + // Fifths lead: replace/augment with a +7-semitone (1.5×) partner layer. + if (p->ss_fifth_gain > 0.0 && n < 7) { + int idx = n; + double fi = fc * 1.5; + if (fi > sr * 0.45) fi = sr * 0.45; + v->ss_inc[idx] = fi / sr; + v->ss_phase[idx] = voice_rand_phase(v); + v->ss_gain[idx] = p->ss_fifth_gain / sqrt((double)n); + v->ss_square[idx] = p->ss_square; + v->ss_nosc = n + 1; + } + } + + // PWM LFO (square width motion). + if (p->ss_pwm_hz > 0.0) { + v->ss_pwm_inc = voice_jitter(v, p->ss_pwm_hz, 0.10, mul) / sr; + v->ss_pwm_depth = p->ss_pwm_depth; + v->ss_pwm_phase = voice_rand_phase(v); + } else { + v->ss_pwm_inc = 0.0; v->ss_pwm_depth = 0.0; v->ss_pwm_phase = 0.0; + } + // Pitch-tracked 1-pole HP at the fundamental (Szabo mud cut). + v->ss_hp_g = gm_onepole_g(fc, sr); + v->ss_hp_x1 = v->ss_hp_y1 = 0.0; + + // SVF resonant LPF + cutoff filter-envelope + slow sweep LFO (cutoff drift j). + v->ss_svf_lp = v->ss_svf_bp = 0.0; + v->ss_cut_base = voice_jitter(v, p->ss_cut0 > 0.0 ? p->ss_cut0 : 4000.0, 0.05, mul); + v->ss_cut = v->ss_cut_base; + v->ss_res = clampd(p->ss_res, 0.0, 0.97); + if (p->ss_cut_env > 0.0) { + v->ss_cut_env = p->ss_cut_env; + v->ss_cut_env_amt = p->ss_cut_env; + double cms = p->ss_cut_env_ms * 0.001; if (cms < 0.001) cms = 0.001; + v->ss_cut_env_dec = exp(-1.0 / (cms * sr)); + } else { + v->ss_cut_env = 0.0; v->ss_cut_env_amt = 0.0; v->ss_cut_env_dec = 1.0; + } + if (p->ss_sweep_hz > 0.0) { + v->ss_sweep_inc = voice_jitter(v, p->ss_sweep_hz, 0.10, mul) / sr; + v->ss_sweep_depth = p->ss_sweep_oct; + v->ss_sweep_phase = voice_rand_phase(v); + } else { + v->ss_sweep_inc = 0.0; v->ss_sweep_depth = 0.0; v->ss_sweep_phase = 0.0; + } + + // Slow amplitude attack ramp (pads/strings); jittered (lever g). + double atk = voice_jitter(v, p->ss_attack_ms > 0.0 ? p->ss_attack_ms : 4.0, 0.12, mul); + if (atk < 1.0) atk = 1.0; + v->ss_attack_env = 0.0; + v->ss_attack_inc = 1.0 / (atk * 0.001 * sr); + + // Collective vibrato. + if (p->ss_vib_hz > 0.0) { + v->ss_vib_inc = voice_jitter(v, p->ss_vib_hz, 0.10, mul) / sr; + v->ss_vib_depth = p->ss_vib_depth; + v->ss_vib_phase = voice_rand_phase(v); + } else { + v->ss_vib_inc = 0.0; v->ss_vib_depth = 0.0; v->ss_vib_phase = 0.0; + } + + // Ensemble chorus (short modulated delay line). + memset(v->ss_chorus_buf, 0, sizeof(v->ss_chorus_buf)); + v->ss_chorus_w = 0; + if (p->ss_chorus_mix > 0.0) { + v->ss_chorus_inc = voice_jitter(v, p->ss_chorus_hz, 0.10, mul) / sr; + v->ss_chorus_depth = p->ss_chorus_depth; // seconds + v->ss_chorus_mix = p->ss_chorus_mix; + v->ss_chorus_phase = voice_rand_phase(v); + } else { + v->ss_chorus_inc = 0.0; v->ss_chorus_depth = 0.0; v->ss_chorus_mix = 0.0; + v->ss_chorus_phase = 0.0; + } + + v->ss_drive = p->ss_drive; + + // Sub-octave (bass+lead). + if (p->ss_sub_mix > 0.0) { + v->ss_sub_inc = (fc * 0.5) / sr; + v->ss_sub_phase = voice_rand_phase(v); + v->ss_sub_mix = p->ss_sub_mix; + v->ss_sub_square = p->ss_sub_sq; + } else { + v->ss_sub_inc = 0.0; v->ss_sub_mix = 0.0; v->ss_sub_phase = 0.0; + v->ss_sub_square = 0; + } + + // Chiff / orchestra-hit transient noise burst through a band-pass. + if (p->ss_chiff_amt > 0.0) { + v->ss_chiff_amt = voice_jitter(v, p->ss_chiff_amt, 0.15, mul); + v->ss_chiff_env = 1.0; + double cms = p->ss_chiff_ms * 0.001; if (cms < 0.001) cms = 0.001; + v->ss_chiff_dec = exp(-1.0 / (cms * sr)); + double cf = (p->ss_chiff_bp > 0.0) ? (f0 * p->ss_chiff_bp) : 1500.0; + if (cf > sr * 0.45) cf = sr * 0.45; if (cf < 80.0) cf = 80.0; + double Q = 2.0; + double w0 = 2.0 * M_PI * cf / sr; + double al = sin(w0) / (2.0 * Q); + double a0 = 1.0 + al; + v->ss_chiff_nb0 = al / a0; v->ss_chiff_nb2 = -al / a0; + v->ss_chiff_na1 = (-2.0 * cos(w0)) / a0; + v->ss_chiff_na2 = (1.0 - al) / a0; + v->ss_chiff_x1 = v->ss_chiff_x2 = v->ss_chiff_y1 = v->ss_chiff_y2 = 0.0; + } else { + v->ss_chiff_amt = 0.0; v->ss_chiff_env = 0.0; v->ss_chiff_dec = 0.0; + } + + // Halo amplitude tremolo. + if (p->ss_trem_hz > 0.0) { + v->ss_trem_inc = voice_jitter(v, p->ss_trem_hz, 0.10, mul) / sr; + v->ss_trem_depth = p->ss_trem_depth; + v->ss_trem_phase = voice_rand_phase(v); + } else { + v->ss_trem_inc = 0.0; v->ss_trem_depth = 0.0; v->ss_trem_phase = 0.0; + } + + // Orchestra-hit body decay + downward pitch blip (a fast pitch multiplier + // that relaxes from +blip cents to 0 over ~15 ms — the "punch"). + v->ss_body_amp = 1.0; + if (p->ss_body_ms > 0.0) { + double bms = p->ss_body_ms * 0.001; if (bms < 0.001) bms = 0.001; + v->ss_body_dec = exp(-1.0 / (bms * sr)); + } else { + v->ss_body_dec = 1.0; + } + if (p->ss_pitch_blip > 0.0) { + v->ss_pitch_mult = cents_to_ratio(p->ss_pitch_blip); + v->ss_pitch_dec = exp(-1.0 / (0.015 * sr)); // 15 ms blip + } else { + v->ss_pitch_mult = 1.0; v->ss_pitch_dec = 1.0; + } + v->ss_out_scale = p->ss_out_scale > 0.0 ? p->ss_out_scale : 0.5; +} + +// Vocal formant note-on: source saws → 3 parallel formant bandpass biquads. +// Per-family mul: choir/voice 0.9 (dossier 00 §7) — per-singer detune + +// aspiration noise + decorrelated vibrato make a believable section. +static void gm_formant_init(GMVoice *v, const GMProgramParams *p, double f0, + double sr) { + const double mul = 0.9; + int n = p->fmt_nsrc; if (n < 1) n = 1; if (n > 3) n = 3; + v->fmt_nsrc = n; + for (int i = 0; i < n; i++) { + double cents = (n > 1) ? ((double)i / (double)(n - 1) - 0.5) * 2.0 + * p->fmt_detune_cents : 0.0; + // Per-singer detune (lever a) + a little extra wobble. + double fi = voice_detune(v, f0 * cents_to_ratio(cents), 6.0, mul); + if (fi > sr * 0.45) fi = sr * 0.45; + v->fmt_src_inc[i] = fi / sr; + v->fmt_src_phase[i] = voice_rand_phase(v); // lever c + // Decorrelated per-source vibrato (essential — else it reads as an organ). + v->fmt_vib_inc[i] = voice_jitter(v, p->fmt_vib_hz > 0.0 ? p->fmt_vib_hz : 5.0, + 0.12, mul) / sr; + v->fmt_vib_phase[i] = voice_rand_phase(v); + } + v->fmt_vib_depth = p->fmt_vib_depth; + // 3 parallel formant bandpass biquads (RBJ), absolute frequencies. + int nbands = 0; + for (int k = 0; k < 3; k++) { + if (p->fmt_f[k] <= 0.0) { v->fmt_g[k] = 0.0; continue; } + double ff = p->fmt_f[k]; + // Tiny formant-frequency wobble (per-singer vocal-tract variation, b). + ff = voice_jitter(v, ff, 0.03, mul); + if (ff > sr * 0.45) ff = sr * 0.45; if (ff < 40.0) ff = 40.0; + double bw = p->fmt_bw[k] > 1.0 ? p->fmt_bw[k] : 80.0; + double Q = ff / bw; if (Q < 0.5) Q = 0.5; if (Q > 40.0) Q = 40.0; + double w0 = 2.0 * M_PI * ff / sr; + double al = sin(w0) / (2.0 * Q); + double a0 = 1.0 + al; + v->fmt_b0[k] = al / a0; v->fmt_b2[k] = -al / a0; + v->fmt_a1[k] = (-2.0 * cos(w0)) / a0; + v->fmt_a2[k] = (1.0 - al) / a0; + v->fmt_x1[k] = v->fmt_x2[k] = v->fmt_y1[k] = v->fmt_y2[k] = 0.0; + double g = pow(10.0, p->fmt_gain_db[k] / 20.0); + v->fmt_g[k] = voice_jitter(v, g, 0.08, mul); // lever b + nbands = k + 1; + } + v->fmt_nbands = (double)nbands; + double atk = voice_jitter(v, p->fmt_attack_ms > 0.0 ? p->fmt_attack_ms : 40.0, + 0.12, mul); + if (atk < 1.0) atk = 1.0; + v->fmt_attack_env = 0.0; + v->fmt_attack_inc = 1.0 / (atk * 0.001 * sr); + v->fmt_breath_amt = p->fmt_breath; + v->fmt_breath_lp = 0.0; + // Halo tremolo (rides on the formant output amplitude). + if (p->ss_trem_hz > 0.0) { + v->ss_trem_inc = voice_jitter(v, p->ss_trem_hz, 0.10, mul) / sr; + v->ss_trem_depth = p->ss_trem_depth; + v->ss_trem_phase = voice_rand_phase(v); + } else { + v->ss_trem_inc = 0.0; v->ss_trem_depth = 0.0; v->ss_trem_phase = 0.0; + } + v->ss_out_scale = p->fmt_out_scale > 0.0 ? p->fmt_out_scale : 1.5; +} + // Forward declare batch-2 dispatch (defined after gm_voice_init). static int gm_voice_init_batch2(GMVoice *v, int program, double freq, double sample_rate); @@ -992,10 +1600,14 @@ int gm_program_implemented(int program) { if (program < 0) return 0; if (program < GM_PIANO_PROGRAM_COUNT) return 1; // 0-7 if (program >= 8 && program <= 15) return 1; // Chromatic Perc + if (program >= 16 && program <= 23) return 1; // Organ if (program >= 24 && program <= 31) return 1; // Guitar if (program >= 32 && program <= 39) return 1; // Bass if (program >= 40 && program <= 47) return 1; // Strings + if (program >= 48 && program <= 55) return 1; // Ensemble if (program >= 56 && program <= 63) return 1; // Brass + if (program >= 80 && program <= 87) return 1; // Synth Lead + if (program >= 88 && program <= 95) return 1; // Synth Pad if (program >= 64 && program <= 71) return 1; // Reed if (program >= 72 && program <= 79) return 1; // Pipe if (program >= 104 && program <= 111) return 1; // Ethnic @@ -1218,6 +1830,73 @@ static int gm_voice_init_batch2(GMVoice *v, int program, double freq, return 0; } + // ── Organ (GM 17-24) — additive drawbars / free-reed ── + if (program >= GM_ORGAN_FIRST && program <= GM_ORGAN_FIRST + 7) { + v->engine = GM_ENGINE_ORGAN; + gm_organ_init(v, &gm_organ_programs[program - GM_ORGAN_FIRST], f0, sr); + return 0; + } + + // ── Ensemble (GM 49-56) — supersaw pads / vocal formant / orchestra hit ── + if (program >= GM_ENSEMBLE_FIRST && program <= GM_ENSEMBLE_FIRST + 7) { + int idx = program - GM_ENSEMBLE_FIRST; // 0..7 + if (idx <= 3) { // 48-51 supersaw pads + v->engine = GM_ENGINE_SUPERSAW; + gm_supersaw_init(v, &gm_ensemble_programs[idx], f0, sr); + return 0; + } + if (idx == 4) { // 52 Choir Aahs + v->engine = GM_ENGINE_FORMANT; + gm_formant_init(v, &gm_choir_program, f0, sr); + return 0; + } + if (idx == 5) { // 53 Voice Oohs + v->engine = GM_ENGINE_FORMANT; + gm_formant_init(v, &gm_voiceoohs_program, f0, sr); + return 0; + } + if (idx == 6) { // 54 Synth Voice + v->engine = GM_ENGINE_FORMANT; + gm_formant_init(v, &gm_synthvoice_program, f0, sr); + return 0; + } + // idx == 7 → 55 Orchestra Hit (supersaw cluster + transient + decay) + v->engine = GM_ENGINE_SUPERSAW; + gm_supersaw_init(v, &gm_ensemble_programs[4], f0, sr); + return 0; + } + + // ── Synth Lead (GM 81-88) — subtractive named waveforms; 86 Voice = formant ── + if (program >= GM_LEAD_FIRST && program <= GM_LEAD_FIRST + 7) { + int idx = program - GM_LEAD_FIRST; // 0..7 + if (idx == 5) { // 86 Voice → formant lead + v->engine = GM_ENGINE_FORMANT; + gm_formant_init(v, &gm_leadvoice_program, f0, sr); + return 0; + } + v->engine = GM_ENGINE_SUPERSAW; + gm_supersaw_init(v, &gm_lead_programs[idx], f0, sr); + return 0; + } + + // ── Synth Pad (GM 89-96) — detuned multi-osc; 92 Choir + 95 Halo = formant ── + if (program >= GM_PAD_FIRST && program <= GM_PAD_FIRST + 7) { + int idx = program - GM_PAD_FIRST; // 0..7 + if (idx == 3) { // 92 Choir → formant pad + v->engine = GM_ENGINE_FORMANT; + gm_formant_init(v, &gm_choirpad_program, f0, sr); + return 0; + } + if (idx == 6) { // 95 Halo → bright formant + tremolo + v->engine = GM_ENGINE_FORMANT; + gm_formant_init(v, &gm_halo_program, f0, sr); + return 0; + } + v->engine = GM_ENGINE_SUPERSAW; + gm_supersaw_init(v, &gm_pad_programs[idx], f0, sr); + return 0; + } + // ── Subtractive: Synth Bass 1/2 (39-40) + reed approximations (110-112) ── for (int i = 0; i < GM_SYNTHBASS_ROWS; i++) { if (gm_synthbass_programs[i].program == program) { @@ -1634,6 +2313,245 @@ static inline double generate_waveguide_sample(GMVoice *v, double sample_rate, return out * env; } +// ── Additive Hammond drawbar / free-reed organ. Sum of sine partials at fixed +// footage ratios (or detuned saw/pulse reed banks) + key-click + percussion +// ping + Leslie rotary. Sustained — no decay on the drawbars. ── +static inline double generate_organ_sample(GMVoice *v, double sample_rate, + double env) { + (void)sample_rate; + double s = 0.0; + int N = v->org_nbars; + for (int d = 0; d < N; d++) { + double w; + if (v->org_freereed) { + // Free-reed banks: bandlimited-ish saw / pulse for the buzzy reed tone. + if (v->org_bar_square[d]) { + w = v->org_phase[d] < 0.5 ? 1.0 : -1.0; + } else { + w = 2.0 * v->org_phase[d] - 1.0; + } + } else { + w = wt_sin(v->org_phase[d]); // Hammond = pure sine drawbars + } + s += v->org_amp[d] * w; + v->org_phase[d] += v->org_inc[d]; + if (v->org_phase[d] >= 1.0) v->org_phase[d] -= 1.0; + } + // Percussive-organ ping (fast-decaying extra harmonic at note-on). + if (v->org_perc_env > 0.0001) { + s += v->org_perc_amp * v->org_perc_env * wt_sin(v->org_perc_phase); + v->org_perc_phase += v->org_perc_inc; + if (v->org_perc_phase >= 1.0) v->org_perc_phase -= 1.0; + v->org_perc_env *= v->org_perc_dec; + } + // Free-reed breath noise (filtered) + gentle output LPF. + if (v->org_breath_amt > 0.0) { + double white = ((double)xorshift32(&v->rng_seed) / (double)UINT32_MAX) * 2.0 - 1.0; + v->org_breath_lp = 0.2 * white + 0.8 * v->org_breath_lp; + s += v->org_breath_amt * v->org_breath_lp; + } + if (v->org_lp_g > 0.0) { + v->org_lp += v->org_lp_g * (s - v->org_lp); + s = v->org_lp; + } + // Rock-organ overdrive. + if (v->org_drive > 0.0) { + double pre = 1.0 + 4.0 * v->org_drive; + s = tanh(pre * s) * (1.0 / pre) * (1.0 + v->org_drive); + } + // Key-click: short HF noise burst at note-on (HF-tilted via differencing). + if (v->org_click_env > 0.0001) { + double white = ((double)xorshift32(&v->rng_seed) / (double)UINT32_MAX) * 2.0 - 1.0; + double hf = white - v->org_click_lp; // crude HP (HF emphasis) + v->org_click_lp = white; + s += v->org_click_amp * v->org_click_env * hf; + v->org_click_env *= v->org_click_dec; + } + // Leslie rotary: amplitude tremolo (the pitch/Doppler part is approximated by + // the amplitude swirl here; bounded depth). + if (v->org_leslie_inc > 0.0) { + double l = 1.0 - v->org_leslie_depth * 0.5 * (1.0 - wt_sin(v->org_leslie_phase)); + v->org_leslie_phase += v->org_leslie_inc; + if (v->org_leslie_phase >= 1.0) v->org_leslie_phase -= 1.0; + s *= l; + } + return clampd(s, -1.8, 1.8) * env; +} + +// One naive saw or PWM-square oscillator value for the supersaw stack. +static inline double gm_ss_osc(double phase, int square, double pw) { + if (square) return phase < pw ? 1.0 : -1.0; + return 2.0 * phase - 1.0; +} + +// ── Detuned multi-osc subtractive: supersaw ensemble / synth lead / synth pad. +// 7-saw Szabo stack (or simple detuned stack) → pitch-HP → resonant SVF LPF +// (with attack filter-envelope + slow sweep LFO) → chorus → drive. ── +static inline double generate_supersaw_sample(GMVoice *v, double sample_rate, + double env) { + double sr = sample_rate; + // Slow amplitude attack ramp. + if (v->ss_attack_env < 1.0) { + v->ss_attack_env += v->ss_attack_inc; + if (v->ss_attack_env > 1.0) v->ss_attack_env = 1.0; + } + // Collective vibrato + orchestra-hit pitch blip → a global pitch multiplier. + double pmult = 1.0; + if (v->ss_vib_inc > 0.0) { + pmult *= 1.0 + v->ss_vib_depth * wt_sin(v->ss_vib_phase); + v->ss_vib_phase += v->ss_vib_inc; + if (v->ss_vib_phase >= 1.0) v->ss_vib_phase -= 1.0; + } + if (v->ss_pitch_mult > 1.0) { + pmult *= v->ss_pitch_mult; + v->ss_pitch_mult = 1.0 + (v->ss_pitch_mult - 1.0) * v->ss_pitch_dec; + if (v->ss_pitch_mult < 1.000001) v->ss_pitch_mult = 1.0; + } + // PWM width (square oscillators). + double pw = 0.5; + if (v->ss_pwm_inc > 0.0) { + pw = 0.5 + v->ss_pwm_depth * 0.45 * wt_sin(v->ss_pwm_phase); + v->ss_pwm_phase += v->ss_pwm_inc; + if (v->ss_pwm_phase >= 1.0) v->ss_pwm_phase -= 1.0; + if (pw < 0.05) pw = 0.05; if (pw > 0.95) pw = 0.95; + } + // Sum the detuned oscillator stack. + double sig = 0.0; + int n = v->ss_nosc; + for (int i = 0; i < n; i++) { + sig += v->ss_gain[i] * gm_ss_osc(v->ss_phase[i], v->ss_square[i], pw); + v->ss_phase[i] += v->ss_inc[i] * pmult; + if (v->ss_phase[i] >= 1.0) v->ss_phase[i] -= 1.0; + } + // Sub-octave (bass+lead). + if (v->ss_sub_mix > 0.0) { + sig += v->ss_sub_mix * gm_ss_osc(v->ss_sub_phase, v->ss_sub_square, 0.5); + v->ss_sub_phase += v->ss_sub_inc * pmult; + if (v->ss_sub_phase >= 1.0) v->ss_sub_phase -= 1.0; + } + // Pitch-tracked 1-pole highpass (Szabo mud cut). + if (v->ss_hp_g > 0.0) { + double lp = v->ss_hp_y1 + v->ss_hp_g * (sig - v->ss_hp_y1); + v->ss_hp_y1 = lp; + sig = sig - lp; // highpass = signal − lowpass + } + // Chiff / orchestra-hit transient (band-passed noise burst at onset). + if (v->ss_chiff_env > 0.0001) { + double white = ((double)xorshift32(&v->rng_seed) / (double)UINT32_MAX) * 2.0 - 1.0; + double nf = v->ss_chiff_nb0 * white + v->ss_chiff_nb2 * v->ss_chiff_x2 + - v->ss_chiff_na1 * v->ss_chiff_y1 - v->ss_chiff_na2 * v->ss_chiff_y2; + v->ss_chiff_x2 = v->ss_chiff_x1; v->ss_chiff_x1 = white; + v->ss_chiff_y2 = v->ss_chiff_y1; v->ss_chiff_y1 = nf; + sig += v->ss_chiff_amt * v->ss_chiff_env * nf; + v->ss_chiff_env *= v->ss_chiff_dec; + } + + // Cutoff: base + decaying attack envelope + slow sweep LFO (octaves). + if (v->ss_cut_env > 0.1) v->ss_cut_env *= v->ss_cut_env_dec; + double cut = v->ss_cut_base + v->ss_cut_env; + if (v->ss_sweep_inc > 0.0) { + double oct = v->ss_sweep_depth * wt_sin(v->ss_sweep_phase); + v->ss_sweep_phase += v->ss_sweep_inc; + if (v->ss_sweep_phase >= 1.0) v->ss_sweep_phase -= 1.0; + cut *= pow(2.0, oct); + } + cut = clampd(cut, 30.0, sr * 0.45); + // Chamberlin SVF (f = 2·sin(π·fc/sr), q = 1/res-ish → use 2(1-res) damping). + double f = 2.0 * sin(M_PI * cut / sr); if (f > 1.0) f = 1.0; + double q = 2.0 * (1.0 - v->ss_res); // resonance: smaller q = more peak + double hp = sig - v->ss_svf_lp - q * v->ss_svf_bp; + v->ss_svf_bp += f * hp; + v->ss_svf_lp += f * v->ss_svf_bp; + double out = v->ss_svf_lp; + + // Charang / overdrive. + if (v->ss_drive > 0.0) { + double pre = 1.0 + 5.0 * v->ss_drive; + out = tanh(pre * out) * (1.0 / pre) * (1.0 + v->ss_drive); + } + + // Ensemble chorus (short modulated delay line, mixed with dry). + if (v->ss_chorus_mix > 0.0) { + const int CN = 1024; + v->ss_chorus_buf[v->ss_chorus_w] = (float)out; + double depth_samp = v->ss_chorus_depth * sr; // seconds → samples + double base = depth_samp + 2.0; + double mod = base + depth_samp * wt_sin(v->ss_chorus_phase); + v->ss_chorus_phase += v->ss_chorus_inc; + if (v->ss_chorus_phase >= 1.0) v->ss_chorus_phase -= 1.0; + if (mod < 1.0) mod = 1.0; if (mod > (double)(CN - 2)) mod = (double)(CN - 2); + double wet = gm_frac_read(v->ss_chorus_buf, CN, v->ss_chorus_w, mod); + v->ss_chorus_w = (v->ss_chorus_w + 1) % CN; + out = (1.0 - v->ss_chorus_mix) * out + v->ss_chorus_mix * wet; + } + + // Halo amplitude tremolo. + if (v->ss_trem_inc > 0.0) { + double t = 1.0 - v->ss_trem_depth * 0.5 * (1.0 - wt_sin(v->ss_trem_phase)); + v->ss_trem_phase += v->ss_trem_inc; + if (v->ss_trem_phase >= 1.0) v->ss_trem_phase -= 1.0; + out *= t; + } + + // Orchestra-hit body decay (percussive stab; 1.0 = sustained). + if (v->ss_body_dec < 1.0) { + out *= v->ss_body_amp; + v->ss_body_amp *= v->ss_body_dec; + } + + out *= v->ss_out_scale * v->ss_attack_env; + return clampd(out, -2.0, 2.0) * env; +} + +// ── Vocal formant bank: detuned saw sources → 3 parallel formant bandpass +// biquads, summed with the vowel-table gains. Choir / voice / synth-voice. ── +static inline double generate_formant_sample(GMVoice *v, double sample_rate, + double env) { + (void)sample_rate; + if (v->fmt_attack_env < 1.0) { + v->fmt_attack_env += v->fmt_attack_inc; + if (v->fmt_attack_env > 1.0) v->fmt_attack_env = 1.0; + } + // Buzz source: sum of detuned saws, each with decorrelated vibrato. + double src = 0.0; + int n = v->fmt_nsrc; + for (int i = 0; i < n; i++) { + double vib = 1.0 + v->fmt_vib_depth * wt_sin(v->fmt_vib_phase[i]); + v->fmt_vib_phase[i] += v->fmt_vib_inc[i]; + if (v->fmt_vib_phase[i] >= 1.0) v->fmt_vib_phase[i] -= 1.0; + src += (2.0 * v->fmt_src_phase[i] - 1.0); + v->fmt_src_phase[i] += v->fmt_src_inc[i] * vib; + if (v->fmt_src_phase[i] >= 1.0) v->fmt_src_phase[i] -= 1.0; + } + src /= (double)n; + // Aspiration / breath noise into the formants (the airy choral texture). + if (v->fmt_breath_amt > 0.0) { + double white = ((double)xorshift32(&v->rng_seed) / (double)UINT32_MAX) * 2.0 - 1.0; + v->fmt_breath_lp = 0.3 * white + 0.7 * v->fmt_breath_lp; + src += v->fmt_breath_amt * v->fmt_breath_lp; + } + // 3 parallel formant bandpass biquads, summed with vowel gains. + int nb = (int)v->fmt_nbands; + double out = 0.0; + for (int k = 0; k < nb; k++) { + if (v->fmt_g[k] <= 0.0) continue; + double fb = v->fmt_b0[k] * src + v->fmt_b2[k] * v->fmt_x2[k] + - v->fmt_a1[k] * v->fmt_y1[k] - v->fmt_a2[k] * v->fmt_y2[k]; + v->fmt_x2[k] = v->fmt_x1[k]; v->fmt_x1[k] = src; + v->fmt_y2[k] = v->fmt_y1[k]; v->fmt_y1[k] = fb; + out += v->fmt_g[k] * fb; + } + // Halo tremolo (rides on the output amplitude). + if (v->ss_trem_inc > 0.0) { + double t = 1.0 - v->ss_trem_depth * 0.5 * (1.0 - wt_sin(v->ss_trem_phase)); + v->ss_trem_phase += v->ss_trem_inc; + if (v->ss_trem_phase >= 1.0) v->ss_trem_phase -= 1.0; + out *= t; + } + out *= v->ss_out_scale * v->fmt_attack_env; + return clampd(out, -2.0, 2.0) * env; +} + double gm_voice_render(GMVoice *v, double sample_rate, double env, double frequency) { switch (v->engine) { @@ -1643,6 +2561,9 @@ double gm_voice_render(GMVoice *v, double sample_rate, double env, case GM_ENGINE_MODAL: return generate_modal_sample(v, sample_rate, env); case GM_ENGINE_SYNTHBASS: return generate_synthbass_sample(v, sample_rate, env); case GM_ENGINE_WAVEGUIDE: return generate_waveguide_sample(v, sample_rate, env, frequency); + case GM_ENGINE_ORGAN: return generate_organ_sample(v, sample_rate, env); + case GM_ENGINE_SUPERSAW: return generate_supersaw_sample(v, sample_rate, env); + case GM_ENGINE_FORMANT: return generate_formant_sample(v, sample_rate, env); default: return 0.0; } } diff --git a/fedac/native/src/gm_synth.h b/fedac/native/src/gm_synth.h index 9716d064c2..ff6e1d4f88 100644 --- a/fedac/native/src/gm_synth.h +++ b/fedac/native/src/gm_synth.h @@ -48,7 +48,10 @@ typedef enum { GM_ENGINE_PLUCK, // extended Karplus-Strong (harpsichord/clavi/guitar/bass/ethnic) GM_ENGINE_MODAL, // modal bank (chromatic perc / kalimba / percussive / timpani) GM_ENGINE_SYNTHBASS, // subtractive (synth bass / reed approximations / synth brass) - GM_ENGINE_WAVEGUIDE // bidirectional digital waveguide (bowed/brass/reed/flute) + GM_ENGINE_WAVEGUIDE, // bidirectional digital waveguide (bowed/brass/reed/flute) + GM_ENGINE_ORGAN, // additive Hammond drawbars + Leslie; free-reed accordion/harmonica + GM_ENGINE_SUPERSAW, // detuned multi-osc subtractive: ensemble / synth lead / synth pad + GM_ENGINE_FORMANT // source-filter vocal formant bank: choir / voice / synth-voice } GMEngine; // Nonlinearity / excitation mode inside GM_ENGINE_WAVEGUIDE. The shared @@ -197,6 +200,69 @@ typedef struct { double wg_out_scale; // output gain // Section shimmer (brass section / tremolo strings) — amplitude LFO. double wg_trem_phase, wg_trem_inc, wg_trem_depth; + + // -- Additive Hammond drawbars + free-reed (GM_ENGINE_ORGAN) -- + // 9 sine drawbars at fixed footage ratios; or detuned saw/pulse reed banks. + int org_nbars; // active drawbar / reed-bank count + double org_phase[9]; // per-bar oscillator phase + double org_inc[9]; // per-bar phase increment (f0*ratio/sr) + double org_amp[9]; // per-bar registration amplitude + int org_freereed; // 1 = saw/pulse reed banks (accordion/harmonica) + int org_bar_square[9]; // free-reed: 1 = pulse, 0 = saw for this bank + double org_drive; // rock-organ overdrive (tanh) + double org_perc_env, org_perc_dec, org_perc_amp; // percussive-organ 2nd-harm ping + double org_perc_phase, org_perc_inc; + double org_click_env, org_click_dec, org_click_amp; // key-click HF noise burst + double org_click_lp; // 1-pole HP-ish state for click + double org_leslie_phase, org_leslie_inc, org_leslie_depth; // Leslie amp+pitch LFO + double org_breath_lp, org_breath_amt; // free-reed breath noise + double org_lp; // free-reed gentle output LPF state + double org_lp_g; // free-reed LPF coefficient (0 = bypass) + + // -- Detuned multi-osc subtractive (GM_ENGINE_SUPERSAW): ensemble/lead/pad -- + int ss_nosc; // active oscillator count (1..7) + double ss_phase[7]; // per-osc phase + double ss_inc[7]; // per-osc phase increment + double ss_gain[7]; // per-osc gain (Szabo center/side or unity) + int ss_square[7]; // 1 = pulse/square osc, 0 = saw + double ss_pwm_phase, ss_pwm_inc, ss_pwm_depth; // PWM LFO (square width) + double ss_hp_x1, ss_hp_y1; // pitch-tracked 1-pole HP (supersaw mud cut) + double ss_hp_g; // HP coefficient + // State-variable LPF (Chamberlin) + slow cutoff sweep LFO + attack env. + double ss_svf_lp, ss_svf_bp; // SVF integrator state + double ss_cut, ss_cut_base; // current + base cutoff (Hz) + double ss_cut_env, ss_cut_env_dec, ss_cut_env_amt; // attack filter-envelope (Hz add) + double ss_res; // resonance (0..~0.98) + double ss_sweep_phase, ss_sweep_inc, ss_sweep_depth; // slow cutoff LFO (octaves) + double ss_attack_env, ss_attack_inc; // slow amplitude attack ramp 0..1 + double ss_vib_phase, ss_vib_inc, ss_vib_depth; // collective vibrato + float ss_chorus_buf[1024]; // short modulated chorus delay line + int ss_chorus_w; + double ss_chorus_phase, ss_chorus_inc, ss_chorus_depth, ss_chorus_mix; + double ss_drive; // charang/overdrive tanh + double ss_sub_phase, ss_sub_inc, ss_sub_mix; // bass+lead sub-octave + int ss_sub_square; + double ss_chiff_env, ss_chiff_dec, ss_chiff_amt; // chiff/transient noise burst + double ss_chiff_nb0, ss_chiff_nb2, ss_chiff_na1, ss_chiff_na2; // chiff BPF + double ss_chiff_x1, ss_chiff_x2, ss_chiff_y1, ss_chiff_y2; + double ss_trem_phase, ss_trem_inc, ss_trem_depth; // halo amplitude tremolo + double ss_body_dec; // orchestra-hit body decay (1 = sustained) + double ss_body_amp; // orchestra-hit body amplitude env + double ss_pitch_mult, ss_pitch_dec; // orchestra-hit downward pitch blip + double ss_out_scale; // output gain + + // -- Vocal formant bank (GM_ENGINE_FORMANT): choir / voice / synth-voice -- + // Up to 3 detuned saw/pulse glottal sources → 3 parallel formant biquads. + int fmt_nsrc; // source-oscillator count (1=solo, 3=choir) + double fmt_src_phase[3]; // per-source phase + double fmt_src_inc[3]; // per-source phase increment + double fmt_nbands; // active formant band count (3) + double fmt_b0[3], fmt_b2[3], fmt_a1[3], fmt_a2[3]; // 3 formant bandpass biquads + double fmt_x1[3], fmt_x2[3], fmt_y1[3], fmt_y2[3]; + double fmt_g[3]; // per-formant linear gain + double fmt_attack_env, fmt_attack_inc; // slow onset ramp + double fmt_breath_lp, fmt_breath_amt; // aspiration noise into formants + double fmt_vib_phase[3], fmt_vib_inc[3], fmt_vib_depth; // decorrelated per-source vibrato } GMVoice; // Build the wt_sin wavetable. Idempotent; safe to call repeatedly. gm_voice_init @@ -207,8 +273,9 @@ void gm_synth_init(void); void gm_set_organic(double amt); // 1 if `program` (0-based GM) has a bespoke algorithmic voice in this build. -// Implemented: Piano 0-7, Chromatic Perc 8-15, Guitar 24-31, Bass 32-39, -// Strings 40-47, Brass 56-63, Reed 64-71, Pipe 72-79, Ethnic 104-111, +// Implemented: Piano 0-7, Chromatic Perc 8-15, Organ 16-23, Guitar 24-31, +// Bass 32-39, Strings 40-47, Ensemble 48-55, Brass 56-63, Reed 64-71, +// Pipe 72-79, Synth Lead 80-87, Synth Pad 88-95, Ethnic 104-111, // Percussive 112-119. int gm_program_implemented(int program);