Lately I have been trying to design different delay fxs which allow me to sweep the delay time without the usual artifacts of digital delays. I was happy that someone in the forum (forgot who) mentioned XFadeDelay from the wslib which allows me to create a nice sounding delay without these artifacts.
However, what I really would love to do is a delay that has the ‘doppler effect’ of an analog tape or a bucket brigade chip. Here is an excellent an extensive article by Julius Smith describing how a bucket brigade device is designed and tips to how you can emulate it in the DSP world: https://colinraffel.com/publications/dafx2010practical.pdf
I don’t understand every technical aspect of the article but I think I got an overall understanding of the issues involved.
Below is code demonstration my different attempts to create a delay that behaves similar to a bucket brigade chip. The biggest issue is aliasing which is also a major concern when designing bucket brigade devices. Any tips to alternate or improved ways of achieving this will be much appreciated. Maybe someone already designed a bucket brigade type ugen?
(
s.waitForBoot{
~sawSeq = {
var t = Impulse.ar(LFNoise0.ar(2).range(1, 2).round);
var f = Demand.ar(t, 0, Drand([40, 50, 52, 47] + 12, inf));
LFSaw.ar(f.midicps) * 0.2 * Env.perc(0.02, 0.1).ar(0, t)!2;
};
Ndef(\sig, { Out.ar(0, ~sawSeq.()) });
Ndef(\dly).clear;
Ndef(\dly).edit;
Ndef(\dly, {
var in = In.ar(0, 2);
var local = LocalIn.ar(2);
local = XFadeDelay.ar(local, 2, \delaytime.kr(0.2) ); // works well, but no 'doppler effect'
// local = DelayN.ar(local, 2, \time.kr(0.2) ); // this produces unwanted artifacts when sweeping time, same as CombC
local = XFade2.ar(in, local, 0);
LocalOut.ar(local);
local = local * \feed.kr(0.8);
ReplaceOut.ar(0, XFade2.ar(in, local, \mix.kr(0.5)* 2 - 1))
});
Spec.add(\delaytime, [0.1, 1.2]);
Spec.add(\feed, [0, 1]);
Spec.add(\mix, [0, 1]);
FreqScope.new;
}
)
(
// test with sinewave - minimal aliasing when sweeping delay time due to the anti aliasing inside the XFadeDelay ugen - no 'doppler effect'
Ndef(\sig, { Out.ar(0, SinOsc.ar(440, 0, 0.1)!2) });
)
(
// Trying to emulate the basics of a Bucket Brigade Chip. This gives me the desired doppler effect when sweeping the delaytime but for delaytimes != bufDur very strong aliasing. This approach seems to work best when rate => 1, so bufDur has to be set to the shortest delay time, long delays will downsample the signal. The effect of downsampling is cool to some degree, but at a point the degrading of the input is 'too much', try setting the mix to 1 and slowly sweep the delay time from 0.1 upwards
Ndef(\sig, { Out.ar(0, ~sawSeq.()) });
~dly = {|bufDur = 0.1|
var sr = SampleRate.ir;
var in = In.ar(0, 2);
var buf = LocalBuf(bufDur * sr, 2).clear;
var rate = K2A.ar(bufDur / \delaytime.kr(0.3));
var phasor = Phasor.ar(0, rate, 0, bufDur * sr);
var bufRd = BufRd.ar(2, buf, phasor);
var recSig = bufRd * \feed.kr(0.5) + in;
var rec = BufWr.ar(recSig, buf, phasor);
var sig = XFade2.ar(in!2, bufRd!2, \mix.kr(0.5) * 2 - 1);
sig
};
Ndef(\dly, { ReplaceOut.ar(0, ~dly.(bufDur:0.1))});
)
( // below the buffer duration is a half second, so for a delay time of 0.1 the signal is 'upsampled' by a factor 5. This helps the slower delay times, but now sweeping past delaytime = 0.5 creates more obvious aliasing
Ndef(\dly, { ReplaceOut.ar(0, ~dly.(bufDur:0.5))});
)
( // The artifacts of aliasing can most easily be visualized using a sinewave as input. When setting the delaytime = bufDur, 0.5 in this case, there is no aliasing (resolution of fader in gui is not fine enough, enter value manually in gui) since the resulting rate of the phasor driving BufRd and BufWr is 1. For all other values the aliasing becomes more or less obvious
Ndef(\sig, { Out.ar(0, SinOsc.ar(440, 0, 0.1)!2) });
)
(
// With filtering. Here both using a IIR filter on the BufRd and a LFP on the BufRd with the same cutoff freq.
Ndef(\sig, { Out.ar(0, ~sawSeq.()) });
Ndef(\dly, {
var sr = SampleRate.ir;
var in = In.ar(0, 2);
var bufDur = 0.3;
var buf = LocalBuf(bufDur * sr, 2).clear;
var rate = K2A.ar(bufDur / \delaytime.kr(0.3));
var phasor = Phasor.ar(0, rate, 0, bufDur * sr);
var bufRd = BufRd.ar(2, buf, phasor);
var recSig = bufRd * \feed.kr(0.5) + in;
var rec = BufWr.ar(IIRFilter.ar(recSig, \lpfreq.kr(4000)), buf, phasor);
var sig = XFade2.ar(in!2, LPF.ar(bufRd, \lpfreq.kr(4000))!2, \mix.kr(0.5) * 2 - 1);
ReplaceOut.ar(0, sig)
});
Ndef(\dly).edit;
Spec.add(\lpfreq, [20, 10000]);
Ndef(\dly).set(\delaytime, 0.3);
)
( // Again with a sine wave
Ndef(\sig, { Out.ar(0, SinOsc.ar(440, 0, 0.1)!2) });
)
(
Ndef(\dly).clear;
Ndef(\sig).clear;
)