The good ol' unit delay ladder filter problem
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- 1640 posts since 4 Dec, 2006
Well, at the risk of revealing my ignorance yet again, I was just doing a thought experiement and thought I would check to see if it makes any sense.
Since the culprit is the 1 sample delay in the feedback, I was wondering if one way to get rid of the delay would be to run two ladder filters instead of one.
First, you run the input through the first filter.
Second, take the output of the first filter and multiply it by whatever feedback coefficient you have.
Third, subtract it from the original input.
Fourth, run the now modified input through the second filter.
Fifth, update the first filter's state to match that of the second filter.
Do this for every sample.
Since the culprit is the 1 sample delay in the feedback, I was wondering if one way to get rid of the delay would be to run two ladder filters instead of one.
First, you run the input through the first filter.
Second, take the output of the first filter and multiply it by whatever feedback coefficient you have.
Third, subtract it from the original input.
Fourth, run the now modified input through the second filter.
Fifth, update the first filter's state to match that of the second filter.
Do this for every sample.
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
This is the same as just doing 1 iteration to solve the loop. Instead of running two separate filters, you can just skip updating the state variables on the first round.Leslie Sanford wrote:Well, at the risk of revealing my ignorance yet again, I was just doing a thought experiement and thought I would check to see if it makes any sense.
Since the culprit is the 1 sample delay in the feedback, I was wondering if one way to get rid of the delay would be to run two ladder filters instead of one.
First, you run the input through the first filter.
Second, take the output of the first filter and multiply it by whatever feedback coefficient you have.
Third, subtract it from the original input.
Fourth, run the now modified input through the second filter.
Fifth, update the first filter's state to match that of the second filter.
Do this for every sample.
-
- KVRist
- Topic Starter
- 66 posts since 29 Jun, 2006 from Bay area, CA
The responses on this thread have been really great and helpful! So, I was wondering what you meant by solving via iteration. I was trying to solve it with a table of gains at different frequencies (when operating on white noise input), which of course did not work since the gain at the low end can become unstable (as opposed to just loud).mystran wrote:Just a round-up of methods for fixing:
1. solve by iteration, works fine, but could just as well oversample
2. solve analytically, doesn't work nicely with embedded non-linearities
2. add an extra zero at -1 into the feedback path (from original paper by A.Huovilainen), works reasonably with oversampling
3. add zeros to the lowpass stages to compensate, either static (the famous Stilson paper) or dynamic (Stilson's thesis has a 1st order fit that works wonders)
4. tune for the 180 with-delay point and solve the required feedback (Robin's paper got math)
Any others?
Wouldn't adding a zero into the lowpass stages result in a loss of bass? I am assuming this is adding a zero at a low frequency.. also, what do you mean by solving by iteration or solving analytically?
EDIT: I added a hard clipper to my feedback path and it seemed to limit the self-oscillation to the point that maybe the rest could be done with a gain table..? Any thoughts on that?
OPL2 is forever
-
- KVRian
- 1275 posts since 9 Jan, 2006
Hi Leslie, it's strange you saying that as reading this thread had sent me down the exact same thought experiment.Leslie Sanford wrote:Well, at the risk of revealing my ignorance yet again, I was just doing a thought experiement and thought I would check to see if it makes any sense.
Since the culprit is the 1 sample delay in the feedback, I was wondering if one way to get rid of the delay would be to run two ladder filters instead of one.
First, you run the input through the first filter.
Second, take the output of the first filter and multiply it by whatever feedback coefficient you have.
Third, subtract it from the original input.
Fourth, run the now modified input through the second filter.
Fifth, update the first filter's state to match that of the second filter.
Do this for every sample.
I have a little free time now, so I might code the thing and see how it goes
EDIT: I'm assuming the steepness of the one-pole low pass filters will change as the filter approaches nyquist - so this may still need oversampling or some kind of compensation
- KVRAF
- 2568 posts since 4 Sep, 2006 from 127.0.0.1
right after my post (where i explained why there is no 4 samples delay) i did the same experiment
i thought: what happens if i first runthe current input sample into the ladder, and then use the output of the 4th stage (which includes the current input) into another copy of the ladder as feedback, this time having the "feedback" to include the current input
well, it wasn't very exciting
and this is like twice the cpu power needed
in my case, i already have 4 oversampling from the oscillator that goes to the filter, using this experimental algo is like the cpu consumption of 8x oversampling in the filter..
tho, i didn't realy tryied to understand why it didn't work here
i thought: what happens if i first runthe current input sample into the ladder, and then use the output of the 4th stage (which includes the current input) into another copy of the ladder as feedback, this time having the "feedback" to include the current input
well, it wasn't very exciting
and this is like twice the cpu power needed
in my case, i already have 4 oversampling from the oscillator that goes to the filter, using this experimental algo is like the cpu consumption of 8x oversampling in the filter..
tho, i didn't realy tryied to understand why it didn't work here
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
- KVRAF
- 2568 posts since 4 Sep, 2006 from 127.0.0.1
another thing i would say, it's my own personal opinion
when i'm doing oversampled stuff like the 4x filter oversampling i mentioned - when time comes for bringing the signal back to the original sampling rate - in my case, i don't do any fancy antialiasing right now
i just throw away samples
BUT, i use the _last_ sample from each 4 (this means the 4th sample)
when i'm doing oversampled stuff like the 4x filter oversampling i mentioned - when time comes for bringing the signal back to the original sampling rate - in my case, i don't do any fancy antialiasing right now
i just throw away samples
BUT, i use the _last_ sample from each 4 (this means the 4th sample)
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
-
- KVRian
- 1275 posts since 9 Jan, 2006
It doesn't sound as though you did quite what Leslie described, or perhaps you left out the details?antto wrote:i thought: what happens if i first runthe current input sample into the ladder, and then use the output of the 4th stage (which includes the current input) into another copy of the ladder as feedback, this time having the "feedback" to include the current input
The first filter should be set to the state of the second filter. Or as mystran wrote run the same filter twice, but only update the state on the second run. Perhaps that's what you actually did?
I had a crack at it, but there were bugs in my code that I didn't have time to fix
I agree having to run the filter twice is a problem especially if you need oversampling for anti-aliasing or whatever.
- KVRAF
- 2568 posts since 4 Sep, 2006 from 127.0.0.1
yes, the first filter had sepparate variables
they are set to the values of the second filter's last values
<copy filter2 values to filter1>
first filter is processed with: input - k * filter2.stage4 as feedback
then, the result is used for the second filter:
input - k * filter1.stage4
output = filter2.stage4
they are set to the values of the second filter's last values
<copy filter2 values to filter1>
first filter is processed with: input - k * filter2.stage4 as feedback
then, the result is used for the second filter:
input - k * filter1.stage4
output = filter2.stage4
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
Zeroes to push the high freqs down a bit. With the pole-only lowpass at high-frequencies you got too much gain anyway.LouisG wrote: Wouldn't adding a zero into the lowpass stages result in a loss of bass? I am assuming this is adding a zero at a low frequency.. also, what do you mean by solving by iteration or solving analytically?
Iteration would be starting with an initial estimate for the feedback (you could use just zero but the one-delay sample is a better guess usually) and refine the estimate by running the filter multiple times without updating the state variables, until the output and the estimated feedback are close enough. In practice one or two iterations get pretty close.
Solving analytically would be to write the zero-feedback filter down as an equation, and solve for the output value.
You might want to use soft rather than hard-clipper. But yeah, a clipper makes self-oscillation possible. You'd do that (or something similar) with any of the compensation methods too (if you want self-oscillation). The point is to try to separate cutoff and resonance, typically such that your self-oscillation feedback is the same (or at least the resonance setting for self-oscillation is the same) no matter what the cutoff. At that point you could use a correction polynomial or a table to tune the cutoff.EDIT: I added a hard clipper to my feedback path and it seemed to limit the self-oscillation to the point that maybe the rest could be done with a gain table..? Any thoughts on that?
Ofcourse nothing prevents you from having one table for cutoff, and then another table (from cutoff -> self-osc gain) to scale the resonance..
-
Leslie Sanford Leslie Sanford https://www.kvraudio.com/forum/memberlist.php?mode=viewprofile&u=131095
- KVRAF
- 1640 posts since 4 Dec, 2006
Well, I tried implementing the approach I described and the result have been less than inspiring.
Here's my unoptimized C++ code:
feedbackLevel and the f coefficient have a range of [0, 1].
Turning the feedbackLevel up all the way does give me resonance at low frequencies. However, as I raise the cutoff frequency, the overall amplitude gets lower and lower. When the f coefficient is around 0.7 with the feedbackLevel turned up pretty high, the resonance stops completely. What I hear is the original sound at normal amplitude. As I increase the f coefficient beyond that, the overal all amplitude gets greater and greater with no resonance.
Here's my unoptimized C++ code:
Code: Select all
void LadderFilter::Process(float *input, float *output, int count)
{
float in;
float t1, t2, t3, t4;
for(int i = 0; i < count; i++)
{
t1 = f * *input + (1.0f - f) * out1;
t2 = f * t1 + (1.0f - f) * out2;
t3 = f * t2 + (1.0f - f) * out3;
t4 = f * t3 + (1.0f - f) * out4;
in = *input - (feedbackLevel * 4.0f * t4);
out1 = f * in + (1.0f - f) * out1;
out2 = f * out1 + (1.0f - f) * out2;
out3 = f * out2 + (1.0f - f) * out3;
out4 = f * out3 + (1.0f - f) * out4;
*output = out4;
input++;
output++;
}
}
Turning the feedbackLevel up all the way does give me resonance at low frequencies. However, as I raise the cutoff frequency, the overall amplitude gets lower and lower. When the f coefficient is around 0.7 with the feedbackLevel turned up pretty high, the resonance stops completely. What I hear is the original sound at normal amplitude. As I increase the f coefficient beyond that, the overal all amplitude gets greater and greater with no resonance.
- KVRAF
- 2568 posts since 4 Sep, 2006 from 127.0.0.1
in my case, i used (equivalently) :
t1 = f * (*input - feedbackLevel * 4.0f * out4) + (1.0f - f) * out1;
t1 = f * (*input - feedbackLevel * 4.0f * out4) + (1.0f - f) * out1;
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
..as long as it has BASS and it's LOUD!
irc.libera.chat >>> #kvr
- KVRian
- 954 posts since 25 May, 2010 from Hessisch Uganda, Germany
Hi there!
"Preserving The Structure Of The Moog VCF In The Digital Domain"
Cheers Björn
There's a brilliant paper by Federico Fontana on how to "crack" the delay-free loop (based on the original idea by Aki Härmä; see references):LouisG wrote:[...]I couldn't find exactly the info I'm looking for regarding the unit delay in a moog-alike ladder filter.[...]
"Preserving The Structure Of The Moog VCF In The Digital Domain"
Cheers Björn
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
You are running the first pass with no feedback (ie initial guess of zero). While this theoretically fine, you'll need a whole lot more iterations (especially so at high frequencies) compared to if you take the output from the last sample to use as an initial estimate. Ie..Leslie Sanford wrote:Well, I tried implementing the approach I described and the result have been less than inspiring.
Here's my unoptimized C++ code:
[...]
feedbackLevel and the f coefficient have a range of [0, 1].
Turning the feedbackLevel up all the way does give me resonance at low frequencies. However, as I raise the cutoff frequency, the overall amplitude gets lower and lower. When the f coefficient is around 0.7 with the feedbackLevel turned up pretty high, the resonance stops completely. What I hear is the original sound at normal amplitude. As I increase the f coefficient beyond that, the overal all amplitude gets greater and greater with no resonance.
Code: Select all
void LadderFilter::Process(float *input, float *output, int count)
{
float in;
float t1, t2, t3, t4;
for(int i = 0; i < count; i++)
{
// start with feedback from last sample
in = *input - (feedbackLevel * 4.0f * out4);
t1 = f * in + (1.0f - f) * out1;
t2 = f * t1 + (1.0f - f) * out2;
t3 = f * t2 + (1.0f - f) * out3;
t4 = f * t3 + (1.0f - f) * out4;
// then use the new estimate as feedback
in = *input - (feedbackLevel * 4.0f * t4);
out1 = f * in + (1.0f - f) * out1;
out2 = f * out1 + (1.0f - f) * out2;
out3 = f * out2 + (1.0f - f) * out3;
out4 = f * out3 + (1.0f - f) * out4;
*output = out4;
input++;
output++;
}
}
-
- KVRAF
- 8389 posts since 11 Apr, 2003 from back on the hillside again - but now with a garden!
I've heard of 4pole ladders using the 3rd pole for feedback before, I think one or two on musicdsp do that.
Leslie, as I read it, your code is kinda making a hybrid 8pole filter & using the 4th pole output for feedback. My feeling is that whatever was starting to happen in terms of resonance by the end of the 4th pole, is then subdued by another 24dB/Oct of ladder. You are effectively filtering off the resonance. I could be wrong.
mystran's adjustment looks promising, but I'm not convinced it will actually answer the problem since there is the best guess. We need to remember that this is a discrete LTI system, and feedback automatically implies delay due to physics. Compensation via gain adjustment is probably the way to do it. I'd be happy to be shown as wrong tho.
Leslie, as I read it, your code is kinda making a hybrid 8pole filter & using the 4th pole output for feedback. My feeling is that whatever was starting to happen in terms of resonance by the end of the 4th pole, is then subdued by another 24dB/Oct of ladder. You are effectively filtering off the resonance. I could be wrong.
mystran's adjustment looks promising, but I'm not convinced it will actually answer the problem since there is the best guess. We need to remember that this is a discrete LTI system, and feedback automatically implies delay due to physics. Compensation via gain adjustment is probably the way to do it. I'd be happy to be shown as wrong tho.
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
It's not 8 pole (only 4 state variables too), it's just evaluated twice.duncanparsons wrote:I've heard of 4pole ladders using the 3rd pole for feedback before, I think one or two on musicdsp do that.
Leslie, as I read it, your code is kinda making a hybrid 8pole filter & using the 4th pole output for feedback.
It's really just iteration to solve the feedback (with a fixed iteration count of one). The problem is single iteration is too little if you start with 0 as the initial estimate. Using the previous sample value is a better initial guess, and gets you closer with a single iteration.

