Integrator filter with delayless feedback path

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

antto wrote:tan(pi*fc/fs) :shock:

tan() grows to +inf then goes from -inf back to +inf .. is that correct?!
or is this meant to work only _with_ oversampling?
Because we are using PI * fc/fs here, the maximum value which gets input to the tan function is PI/2 ... which sadly results in +inf. Using 2x oversampling you can go very safely up to outputRate/2 (which equals PI/4 and tan(PI/4)==1).
Last edited by neotec on Thu Nov 17, 2011 10:12 am, edited 1 time in total.
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post

antto wrote:tan(pi*fc/fs) :shock:

tan() grows to +inf then goes from -inf back to +inf .. is that correct?!
or is this meant to work only _with_ oversampling?
Yeah it's correct. It becomes +inf at pi/2 which is when fc/fs=1/2 ie Nyquist.

What's going on here is that bilinear transform maps the (analog) infinite frequency to the Nyquist frequency, so if we want to have cutoff at Nyquist, we must design an analog prototype that would have cutoff at infinity.

Post

antto wrote:tan() grows to +inf then goes from -inf back to +inf .. is that correct?!
yes, that's correct.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

oh, excuse me, i was plotting the tan() with my curve plotter set to x[0,5] which made it cycle more times
now looking at it correctly, where fc/fs goes to 0.5 as it should, it only reaches +inf at nyquist
2x oversampling would make it only reach 1.0

so uhm..
neotec wrote: transformed into a delayless one pole lowpass:

Code: Select all

pin = in - (f * in + buf) / (1 + f)
out = buf + f * pin
buf = f * pin + out
and 'f' is now:

Code: Select all

f = tan(PI * fc / fs)
this is the 1st order LP filter (aka R-C filter) ?

i understand the idea i think..
for a more complex filter like a ladder
the feedback value comes from the 4th stage (aka output)
to reduce this - oversampling can be used to make this 1 sample delay "smaller" (as duration)
the real analog filter also has such delay but is so small..
an insane factor of oversampling would make the filter work, but will introduce precision problems at low freqs..

the KeepTopology stuff to me looks like an "adaptive" amount of reducing the delay

i still don't understand exactly how it works..
i have a feedback compressor which also suffers from this 1 sample delay, i think i'll be able to make it delayless after examining the one-pole code here ^
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

antto wrote:i still don't understand exactly how it works..
i have a feedback compressor which also suffers from this 1 sample delay, i think i'll be able to make it delayless after examining the one-pole code here ^
The idea is more or less simple, antto:
Consider an analogue lowpass filter build using an integrator and a negative feedback loop:

Code: Select all

in >---[-]--[Integrator]---+--> out
        |                  |
        |                  |
        |--------<---------|
In 'code' form you would write:

Code: Select all

out = integrator(in - lastOut)
This 'lastOut' is the problem because this value was calculated one sample ago. This results in the so called unit delay.

This would be the naive digital implementation (based on the equation above) where you can clearly see our unit delay:

Code: Select all

in >---[-]--[Integrator]---+--> out
        |                  |
        |                  |
        |---<--[z^-1]--<---|
The KeepTopology stuff now predicts the value of 'lastOut' so this value is known before the filter calculates it. This effectively cancels out the unit delay, because it now works exactly as in the analogue domain.

And now we apply this for our Integrator filter:

A bilinear integrator is given by:

Code: Select all

out = buf + f * input
buf = f * input + out
with 'f = tan(PI * fc/fs)'.

Now we make it a lowpass filter:

Code: Select all

// out = integrator(in - lastOut)
iin = input - lastOut
out = buf + f * iin
buf = f * iin + out
But now there's our unit delay again, so we need:

Code: Select all

iin = input - out
but we don't know 'out' yet. The easiest thing we can do now is to create an equation which predicts 'out':

Code: Select all

I. Our input and output calculation
iin = input - out
out = buf + f * iin

II. Replace 'iin' in 'out' equation
out = buf + f * (input - out)

III. Solve for out
out = buf + f * input - f * out
out + f * out = buf + f * input
out * (1 + f) = buf + f * input
out = (buf + f * input) / (1 + f)
Now we have an equation which allows us to 'know' the value of 'out' before it got calculated. Inserting this in our 1 pole LPF leads to:

Code: Select all

iin = input - ((buf + f * input) / (1 + f))
out = buf + f * iin
buf = f * iin + out
see how we use the prediction equation instead of the 'lastOut' here? ... now we have a zero-delay lowpass filter ... we defeated the unit delay.

You can of course drop the 'out' calculation in the LPF above and only use the predicted value:

Code: Select all

out = (buf + f * input) / (1 + f);
buf = f * (input - out) + out
Last edited by neotec on Thu Nov 17, 2011 3:52 pm, edited 2 times in total.
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post

antto wrote: the KeepTopology stuff to me looks like an "adaptive" amount of reducing the delay
Not really. Let's treat state as constant first and we have output=filter(input+k*output) for some value of input and k (and constant state). The output is an unknown, for which we want to find a solution. If the filter is linear it's easy enough to solve this directly.

