You are probably already using Zero Delay Feedback filters, so let your customers know!

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

Post

AdmiralQuality wrote:As someone who's NOT using [the unspeakable term for whatever you want to call it], how about some of you geniuses share the algo with us, considering how it's all common knowledge and everything.

A few lines of code or pseudocode, please. Unpronounceable math symbols and opaque terminology not required or helpful. Nor is introduction of non-linearities. Just a nice straight linear IIR with negative feedback. For us simpletons. Please?
Just straight implementation from Vadims' book.

Code: Select all

inline static float tptlp(float& state,float inp,float cutoff,float sampleRate)
{
        //BLT frequency wrap compensation
        cutoff = tan(cutoff * (juce::float_Pi) / (sampleRate)) ;

        //Linear prediction step
        double hp = (inp - state) * cutoff / (1 + cutoff);

        //State update step
        double lp = hp + state;
        state = lp + hp;
        return lp;
};
Corrected code.
Last edited by 2DaT on Sun Dec 01, 2013 9:03 am, edited 1 time in total.

Post

2DaT wrote:
AdmiralQuality wrote:As someone who's NOT using [the unspeakable term for whatever you want to call it], how about some of you geniuses share the algo with us, considering how it's all common knowledge and everything.

A few lines of code or pseudocode, please. Unpronounceable math symbols and opaque terminology not required or helpful. Nor is introduction of non-linearities. Just a nice straight linear IIR with negative feedback. For us simpletons. Please?
Just straight implementation from Vadims' book.

Code: Select all

