Files
AV-Live/sound_algo/live/fx_bus.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

341 lines
13 KiB
Markdown

// =====================================================================
// live/fx_bus.scd -- Systeme FX live complet
//
// 9 bus FX : reverb / delay / chorus / phaser / flanger / bitcrush /
// tape / shimmer / distortion
// + helpers send / setFx
// + automation LFO sur tout param
// + presets / snapshots
// + tap tempo pour delay
// + freeze reverb (capture)
// + crossfade entre FX
//
// USAGE
// 1. SETUP FX -- charge SynthDefs + alloue les bus + lance les FX
// en queue + cree les helpers (~send / ~lfo / ~fxPreset / ~tap /
// ~crossfadeFx). Compatible .load (un seul bloc top-level).
// 2. Helpers ~sendXxx pour envoyer dans un FX (live)
// 3. Helpers ~lfoXxx pour automatiser un parametre (live)
// 4. Presets ~fxPreset.(\name) pour rappeler un combo (live)
// =====================================================================
// =====================================================================
// 1. SETUP FX (single-block) -- SynthDefs + bus + FX en queue + helpers
// =====================================================================
(
// --- BUS allocation ---
~fxBuses !? { ~fxBuses.do(_.free) };
~fxBuses = (
rev: Bus.audio(s, 2),
dly: Bus.audio(s, 2),
chr: Bus.audio(s, 2),
phs: Bus.audio(s, 2),
flg: Bus.audio(s, 2),
crsh: Bus.audio(s, 2),
tape: Bus.audio(s, 2),
shim: Bus.audio(s, 2),
dist: Bus.audio(s, 2),
);
// --- REVERB (FreeVerb stereo + freeze) ---
// --- DELAY ping-pong avec saturation tape ---
// --- CHORUS (modulation stereo) ---
// --- PHASER (4-stage allpass + LFO) ---
// --- FLANGER (court delay + LFO + feedback) ---
// --- BITCRUSHER (lo-fi sample rate + bit depth) ---
// --- TAPE (saturation analog + wow/flutter) ---
// --- SHIMMER REVERB (octave up + reverb hall) ---
// --- DISTORTION (overdrive + tone) ---
// --- SEND (copie audio vers un FX bus, ratio reglable) ---
s.sync;
// --- INSTANCIER les 9 FX en queue + master limiter ----------------
// .do sur Event : 1 arg = value. Le 2-arg form est |value, key| (synth d'abord).
~fxFx !? { ~fxFx.do { |syn| syn !? { syn.free } } };
~fxFx = (
rev: Synth.tail(s.defaultGroup, \fxReverb, [\in, ~fxBuses[\rev].index]),
dly: Synth.tail(s.defaultGroup, \fxDelay, [\in, ~fxBuses[\dly].index]),
chr: Synth.tail(s.defaultGroup, \fxChorus, [\in, ~fxBuses[\chr].index]),
phs: Synth.tail(s.defaultGroup, \fxPhaser, [\in, ~fxBuses[\phs].index]),
flg: Synth.tail(s.defaultGroup, \fxFlanger, [\in, ~fxBuses[\flg].index]),
crsh: Synth.tail(s.defaultGroup, \fxBitcrush,[\in, ~fxBuses[\crsh].index]),
tape: Synth.tail(s.defaultGroup, \fxTape, [\in, ~fxBuses[\tape].index]),
shim: Synth.tail(s.defaultGroup, \fxShimmer, [\in, ~fxBuses[\shim].index]),
dist: Synth.tail(s.defaultGroup, \fxDist, [\in, ~fxBuses[\dist].index, \mix, 0.5]),
);
// Master limiter en queue absolue
~masterLim !? { ~masterLim.free };
~masterLim = Synth.tail(s.defaultGroup, \masterLim);
~fxSends = (); // dictionnaire des Synth de send actifs
~fxLfos = (); // dictionnaire des Routines LFO actives
// ---------- HELPERS SEND ---------------------------------------------
~send = { |fxName, amount = 0.3|
var key = fxName.asSymbol;
~fxSends[key] !? { ~fxSends[key].free };
if (amount > 0) {
~fxSends[key] = Synth.before(~masterLim, \fxSend,
[\srcBus, 0, \dstBus, ~fxBuses[key].index, \amount, amount]);
} { ~fxSends.removeAt(key) };
("send " ++ key ++ " = " ++ amount).postln;
};
// Raccourcis
~sendReverb = { |a = 0.3| ~send.(\rev, a) };
~sendDelay = { |a = 0.3| ~send.(\dly, a) };
~sendChorus = { |a = 0.3| ~send.(\chr, a) };
~sendPhaser = { |a = 0.3| ~send.(\phs, a) };
~sendFlanger = { |a = 0.3| ~send.(\flg, a) };
~sendBitcrush = { |a = 0.3| ~send.(\crsh, a) };
~sendTape = { |a = 1.0| ~send.(\tape, a) };
~sendShimmer = { |a = 0.3| ~send.(\shim, a) };
~sendDist = { |a = 0.3| ~send.(\dist, a) };
// Set un parametre de FX (~setFx.(\rev, \room, 0.95))
~setFx = { |fxName, param, value|
~fxFx[fxName.asSymbol] !? { ~fxFx[fxName.asSymbol].set(param, value) };
};
// ---------- AUTOMATION LFO -------------------------------------------
~lfo = { |fxName, param, rateHz = 0.5, lo = 0.0, hi = 1.0, shape = \sin|
var key = (fxName ++ "_" ++ param).asSymbol;
~fxLfos[key] !? { ~fxLfos[key].stop };
~fxLfos[key] = Routine({
var phase = 0;
inf.do { |i|
var x;
x = switch (shape,
\sin, { (phase * 2 * pi).sin * 0.5 + 0.5 },
\tri, { (phase * 4 - 1).abs.linlin(0, 1, 1, 0) },
\saw, { phase },
\sqr, { if (phase < 0.5) {0} {1} },
\rand, { 1.0.rand }
);
~setFx.(fxName, param, lo + (x * (hi - lo)));
phase = (phase + (rateHz * 0.05)).mod(1);
0.05.wait;
};
}).play;
("LFO " ++ fxName ++ "." ++ param ++ " " ++ rateHz ++ "Hz " ++ shape).postln;
};
~lfoStop = { |fxName, param|
var key = (fxName ++ "_" ++ param).asSymbol;
~fxLfos[key] !? { ~fxLfos[key].stop; ~fxLfos.removeAt(key) };
("LFO " ++ fxName ++ "." ++ param ++ " STOPPED").postln;
};
~lfoStopAll = {
~fxLfos.do { |r| r.stop }; ~fxLfos = ();
"all LFOs stopped".postln;
};
// ---------- TAP TEMPO pour le delay ----------------------------------
~tapTempo = ();
~tap = {
var now = SystemClock.seconds;
~tapTempo[\last] !? { |last|
var interval = now - last;
if (interval < 2.0 and: { interval > 0.1 }) {
~setFx.(\dly, \delayTime, interval);
("TAP : " ++ interval.round(0.001) ++ "s = " ++
(60 / interval).round(0.1) ++ " BPM").postln;
};
};
~tapTempo[\last] = now;
};
// ---------- CROSSFADE entre 2 FX -------------------------------------
~crossfadeFxRoutine = ~crossfadeFxRoutine ?? { nil };
~crossfadeFx = { |fromFx, toFx, dur = 4|
~crossfadeFxRoutine !? { ~crossfadeFxRoutine.stop };
~crossfadeFxRoutine = Routine({
var steps = (dur * 20).asInteger;
steps.do { |i|
var x = (i + 1) / steps;
~send.(fromFx, 0.3 * (1 - x));
~send.(toFx, 0.3 * x);
(dur / steps).wait;
};
~crossfadeFxRoutine = nil;
}).play;
("crossfade " ++ fromFx ++ " -> " ++ toFx ++ " sur " ++ dur ++ "s").postln;
};
~crossfadeFxStop = {
~crossfadeFxRoutine !? { ~crossfadeFxRoutine.stop; ~crossfadeFxRoutine = nil };
"crossfade stopped".postln;
};
// ---------- PRESETS FX (combos pret a l'emploi par genre) ------------
//
// REVERT : ~fxPresetReset retiree car cause une fuite/crash explosive
// sclang quand combinee avec switch+sub-calls. La cascade des sends
// reste un probleme connu mais geree par ~ff.(\dry) explicit reset.
~fxPresetReset = nil;
// Dispatch via dictionary pour eviter le bug de switch() qui crashe sclang
// avec 11 cases sur certaines versions SC.
~fxPresetTable = (
dry: {
[\rev,\dly,\chr,\phs,\flg,\crsh,\tape,\shim,\dist]
.do { |k| ~send.(k, 0) };
"preset DRY".postln;
},
dub: {
~sendReverb.(0.5); ~sendDelay.(0.6); ~sendTape.(0.7);
~setFx.(\dly, \delayTime, 0.5);
~setFx.(\dly, \feedback, 0.7);
~setFx.(\rev, \room, 0.92);
"preset DUB (rev hall + delay long + tape)".postln;
},
space: {
~sendReverb.(0.55); ~sendShimmer.(0.4); ~sendChorus.(0.3);
~setFx.(\rev, \room, 0.95);
"preset SPACE (shimmer + reverb + chorus)".postln;
},
trance: {
~sendReverb.(0.35); ~sendDelay.(0.4); ~sendChorus.(0.5);
~setFx.(\dly, \delayTime, 0.375);
~setFx.(\dly, \feedback, 0.5);
"preset TRANCE (rev + delay 1/8 + chorus)".postln;
},
dubstep: {
~sendReverb.(0.2); ~sendDist.(0.3); ~sendBitcrush.(0.2);
~setFx.(\dist, \drive, 4); ~setFx.(\dist, \tone, 0.4);
~setFx.(\crsh, \bits, 6);
"preset DUBSTEP (dist + bitcrush + reverb)".postln;
},
retro: {
~sendBitcrush.(0.4); ~sendTape.(1.0); ~sendChorus.(0.4);
~setFx.(\crsh, \bits, 8); ~setFx.(\crsh, \sampleRate, 12000);
~setFx.(\tape, \drive, 1.8);
"preset RETRO (bitcrush + tape + chorus)".postln;
},
industrial: {
~sendDist.(0.6); ~sendFlanger.(0.4); ~sendBitcrush.(0.3);
~setFx.(\dist, \drive, 5);
~setFx.(\flg, \rate, 0.15); ~setFx.(\flg, \feedback, 0.7);
"preset INDUSTRIAL (dist + flanger + bitcrush)".postln;
},
dream: {
~sendShimmer.(0.6); ~sendReverb.(0.5); ~sendChorus.(0.4);
~setFx.(\shim, \shimmer, 0.7);
"preset DREAM (shimmer + reverb + chorus)".postln;
},
cosmic: {
~sendPhaser.(0.5); ~sendShimmer.(0.4); ~sendReverb.(0.4);
~lfo.(\phs, \rate, 0.05, 0.1, 0.8);
"preset COSMIC (phaser LFO + shimmer + reverb)".postln;
},
drone: {
~sendReverb.(0.7); ~sendShimmer.(0.5);
~setFx.(\rev, \freeze, 1); ~setFx.(\rev, \room, 0.99);
"preset DRONE (reverb FROZEN + shimmer)".postln;
}
);
~fxPreset = { |name|
var fn = ~fxPresetTable[name.asSymbol];
if (fn.notNil) { fn.value } { ("!! preset " ++ name ++ " inconnu").postln };
};
"=== 9 FX BUSES + master limiter en queue ===".postln;
" bus dispos : rev / dly / chr / phs / flg / crsh / tape / shim / dist".postln;
"=== ~send / ~sendReverb / ~setFx / ~lfo / ~tap / ~crossfadeFx / ~fxPreset prets ===".postln;
"=== presets : dry / dub / space / trance / dubstep / retro / industrial / dream / cosmic / drone ===".postln;
)
// =====================================================================
// 2. EXEMPLES / TWEAKS LIVE -- Cmd+Entree sur ligne pendant track
// (tout ce qui est en bas est interactif et NON .load -- garde
// du multi-bloc pour evaluer un effet a la fois)
// =====================================================================
// --- Send levels ----------------------------------------------------
( ~sendReverb.(0.4); ~sendDelay.(0.3); )
( ~sendReverb.(0); ~sendDelay.(0); ~sendChorus.(0); )
( ~sendShimmer.(0.5); ~setFx.(\shim, \shimmer, 0.7); )
( ~sendBitcrush.(0.5); ~setFx.(\crsh, \bits, 4); )
( ~sendDist.(0.5); ~setFx.(\dist, \drive, 4); ~setFx.(\dist, \tone, 0.3); )
( ~sendFlanger.(0.5); ~setFx.(\flg, \rate, 0.15); )
( ~sendPhaser.(0.4); ~setFx.(\phs, \rate, 0.2); ~setFx.(\phs, \depth, 0.8); )
( ~sendTape.(1.0); ~setFx.(\tape, \drive, 2.5); )
// --- Tempo synced delay ---------------------------------------------
( ~setFx.(\dly, \delayTime, 60 / TempoClock.default.tempo / 4); ) // 1/16
( ~setFx.(\dly, \delayTime, 60 / TempoClock.default.tempo / 2); ) // 1/8
( ~setFx.(\dly, \delayTime, 60 / TempoClock.default.tempo); ) // 1/4
( ~setFx.(\dly, \delayTime, 60 / TempoClock.default.tempo * 0.75); ) // 1/4 dotted
// --- Reverb size ----------------------------------------------------
( ~setFx.(\rev, \room, 0.5); ~setFx.(\rev, \damp, 0.6); ) // room
( ~setFx.(\rev, \room, 0.85);~setFx.(\rev, \damp, 0.4); ) // hall
( ~setFx.(\rev, \room, 0.97);~setFx.(\rev, \damp, 0.1); ) // cathedral
// --- LFO automation -------------------------------------------------
( ~lfo.(\dly, \feedback, 0.1, 0.2, 0.85, \sin); ) // feedback qui pulse
( ~lfo.(\rev, \mix, 0.05, 0.1, 0.7, \tri); ) // wash de reverb
( ~lfo.(\crsh, \bits, 0.2, 4, 12, \sqr); ) // glitch lo-fi
( ~lfo.(\phs, \rate, 0.02, 0.1, 1.5, \sin); ) // phaser qui accelere
( ~lfo.(\shim, \shimmer, 0.1, 0.2, 0.8, \rand); ) // shimmer chaos
( ~lfoStopAll.value; )
// --- Tap tempo (au moins 2 fois pour calibrer) ---------------------
( ~tap.value; )
// --- Freeze reverb (drone infini) -----------------------------------
( ~setFx.(\rev, \freeze, 1); ~sendReverb.(0.6); )
( ~setFx.(\rev, \freeze, 0); )
// --- Crossfade entre FX --------------------------------------------
( ~crossfadeFx.(\dly, \rev, 8); ) // delay -> reverb sur 8s
( ~crossfadeFx.(\dist, \shim, 4); ) // dist -> shimmer sur 4s
// --- Presets --------------------------------------------------------
( ~fxPreset.(\dry); ) // tout off
( ~fxPreset.(\dub); ) // dub techno
( ~fxPreset.(\trance); ) // uplifting trance
( ~fxPreset.(\dubstep); ) // dubstep dirty
( ~fxPreset.(\space); ) // space ambient
( ~fxPreset.(\retro); ) // 80s lo-fi
( ~fxPreset.(\industrial); ) // hard distorted
( ~fxPreset.(\dream); ) // dream pop
( ~fxPreset.(\cosmic); ) // psy cosmic + LFO phaser
( ~fxPreset.(\drone); ) // drone freeze infini
// =====================================================================
// 3. STOP FX -- libere tout
// =====================================================================
(
~lfoStopAll.value;
~fxSends.do { |s| s.free }; ~fxSends = ();
~fxFx.do { |s| s.free }; ~fxFx = ();
~fxBuses.do { |b| b.free }; ~fxBuses = ();
"=== ALL FX STOPPED ===".postln;
)