// =====================================================================
//  live.scd  --  Boite a outils live performance
//
//  Ce fichier ajoute par-dessus engine.scd + fx_bus.scd + tweaks.scd :
//
//    1. LFO assignables sur N'IMPORTE QUELLE variable globale (~xxx)
//    2. Envelopes one-shot assignables (attack/sustain/release, sweep, burst)
//    3. VCF master inseres en chaine (moog / ms20 / ladder / formant /
//       comb / hpf / bpf / notch) avec presets et sweep auto
//    4. Tricks sceniques live : stutter, choke, drop, handsUp, breakdown,
//       buildup, tapeStop, glitch, freeze, reverbTail, tapeReverse,
//       swap (crossfade scenes), jumpCut
//    5. Sidechain dynamique entre N'IMPORTE QUELLE source et cible
//    6. Stuttering / glitch / pitch : slipTime, bitCrushNow, ringMod,
//       freqShift, granFreeze, scratch, repeat
//    7. Auto-pan / tremolo / autopan par voix
//    8. FX additionnels (12 nouveaux SynthDef)
//    9. Master compresseur / saturation switchable
//
//  PREREQUIS : engine.scd evalue + live/fx_bus.scd evalue + serveur boote.
//  USAGE     : `(~base ++ "live/live.scd").load` puis Cmd+Entree sur les
//              exemples en commentaire en bas.
//
//  Convention : helpers exposes en variables d'environnement ~xxx
//              + UN SEUL bloc top-level (compatible .load)
// =====================================================================

