Effects catalog

This tutorial is a collection of effects configurations, organized by no particular principle. More effects will be added as time goes on.

I want to focus on effects that are not particularly difficult to do, but that aren't necessarily obvious. Effects depend heavily on order-of-execution and management of signals on buses. My examples will use MixerChannel for signal routing, but you can easily construct your own node structure to get the same result. The effect design principles are more important than the specific implementation.

Ducking

Ducking is a dynamic processing effect that reduces level of one signal when a different signal crosses a threshold. It's most typically used to reduce the level of background music when a voice-over or other foreground material is present.

First, we need some mixer channels to hold the signals.

s.boot;

(
   // my habit: I always like to have a master
~master = MixerChannel(\master, s, 2, 2);
   // the foreground signal that causes the other signal to be ducked
~ducker = MixerChannel(\ducker, s, 1, 2, outbus: ~master);
   // the background signal
~ducked = MixerChannel(\ducker, s, 1, 2, outbus: ~master);

   // need a bus to hold the control signal
   // WITHOUT panning or level control
~duckbus = Bus.audio(s, 1);
~ducker.newPreSend(~duckbus, 1);
   // presend to bus doesn't fix node order, so:
~ducker.addAsDependantOf(~ducked);

   // play the background signal
~bg = ~ducked.play({
   var   freq, trig, len;
   trig = Impulse.kr(8);
   freq = Demand.kr(trig, 0, Dseq([Dseries(200, 100, 16)], inf));
   len = SinOsc.kr(0.1, 0, 0.04, 0.09);
   SinOsc.ar(freq, 0) * Decay2.kr(trig, 0.005, len);
});
)

Now, the effect. I usually use Instr to write effects. A graphic interface is very helpful when tuning the effect, and Instr provides a convenient way to make GUI controls for all the Instr's inputs.

   // ducking effect -- basic Instr template 
(
Instr(\ducker, { arg myBus, ctlBus, numChannels, thresh, slopeBelow, slopeAbove,
      clampTime, relaxTime, postGain;
   var sig, ctl;
   sig = In.ar(myBus, numChannels);
   ctl = In.ar(ctlBus, numChannels);
   Compander.ar(sig, ctl, thresh, slopeBelow, slopeAbove, clampTime, relaxTime, postGain);
}, [\audiobus, \audiobus, nil, [0, 1, \linear, 0, 0.5], [0.01, 10, \exponential, 0, 1],
   [0.01, 10, \exponential, 0, 1], [0.001, 5, \exponential], [0.001, 5, \exponential],
   [0.1, 10, \exponential, 0, 1]]);
)

When you play the Instr as an effect on the mixer channel, ReplaceOut is used instead of Out so that the processed signal supersedes the original. If you're not using MixerChannel, use my class FxPatch instead of Patch to get the same result.

To tune the effect, do this. Most of the parameters are not specified, meaning that the default controls will be created according to the specs in the Instr definition.

Note: You must give a hard-coded value for numChannels. Otherwise, building the synthdef will fail. However, this value may be different for different instances of the same effect.

~duck = ~ducked.playfx(Instr.at(\ducker), [~ducked.inbus.index, ~duckbus.index, 1]); 
~duck.gui;

This provides a GUI panel where you can adjust the parameters. From the panel, you can also print the patch definition to the post window, ready to paste into your code.

For this exercise, I've already tuned it. Here we have a low threshold with normal gain below the threshold (when the control signal is silent) but practically no gain above the threshold. The net effect is that when the control signal crosses the threshold, the source signal's amplitude is reduced.

When you're using the effect in a piece, you'll probably want to specify all the parameters up front like this:

~duck.free;  // if you did the gui, release the server resources
~duck = ~ducked.playfx(Instr.at(\ducker), [~ducked.inbus.index, ~duckbus.index, 1,
   0.05, 1, 0.01, 0.01, 0.1, 1]); 

Now, play something on the ~ducker channel to hear the effect work.

   // control signal: our old friend
(
~buf = Buffer.read(s, "sounds/a11wlk01.wav");

SynthDef(\playbuf, { |out, bufnum, len|
   Out.ar(out, PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum))
      * EnvGen.kr(Env.linen(0.05, 0.9, 0.05), timeScale:len, doneAction:2)
   )
}).send(s);
)

(
r = Routine({
   {   ~ducker.play(\playbuf, [\bufnum, ~buf.bufnum, \len, ~buf.numFrames/~buf.sampleRate]);
      rrand(5, 10).wait;
   }.loop;
}).play; 
)

When you're satisfied it's working, stop the routine and free up the channels.

r.stop;
[~ducker, ~ducked, ~duckbus, ~master, ~buf].do(_.free);