Converting a function for decay times/rates by frequency into a filter for Karplus Strong (waveguide)?

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

Post

I've spent a lot of time building a modal guitar/string synth based on resonant bandpasses where there is one bandpass per mode. It's a nice method because it's very easy to tune each mode's frequency/decay directly. But I'm running into some limitations so I want to try to recreate it as a Karplus Strong waveguide model.

I've got a basic Karplus Strong synth set up for this. The biggest thing I don't understand is how to tune the decays of the partials. In modal synthesis, I would use an equation to calculate all the decays of the each partial by their frequency (ie. set the Q values of reso bandpasses, ie. Where Q = pi * time_to_reach_1/e_amplitude * frequency).

In Karplus Strong, on the other hand, you set the decay times by running the delayed signal through a filter to attenuate the signal by a varying amount by frequency:

Image

I have a function that can graph the Q values (or times for exponential decay to 1/e amplitude) I want for every frequency for each note. Is there any way I can rapidly/efficiently/automatically turn that into a filter function I can use equivalently in Karplus Strong?

Thanks.

Post

So for each partial, you have the the time to decay to 1/e amplitude? Great - let's call that K[f].

First, transform each one to get the amplitude-reduction after one period of the fundamental (T = 1/F). I believe this should look like A[f] = exp(-T/K[f]).

What you actually need inside the Karplus-Strong loop is a filter A such that A[f] matches the above values. Every cycle of the fundamental, this will decay the partials by the same amount as your exponential-decay would.

Now, exactly how you design/implement that filter is its own question. A zero-phase filter (FIR) avoids phase issues, which would detune your partials relative to each other. However, if your decay times aren't smooth, you also need lots of accuracy (so that you can target each partial separately), and zero-phase filters require latency (which by can't be longer than the loop itself). This design (starting from partials) also ends up with a different filter for each note/pitch.

That's the general idea, though.

Post

signalsmith wrote: Wed Nov 06, 2019 1:06 pm So for each partial, you have the the time to decay to 1/e amplitude? Great - let's call that K[f].

First, transform each one to get the amplitude-reduction after one period of the fundamental (T = 1/F). I believe this should look like A[f] = exp(-T/K[f]).

What you actually need inside the Karplus-Strong loop is a filter A such that A[f] matches the above values. Every cycle of the fundamental, this will decay the partials by the same amount as your exponential-decay would.

Now, exactly how you design/implement that filter is its own question. A zero-phase filter (FIR) avoids phase issues, which would detune your partials relative to each other. However, if your decay times aren't smooth, you also need lots of accuracy (so that you can target each partial separately), and zero-phase filters require latency (which by can't be longer than the loop itself). This design (starting from partials) also ends up with a different filter for each note/pitch.

That's the general idea, though.
Thanks man. That's super helpful. You know I've been thinking back and forth on this and these approaches both have pros and cons. With modal synthesis it's so easy to set the frequencies, inharmonicity, and decay times on the fly. You just pay for it with having to run hundreds and hundreds of bandpasses and you can run into frequency limitations with the number of partials you can muster.

On the other hand with Karplus/waveguide you might get more efficiency off the start, and you can do more complex damping/routing because you can damp at multiple points in the signal flow and get outputs from those points, but then you pay for it in the struggle to build on the fly filters for the decays like you're describing and you end up having to cascade allpasses to simulate inharmonicity (which still tends to run into error at high frequencies even with the best published methods unless you're using a lot of them). And you have to deal with delay compensation like you're talking about.

I think there would be no reasonable way to automatically generate an FIR to "best fit" a smooth but complex equation of decay times to 1/e by frequency on the fly, right? So I'd have to generate filters for that in advance (ie. one per note) and save them all. It would be a lot less flexible eg. if changing other parameters of the string simulation.

I think I'm going to try to make it work using my current modal approach just by finding efficiencies where I can. If I get stuck or run out of CPU completely despite my best efforts I'll have to find a way to make this work instead. I appreciate the basic concepts. I'll have to read about FIR filters. I don't know much about making filters. I am just using prebuilt ones from GitHub etc. at the moment.

Post

mikejm wrote: Thu Nov 07, 2019 1:45 am I think there would be no reasonable way to automatically generate an FIR to "best fit" a smooth but complex equation of decay times to 1/e by frequency on the fly, right? So I'd have to generate filters for that in advance (ie. one per note) and save them all. It would be a lot less flexible eg. if changing other parameters of the string simulation.
Least-square fitting is actually reasonably fast, especially if you always sample the spectrum using the same grid, in which case you can compute LDL factorisation for the pseudo-inverse in advance, but since the error can oscillate somewhat (even for a smooth target), it's hard to guarantee that such a filter keeps the feedback loop stable.

Loop latency also quickly becomes an issue. With 16 taps at 44.1kHz, your pitch is already capped at 5.5kHz (ie. 8 samples of latency). I would personally just stick to IIR filters and if you're going to use all-pass filters for inharmonicity anyway, these could be made to serve the dual purpose of damping by moving the zeroes closer to the unit circle (although the design of such a setup is probably even harder).

edit: that said, it might indeed be easier to use modal synthesis

Post

There are ways to do modal synthesis with lots of modes more efficiently. For instance, you can implement modal synthesis on the top of FFT (additive synthesis): instead of calculating each mode at audio rate (which gets slow with lots of modes), you calculate each mode by adding it to the FFT's spectrum. There's a paper where they use a filter bank for this (similar to how MP3 works).

Post

MadBrain wrote: Sat Nov 09, 2019 5:21 am There are ways to do modal synthesis with lots of modes more efficiently. For instance, you can implement modal synthesis on the top of FFT (additive synthesis): instead of calculating each mode at audio rate (which gets slow with lots of modes), you calculate each mode by adding it to the FFT's spectrum. There's a paper where they use a filter bank for this (similar to how MP3 works).
There's two things to watch out for here: first, if your modes don't fall on the spectrum bins exactly, then you need a sinc-scatter to completely avoid time-aliasing at FFT block boundaries (which can be approximated, but this leads to the usual "spectral artefacts"). Also, if you want reasonable modulation rates, you'll need to overlap the blocks heavily, which can quickly lead to a situation where spectral interpolation and brute-force DFT on per-sample basis can actually end up faster.

That said, there's a large search-space of potential solutions and I've only explored a few of them. :)

Post Reply

Return to “DSP and Plugin Development”