(

// =====================================================================
//  0. SYNTHDEFS additionnels
// =====================================================================

// --- VCF Moog (filtre 4-pole, MoogFF) ---------------------------------


// --- VCF MS20 (Sallen-Key style, agressif) ----------------------------


// --- VCF Ladder (24 dB/oct vintage) -----------------------------------


// --- VCF Formant (voyelles a/e/i/o/u) ---------------------------------
// vowel : 0=a 1=e 2=i 3=o 4=u, mix entre crossfade


// --- VCF Comb (resonator) ---------------------------------------------


// --- VCF HPF resonant -------------------------------------------------


// --- VCF BPF resonant -------------------------------------------------


// --- VCF Notch (sweepable, supprime une bande) ------------------------


// --- FX additionnels (lus depuis fxBuses[\xxx], compatibles fx_bus.scd) ---

// Ring modulator avec freq mod


// Frequency shifter


// Granular freeze (simule un "grain hold" via PitchShift en boucle)


// Comb filter resonator


// Formant filter (Klank) - voyelle parametrable


// Auto-filter LFO sur cutoff


// Tremolo (amp LFO)


// Auto-pan


// Stutter rythmique (gate carre)


// Resonator Klank (pitched ringing)


// Reverb a ressort (allpass nested + filtre tonal)


// Plate reverb (court + brillant + dense)


// --- Master compresseur (insert avant masterLim) ----------------------
// Compander : threshold, slopeBelow=1 (no expand), slopeAbove<1 (compress)


// --- Master saturation (tanh / softclip / fold) -----------------------
// mode : 0=tanh, 1=softclip, 2=fold


// --- Bus master temporaire pour effets globaux serie -------------------
// utilise par ~slipTime, ~ringMod momentanes : un Synth applique l'effet
// inline dans une chaine ReplaceOut sur le bus 0.










s.sync;


// =====================================================================
//  1. LFO assignables sur N'IMPORTE QUELLE variable globale ~xxx
// =====================================================================
// Stockage : ~lfos (different de ~fxLfos qui est reserve aux LFO FX)
~lfos !? { ~lfos.do(_.stop) };
~lfos = ();

// Helper interne : recupere une variable par symbol
~getVar = { |sym|
    currentEnvironment[sym.asSymbol]
};

// Helper interne : assigne une variable par symbol
~setVar = { |sym, val|
    currentEnvironment[sym.asSymbol] = val
};

// LFO continu : assigne une oscillation a ~paramName
//   shape : \sin \tri \saw \sqr \rand \smoothRand \drunk \noise
//   center : valeur centrale (par defaut = valeur courante de la var)
//   doReload : si true, appelle ~reload a chaque step (utile pour les
//              params lus dans Pdef -- pour des params pousses au Synth
//              en live, mettre false)
// IMPORTANT : doReload est maintenant FALSE par defaut (depuis le
// refacto engine.scd avec Pfunc { ~xxx } sur les params scalaires).
// Les Pdef lisent les ~xxx dynamiquement a chaque event, donc plus
// besoin de rebuild les Pdef a chaque step LFO. Cela evitait une fuite
// scsynth massive quand un LFO actif rebuilait Pdef(\acidSeq) =
// PmonoArtic 20 fois/seconde.
~lfoTo = { |paramName, rate = 1.0, depth = 0.3, shape = \sin,
           center = nil, doReload = false, pulseWidth = 0.5|
    var sym = paramName.asSymbol;
    var c = center ?? { ~getVar.(sym) ? 0 };
    var stepDur = 0.05;
    ~lfos[sym] !? { ~lfos[sym].stop };
    ~lfos[sym] = Routine({
        var phase = 0;
        var lastRand = 0;
        var nextRand = 1.0.rand2;
        var drunk = 0;
        inf.do { |i|
            var x;
            x = switch (shape,
                \sin,        { (phase * 2 * pi).sin },
                \tri,        { ((phase * 4 - 1).abs - 1).neg },
                \saw,        { (phase * 2) - 1 },
                \sqr,        { if (phase < pulseWidth) { 1 } { -1 } },
                \rand,       {
                    if (i.even) { lastRand = 1.0.rand2 };
                    lastRand
                },
                \smoothRand, {
                    var p = (phase * 2);
                    if (p >= 1.0) {
                        lastRand = nextRand;
                        nextRand = 1.0.rand2;
                        phase = phase - 0.5;
                        p = phase * 2;
                    };
                    lastRand + ((nextRand - lastRand) * p)
                },
                \drunk,      {
                    drunk = (drunk + (rrand(-0.15, 0.15))).clip(-1, 1);
                    drunk
                },
                \noise,      { 1.0.rand2 }
            );
            ~setVar.(sym, c + (x * depth));
            if (doReload) { ~reload !? { ~reload.value } };
            phase = (phase + (rate * stepDur)).mod(1.0);
            stepDur.wait;
        };
    }).play;
    ("LFO ~" ++ sym ++ " " ++ rate ++ "Hz depth=" ++ depth
        ++ " " ++ shape ++ " center=" ++ c.round(0.01)).postln;
};

// LFO sync au tempo : beatsDiv = nombre de beats par cycle complet
//   ex: 4 = un cycle sur 4 beats (ralenti), 0.25 = 4 cycles par beat
// FIX FUITE RAM : doReload defaut a false (etait true). Avec true, chaque
// tick LFO appelait ~reload.value qui recompile les 8 Pdef -> 5-40
// recompilations/sec selon BPM, fuite client + server. Pfunc dans engine.scd
// lit deja les variables dynamiquement, doReload n'est plus necessaire.
~lfoSync = { |paramName, beatsDiv = 4, depth = 0.3, shape = \sin,
              center = nil, doReload = false|
    var bps = TempoClock.default.tempo;
    var rate = bps / beatsDiv;
    ~lfoTo.(paramName, rate, depth, shape, center, doReload);
};

// Stop un LFO sur une var precise
~lfoStopVar = { |paramName|
    var sym = paramName.asSymbol;
    ~lfos[sym] !? { ~lfos[sym].stop; ~lfos.removeAt(sym) };
    ("LFO ~" ++ sym ++ " STOPPED").postln;
};

// Stop tous les LFO de variables (different de ~lfoStopAll de fx_bus
// qui est pour les LFO FX). On garde un nom different pour ne pas
// ecraser ~lfoStopAll de fx_bus.scd. Voir aussi ~lfoStopEverything.
~lfoStopAllVars = {
    ~lfos.do { |r| r.stop }; ~lfos = ();
    "all variable LFOs stopped".postln;
};

// Stop tous les LFOs (FX + variables)
~lfoStopEverything = {
    ~lfos.do { |r| r.stop }; ~lfos = ();
    ~fxLfos !? { ~fxLfos.do { |r| r.stop }; ~fxLfos = () };
    "ALL LFOs stopped (vars + fx)".postln;
};

// Liste les LFO actifs
~lfoList = {
    "=== LFO actifs ===".postln;
    if (~lfos.size == 0) {
        "  (aucun LFO sur variable)".postln;
    } {
        ~lfos.keysValuesDo { |k, v| ("  ~" ++ k ++ "  -> Routine").postln };
    };
    if ((~fxLfos.notNil) and: { ~fxLfos.size > 0 }) {
        "--- LFO FX ---".postln;
        ~fxLfos.keysValuesDo { |k, v| ("  " ++ k).postln };
    };
};


// =====================================================================
//  2. Envelopes one-shot assignables
// =====================================================================
// Stockage des envelopes en cours (Routines)
~envs !? { ~envs.do(_.stop) };
~envs = ();

// Env attack/sustain/release sur une var globale
//   target : valeur de pic a la fin de l'attack
//   returnTo : valeur retour apres release (par defaut = valeur initiale)
~envTo = { |paramName, attack = 0.1, sustain = 0.5, release = 0.5,
            target = 1.0, returnTo = nil, doReload = true|
    var sym = paramName.asSymbol;
    var start = ~getVar.(sym) ? 0;
    var end = returnTo ? start;
    var stepDur = 0.02;
    ~envs[sym] !? { ~envs[sym].stop };
    ~envs[sym] = Routine({
        // ATTACK : start -> target
        var attackSteps = (attack / stepDur).max(1).asInteger;
        attackSteps.do { |i|
            var x = (i + 1) / attackSteps;
            ~setVar.(sym, start + ((target - start) * x));
            if (doReload) { ~reload !? { ~reload.value } };
            stepDur.wait;
        };
        // SUSTAIN : maintien
        sustain.wait;
        // RELEASE : target -> end
        attackSteps = (release / stepDur).max(1).asInteger;
        attackSteps.do { |i|
            var x = (i + 1) / attackSteps;
            ~setVar.(sym, target + ((end - target) * x));
            if (doReload) { ~reload !? { ~reload.value } };
            stepDur.wait;
        };
        ~envs.removeAt(sym);
    }).play;
    ("ENV ~" ++ sym ++ " " ++ start.round(0.01) ++ " -> "
        ++ target.round(0.01) ++ " -> " ++ end.round(0.01)).postln;
};

// Env percussive courte (linenv : attack rapide + decay)
~envBurst = { |paramName, target = 1.0, dur = 0.5, curve = -3,
                doReload = true|
    var sym = paramName.asSymbol;
    var start = ~getVar.(sym) ? 0;
    var stepDur = 0.02;
    ~envs[sym] !? { ~envs[sym].stop };
    ~envs[sym] = Routine({
        var totalSteps = (dur / stepDur).max(2).asInteger;
        var env = Env.perc(0.005, dur, target - start, curve);
        // attack
        ~setVar.(sym, target);
        if (doReload) { ~reload !? { ~reload.value } };
        // release exponentiel approche
        totalSteps.do { |i|
            var x = (i + 1) / totalSteps;
            // courbe approx exp negative
            var v = target - ((target - start) * x.pow(0.5));
            ~setVar.(sym, v);
            if (doReload) { ~reload !? { ~reload.value } };
            stepDur.wait;
        };
        ~setVar.(sym, start);
        if (doReload) { ~reload !? { ~reload.value } };
        ~envs.removeAt(sym);
    }).play;
};

// Sweep lineaire / expo entre fromVal et toVal en dur secondes
//   curve : \lin ou \exp
~envSweep = { |paramName, fromVal, toVal, dur = 4, curve = \lin,
                doReload = true|
    var sym = paramName.asSymbol;
    var stepDur = 0.04;
    ~envs[sym] !? { ~envs[sym].stop };
    ~envs[sym] = Routine({
        var steps = (dur / stepDur).max(2).asInteger;
        steps.do { |i|
            var x = (i + 1) / steps;
            var v;
            if (curve == \exp) {
                v = fromVal * ((toVal / fromVal.max(0.0001)).pow(x));
            } {
                v = fromVal + ((toVal - fromVal) * x);
            };
            ~setVar.(sym, v);
            if (doReload) { ~reload !? { ~reload.value } };
            stepDur.wait;
        };
        ~envs.removeAt(sym);
    }).play;
    ("SWEEP ~" ++ sym ++ " " ++ fromVal ++ " -> " ++ toVal
        ++ " sur " ++ dur ++ "s (" ++ curve ++ ")").postln;
};

// Variantes pratiques pour le filtre melody
~filterSweepUp   = { |dur = 4|
    ~envSweep.(\melodyCutoff, 200, 6000, dur, \exp);
};
~filterSweepDown = { |dur = 4|
    ~envSweep.(\melodyCutoff, 6000, 200, dur, \exp);
};

// Duck momentane sur l'amp master via masterAmp ou via tous les amps
~ampDuck = { |dur = 0.3, depth = 0.7|
    // applique un dip sur kickAmp/melodyAmp/acidAmp/harmonyAmp
    var startKick = ~kickAmp ? 0.5;
    var startMel  = ~melodyAmp ? 0.16;
    var startAcid = ~acidAmp ? 0.32;
    var startHarm = ~harmonyAmp ? 0.14;
    Routine({
        ~kickAmp    = startKick * (1 - depth);
        ~melodyAmp  = startMel  * (1 - depth);
        ~acidAmp    = startAcid * (1 - depth);
        ~harmonyAmp = startHarm * (1 - depth);
        ~reload !? { ~reload.value };
        dur.wait;
        ~kickAmp    = startKick;
        ~melodyAmp  = startMel;
        ~acidAmp    = startAcid;
        ~harmonyAmp = startHarm;
        ~reload !? { ~reload.value };
    }).play;
    ("AMP DUCK depth=" ++ depth ++ " dur=" ++ dur).postln;
};


// =====================================================================
//  3. VCF Master : insert filtre temps reel sur le master
// =====================================================================
// Le VCF lit le bus 0 et ReplaceOut.
// IMPORTANT : insertion en `addBefore: ~masterLim` pour rester
// avant le limiter. Si masterLim n'existe pas, on l'ajoute en tail.
~vcfActive = nil;
~vcfActiveType = nil;

~vcfOn = { |type = \moog, freq = 1500, q = 0.5|
    var defName, args = [\busIn, 0, \freq, freq, \mix, 1.0];
    // map type -> nom complet de SynthDef
    defName = switch (type.asSymbol,
        \moog,    \vcfMoog,
        \ms20,    \vcfMs20,
        \ladder,  \vcfLadder,
        \formant, \vcfFormant,
        \comb,    \vcfComb,
        \hpf,     \vcfHpf,
        \bpf,     \vcfBpf,
        \notch,   \vcfNotch,
        // default
        \vcfMoog
    );
    // Args adaptes par type
    args = switch (defName,
        \vcfMoog,    [\busIn, 0, \freq, freq, \gain, q * 4, \mix, 1.0],
        \vcfMs20,    [\busIn, 0, \freq, freq, \q, q * 2, \mix, 1.0],
        \vcfLadder,  [\busIn, 0, \freq, freq, \q, q * 2, \mix, 1.0],
        \vcfFormant, [\busIn, 0, \vowel, freq.asInteger.clip(0, 4),
                      \q, q.clip(0.05, 1), \mix, 1.0],
        \vcfComb,    [\busIn, 0, \freq, freq, \decay, q * 2, \mix, 0.6],
        \vcfHpf,     [\busIn, 0, \freq, freq, \q, q, \mix, 1.0],
        \vcfBpf,     [\busIn, 0, \freq, freq, \q, q, \mix, 1.0],
        \vcfNotch,   [\busIn, 0, \freq, freq, \q, q, \mix, 1.0]
    );
    ~vcfActive !? { ~vcfActive.free };
    if (~masterLim.notNil) {
        ~vcfActive = Synth.before(~masterLim, defName, args);
    } {
        ~vcfActive = Synth.tail(s.defaultGroup, defName, args);
    };
    ~vcfActiveType = type;
    ("VCF " ++ type ++ " ON freq=" ++ freq ++ " q=" ++ q).postln;
};

~vcfSet = { |param, value|
    ~vcfActive !? { ~vcfActive.set(param.asSymbol, value) };
};

// Sweep automatique du cutoff (ne touche pas le SynthDef, set en live).
// FIX FUITE RAM : Routine maintenant trackee dans ~vcfSweepRoutine (avant
// elle etait orpheline ; si l'utilisateur cancelait mid-sweep ou
// enchainait deux ~vcfSweep d'affilee, les anciennes Routines continuaient
// silencieusement). ~vcfOff et ~killAllOrphans la stoppent.
~vcfSweep = { |fromHz = 200, toHz = 5000, dur = 4, curve = \exp|
    ~vcfSweepRoutine !? { try { ~vcfSweepRoutine.stop } };
    ~vcfSweepRoutine = Routine({
        var steps = (dur / 0.03).max(2).asInteger;
        steps.do { |i|
            var x = (i + 1) / steps;
            var f;
            if (curve == \exp) {
                f = fromHz * ((toHz / fromHz.max(0.001)).pow(x));
            } {
                f = fromHz + ((toHz - fromHz) * x);
            };
            ~vcfSet.(\freq, f);
            0.03.wait;
        };
        ~vcfSweepRoutine = nil;
    }).play;
    ("VCF SWEEP " ++ fromHz ++ " -> " ++ toHz ++ "Hz sur " ++ dur ++ "s").postln;
};

~vcfOff = {
    ~vcfSweepRoutine !? { try { ~vcfSweepRoutine.stop }; ~vcfSweepRoutine = nil };
    ~vcfActive !? { ~vcfActive.free; ~vcfActive = nil; ~vcfActiveType = nil };
    "VCF OFF".postln;
};

// Presets prets a l'emploi
~vcfPreset = { |preset|
    switch (preset.asSymbol,
        \acid,     { ~vcfOn.(\moog,    600,  0.85) },
        \dub,      { ~vcfOn.(\ladder,  900,  0.5) },
        \deep,     { ~vcfOn.(\moog,    400,  0.4) },
        \bright,   { ~vcfOn.(\bpf,    3500,  0.4) },
        \dark,     { ~vcfOn.(\moog,    250,  0.3) },
        \formantA, { ~vcfOn.(\formant,    0, 0.15) },
        \formantE, { ~vcfOn.(\formant,    1, 0.15) },
        \formantI, { ~vcfOn.(\formant,    2, 0.15) },
        \formantO, { ~vcfOn.(\formant,    3, 0.15) },
        \formantU, { ~vcfOn.(\formant,    4, 0.15) },
        // par defaut
        { "preset VCF inconnu".postln }
    );
};


// =====================================================================
//  4. Tricks sceniques live (combos)
// =====================================================================

// --- Tracking des Routines de tricks (anti-spam / anti-fuite RAM) ---
// Au reload du fichier : stoppe et libere les anciennes Routines avant
// de re-creer le dict (sinon les anciennes continuent a tourner et
// accumulent des Synths cote scsynth).
~trickRoutines !? {
    ~trickRoutines.keysValuesDo { |k, r| r !? { try { r.stop } } };
};
~trickRoutines = ();
~trickStop = { |key|
    ~trickRoutines[key.asSymbol] !? {
        ~trickRoutines[key.asSymbol].stop;
        ~trickRoutines[key.asSymbol] = nil;
    };
};
~trickStopAll = {
    ~trickRoutines.keysValuesDo { |k, r| r !? { r.stop } };
    ~trickRoutines = ();
    "TRICKS STOPPED ALL".postln;
};

// Stutter master via amp gate (modulation sur kick/mel/acid/harm)
~stutter = { |rateHz = 8, dur = 1.0|
    ~trickStop.(\stutter);
    ~trickRoutines[\stutter] = Routine({
        var n = (dur * rateHz).max(2).asInteger;
        var period = 1.0 / rateHz;
        var startK = ~kickAmp; var startM = ~melodyAmp;
        var startA = ~acidAmp; var startH = ~harmonyAmp;
        n.do { |i|
            var on = i.even;
            ~kickAmp    = if (on) { startK } { 0 };
            ~melodyAmp  = if (on) { startM } { 0 };
            ~acidAmp    = if (on) { startA } { 0 };
            ~harmonyAmp = if (on) { startH } { 0 };
            ~reload !? { ~reload.value };
            period.wait;
        };
        ~kickAmp = startK; ~melodyAmp = startM;
        ~acidAmp = startA; ~harmonyAmp = startH;
        ~reload !? { ~reload.value };
        ~trickRoutines[\stutter] = nil;
    }).play;
    ("STUTTER " ++ rateHz ++ "Hz " ++ dur ++ "s").postln;
};

// Coupe la rythmique
~chokePerc = {
    ~kickAmp = 0; ~hatAmp = 0;
    ~snareAmp !? { ~snareAmp = 0 };
    ~clapAmp !? { ~clapAmp = 0 };
    ~percAmp !? { ~percAmp = 0 };
    ~reload !? { ~reload.value };
    "CHOKE PERC".postln;
};

// Coupe melodies / lead / acid
~chokeLead = {
    ~melodyAmp = 0; ~harmonyAmp = 0; ~acidAmp = 0;
    ~reload !? { ~reload.value };
    "CHOKE LEAD".postln;
};

// Drop : silence -> snareFill -> reverb freeze -> blast
~drop = { |buildDur = 4|
    var startK = ~kickAmp ? 0.5;
    var startM = ~melodyAmp ? 0.16;
    var startA = ~acidAmp ? 0.32;
    var startH = ~harmonyAmp ? 0.14;
    ~trickStop.(\drop);
    ~trickRoutines[\drop] = Routine({
        // 1. fade out tout sur buildDur secondes
        ~envSweep.(\kickAmp,    startK, 0, buildDur, \lin, true);
        ~envSweep.(\melodyAmp,  startM, 0, buildDur, \lin, false);
        ~envSweep.(\acidAmp,    startA, 0, buildDur, \lin, false);
        ~envSweep.(\harmonyAmp, startH, 0, buildDur, \lin, false);
        // riser pendant le build
        Synth(\riser, [\dur, buildDur, \amp, 0.4]);
        buildDur.wait;
        // 2. snare fill juste avant le drop
        ~snareFill !? { ~snareFill.value };
        // 3. reverb freeze + delay 1/4
        ~setFx !? {
            ~setFx.(\rev, \freeze, 1);
            ~sendReverb !? { ~sendReverb.(0.6) };
            ~setFx.(\dly, \delayTime, 60 / TempoClock.default.tempo);
            ~sendDelay !? { ~sendDelay.(0.5) };
        };
        0.5.wait;
        // 4. BLAST - on rebalance tout
        ~kickAmp = startK; ~melodyAmp = startM;
        ~acidAmp = startA; ~harmonyAmp = startH;
        ~reload !? { ~reload.value };
        // impact
        Synth(\impact, [\amp, 0.7]);
        // unfreeze apres 1s
        1.0.wait;
        ~setFx !? { ~setFx.(\rev, \freeze, 0) };
        ~trickRoutines[\drop] = nil;
    }).play;
    ("DROP build=" ++ buildDur ++ "s").postln;
};

// Hands up : sub kick mute + lead boost + delay courte + clap
~handsUp = {
    ~kickAmp = 0;     // pas de sub
    ~melodyAmp = (~melodyAmp ? 0.16) * 1.6;
    ~clapAmp !? { ~clapAmp = 0.5 };
    ~setFx !? {
        ~setFx.(\dly, \delayTime, 60 / TempoClock.default.tempo / 4);
        ~sendDelay !? { ~sendDelay.(0.5) };
    };
    ~reload !? { ~reload.value };
    "HANDS UP".postln;
};

// Breakdown : kill rythmique progressif puis melody seule + shimmer
~breakdown = { |dur = 8|
    var startK = ~kickAmp ? 0.5;
    var startA = ~acidAmp ? 0.32;
    var startH = (~hatAmp ? 0.18);
    ~trickStop.(\breakdown);
    ~trickRoutines[\breakdown] = Routine({
        ~envSweep.(\kickAmp, startK, 0, dur * 0.4, \lin, true);
        ~envSweep.(\acidAmp, startA, 0, dur * 0.4, \lin, false);
        ~envSweep.(\hatAmp,  startH, 0, dur * 0.4, \lin, false);
        ~sendShimmer !? { ~sendShimmer.(0.5) };
        ~sendReverb !? { ~sendReverb.(0.5) };
        dur.wait;
        ~trickRoutines[\breakdown] = nil;
    }).play;
    ("BREAKDOWN " ++ dur ++ "s").postln;
};

// Buildup : crescendo (riser + fadein de tout + filterSweepUp)
~buildup = { |dur = 8|
    var endK = 0.5; var endM = 0.16; var endA = 0.32;
    Synth(\riser, [\dur, dur, \amp, 0.45]);
    ~envSweep.(\kickAmp,   ~kickAmp ? 0,   endK, dur, \lin, true);
    ~envSweep.(\melodyAmp, ~melodyAmp ? 0, endM, dur, \lin, false);
    ~envSweep.(\acidAmp,   ~acidAmp ? 0,   endA, dur, \lin, false);
    ~filterSweepUp.(dur);
    ("BUILDUP " ++ dur ++ "s").postln;
};

// Liste centralisee des Pdef sequenceurs (utilise par tapeStop / tapeStart
// / playAll). Si tu ajoutes une voix, mets a jour ici.
~allSeqs = [
    \kickSeq, \hatSeq, \snareSeq, \clapSeq, \percSeq,
    \acidSeq, \melodySeq, \harmonySeq
];

// FIX FUITE RAM CLIENT : ~reload debounce.
//
// Probleme : les tracks appellent ~reload.value 5-10x par section (dans
// les `if (X.coin) { ...; ~reload.value }` loops). Chaque reload recompile
// 8 Pdef Pbind avec ~50 Pfunc closures qui retiennent currentEnvironment
// = ~10 KB sclang par reload. Sur une track de 5 min : ~250 reloads =
// 2.5 MB client memory pile. Sur un album : 50 MB. Sclang grossit jusqu'au
// crash.
//
// Solution : 1er appel fire IMMEDIAT + arme une fenetre 100 ms. Les appels
// pendant la fenetre sont coalesces en UN SEUL trailing call. Les variables
// ~xxx etant lues a l'execution du Pdef, le trailing call recupere
// automatiquement la valeur la plus recente.
//
// DEBOUNCING ~reload : 4-10 ~reload rapides -> 2 executions reelles.
// Reduit la pression sur le GC sclang (Pfunc closures).
//
// IMPORTANT : guard idempotent contre reload multiple de live.scd.
if (~reloadImmediate.isNil) { ~reloadImmediate = ~reload };
~reloadPending = false;
~reloadDebouncer = nil;
~reloadWindow = 0.1;  // 100 ms

~reload = {
    if (~reloadDebouncer.isNil) {
        // Hors fenetre : execute tout de suite + ouvre fenetre
        ~reloadImmediate.value;
        ~reloadDebouncer = Routine({
            ~reloadWindow.wait;
            if (~reloadPending) {
                ~reloadImmediate.value;
                ~reloadPending = false;
            };
            ~reloadDebouncer = nil;
        }).play;
    } {
        // Dans la fenetre : marque pending, le trailing call l'executera
        ~reloadPending = true;
    };
};

~reloadNow = { ~reloadImmediate.value };

// FIX FUITE RAM SCSYNTH : ~playActive ne lance QUE les Pdef dont l'amp > 0.
//
// Probleme : la pluspart des tracks font
//   [\kickSeq, \hatSeq, \snareSeq, \clapSeq, \percSeq, \acidSeq, \melodySeq, \harmonySeq].do { |k| Pdef(k).play };
// meme quand snare/clap/perc/harmony ont amp=0. Resultat : 3-4 Pdef
// fantomes qui spawnent des Synths silencieux a 9/sec chacun = 30+
// Synths/sec inutiles cote scsynth. Sur une track de 5 min = 9000+
// Synth instantiations gachees + UGens correspondants alloues puis
// liberes (churn massif).
//
// Solution : verifier l'amp avant chaque .play. Les Pdef sans amp restent
// .stop. L'utilisateur peut les ramener vivant en setant ~xxxAmp > 0
// puis appelant ~playActive.value de nouveau.
~allSeqsAmpKey = (
    kickSeq: \kickAmp, hatSeq: \hatAmp, snareSeq: \snareAmp,
    clapSeq: \clapAmp, percSeq: \percAmp, acidSeq: \acidAmp,
    melodySeq: \melodyAmp, harmonySeq: \harmonyAmp
);

~playActive = {
    var played = List.new, skipped = List.new;
    ~allSeqsAmpKey.keysValuesDo { |seqKey, ampKey|
        var amp = currentEnvironment[ampKey] ? 0;
        if (amp > 0) {
            Pdef(seqKey).play;
            played.add(seqKey);
        } {
            Pdef(seqKey).stop;
            skipped.add(seqKey);
        };
    };
    ("[playActive] play=" ++ played.asArray
     ++ "  skip=" ++ skipped.asArray).postln;
};

// FIX FUITE RAM TRACK : helper a appeler en debut de chaque ~track Routine
// pour eviter que la track precedente survive en pieces detachees.
//
// Probleme : `~track !? { ~track.stop }` arretait la Routine principale
// MAIS pas les Pdef qu'elle avait .play() (kickSeq/melodySeq/etc.) ni les
// ~trickRoutines (stutter, drop), ~lfos, ~envs, ~fxLfos qu'elle avait
// declenches. Resultat : enchainer 3 tracks empilait 3 jeux complets ->
// 3x les Synths, 3x les Routines, fuite explosive.
//
// ~trackBegin fait le meme cleanup que ~killAllOrphans mais SANS stopper
// ~track lui-meme (sinon il s'auto-tuerait).
~trackBegin = {
    // 1. Stop tous les Pdef sequenceurs
    ~allSeqs.do { |k| Pdef(k).stop };
    // 2. Stop ~trickRoutines (stutter, drop, glitch, freeze, ...)
    ~trickStopAll !? { ~trickStopAll.value };
    // 3. Stop ~lfos (params live)
    ~lfos !? {
        ~lfos.keysValuesDo { |k, v| try { v.stop } };
        ~lfos.clear;
    };
    // 4. Stop ~envs (envelopes ponctuelles)
    ~envs !? {
        ~envs.keysValuesDo { |k, v| try { v.stop } };
        ~envs.clear;
    };
    // 5. Stop ~fxLfos (LFO sur sends FX)
    ~fxLfos !? {
        ~fxLfos.keysValuesDo { |k, v| try { v.stop } };
        ~fxLfos.clear;
    };
    // 6. Stop VCF sweep en cours
    ~vcfSweepRoutine !? { try { ~vcfSweepRoutine.stop }; ~vcfSweepRoutine = nil };
    // 7. Stop auto-fx (panning/tremolo) sur les voix principales
    ~stopAutoFx !? {
        [\kick, \hat, \snare, \clap, \perc, \acid, \melody, \harmony]
            .do { |v| try { ~stopAutoFx.(v) } };
    };
    "[trackBegin] cleanup OK".postln;
};

// Play All : relance instantanement tous les Pdef sequenceurs (pendant
// direct de ~tapeStop). N'altere ni tempo ni amp -- juste .play().
~playAll = {
    ~allSeqs.do { |k| Pdef(k).play };
    "PLAY ALL".postln;
};

// Tape start : symetrique de ~tapeStop. Relance les Pdef puis accelere
// le tempo de 0.01 jusqu'a startTempo (passe en argument ou actuel).
~tapeStart = { |dur = 2, targetTempo|
    var endTempo = targetTempo ? TempoClock.default.tempo;
    ~trickStop.(\tapeStart);
    ~trickRoutines[\tapeStart] = Routine({
        var steps = (dur / 0.03).max(2).asInteger;
        // relance les Pdef AU PLUS BAS tempo (ou ils repartent au ralenti)
        TempoClock.default.tempo = 0.01;
        ~allSeqs.do { |k| Pdef(k).play };
        steps.do { |i|
            var x = (i + 1) / steps;
            // courbe expo inverse (monte vite puis lentement)
            var t = endTempo * x.sqrt;
            TempoClock.default.tempo = t.max(0.01);
            0.03.wait;
        };
        TempoClock.default.tempo = endTempo;
        ~trickRoutines[\tapeStart] = nil;
    }).play;
    ("TAPE START " ++ dur ++ "s -> " ++ endTempo).postln;
};

// Tape stop : ralenti progressif du tempo puis stop
~tapeStop = { |dur = 2|
    var startTempo = TempoClock.default.tempo;
    ~trickStop.(\tapeStop);
    ~trickRoutines[\tapeStop] = Routine({
        var steps = (dur / 0.03).max(2).asInteger;
        steps.do { |i|
            var x = (i + 1) / steps;
            // courbe expo (descend lentement puis vite)
            var t = startTempo * ((1 - x).pow(2));
            TempoClock.default.tempo = t.max(0.01);
            0.03.wait;
        };
        // stop
        ~allSeqs.do { |k| Pdef(k).stop };
        TempoClock.default.tempo = startTempo;
        ~trickRoutines[\tapeStop] = nil;
    }).play;
    ("TAPE STOP " ++ dur ++ "s").postln;
};

// Glitch : bitcrush + stutter + delay courte momentanes
~glitch = { |dur = 2|
    Synth(\fxMasterBitcrush, [\dur, dur, \bits, 5, \sr, 5000, \mix, 0.7]);
    ~stutter.(16, dur);
    ~setFx !? {
        ~setFx.(\dly, \delayTime, 0.05);
        ~sendDelay !? { ~sendDelay.(0.4) };
    };
    ~trickStop.(\glitch);
    ~trickRoutines[\glitch] = Routine({
        dur.wait;
        ~sendDelay !? { ~sendDelay.(0) };
        ~trickRoutines[\glitch] = nil;
    }).play;
    ("GLITCH " ++ dur ++ "s").postln;
};

// Freeze : reverb freeze + cut sec
~freeze = { |dur = 4|
    var startK = ~kickAmp ? 0.5;
    var startM = ~melodyAmp ? 0.16;
    var startA = ~acidAmp ? 0.32;
    ~trickStop.(\freeze);
    ~trickRoutines[\freeze] = Routine({
        ~setFx !? {
            ~setFx.(\rev, \freeze, 1);
            ~sendReverb !? { ~sendReverb.(0.7) };
        };
        ~kickAmp = 0; ~melodyAmp = 0; ~acidAmp = 0;
        ~reload !? { ~reload.value };
        dur.wait;
        ~setFx !? {
            ~setFx.(\rev, \freeze, 0);
            ~sendReverb !? { ~sendReverb.(0.3) };
        };
        ~kickAmp = startK; ~melodyAmp = startM; ~acidAmp = startA;
        ~reload !? { ~reload.value };
        ~trickRoutines[\freeze] = nil;
    }).play;
    ("FREEZE " ++ dur ++ "s").postln;
};

// Reverb tail : kill all + reverb send max + dur secondes de queue
~reverbTail = { |dur = 4|
    var startK = ~kickAmp ? 0.5;
    var startM = ~melodyAmp ? 0.16;
    var startA = ~acidAmp ? 0.32;
    var startH = ~harmonyAmp ? 0.14;
    ~trickStop.(\reverbTail);
    ~trickRoutines[\reverbTail] = Routine({
        ~sendReverb !? { ~sendReverb.(0.9) };
        ~setFx !? { ~setFx.(\rev, \room, 0.97); ~setFx.(\rev, \damp, 0.1) };
        ~kickAmp = 0; ~melodyAmp = 0; ~acidAmp = 0; ~harmonyAmp = 0;
        ~reload !? { ~reload.value };
        dur.wait;
        ~kickAmp = startK; ~melodyAmp = startM;
        ~acidAmp = startA; ~harmonyAmp = startH;
        ~reload !? { ~reload.value };
        ~sendReverb !? { ~sendReverb.(0.3) };
        ~trickRoutines[\reverbTail] = nil;
    }).play;
    ("REVERB TAIL " ++ dur ++ "s").postln;
};

// Tape reverse : effet inverse via riser cutoff inverse + freeze
~tapeReverse = { |dur = 2|
    Synth(\sweep, [\dur, dur, \startCutoff, 200, \endCutoff, 8000, \amp, 0.3]);
    ~filterSweepUp.(dur);
    ~setFx !? {
        ~setFx.(\rev, \freeze, 1);
        ~sendReverb !? { ~sendReverb.(0.5) };
    };
    ~trickStop.(\tapeReverse);
    ~trickRoutines[\tapeReverse] = Routine({
        dur.wait;
        ~setFx !? { ~setFx.(\rev, \freeze, 0) };
        ~trickRoutines[\tapeReverse] = nil;
    }).play;
    ("TAPE REVERSE " ++ dur ++ "s").postln;
};

// Swap : crossfade entre deux scenes (utilise scenes.scd ~scenes ou
// simplement bascule deux preset par nom). Si ~scene helper existe.
~swap = { |sceneA, sceneB, dur = 4|
    ~trickStop.(\swap);
    ~trickRoutines[\swap] = Routine({
        if (~scene.notNil) {
            // fade out current
            ~envSweep.(\melodyAmp, ~melodyAmp, 0, dur * 0.5, \lin, false);
            ~envSweep.(\acidAmp,   ~acidAmp,   0, dur * 0.5, \lin, false);
            (dur * 0.5).wait;
            ~scene.(sceneB);
        } {
            ("swap : pas de helper ~scene -- swap " ++ sceneA
                ++ " <-> " ++ sceneB ++ " (no-op)").postln;
        };
        ~trickRoutines[\swap] = nil;
    }).play;
};

// Jump cut : silence sec dur secondes puis reprend tout
~jumpCut = { |dur = 1|
    var startK = ~kickAmp; var startM = ~melodyAmp;
    var startA = ~acidAmp; var startH = ~harmonyAmp;
    ~trickStop.(\jumpCut);
    ~trickRoutines[\jumpCut] = Routine({
        ~kickAmp = 0; ~melodyAmp = 0; ~acidAmp = 0; ~harmonyAmp = 0;
        ~reload !? { ~reload.value };
        dur.wait;
        ~kickAmp = startK; ~melodyAmp = startM;
        ~acidAmp = startA; ~harmonyAmp = startH;
        ~reload !? { ~reload.value };
        ~trickRoutines[\jumpCut] = nil;
    }).play;
    ("JUMP CUT " ++ dur ++ "s").postln;
};


// =====================================================================
//  5. Sidechain dynamique (source -> target)
// =====================================================================
// Le moteur a deja ~scBus utilise par kick. On etend pour permettre
// d'autres SC dynamiques en jouant simplement sur les ~xxxScAmt
// existants. Pour un vrai SC d'une voix arbitraire vers une autre
// arbitraire, on creerait un nouveau bus -- ici on offre une API
// qui module les SC amount existants en fonction d'une source.
~scTrigger = { |source = \kick, target = \acid, amount = 0.7,
                attack = 0.005, release = 0.2|
    // pour kick -> acid/lead : ce sont les SC built-in : on set juste les amounts
    var key = (source.asString ++ "->" ++ target.asString).asSymbol;
    if (source == \kick) {
        switch (target.asSymbol,
            \acid,    { ~acidScAmt = amount; ~kickScShape = release },
            \melody,  { ~leadScAmt = amount; ~kickScShape = release },
            \harmony, { ~leadScAmt = amount; ~kickScShape = release },
            \master,  {
                // duck global synthetique : ampDuck a chaque kick sans
                // implementer un vrai bus de SC : on map kick.amp -> tous
                // les amps via un helper Routine. Approche pragmatique.
                ~scAuto.(amount);
            },
            { "scTrigger : target inconnu".postln }
        );
        ~reload !? { ~reload.value };
    } {
        ("scTrigger : seule la source \\kick est cablee dans le moteur "
            ++ "(snare/clap necessitent un bus dedie)").postln;
    };
    ("SC " ++ key ++ " amount=" ++ amount).postln;
};

// SC auto sur tout le mix (fait pomper tout sur le kick)
~scAuto = { |amount = 0.6|
    ~acidScAmt = amount;
    ~leadScAmt = amount;
    ~reload !? { ~reload.value };
    ("SC AUTO global = " ++ amount).postln;
};

~scOff = {
    ~acidScAmt = 0; ~leadScAmt = 0;
    ~reload !? { ~reload.value };
    "SC OFF".postln;
};


// =====================================================================
//  6. Stuttering / glitch / pitch momentanes
// =====================================================================
// Tous via Synth a duree finie (doneAction:2) -> pas besoin de free.

~slipTime = { |amount = 0.5, dur = 1|
    // amount : 0.5 = -1 octave, 1.0 = identite, 2.0 = +1 octave
    Synth(\fxMasterPitch, [\dur, dur, \pitch, amount.clip(0.25, 4.0)]);
    ("SLIP TIME pitch=" ++ amount ++ " dur=" ++ dur).postln;
};

~bitCrushNow = { |bits = 6, sr = 6000, dur = 1|
    Synth(\fxMasterBitcrush, [\dur, dur, \bits, bits, \sr, sr, \mix, 0.7]);
    ("BITCRUSH bits=" ++ bits ++ " sr=" ++ sr).postln;
};

~ringMod = { |freq = 200, dur = 1|
    Synth(\fxMasterRing, [\dur, dur, \freq, freq, \mix, 0.6]);
    ("RING MOD " ++ freq ++ "Hz " ++ dur ++ "s").postln;
};

~freqShift = { |shift = 100, dur = 1|
    Synth(\fxMasterFreqShift, [\dur, dur, \shift, shift]);
    ("FREQ SHIFT " ++ shift ++ "Hz " ++ dur ++ "s").postln;
};

~granFreeze = { |dur = 2|
    Synth(\fxMasterGran, [\dur, dur, \grainDur, 0.15, \pitch, 1.0]);
    ("GRAN FREEZE " ++ dur ++ "s").postln;
};

// Utilise le SynthDef \scratch existant (synth/_index.scd)
~scratch = { |dir = 1, dur = 0.3|
    Synth(\scratch, [\dur, dur, \freq, rrand(400, 1500),
                     \sweepDir, dir, \amp, 0.4]);
    ("SCRATCH dir=" ++ dir ++ " dur=" ++ dur).postln;
};

// Beat repeat : on prend un snippet du bus 0 et on le loop pendant dur
// Approche pragmatique : on utilise fxStutter pour chopper le master en
// gates rythmiques pendant dur -- pas de capture/replay (necessiterait
// un buffer dedie).
~repeat = { |rate = 8, dur = 1|
    // gate rythmique sur le master via stutter rapide
    ~stutter.(rate, dur);
    ("REPEAT " ++ rate ++ "Hz " ++ dur ++ "s").postln;
};


// =====================================================================
//  7. Auto-pan / tremolo / autopan par voix (via LFO sur amp/pan)
// =====================================================================
// Comme les voix sont des Pdef avec amp dans l'event, on module les
// variables ~xxxAmp / ~xxxPan via les LFO existants.
~autoFx = ();   // dictionnaire d'auto-fx par cible

// FIX FUITE RAM : doReload force a false (etait true en 6e arg de ~lfoTo).
// A 5Hz un tremolo declenchait 100 recompils Pdef/sec -> Synths fuiteurs
// massifs. Pfunc { ~xxxAmp } dans engine.scd suffit a propager la valeur
// en live sans recompil.
~autoPan = { |target = \melody, rate = 0.5, depth = 0.8|
    // on vise la variable ~xxxPan -- si elle n'existe pas, on la cree
    var sym = (target.asString ++ "Pan").asSymbol;
    ~setVar.(sym, 0);
    ~lfoTo.(sym, rate, depth, \sin, 0, false);
    ~autoFx[target.asSymbol] = sym;
    ("AUTOPAN " ++ target ++ " rate=" ++ rate
        ++ " depth=" ++ depth ++ " (sur ~" ++ sym ++ ")").postln;
};

~tremolo = { |target = \melody, rate = 5, depth = 0.5|
    var sym = (target.asString ++ "Amp").asSymbol;
    var center = ~getVar.(sym) ? 0.16;
    ~lfoTo.(sym, rate, center * depth, \sin, center, false);
    ~autoFx[target.asSymbol] = sym;
    ("TREMOLO " ++ target ++ " rate=" ++ rate ++ "Hz depth=" ++ depth).postln;
};

~stopAutoFx = { |target = \melody|
    var sym = ~autoFx[target.asSymbol];
    sym !? { ~lfoStopVar.(sym); ~autoFx.removeAt(target.asSymbol) };
    ("AUTO FX " ++ target ++ " STOPPED").postln;
};


// =====================================================================
//  8. FX additionnels : les nouveaux FX SynthDef sont definis plus haut.
//     Helper pour les ajouter au bus FX (~fxFx) et permettre ~send dessus.
// =====================================================================
// Les FX additionnels sont : ringmod, fshift, gran, comb, formant,
// autoflt, trem, autopan, stut, reson, spring, plate.
// Ils peuvent etre instancies en send avec ~addFx.(\name, args)
// qui cree un nouveau bus + un Synth de FX en queue avant le master.

~addFx = { |name, args = #[], def = nil|
    var key = name.asSymbol;
    var defName = def ? switch (key,
        \ringmod, \fxRingMod,
        \fshift,  \fxFreqShift,
        \gran,    \fxGran,
        \comb,    \fxComb,
        \formant, \fxFormant,
        \autoflt, \fxAutoFilter,
        \trem,    \fxTremolo,
        \autopan, \fxAutopan,
        \stut,    \fxStutter,
        \reson,   \fxResonator,
        \spring,  \fxSpringRev,
        \plate,   \fxPlate,
        \fxRingMod
    );
    ~fxBuses !? {
        // si le bus n'existe pas, on l'alloue (et on track pour cleanup
        // ulterieur via ~freeDynFxBuses : les bus statiques de fx_bus.scd
        // sont free par leur propre guard, mais ceux ajoutes a chaud par
        // ~addFx etaient orphelins jusqu'ici -> fuite RAM serveur).
        if (~fxBuses[key].isNil) {
            ~fxBuses[key] = Bus.audio(s, 2);
            ~fxBusesDyn = ~fxBusesDyn ?? { () };
            ~fxBusesDyn[key] = ~fxBuses[key];
        };
        ~fxFx[key] !? { ~fxFx[key].free };
        if (~masterLim.notNil) {
            ~fxFx[key] = Synth.before(~masterLim, defName,
                [\in, ~fxBuses[key].index] ++ args);
        } {
            ~fxFx[key] = Synth.tail(s.defaultGroup, defName,
                [\in, ~fxBuses[key].index] ++ args);
        };
        ("FX " ++ key ++ " (" ++ defName ++ ") ajoute -- ~send.(\\"
            ++ key ++ ", amount) pour l'utiliser").postln;
    };
};


// =====================================================================
//  9. Master compresseur / saturation switchable
// =====================================================================
~masterComp = nil;
~masterSatSyn = nil;

~compOn = { |preset = \glue|
    var args = switch (preset.asSymbol,
        \soft,  [\threshold, 0.5, \ratio, 0.7, \attack, 0.02,
                 \release, 0.2,  \makeup, 1.2],
        \glue,  [\threshold, 0.4, \ratio, 0.5, \attack, 0.01,
                 \release, 0.15, \makeup, 1.4],
        \punch, [\threshold, 0.3, \ratio, 0.4, \attack, 0.003,
                 \release, 0.05, \makeup, 1.6],
        \pump,  [\threshold, 0.25,\ratio, 0.25,\attack, 0.001,
                 \release, 0.25, \makeup, 1.8],
        // default
        [\threshold, 0.4, \ratio, 0.5, \attack, 0.01,
         \release, 0.15, \makeup, 1.4]
    );
    ~masterComp !? { ~masterComp.free };
    if (~masterLim.notNil) {
        ~masterComp = Synth.before(~masterLim, \masterComp, args);
    } {
        ~masterComp = Synth.tail(s.defaultGroup, \masterComp, args);
    };
    ("COMP " ++ preset ++ " ON").postln;
};

~compOff = {
    ~masterComp !? { ~masterComp.free; ~masterComp = nil };
    "COMP OFF".postln;
};

~satOn = { |amount = 1.5, mode = 0|
    ~masterSatSyn !? { ~masterSatSyn.free };
    if (~masterLim.notNil) {
        ~masterSatSyn = Synth.before(~masterLim, \masterSat,
            [\amount, amount, \mode, mode, \mix, 1.0]);
    } {
        ~masterSatSyn = Synth.tail(s.defaultGroup, \masterSat,
            [\amount, amount, \mode, mode, \mix, 1.0]);
    };
    ("SAT amount=" ++ amount ++ " mode=" ++ mode).postln;
};

~satOff = {
    ~masterSatSyn !? { ~masterSatSyn.free; ~masterSatSyn = nil };
    "SAT OFF".postln;
};


"=== live.scd CHARGE ===".postln;
"  ~lfoTo / ~lfoSync / ~lfoStopVar / ~lfoStopAllVars / ~lfoList".postln;
"  ~envTo / ~envBurst / ~envSweep / ~filterSweepUp/Down / ~ampDuck".postln;
"  ~vcfOn / ~vcfSet / ~vcfSweep / ~vcfOff / ~vcfPreset".postln;
"  ~stutter ~chokePerc ~chokeLead ~drop ~handsUp ~breakdown".postln;
"  ~buildup ~tapeStop ~glitch ~freeze ~reverbTail ~tapeReverse".postln;
"  ~swap ~jumpCut ~scTrigger ~scAuto ~scOff".postln;
"  ~slipTime ~bitCrushNow ~ringMod ~freqShift ~granFreeze ~scratch ~repeat".postln;
"  ~autoPan ~tremolo ~stopAutoFx ~addFx ~compOn/Off ~satOn/Off".postln;

)


