Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement an interpolating delay line and all pass filter with the karplus strong algorithm?

Ok, I've implemented the karplus strong algorithm in C. It's a simple algorithm to simulate a plucked string sound. You start with a ring buffer of length n (n = sampling freq/freq you want), pass it through a simple two point average filter y[n] = (x[n] + x[n-1])/2, output it, and then feed it back into the delay line. Rinse and repeat. This smooths out the noise over time to create a natural plucked string sound.

But I noticed that with an integer delay line length, several high pitches could be matched to the same delay length. Also, the integer delay length doesn't allow for smoothly varying pitches (like in vibrato or glissando) I've read several papers on the extensions to the karplus algorithm, and they all talk about using either an interpolated delay line for fractional delay or an all pass filter

http://quod.lib.umich.edu/cgi/p/pod/dod-idx?c=icmc;idno=bbp2372.1997.068
http://www.jaffe.com/Jaffe-Smith-Extensions-CMJ-1983.pdf
http://www.music.mcgill.ca/~gary/courses/projects/618_2009/NickDonaldson/index.html

I've implemented interpolated delay lines before, but only on wave tables where the waveform buffer doesn't change. I just step through the delay at different rates. But what confuses me is that when it comes to the KS algorithm, the papers seem to be talking about actually changing the delay length instead of just the rate I'm stepping through it. The ks algorithm complicates things because I'm supposed to be constantly feeding values back into the delay line.

So how would I go about implementing this? Do I feed the interpolated value back in or what? Do I get rid of the two point averaging low pass filter completely?

And how would the all pass filter work? Am I supposed to replace the 2 point averaging filter with the all pass filter? How would I glide between distant pitches with glissando using the linear interpolation method or allpass filter method?

like image 203
unknown Avatar asked Nov 16 '25 21:11

unknown


1 Answers

I implemented three variations, all have their pros and cons, but none is perfect as I wish it would. Maybe someone has better algorithms and wants to share it here?

In general, I do it like jbarlow describes. I use a ring buffer length of 2^x, where x is "large enough", e.g. 12, that would mean a maximum delay length of 2^12=4096 samples, this is ~12Hz as the lowest base frequency if rendering @ 48kHz. The reason for the power of two is that the modulo can be done by bitwise AND which is way cheaper than an actual modulo.

// init
int writepointer = 0;

// loop:
writepointer = (writepointer+1) & 0xFFF;

The writepointer is kept simple and starts e.g at 0 and increments always by 1 for each output sample.

The read pointer starts with a delta relative to the write pointer, calculated freshly everytime the frequency should change.

// init
float delta = samplingrate/frequency;
int readpointer = (writepointer-(int)delta)-1) & 0xFFF;
float frac = delta-(int)delta;
weight_a = frac;
weight_b = (1.0-frac);

// loop:
readpointer = (readpointer + 1) & 0xFFF;

It also increments by 1, but lies usually more or less between two integer positions. We use the down-rounded position to store in the integer readpointer. The weight between this and the next samples is weight_a and _b.

Variation #1: Ignore the fractional part and tread the (integer) read pointer as-is.

Pros: side-effect-less, perfect delay (no implicit low pass due to the delay, means full control over the frequency response, no artefacts)

Cons: the base frequency is mostly slightly off, quantized to integer positions. This sounds very detuned for high pitch notes and cannot make subtile pitch changes.

Variation #2: Linear interpolate between the readpointer sample and the next sample. Means I read actually two consecutive samples from the ring buffer and sum them up, weighted by weight_a and weight_b respectively.

Pros: perfect base freqeuncy, no artefacts

Cons: The linear interpolation introduces a low-pass filter that may not be desired. Even worse, the low-pass varries depending on the pitch. If the fractional part turns out to be close to 0 or 1, there is only few low-pass filtering going on, while the fractional part being around 0.5 does heavy low pass filtering. That makes some notes of the instrument being brighter than others, and it can never be brighter than this low pass allows. (bad for steel guitar or harpsichord)

Variation #3: Kind of jittering. I read the delay always from an integer position, but keep track of the error I do, means there is a variable that summs the fractional part up. Once it exceeds 1, I substract 1.0 from the error, and read the delay from the second position.

Pros: perfect base frequency, no implicit low pass

Cons: introduces audible artefacts that make it sound low-fi. (like downsampling with nearest neighbour).

Conclusion: None of the variations is satisfying. Either you cannot have the correct pitch, a neutral frequency response or you introduce artefacts.

I read in literature that an all-pass filter should do it better, but isn't the delay line an allpass already? What would be the difference in implementation?

like image 144
Thilo Köhler Avatar answered Nov 18 '25 10:11

Thilo Köhler



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!