The good ol' unit delay ladder filter problem

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

I incorperated mystran's suggestion but found that the filter still blew up. With the feedback turned all the way up, the filter consistently exploded when f reached about 0.7 or so.

So I scaled f when using the filter iteration:

Code: Select all

void LadderFilter::Process(float *input, float *output, int count)
{
    float in;
    float t1, t2, t3, t4;
    float tf = f * 0.7f;

    for(int i = 0; i < count; i++)
    {
        in = *input - feedbackLevel * 4.0f * out4; 

        t1 = tf * in + (1.0f - tf) * out1;
        t2 = tf * t1 + (1.0f - tf) * out2;
        t3 = tf * t2 + (1.0f - tf) * out3;
        t4 = tf * t3 + (1.0f - tf) * 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++;        
    }
}
There are probably all kinds of problems with this approach, but at least it's not blowing up any more. I found that if I scaled f too much, the filter started blowing up again.

Post

why don't you use the standard [0.5, 0.5] fir method? each integrator value is filtered first, then the normal filter is executed.

Code: Select all

memcpy(temp[4], buf[4]);
input -= (buf[3]*0.5 + old[3]*0.5) * feedback;
buf[0] += (input - (old[0]*0.5 + buf[0]*0.5)) * coefficient;
...
memcpy(old[4], temp[4]);
...
i think that's it; either that, or the old[] portion is added outside the delta. i'm also not sure if the feedback must use the fir filtered value, or can get away with the current or old value alone.

the filter becomes completely stable, but you need to warp coefficients quite a lot. it becomes most efficient to use a lookup table of 128 or 256 entries and linear interpolation. so coefficient calculation = two linear interpolations.

i personally hate this filter since it's impossible to avoid aliasing with it - that for me makes it useless by itself.

also - problem with your parallel idea is the system's hysteresis. since the system has hysteresis, if you run another filter in a state which doesn't match the results will become incorrect after the first sample.

also avoid tanh - if you really must use something similar you can use a hardclipped n(2-abs(n)) which approximates it (~=tanh(n2) with abs(n) <= 1) or (x/(abs(x)+2))*3. the specific harmonic spectra of the function used isn't too important - it's mostly the strength of the harmonics over-all that matters. i recommend the hardclipped parabolic function because it significantly limits aliasing.
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

After reading aciddose's post, I modified my filter thusly:

Code: Select all

void LadderFilter::Process(float *input, float *output, int count)
{
    float in;
    float fb;
    float t1, t2, t3, t4;

    for(int i = 0; i < count; i++)
    {
        fb = feedbackLevel * 4.0f * out4;
        in = *input - fb; 

        t1 = f * (in1 * 0.2f + in * 0.8f) + (1.0f - f) * out1;
        t2 = f * (in2 * 0.2f + out1 * 0.8f) + (1.0f - f) * out2;
        t3 = f * (in3 * 0.2f + out2 * 0.8f) + (1.0f - f) * out3;
        t4 = f * (in4 * 0.2f + out3 * 0.8f) + (1.0f - f) * out4;        

        fb = feedbackLevel * 4.0f * t4;
        in = *input - fb;

        out1 = f * (in1 * 0.2f + in * 0.8f) + (1.0f - f) * out1;
        in1 = in;
        out2 = f * (in2 * 0.2f + out1 * 0.8f) + (1.0f - f) * out2;
        in2 = out1;
        out3 = f * (in3 * 0.2f + out2 * 0.8f) + (1.0f - f) * out3;
        in3 = out2;
        out4 = f * (in4 * 0.2f + out3 * 0.8f) + (1.0f - f) * out4;
        in4 = out3;

        *output = out4;

        input++;
        output++;        
    }
}
I don't think this is exactly what aciddose describes; basically, I just added a zero to the filter. This tappers off the high end of the frequency spectrum, maybe making it more stable? The hard coded coefficients were arrived at after experimentation; I adjusted them until the filter didn't blow up.

Looking at the output through fre(a)koscope, the resonant peak looks in tune, but it's not as peaky as I'd like for it to be. This may be subjective, but skipping the iteration seems to result in a more prominant resonant sound with a high feedback levels. Though the resonant peak appears to go out of tune.

Post

also - what i said about the hard-clipping of the parabolic function isn't actually required. you can go ahead and not clip it and get very nice results.

another function is this: (x/(x*x+1))*2

since you're running the filter using two time-slices per sample you'd want to halve the coefficients, making them 0.25 and 0.75 rather than your 0.2 and 0.8, although lowering them i can't see the harm, it should actually still work with the 0.5 (becoming 0.25) coefficient if implemented correctly.

it also works stably with a single time-slice. in fact, i don't know what motivated you to implement the fir with 4/2 time and the filter with 2/2 time. they should both be on the same time-base if possible and you'll improve stability.

another rarely seen trick is used more often in electronic simulation and physics simulation - adjust the time base dynamically based upon frequency. the sudden jump to 3 time-slices and 1/3 coefficients at 2/3 nyquist doesn't cause any energy transfer in the function, for example, and can be used to save processor time when a majority of the dampers are set to low frequencies.

it's also possible to do the inverse - decrease the time-base dynamically with lower frequency content. for example a ramp wave can be linearly interpolated at most points except for the higher frequency reset. this can also be used for compression.
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 Reply

Return to “DSP and Plugin Development”