// =====================================================================
//  EXEMPLES D'USAGE -- Cmd+Entree sur un bloc pour l'executer
//  PREREQUIS : avoir evalue le bloc SETUP en tete de fichier d'abord
// =====================================================================

// --- 1. LFO assignables ----------------------------------------------

// LFO sin 0.5Hz +/-1500 autour 2500 sur cutoff melody
( ~lfoTo.(\melodyCutoff, 0.5, 1500, \sin, 2500); )

// LFO triangle slow sur cutoff acid
( ~lfoTo.(\acidCutoff, 0.25, 800, \tri, 1000); )

// LFO sqr asymetrique (pulseWidth=0.3) sur kickAmp -> rythmique
( ~lfoTo.(\kickAmp, 2, 0.2, \sqr, 0.5, true, 0.3); )

// LFO smooth random sur amp harmony
( ~lfoTo.(\harmonyAmp, 0.3, 0.08, \smoothRand, 0.14); )

// LFO drunken walk sur drive melody
( ~lfoTo.(\melodyDrive, 0.1, 0.8, \drunk, 1.2); )

// LFO sync au tempo : 1 cycle complet sur 4 beats
( ~lfoSync.(\melodyCutoff, 4, 1500, \sin, 2500); )

// Stop un LFO precis
( ~lfoStopVar.(\melodyCutoff); )

