# How to prevent "zipping" effect on a modulated fractional audio delay line? (Flanger)

I am implementing a Flanger using a fractional delay line. I am modulating the length of the delay line using a sin function. The delay line already uses linear interpolation to compute the delay output for the fractional sample.

When the delay length is decreasing (and pitch is increasing), the modulated length sounds just about perfect, with no artifacts. However, when the delay length is increasing (and pitch is decreasing), I am getting a kind of "zipper" effect, with intermittent clicks, that one would expect from a non-interpolated delay line, as if sharp edges are being produced.

My guess is that I need to do additional interpolation beyond the simple fractional interpolation, but I have some questions.

First, why would I only hear artifacts during the period where my modulated delay length is decreasing?

Second, what is the best method to reduce these artifacts? My first instinct is an all-pass filter, but is it typical to use an all-pass filter as well as an interpolation over a fractional delay length? I just want to make sure I'm getting it right, and not wasting unnecessary cpu cycles :).

Some code will help. In this code, config holds all delay line info, and delaySamples is the current working length of the delay line. There are two read pointers, two write pointers, and two feedback values - one for each channel. Thanks for taking a look!

void delayconfig_setDelayTime(DelayConfig *config, float delay) {
config->delayTime = delay > 0 ? (delay <= 1 ? delay : 1) : 0;
if (config->delayTime < 0.0001) config->delayTime = 0.0001;
config->delaySamples = config->delayTime*SAMPLE_RATE;
int channel;
float *rp, *wp;
for (channel = 0; channel < 2; channel++) {
rp = &(config->rp[channel]);
wp = &(config->wp[channel]);
*rp = *wp - config->delaySamples;
while (*rp < config->delaySamples) {
*rp += config->delaySamples;
}
}
}

float interp(float *delayBuffer, float delaySamples, float position) {
position = fmod(position, delaySamples);
int prev = floorf(position);
int next = fmod(ceilf(position), delaySamples);
float frac = position - prev;
return delayBuffer[prev] + frac*(delayBuffer[next] - delayBuffer[prev]);
}

void delay_process(void *p, float **buffers, int size) {
DelayConfig *config = (DelayConfig *)p;
float out;
int channel, samp;
float *wp, *rp;
for (channel = 0; channel < 2; channel++) {
rp = &(config->rp[channel]);
wp = &(config->wp[channel]);
for (samp = 0; samp < size; samp++) {
// here is where I'm changing my delay length.  The values are tentative while I'm figuring this out, and aren't really improtant.
// The important thing is thing is that it's a sin function
delayconfig_setDelayTime(config, 0.0061f + 0.006f*sin(M_PI*config->count++*INV_SAMPLE_RATE));
if (*rp >= config->delaySamples) (*rp) -= config->delaySamples;
if (*wp >= config->delaySamples) (*wp) -= config->delaySamples;
float interpolated = interp(config->delayBuffer[channel], config->delaySamples, (*rp)++);
int wpi = floorf((*wp)++);
out = interpolated*config->wet + buffers[channel][samp]*(1 - config->wet);
if (out > 1) out = 1;
config->delayBuffer[channel][wpi] = buffers[channel][samp] + out*config->feedback[channel];
buffers[channel][samp] = out;
}
}
}

• Sounds like a bug, not an algorithm problem. If it works upwards, it should do the other way just fine as well. It's potentially in the way you calculate the split between integer and fractional delay. Code is hard to read though, very few comments and unclear types. Jul 17, 2012 at 12:39
• Can you show a graph or wav file of the artifacts? Jul 17, 2012 at 22:55

It's a bug.

M_PI*config->count++

You are processing 2 channels, but incrementing your modulation in the inner loop. So at the end of processing 1 channel, you continue to increment the modulation as you process the second channel.

c1 [ 1,2,3,4]         [9,10,11,12]...
c2           [5,6,7,8]


The modulation has a discontinuity, i.e in my simplifies example - c1 modulation jumps from '4' to '9'. resulting in zipper noise. Hope that makes sense.

• Thanks! I ended up reworking this quite a bit, and was able to get it sounding nice, but haven't gotten around to posting an answer. This was absolutely the main reason. Aug 16, 2012 at 3:13

Some artifacts are always going to be present whenever you deviate from strict LTI (linear time invariant) systems. With time varying systems you will see FM sideband energy dispersions, which present as non removable aliasing noise. This is sadly the case even assuming bug free code, band limited interpolation and a maximally smooth modulator signal (stationary sinusoid).

Of course it helps if you keep the modulator as smooth, as low frequency and as narrow band as possible. For a standard flanger modulated by a sinusoid LFO, the FM noise might not actually be a problem, but if you want to explore a wider range of modulating options you might consider oversampling to combat aliasing. Or just accept it as part of the sound.