Files
AV-Live/sound_algo/live/live.scd
T
L'électron rare de035a8596 Add 'sound_algo/' from commit '38b17c42a0b5d2c7f52a2ad600316282cc52a02d'
git-subtree-dir: sound_algo
git-subtree-mainline: b7940d650f
git-subtree-split: 38b17c42a0
2026-05-07 11:51:51 +02:00

1429 lines
48 KiB
Plaintext

// =====================================================================
// 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.(); )