// Stop tous les LFOs de variables
( ~lfoStopAllVars.(); )

// Liste les LFOs actifs
( ~lfoList.(); )


// --- 2. Envelopes one-shot -------------------------------------------

// Envelope ASR (attack/sustain/release) sur cutoff melody : 1500 -> 6000 -> 1500
( ~envTo.(\melodyCutoff, 0.5, 1.0, 2.0, 6000, 1500); )

// Burst percussif sur cutoff acid : pic a 4000 puis decay 0.3s
( ~envBurst.(\acidCutoff, 4000, 0.3); )

// Sweep expo sur cutoff melody : 200 -> 6000 en 4s
( ~envSweep.(\melodyCutoff, 200, 6000, 4, \exp); )

// Sweep filtre montant 4s
( ~filterSweepUp.(4); )

// Sweep filtre descendant 4s
( ~filterSweepDown.(4); )

// Duck momentane sur tous les amps (depth=0.7 pendant 0.3s)
( ~ampDuck.(0.3, 0.7); )


// --- 3. VCF Master ---------------------------------------------------

// VCF Moog 800Hz q=0.7
( ~vcfOn.(\moog, 800, 0.7); )

// Set un parametre du VCF actif
( ~vcfSet.(\freq, 1500); )

// Sweep automatique du cutoff 200 -> 5000 en 4s
( ~vcfSweep.(200, 5000, 4); )

