Tuning a modified Karplus-Strong loop

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

I've been playing around with KS and ran into a tuning problem that I don't understand.

As I understand it, the classic tuned KS algorithm is to create a delay line with a buffer size based on the integer wavelength, and then add an allPass filter to it's output to compensate for the fractional delay portion.

That works, and when I fill the buffer with a single sine wave (or any other wave), it's perfectly in tune.

But I don't like the way the higher notes decay shorter and I also don't like the sound of the filtered output, thanks to the averaging of the last two output samples.

My idea was to fill the buffer with a single sine wave (or other wave) and NOT average the last two output samples...basically keeping the delayLine intact...and then add my own filtering and adsr to the output for more control.

But when I do that, (removing the output averaging and putting it back into the delayLine), it changes the tuning slightly sharp and I don't know why, or how to compensate for it. I still run it through the allPass, but it doesn't seem to be enough.

Anyone have any ideas what I'm doing wrong here?

Post

A quick answer: if by "averaging last two samples" you mean the usual [.5 .5] FIR (like here https://ccrma.stanford.edu/~jos/pasp/Ka ... rithm.html) then its delay is 0.5 samples and you need to adjust your overall delay time accordingly. If it's something else we need more details.

(E.g. initially you say it like you used only a delay and an allpass but did not mention any loss/dump/decay filters and then it took me to be confused to guess why you have any decay at all).

Post

Thanks for your reply and sorry about the lack of details.

I calculated the following:

Code: Select all

float waveLength = sampleRate / frequency;
int delayInSamples = (int)std::floor(waveLength);  // set the delayLine buffer size

float fracDelay = waveLength - (float)delayInSamples;
float allPassDelay = (1.0f - fracDelay) / (1.0f + fracDelay); // set the allPass delay time
I have nothing else in the loop, just the main delay line, the allPass and the [.5 .5] filter.

What I'm looking to do is remove that [.5 .5] filter so the waveform in the delayLine stays intact, and use filtering external to the loop.

I thought that the allPass made up for the difference between the wavelength actually needed and the integer delayLine. But I guess the [.5 .5] filter adds more delay. What I don't know is how to calculate the difference needed.

Post

I have nothing else in the loop, just the main delay line, the allPass and the [.5 .5] filter.

Then it's like I mentioned above: the [.5 .5] filter introduces 0.5 samples delay. So if you remove this filter, the overall delay of the remaining units should be increased by same 0.5 samples. Though I wonder how you get correct tuning w/o taking this half of the sample into account when the loss filter is there... Either way, are your sure you don't have any +/-1 delay error in you main integer delay line implementation? Such misalign could be easy to made if the [.5 .5] filter was integrated into it. E.g. it may depend on which of these two samples you treat as the actuall delay line output before and now (It's easy to debug by feeding the delay line with a single impulse and examining the output of the whole loop).

Another tip (most likely not related to your current problem though, since it should be an issue regardless of the loss filter being there or not):
fracDelay having the [0...1] range (as it is in your code) is usually not a very good idea - because when this delay value approaches 0, the allpass coefficient is right near 1 and the filter becomes quite numerically unstable (too "resonant" at higher frequencies) also resulting in notably large detuning in the HF range.
Usually it's more convenient to have fracDelay to be in [0.418...1.418] (or similar) range - there the allpass is more stable and a tuning drift in HFs is minimal. This could be very important if you're going to feed the buffer with some wideband waveform/excitation (see for example https://ccrma.stanford.edu/~juhan/pubs/jnam-dafx09.pdf section 2.1).

Post

Jon Dattorro has a good paper on this:
https://ccrma.stanford.edu/~dattorro/Ef ... nPart2.pdf
(his implementation doesn't have the 0.5+0.5 averaging either)

I've implemented the whole thing too... using a [0.5...1.5] range for the fractional delay (which puts the parameter in the -0.333...0.2 range I think) works well for me too. My delay time calculation:

Code: Select all

	int samples  = (int)(time - 1.5f) + 1; // <0 rounds upwards!
	float frac   = time - samples;
	float param  = (frac - 1) / (frac + 1);

Post Reply

Return to “DSP and Plugin Development”