The iterative version finds an approximate solution by relying on the idea that when we calculate the output with some initial guess as the feedback, we'll get a better estimate than our initial guess. We then repeat this until the last guess and calculated result are close enough to each other, at which point we can consider them equal.

Once we have solved the output we don't need any delay in the feedback loop anymore, and we'll just calculate the filter for the purpose of updating state variables (though you could solve for new values of those as well if you wanted).

edit: seems I was too slow on this :(

Post

Code: Select all

out = (buf + f * input) / (1 + f);
buf = f * (input - out) + out
so, here "buf" is actually the output, right?
"out" is predicted "current" output sample?
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

antto wrote: so, here "buf" is actually the output, right?
"out" is predicted "current" output sample?
No. Buf is the delay of the integrator. Out is the solution for the output (ie "predicted" output).

The whole thing is about calculating the output before actually doing any filtering. We don't need the filtering to get the output. We just need to know the state of the filter and the input. Once we have BOTH input and output, we can figure out how to update the state of the filter (so we can then process the next sample) but it's not necessary to actually calculate the filter just to know what the output will be.

Post

uhm, so this isn't the whole filter, just the "y0" prediction
the whole filter would then be:

Code: Select all

// x0 == input
out = (buf + f * input) / (1 + f);
buf = f * (input - out) + out;
// -----------
y0 = out; // predicted
y0 = x0 + f * (y0 - x0); // 1st order LPF
// filter output is now y0
or am i wrong again? :?
It doesn't matter how it sounds..
..as long as it has BASS and it's LOUD!

irc.libera.chat >>> #kvr

Post

antto wrote:or am i wrong again? :?
These two lines is the whole one pole lowpass filter:

Code: Select all

double lowpass1pole(double input)
{
    out = (buf + f * input) / (1 + f);
    buf = f * (input - out) + out 
    return out;
}
:D
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post

I am somewhat ignorant when it comes to the inner workings of filters, but I am interested in these non-linear stages. Are these implemented between poles as waveshaping functions, and normalized?

Post

the more interesting shaping goes on applied to the delta values inside the "poles" rather than between them.

generally it's implemented as naive waveshapers between, though, yes.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

I'm currently very happy with just shaping the state variables (buffers I called them). Using this approach gives great results (IMHO) while preserving the predictability of the system.
Although wave shaping the state variables still messes around with resonance gain and (depending on the shaper) even cutoff.

Nevertheless using this feedback solving approach I now got a cool 24dB lowpass, 12dB SVF and even a 24dB SVF (using Linkwitz-Riley crossover). All of those are prefectly tuned and self oscillate ... that's all I need :D

The 24dB SVF is a bit overkill because you have 4 feedback paths and I will probably just cascade 2 12dB SVFs to have a 24dB, I mainly did it as a prove of concept.

One thing that still bothers me is that you can't just do 'input - LPF24dB(input)' and have a cool HPF (the frequency response is far from correct then). So far I didn't had success creating a real 24dB HPF using integrator high passes and get the same resonance behaviour as in the low pass version. I just fail on solving the high pass feedback equations^^

Here's the 24dB SVF in pseudo DSP block diagram (just FYI):

Code: Select all

           HP
           |
in-->-(+)--+-[i0]-+-[i1]-+-[i2]-+-[i3]-+->LP
       |          |      |      |      |
      (+)--(*-a)--|      |      |      |
       |                 |      |      |
      (+)-----(*-r)------|--BP  |      |
       |                        |      |
      (+)--------(*-a)----------|      |
       |                               |
      (+)-----------(*-1)--------------|             

i0..3 bilinear integrators (f = tan(PI*fc/fs))
a = 2*sqrt(2)
r = 2 / q
q >= 0.5
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post

neotec wrote:One thing that still bothers me is that you can't just do 'input - LPF24dB(input)' and have a cool HPF (the frequency response is far from correct then).
of course, that should bother anyone. that's because the output phase isn't correct using this method.
neotec wrote:Although wave shaping the state variables still messes around with resonance gain and (depending on the shaper) even cutoff.
that's a simple fact of life. analog filters deal with this by not bothering to deal with it. just retune the scale and offset to get as close to what you want as you can. remaining error is just that, remaining error.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

aciddose wrote:of course, that should bother anyone. that's because the output phase isn't correct using this method.
The phase is correct. If it wouldn't be, the resonance peaks wouldn't occur perfectly at 'fc' (for all 'fc'). I've plotted the 4th order ladder filter, -180° at fc, -90° at fc*0.4 ... works as expected (compared to a bode plot of a 'perfect' 4th order lowpass filter).
... when time becomes a loop ...
---
Intel i7 3770k @3.5GHz, 16GB RAM, Windows 7 / Ubuntu 16.04, Cubase Artist, Reaktor 6, Superior Drummer 3, M-Audio Audiophile 2496, Akai MPK-249, Roland TD-11KV+

Post Reply

Return to “DSP and Plugin Development”