// VCF off
( ~vcfOff.(); )

// Preset acid (moog 600Hz q=0.85)
( ~vcfPreset.(\acid); )

// Preset formant voyelle A
( ~vcfPreset.(\formantA); )

// Preset dub (ladder 900Hz q=0.5)
( ~vcfPreset.(\dub); )


// --- 4. Tricks sceniques ---------------------------------------------

// Stutter master 8Hz pendant 1s
( ~stutter.(8, 1); )

// Coupe la rythmique (kick/hat/snare/clap/perc)
( ~chokePerc.(); )

// Coupe melody/lead/acid
( ~chokeLead.(); )

// Drop classique : build 4s + snare fill + freeze + blast
( ~drop.(4); )

// Hands up : mute kick, boost lead, delay courte
( ~handsUp.(); )

// Breakdown progressif sur 8s
( ~breakdown.(8); )

// Buildup crescendo sur 8s (riser + filterSweepUp)
( ~buildup.(8); )

// Tape stop : ralenti tempo puis stop sur 2s
( ~tapeStop.(2); )

// Glitch : bitcrush + stutter + delay courte pendant 2s
( ~glitch.(2); )

// Freeze : reverb freeze + cut sec pendant 4s
( ~freeze.(4); )

// Reverb tail : kill all + reverb send max + queue 4s
( ~reverbTail.(4); )

