f_filter_biquad_A curiosity


#1

Is it possible to update the input of such filters at s-rate?
I'd like to try adding filtered feedback to some custom objects, is it possible to do that with f_filter_biquad_A ?

I tried but i could only achieve 16 sample delay. I'd like to get down to 1 sample delay, is it possible using such function and without writing custom code?


#2

I had a quick look out of curiosity .... so not a definitive answer, that you'll get from @johannes :slight_smile:

the reason for not doing it on single samples, is the cost of calculating the coefficients... so its more expensive to do this every sample.
(but of course audio rate modulation of filter frequencies is also fun if you don't mind the extra cost, Id quite like a version for this ... its one of the nicer things on my spectralis, albeit that is analog )

the api as it is unfortunately hard codes the buffer size... it would have been nice (but less efficient) if you could pass in a bufsize of 1!

it would be possible/simple to add a single sample version of f_filter_biquad_A, this would need a new firmware release. so for immediate gratification, you could copy the function into your own custom object, and pretty much just change the loop.
(you don't even need to worry about optimising the temp vars used, as the optimiser will happily strip these out for you)

btw: do you need audio rate modulation of the filter frequency, if not then the function would be best split in 2, one to calc the coefficients (at k-rate) the other to do the filter on the sample (at s-rate)

@johannes what do you think about adding a single sample variant for next release? so we can mess about with audio rate modulation of filter frequency.


#3

It is currently not possible without duplicating filter code.
The tricky balance is this: inlined functions are faster, but for large functions they consume more memory. When filtering a whole buffer the function call overhead (expense of non-inlining) is negligible, so the trade-of is towards size. When filtering a single sample, the reduced code efficiency of non-inlining can get significant.
I'm checking what I can do for the upcoming release.


#4

@johannes I suspect there are 2 use-cases here (which can be solved with one implementation :slight_smile: )

a) wanting to be do 1-sample because you are doing something which each sample e.g. feedback , which I think is @Sputnki case. ... but filter freq is still ok at k-rate
b) wanting to do audio rate modulation of filter freq, so it needs to be single sample

so I was thinking perhaps 2 low-level methods, one that calcs the co-effieficients the other that does the filtering. that way the user gets to choose if they are going to do the expensive coefficients calc at k-rate or s-rate depending on their requirements.


#5

That is there for the hp/lp/bp RBJ filters:
biquad_lp_coefs
biquad_bp_coefs
biquad_hp_coefs
to compute the filter coefficients
biquad_dsp
to filter s buffer
I'll add a biquad_dsp1 inline function to filter a single sample, biquad_dsp can then use that.


#6

Calculating coefficients at k-rate and processing samples at s-rate is pretty much what i wanted to do, but is it really that much more expensive than doing it with the f_filter_biquad_A function?


#7

f_filter_biquad_A processes a whole buffer.
I've been looking through the filter code in firmware, and my conclusion is that filter code actually does not really belong in firmware, but in a shared header file with objects. There are a gazillion possible implementation variants of biquads. Historically f_filter_biquad_A was the first filter ever in Axoloti, then a more general series of functions were added, splitting the filters in coefficient design and sample buffer computation. But most RBJ filters have some redundancy compared to a general case with 6 coefficients: the same coefficient is used twice, or one coefficient is zero, and this can save some cpu cycles.
I will leave the current filter functions in firmware for a while, but will remove them in the long run, after factory and community objects have cut their dependency on firmware.


#8

so ehm, is there a biquad BP filter I could add to a karplus-strong delay inside the audio-rate path?
I need a filter that only sharpens the BP slope without adding resonance/volume to it and the state-variable is adding way too much resonance..


#9

I think a biquad filter in the feedback path of a karplus-strong will cause instability, the loop gain needs to be below unity at all frequencies. Perhaps 1st order lowpass + 1st order highpass?


#10

yeah, I did that now after checking all the contributions, I saw that there was not a single module with a biquad filter in the audio chain.. so..
also, a little peak on the resonance is not a really big problem as I'm only mixing the filter just a tiny bit with the original signal, creating a sort of (6/100)dB slope (100 being just an example, gonna make it frequency-dependent too, just like the normal feedback attenuation)