inline static float tptlp(float& state,float inp,float cutoff,float sampleRate)
{
        //BLT frequency wrap compensation
        cutoff = tan(cutoff / (sampleRate)) * (juce::float_Pi);

        //Linear prediction step
        double hp = (inp - state) * cutoff / (1 + cutoff);

        //State update step
        double res = hp + state;
        state = res + hp;
        return res;
};
And how does that not introduce a one sample delay in the resonance? (For that matter, why are we RETURNING resonance? Shouldn't the output be state?)

EDIT: I guess I see what's happening now. We treat the resonance as if it's the output. But I still don't see how the delay is eliminated.

Guess I'll have to try it and get back to you...

EDIT2: Oh, wait, this is just for prediction? So how's that fit into the overall algo? Again, requesting the For Dummies version here.

Post

Well in the linear case the feed back is resolved so I think prediction is a misleading term, possibly.

Also remember that in the case of instantaneous feedback that, in this case, the feedback is the output.

2DaT's version is a multi mode version. Here is how to derive a simple lowpass:

The basic building block is the bilinear integrator:

Code: Select all

g = tan(PI*frequency/sample rate);

output = input * g + state;
state = input * g + output;
A lowpass based on a bilinear integrator is as follows:

Code: Select all

output = bilinearIntegrator(input-output)
Note that output refers to y[n], so we have y[n] on both sides of the equation. This is the instantaneous feedback.

The output of the bilinear integrator is

Code: Select all

input * g + state
so we can re-write our lowpass formula:

Code: Select all

output = (input-output)*g + state;
//we need to rearrange the equation so we only have output on the left
output = g*input-g*output + state;
output + g*output = g*input + state;
output(1+g) = g*input + state;
output = (g*input+state)/(1+g);
We have now resolved the delayless loop and obtained the output we just need to update the state of the integrator.

Code: Select all

state = (input-output)*g + output;
So leaving out the derivation you get:

Code: Select all

g = tan(PI*frequency/sample rate);
output = (g*input+state)/(1+g);
state = (input-output)*g + output;
Last edited by matt42 on Sun Dec 01, 2013 8:43 am, edited 1 time in total.

Post

Thanks matt42, I'll give it a try and let you know how it goes.

Post

AdmiralQuality wrote:
2DaT wrote:
AdmiralQuality wrote:As someone who's NOT using [the unspeakable term for whatever you want to call it], how about some of you geniuses share the algo with us, considering how it's all common knowledge and everything.

A few lines of code or pseudocode, please. Unpronounceable math symbols and opaque terminology not required or helpful. Nor is introduction of non-linearities. Just a nice straight linear IIR with negative feedback. For us simpletons. Please?
Just straight implementation from Vadims' book.
And how does that not introduce a one sample delay in the resonance? (For that matter, why are we RETURNING resonance? Shouldn't the output be state?)

EDIT: I guess I see what's happening now. We treat the resonance as if it's the output. But I still don't see how the delay is eliminated.

Guess I'll have to try it and get back to you...

EDIT2: Oh, wait, this is just for prediction? So how's that fit into the overall algo? Again, requesting the For Dummies version here.
Not resonance but result.Its one pole filter so no resonance here :)
The algorthm contains 2 steps : prediction and state update.
The high pass filter output is predicted by solving "zero delay" equation,
after that we update the states accordingly by tpt scheme and get that low pass output.

P.S Corrected code to more readable state, also corrected misplace of brackets of BLT prewrap.
Last edited by 2DaT on Sun Dec 01, 2013 9:09 am, edited 1 time in total.

Post

2DaT wrote: Not resonance but result.Its one pole filter so no resonance here :)
Oh, duh! (You can't be too verbose in variable naming, folks!)

Need resonance. (Let me go out on a limb here with the crazy suggestion that I'd like to build a 4 pole LPF with resonance.)

Post

AdmiralQuality wrote:As someone who's NOT using [the unspeakable term for whatever you want to call it], how about some of you geniuses share the algo with us, considering how it's all common knowledge and everything.

A few lines of code or pseudocode, please. Unpronounceable math symbols and opaque terminology not required or helpful. Nor is introduction of non-linearities. Just a nice straight linear IIR with negative feedback. For us simpletons. Please?
I've posted loads of these worked examples of how to apply basic circuit math that was first used in the 1970s to solve these systems of equations.

Here is an example of how passive and active idealised one pole low pass filters are identical, and showing how recently espoused methods (TPT) are equivalent to the long existing ones:

http://cytomic.com/files/dsp/OnePoleLinearLowPass.pdf

Here are the SVF and Sallen Key structures solved. The SVF one also shows how to manually solve these systems of linear equations (even non-linear ones get linearised) and also how to solve for the bell and shelving coefficients to match the RBJ shapes:

http://cytomic.com/files/dsp/SvfLinearT ... mised2.pdf
http://cytomic.com/files/dsp/SkfLinearT ... mised2.pdf
Last edited by andy-cytomic on Sun Dec 01, 2013 12:59 pm, edited 1 time in total.
The Glue, The Drop, The Scream - www.cytomic.com

Post

andy-cytomic wrote:
AdmiralQuality wrote:As someone who's NOT using [the unspeakable term for whatever you want to call it], how about some of you geniuses share the algo with us, considering how it's all common knowledge and everything.

A few lines of code or pseudocode, please. Unpronounceable math symbols and opaque terminology not required or helpful. Nor is introduction of non-linearities. Just a nice straight linear IIR with negative feedback. For us simpletons. Please?
I've posted loads of these worked examples of how to apply basic circuit math that was first used in the 1970s to solve these systems of equations.

Here is an example of how passive and active idealised one pole low pass filters are identical, and showing how recently espoused up methods are equivalent to the long existing ones:

http://cytomic.com/files/dsp/OnePoleLinearLowPass.pdf

Here are the SVF and Sallen Key structures solved. The SVF one also shows how to manually solve these systems of linear equations (even non-linear ones get linearised) and also how to solve for the bell and shelving coefficients to match the RBJ shapes:

http://cytomic.com/files/dsp/SvfLinearT ... mised2.pdf
http://cytomic.com/files/dsp/SkfLinearT ... mised2.pdf
Thanks, those were exactly what I didn't want. ;)

Post

AdmiralQuality wrote:Thanks, those were exactly what I didn't want. ;)
Hey Admiral, will Poly-Ana 2 feature a 0dbfbf, I see you're already close to getting the code for it? How's the new look coming along? Will we see a release soon?


Great thread everyone and please go on. This conversation is very revealing.

Post

Code: Select all

// copyright (c) 2013 aciddose
// made available under http://creativecommons.org/licenses/by-nc/3.0/
// with the following additional terms:
// attribution must include: inspired by code provided by mystran

float lpvs(const float in, const float cutoff, const float resonance, const float sample_rate, float *buffer, const int stages, const int oversample)
{
	const float f = tanf(futil::pi * cutoff / (sample_rate * float(oversample)));
	const float fb = resonance * powf(2.0f, apow(float(stages), 2.0f));
	const float fb_gain_correction = (1.0f + fb);
	const float g = 1.0f / (1.0f + f);
	const float fg = f*g;
	for (int os = 0; os < oversample; os++) {
		float predicted = in * fg;
		float predicted_fg = 1.0f;
		for (int i = 0; i < stages - 1; i++) {
			predicted += buffer[i] * g;
			predicted *= fg;
			predicted_fg *= fg;
		}
		predicted += buffer[stages - 1] * g;
		predicted /= (1.0f + fb * predicted_fg);
		float xx = in - fb*predicted;
		for (int i = 0; i < stages; i++) {
			float out = g * (buffer[i] + f*xx);
			buffer[i] += f * 2.0f * (xx - out);
			xx = out;
		}
	}
	return buffer[stages - 1] * fb_gain_correction;
} 
This code will (edit: attempt to automatically adjust. it doesn't actually work with stages other than 4. thought I had it working at some point. oh well, fix it if you like) automatically adjust based upon the specified number of stages and includes the simple gain compensation at the end for variable feedback levels.

Reading this code it should become obvious how the algorithm works and how ridiculously inefficient it is. Obviously your buffer needs to be the size as specified by stages. Oversample 2 or 4 is recommended, aliasing will result unless you modify the loop to apply a down-sampling filter rather than simply chucking out the extra samples generated.

It is possible to automatically unroll this code by using recursive templates. That is advanced c++ wizardry and so ignore this comment if you didn't already know that.
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

2 pole linear SVF with external resonance control ( taken from my old c# code)

Code: Select all

 public class Filter
    {
        float s1=0.0f,s2 = 0.0f;
        float v=0, y1=0,y2=0;

        public float Apply(float sample,float g)
        {
            g = Math.Tan(Math.PI * g / Shared.SampleRate);

            float rd = 1 - Params.Resonance;
            v = ((sample- rd * s1*2 - g*s - s2)/(1+ rd*g*2 + g*g));

            y1 = v*g + s1;
            s1 = v*g + y1;

            y2 = y1*g + s2;
            s2 = y1*g + y2;
             
            return y2;
        }
}

Post

toothnclaw wrote:
AdmiralQuality wrote:Thanks, those were exactly what I didn't want. ;)
Hey Admiral, will Poly-Ana 2 feature a 0dbfbf, I see you're already close to getting the code for it? How's the new look coming along? Will we see a release soon?


Great thread everyone and please go on. This conversation is very revealing.
Maybe. I'll give it a try. If it sounds better, and isn't particularly CPU expensive (which it doesn't appear to be) then sure, will add that mode to the filters. (I've promised I won't do anything to change existing sounds, so every new feature needs to be an option that can be switched back to "normal" behavior. So if I do add it, you'll have a choice whether or not to use it.)

But keep in mind that Poly-Ana already does up to 16X oversampling. That does a LOT to eliminate any detectable artifacts of the unit delay. I'll be surprised if there's a noticeable difference at the highest quality levels.

1.20 is coming first though. Soon. (But I'm afraid maybe not before year end, I'm pretty swamped with another project at the moment.)

December half price sale coming up in a few minutes too. Spread the werd!

We now return this thread...

Post

AdmiralQuality wrote: Thanks, those were exactly what I didn't want. ;)
Now I certainly agree that andy's writing style is rather tricky to follow, but .. pseudo-code doesn't really make sense, because the solution to the math IS the code. I don't even mean figuratively .. it's quite literally what you want the code to do: implement a solver for a particular set of equations.

edit: well, I forgot to say that "yes, you can post examples" but at the same time, you can't really learn much, or expect to modify such examples, because the modifications usually need to be done at higher level, and then the math needs to be solved again.

Post

mystran wrote:
AdmiralQuality wrote: Thanks, those were exactly what I didn't want. ;)
Now I certainly agree that andy's writing style is rather tricky to follow, but .. pseudo-code doesn't really make sense, because the solution to the math IS the code. I don't even mean figuratively .. it's quite literally what you want the code to do: implement a solver for a particular set of equations.
If pseudo-code doesn't make sense then code doesn't make sense and then there's no point in implementing it.

Thanks anyway, I've got some examples to work with here.

Post

I want to post my super-minimal mathematics version though:

1. start with the description of signal flow.. i'll use multiply by 1/s as notation for integrator, but it's still just a description of signal flow.. for an SVF since it's simple enough.. and order doesn't matter, this is just a "list of connections"

Code: Select all

  lp = 1/s * bp
  bp = 1/s * hp
  hp = in - lp - bp/Q
2. for each integrator, pick a memory location (here by called a "state variable") where we can track the integration.. i shall call them "stateLP" and "stateBP" for no particular reason...

3. replace each (1/s * input) with (state + f*input), where f is used to scale the normalized analog frequency; usually you want tan(pi*cutoff/samplerate)

Code: Select all

  lp = stateLP + f*bp
  bp = stateBP + f*hp
  hp = in - lp - bp/Q
4. Solve the above system of equations for hp, bp and lp. That's the absolute minimum amount of mathematics we just have to do, otherwise we can't get rid of the delays. Normally I'd use gaussian elimination, but here it's not really worth the trouble.. instead just substitute the definitions of lp and bp to the definition of hp, then solve that (the others are then trivial):

Code: Select all

 hp = in - (stateLP + f * (stateBP + f*hp))
        - (stateBP + f*hp)/Q

.. and solving for hp ..

 hp= (in - stateLP - (1/Q + f)*stateBP)/(f*(1/Q + f) + 1)
5. Write the solution (or just the reduction steps for GE will work too) down as code:

Code: Select all

 float q1 = 1/Q, f = tan(M_PI * cutoff / samplerate);
 float hp = (in - stateLP - (q1 + f)*stateBP)/(f*(q1 + f) + 1);
 float bp = stateBP + f * hp;
 float lp = stateLP + f * bp;
6. then the new state is either "out + f*in" or "state + 2*f*in" depending on which one you happen to like better (first one usually saves one add, second one looks nicer)

Code: Select all

  stateBP += 2 * f * hp;
  stateLP += 2 * f * bp;
7. That's it, we're done.
Last edited by mystran on Sun Dec 01, 2013 9:07 pm, edited 1 time in total.

Post Reply

Return to “DSP and Plugin Development”