// Tape reverse : sweep + freeze pendant 2s
( ~tapeReverse.(2); )

// Jump cut : silence sec 0.5s puis reprend
( ~jumpCut.(0.5); )


// --- 5. Sidechain ----------------------------------------------------

// Sidechain kick -> acid (amount 0.85)
( ~scTrigger.(\kick, \acid, 0.85); )

// Sidechain kick -> melody (amount 0.6)
( ~scTrigger.(\kick, \melody, 0.6); )

// Sidechain auto sur tout le mix (pompe global)
( ~scAuto.(0.7); )

// Stop tous les sidechains
( ~scOff.(); )


// --- 6. Pitch / glitch -----------------------------------------------

// Slip time : pitch -1 octave pendant 1s
( ~slipTime.(0.5, 1); )

// Slip time : pitch +1 octave pendant 0.5s
( ~slipTime.(2.0, 0.5); )

// Bitcrush master 4 bits / 4kHz pendant 1s
( ~bitCrushNow.(4, 4000, 1); )

// Ring mod 150Hz pendant 1s
( ~ringMod.(150, 1); )

// Freq shift +50Hz pendant 1s
( ~freqShift.(50, 1); )

// Gran freeze pendant 2s
( ~granFreeze.(2); )

// Scratch up
( ~scratch.(1, 0.3); )

