I was messing around a bit tonight to try to tip off the signal chain of Ableton’s new Roar saturator, because it seems super simple but really versatile and nice sounding.
I didn’t make a lot of progress, but I thought I’d post it as a bit of a group project if anyone wants to make amendments, suggest changes, or add things on. Leave comments or new implementations as comments and i’ll amend the original!
My intention isn’t to EXACTLY copy the sound or implementation, only to copy the overall signal chain and approach, in a straightforward and relatively clear way. I got only as far as most of one module, probably the modulation matrix stuff and the multi-module stuff is best done in several synthdefs versus in just one.
You’ll need this extension, as well as sc3-plugins to run - but everything else is stock.
(
SynthDef(\roarTest, {
var sig, sigDry, sigWet, sigShaped, feed;
var tone, toneFreq, toneComp, toneAmpLo, toneAmpHi, drive, bias, amount;
var filterFunc, filterFreq, filterLoHi, filterBP, filterRes, filterBW, filterPre;
var feedAmt, feedFreq, feedBW, feedDelay, feedGate;
drive = \drive.kr(spec:ControlSpec( 0, 48, default: 14 )).dbamp;
tone = \tone.kr(spec:ControlSpec( -1, 1, default:-0.4 ));
toneFreq = \toneFreq.kr(spec:ControlSpec( 20, 20000, default: 5520 ));
toneComp = \toneComp.kr(spec:ControlSpec( 0, 1, default: 1 ));
amount = \amount.kr(spec:ControlSpec( 0, 1, default: 0.8 ));
bias = \bias.kr(spec:ControlSpec( -1, 1, default: 0.0 ));
filterFreq = \filterFreq.kr(spec:ControlSpec( 20, 20000, default: 12800 ));
filterLoHi = \filterLoHi.kr(spec:ControlSpec( -1, 1, default: -1 ));
filterBP = \filterBP.kr(spec:ControlSpec( 0, 1, default: 0.2 ));
filterRes = \filterRes.kr(spec:ControlSpec( 0, 1, default: 0.3 ));
filterBW = \filterBW.kr(spec:ControlSpec( 0, 4, default: 0.5 ));
filterPre = \filterPre.kr(spec:ControlSpec( 0, 1, default: 1 ));
feedAmt = \feedAmt.kr(spec:ControlSpec( -90, 12, default: 14 )).dbamp;
feedFreq = \feedFreq.kr(spec:ControlSpec( 20, 20000, default: 80 ));
feedBW = \feedBW.kr(spec:ControlSpec( 0, 4, default: 0.1 ));
feedDelay = \feedDelay.kr(spec:ControlSpec( 0, 4, default: 1/6 )) - ControlDur.ir;
feedGate = \feedGate.kr(spec:ControlSpec( 0.02, 0.3, default: 0.1 ));
toneAmpLo = tone.lincurve(-1.0, 1.0, 2.0, 0.0, -0);
toneAmpHi = tone.lincurve(-1.0, 1.0, 0.0, 2.0, 0);
// sig = \in.ar([0, 0]);
// sig = SAMP("/Users/Shared/_sounds/photek/full/photek1.wav")[0].ar(loop:1);
sig = PlayBuf.ar(1, \buffer.ir, loop:1);
// WET TONE
sigWet = sig
|> BHiShelf.ar(_, toneFreq, 1, toneAmpHi.ampdb)
|> BLowShelf.ar(_, toneFreq, 1, toneAmpLo.ampdb);
// DRY TONE
sigDry = sig
|> BHiShelf.ar(_, toneFreq, 1, 0)
|> BLowShelf.ar(_, toneFreq, 1, 0);
// Dry should be silent if tone = 0, else it should "make up"
// the attenuation from the shelf filters? Use no-op filters on the dry
// signal so delay from filter matches wet signal?
sigDry = (sigDry - sigWet);
// FEEDBACK
feed = LocalIn.ar(2);
feed = feed
*> feedAmt
|> BBandPass.ar(_, feedFreq, feedBW)
|> DelayC.ar(_, 4, feedDelay)
|> LeakDC.ar(_)
*> Amplitude.ar(sig, 0.01, feedGate);
// FILTER
// filterLoHi blends between a lowpass and highpass
// filterBP blends between the lo-hi signal and a bandpass
filterFunc = {
|sig|
blend(
blend(
BLowPass.ar(sig, filterFreq, filterRes),
BHiPass.ar(sig, filterFreq, filterRes),
filterLoHi.linlin(-1, 1, 0, 1)
),
BBandPass.ar(sig, filterFreq, filterBW),
filterBP
)
};
// SHAPE: PRE-FILTER
// filterPre blends between filtering befor the shape stage, or after
sigShaped = sigWet + feed;
sigShaped = blend(sigShaped, filterFunc.(sigShaped), filterPre);
// SHAPE
sigShaped = sigShaped
*> drive
+> bias
// |> tanh(_);
|> SoftClipAmp8.ar(_, drive);
// |> SmoothFoldQ.ar(_, -1, 1, 0.8, 0.5);
// SHAPE: POST-FILTER
sigShaped = blend(sigShaped, filterFunc.(sigShaped), 1 - filterPre);
LocalOut.ar(sigShaped);
sigWet = blend(sigWet, sigShaped, amount);
sig = sigWet + (toneComp * sigDry);
Out.ar(\out.kr(0), \amp.kr(1) * sig * [1, 1]);
}).addReplace;
)
(
// \roarTest.asSynthDesc.controls.collect({
// |c|
// "%%,%".format(
// $\\,
// c.name,
// c.defaultValue
// .round(0.01)
// .asString
// .padLeft(20 - c.name.asString.size.postln)
// )
// }).join(",\n");
Pdef(\roadTestControls, Pbind(
\drive, 6.0,
\tone, -0.2,
\toneFreq, 820.0,
\toneComp, 0.7,
\amount, 0.4,
\bias, 0.1,
\filterFreq, 5800.0,
\filterLoHi, -0.7,
\filterBP, 0.5,
\filterRes, 0.7,
\filterBW, 0.8,
\filterPre, 0.0,
\feedAmt, 5.0,
\feedFreq, 120.0,
\feedBW, 2,
\feedDelay, 1/60,
\feedGate, 0.06,
\buffer, 0.0,
));
Pdef(\roadTest, Pmono(
\roarTest,
\dur, 1/4,
\buffer, ~buffer = ~buffer ?? { Buffer.read(Server.default, "/Users/Shared/_sounds/photek/full/photek3.wav") },
\amp, -3.dbamp,
) <> Pdef(\roadTestControls)).play
)