Flanger
The flanger effect has been coming up a decent bit at CCRMA the past few weeks. I helped one student implement a flanger in ChucK, and a recent exercise in Music 420 had us all implementing one in C++. They’re super simple, basically just a delay line and no feedback in the basic version. The result ranges from pleasantly affected (as heard in music of The Beatles, general 1960s psychedelia, and the recent psych-pop revival) to over-the-top, with the classically trite “airplane landing” sound as the height of such excesses.
In DSP parlance, this effect is realized by a feed-forward comb filter with a time-varying delay. The comb filter creates a series of cancellations at a succession of frequencies, and varying the delay smoothly sweeps where these cancellations fall. To implement this in ChucK, first we set up our synthesis patch.
SndBuf buffy => Gain direct => dac;
buffy => Delay d => dac;
me.sourceDir() + "/geetar.wav" => buffy.read;
1::second => d.max;
Our soundfile buffy
is sent down a direct path and a delayed path, which are then mixed back together at the output dac
. (We could use adc
instead of the SndBuf
if we wanted to use live input, such as a microphone or guitar.) We load into the sound buffer geetar.wav
, my go-to soundfile for testing out pop-music effects; you can download it here. The last line of this snippet sets the maximum possible delay we intend to use, allowing Delay
to make some internal optimizations.
Next we create the control signal (aka low-frequency oscillator, or LFO), in this case just a sine wave. We will use the value of this to dynamically modulate the delay component of the flanger.
SinOsc flangerLfo => blackhole;
0.5 => flangerLfo.freq;
Chucking it to blackhole
allows the oscillator to be processed by ChucK’s VM, but unlike chucking to dac
we won’t actually hear it. This is good, because we want the oscillator to continually generate values, but we want to use these values to control something (the flanger delay) instead of produce actual sound.
Finally, lets set up our infinite loop.
2::ms => dur base;
1::ms => dur mod;
while(true)
{
base + flangerLfo.last()*mod => delayed.delay;
1::ms => now;
}
Based on the last value of the sine LFO, we modulate the delay between base + mod
and base - mod
milliseconds (as flangerLfo.last()
produces values between -1 and 1). We update the value every 1 millisecond, which is faster than usual (usually 5-20 ms is ok), but appears to be necessary for a smooth modulation.
Parameters
Commercial flangers of this variety will typically have two parameters, depth and rate. In our patch, rate is controlled by the frequency of flangerLfo
. Anything above 2 starts to sound off to my ears, and above 8 or so its pretty much not a flanger any more.
Depth is controlled by the money line in our while loop: base + flangerLfo.last()*mod
. You can increase mod
to anything up to and including base
; the closer it is to base
the greater the effect of the flanging. You can also increase or decrease base
to change the character of the flange; between 1 and 5 milliseconds produces good results in my opinion, while 10+ milliseconds sounds pretty whack.