// Scratch down (plus long)
( ~scratch.(-1, 0.5); )

// Beat repeat 8Hz pendant 1s
( ~repeat.(8, 1); )


// --- 7. Auto-pan / tremolo -------------------------------------------

// Auto-pan melody (rate 0.5Hz, depth 0.8)
( ~autoPan.(\melody, 0.5, 0.8); )

// Tremolo melody (rate 5Hz, depth 0.5)
( ~tremolo.(\melody, 5, 0.5); )

// Stop l'auto-fx d'une voie
( ~stopAutoFx.(\melody); )


// --- 8. FX additionnels ----------------------------------------------

// Ring mod en send (200Hz, mix 0.5)
( ~addFx.(\ringmod, [\freq, 200, \mix, 0.5]); ~send.(\ringmod, 0.4); )

// Frequency shifter en send (+80Hz)
( ~addFx.(\fshift, [\shift, 80]); ~send.(\fshift, 0.3); )

// Granular freeze en send (pitch -1 oct)
( ~addFx.(\gran, [\grainDur, 0.15, \pitch, 0.5]); ~send.(\gran, 0.4); )

// Comb resonator en send (220Hz, decay 2s)
( ~addFx.(\comb, [\freq, 220, \decay, 2]); ~send.(\comb, 0.4); )