#11

I finally exploited the functions to make the filter work per sample: put this in the init code and call it at s-rate.

 static __attribute__ ((noinline)) int32_t biquad_dsp_sample(biquad_state *state,
                                                  biquad_coefficients *coefs,
                                                  int32_t filterinput) {
    int32_t accu = ___SMMUL(coefs->cxn_0, filterinput);
    accu = ___SMMLA(coefs->cxn_1, state->filter_x_n1, accu);
    accu = ___SMMLA(coefs->cxn_2, state->filter_x_n2, accu);
    accu = ___SMMLS(coefs->cyn_1, state->filter_y_n1, accu);
    accu = ___SMMLS(coefs->cyn_2, state->filter_y_n2, accu);
    int32_t filteroutput;
    filteroutput = accu << 4;
    state->filter_x_n2 = state->filter_x_n1;
    state->filter_x_n1 = filterinput;
    state->filter_y_n2 = state->filter_y_n1;
    state->filter_y_n1 = filteroutput;
    return __SSAT(filteroutput, 28);
}

This one is another function, you can use it to morph between coefficients (i'm thinking of the z-plane filters, here :smiley: )

static __attribute__ ((noinline)) void biquad_morph_coefs(biquad_coefficients *incoefs1, biquad_coefficients *incoefs2, biquad_coefficients *outcoefs, int32_t morph)
{
		outcoefs->cyn_1 = ___SMMLA(incoefs2->cyn_1-incoefs1->cyn_1<<2,morph<<3,incoefs1->cyn_1);
      	outcoefs->cyn_2 = ___SMMLA(incoefs2->cyn_2-incoefs1->cyn_2<<2,morph<<3,incoefs1->cyn_2);
      	outcoefs->cxn_0 = ___SMMLA(incoefs2->cxn_0-incoefs1->cxn_0<<2,morph<<3,incoefs1->cxn_0);
      	outcoefs->cxn_1 = ___SMMLA(incoefs2->cxn_1-incoefs1->cxn_1<<2,morph<<3,incoefs1->cxn_1);
      	outcoefs->cxn_2 = ___SMMLA(incoefs2->cxn_2-incoefs1->cxn_2<<2,morph<<3,incoefs1->cxn_2);
}

You can see it applied in sptnk/filter/morph


#12

is there a way to get the a1, a2, b0, b1 and b2 variables from the biquad calculation, so I can make a display readout for my paraEQ with a transform function?


#13

You can access get the coefficients from the biquad_coefficients struct with a pointer:

say you have defined

biquad_coefficients coefs;

individual coefficients are stored inside cyn_1, cyn_2, cxn_0, cxn_1 and cxn_2 variables, inside the biquad_coefficients struct.

If i'm not wrong (but i'm still pretty rusty on pointers), you can get a coefficient this way:

int32_t foo = &coefs->cyn_1;

This way you've stored cyn_1 in the variable foo.


#14

oh, seems I figured it out afterall, seems I first misunderstood your explanation, thanks for the help!
hmm, getting these paraEQ coefficients into the graph is harder then I thought.. showed response is far from right..


#15

yes yes yes yes yes :stuck_out_tongue:
got it working! only need to find a way to do the frequency scaling of the graph right, as at the moment the bass part of the spectrum is really messy/noisy if a filter is positioned there.

aaaaand fixed :stuck_out_tongue: (though, still below 300hz the response can't handle the small numbers I think)


#16

That's awesome! Did you do a frequency response to get the curve or have made it another way?


#17

I took the magnitude calculation based on the filter coefficients.
There are still some optimalisation needed as, for example, it now updates as soon as one of the parameters of the EQ changes, causing it to have cpu spikes. But I'm definitely at the right track. Also added the normal BP, LP and HP filters modes to a newer version and an extra overall external pitch-control, so you could completely design your own filter.
Ps, the graph shown here still has a fault, as it doesn't display in dB, also fixed in the newer version