// Formant filter A en send
( ~addFx.(\formant, [\vowel, 0]); ~send.(\formant, 0.5); )

// Auto-filter LFO en send (sweep 200-4000Hz a 0.3Hz)
( ~addFx.(\autoflt, [\rate, 0.3, \lo, 200, \hi, 4000]); ~send.(\autoflt, 0.5); )

// Tremolo en send (6Hz, depth 0.7)
( ~addFx.(\trem, [\rate, 6, \depth, 0.7]); ~send.(\trem, 1.0); )

// Auto-pan en send
( ~addFx.(\autopan, [\rate, 0.5, \depth, 1]); ~send.(\autopan, 1.0); )

// Stutter en send (8Hz duty 0.5)
( ~addFx.(\stut, [\rate, 8, \dutyCycle, 0.5]); ~send.(\stut, 1.0); )

// Resonator pitched en send (220Hz, decay 3s)
( ~addFx.(\reson, [\freq, 220, \decay, 3]); ~send.(\reson, 0.5); )

// Spring reverb en send (decay 1.5s)
( ~addFx.(\spring, [\decay, 1.5]); ~send.(\spring, 0.4); )

// Plate reverb en send (decay 2.5s, damp 0.3)
( ~addFx.(\plate, [\decay, 2.5, \damp, 0.3]); ~send.(\plate, 0.4); )


// --- 9. Master comp / sat --------------------------------------------

// Compresseur preset glue
( ~compOn.(\glue); )

// Compresseur preset punch
( ~compOn.(\punch); )

// Compresseur preset pump (sidechain-style)
( ~compOn.(\pump); )

// Compresseur off
( ~compOff.(); )

// Saturation tanh
( ~satOn.(1.5, 0); )

// Saturation softclip
( ~satOn.(2.0, 1); )

// Saturation fold (extreme)
( ~satOn.(1.8, 2); )

// Saturation off
( ~satOff.(); )


// --- Combos sceniques en chaine --------------------------------------

// Drop classique
( ~drop.(4); )

// Sequence complete : breakdown + buildup + drop
( ~breakdown.(8); ~buildup.(8); ~drop.(2); )

// Acid focus : preset VCF + LFO sync sur cutoff
( ~vcfPreset.(\acid); ~lfoSync.(\acidCutoff, 8, 600, \sin, 800); )

// Mix punchy : SC auto + comp pump + saturation
( ~scAuto.(0.7); ~compOn.(\pump); ~satOn.(1.5); )

// Reset complet : stop tous les LFOs + VCF off + comp/sat off
( ~lfoStopEverything.(); ~vcfOff.(); ~compOff.(); ~satOff.(); )
