Codebox is killing me - I don't understand the variable declaration it seems

Pierre Alexandre Tremblay's icon

so the cool code below is the 'port'. it is a basic biquad, with some magic love in the feedback loop.

2 questions:
1) I get an error at the line "b0a0 = b0/a0;" telling me that b0 is not declared. it is in my if else if, in principle. I had to remove a 'case' and put numbers as it seems the if comparison did not want to compare to "bandpass' and such... anyway, I don't get why it does not initialise properly.

2) optimisation wise: I don't want to have the coefficients to be calculated at audio rate. Should I just do an abstraction and do the maths elsewhere?

3) why gen~ you might ask? Simple: I wanted to see if I could port easily some code for even faster prototyping, which is the sales pitch I think. Anyway, I will go to mxj~ soon but I'm open to any suggestion

thanks all

pa

//    Butt~ - a dirty filter based on a basic biquad...
//    by Pierre Alexandre Tremblay
// v.2 - optimised at the University of Huddersfield, thanks to the hints and discussions with Peter Castine, Alex Harker, Kit Clayton and Apple Dev Website
// v.3 in gen~

// the methods

Param freq(666,min=1,max=20000);
Param bw(1,min=0.01,max=100);
Param dirt(0,min=0,max=1);
Param type(3);

// the delays

History b_in1, b_in2, b_ou1, b_ou2;
b_in0 = in1;

// constants

temp_ln=0.34657359027997265471;
b_piDsr=(2. * pi) / samplerate;

// process bandwidth
omega = (b_piDsr) * (freq);
tsin = sin(omega);
tcos = cos(omega);
alpha = tsin * sinh(temp_ln * (bw) * omega / tsin);        // bandwidth

// then process coefficients
if(type == 1) {
    b0=(1.0-tcos)/2.0;
    b1=1.0-tcos;
    b2=(1.0-tcos)/2.0;
    a0=1.0+alpha;
    a1=-2.0*tcos;
    a2=1.0-alpha;
}
else if(type == 2) {
    b0 = (1.0 + tcos) /2.0;
    b1 = -(1.0 + tcos);
    b2 = (1.0 + tcos) /2.0;
    a0 = 1.0 + alpha;
    a1 = -2.0 * tcos;
    a2 = 1.0 - alpha;
}
else if (type == 3) {
    b0=alpha;
    b1=0.0;
    b2=-alpha;
    a0=1.0+alpha;
    a1=-2.0*tcos;
    a2=1.0-alpha;
}
else if (type == 4) {
b0 = 1.0;
    b1 = -2.0 * tcos;
b2 = 1.0;
    a0 = 1.0 + alpha;
    a1 = -2.0 * tcos;
a2 = 1.0 - alpha;
}

b0a0 = b0/a0;
b1a0 = b1/a0;
b2a0 = b2/a0;
a1a0 = a1/a0;
a2a0 = a2/a0;        

out1 = b0a0*b_in0 + b1a0*b_in1 + b2a0*b_in2 - a1a0*b_ou1 - a2a0*b_ou2;

b_in2 = b_in1 * (1. - (dirt * noise())); // adding some dirty to the buffers!
b_in1 = b_in0 * (1. - (dirt * noise()));
b_ou2 = b_ou1 * (1. - (dirt * noise()));
b_ou1 = out1 * (1. - (dirt * noise()));

vichug's icon

is it possible to declare a variable solely form inside an if/else statement in other languages ? it seems reasonable for a compiler to refuse it, since it's hard to determine if there are cases where the condition is never verified and hence the variable would not be stated at all. Moreover here there is no up or down limit for type

Pierre Alexandre Tremblay's icon

thanks for your help. I've tried with declaring them before and it was not working either. and I give a default value to 'type' as 3.

This is why my title is that I don't understand it. From the C code I had to remove the declarations and cast and now I either get nothing or errors...

any pointers welcome

p

Pierre Alexandre Tremblay's icon

btw, it works if I pre-declare this:

b0 = 1;
b1 = 0;
b2 = 0;
a0 = 1;
a1 = 0;
a2 = 0;

will investigate further...

Rick's icon
Max Patch
Copy patch and select New From Clipboard in Max.

Maybe this works? Protect your ears and speakers.

vichug's icon

well i guess how problematic it can be... if i declare

b0 = 0;
b1 = 0;
b2 = 0;
a0 = 0;
a1 = 0;
a2 = 0;

before the if statements, it does compile, but indeed i'm not sure the process will work well... maybe turn these into parameters would work ?

Pierre Alexandre Tremblay's icon

ok now I love gen~ = I don't know why but it seems the code run twice as fast as my old C compile... I'll have to recompile my old code in C now to check, but hey! I'm a happy man. Here is the final code!

stkr's icon

the final code you posted seems to work well.

regards:
"
2) optimisation wise: I don’t want to have the coefficients to be calculated at audio rate. Should I just do an abstraction and do the maths elsewhere?
"

...you are doing this 'correct' already - all your coefficients are being processed at param (control) rate, as they come in as params and do not mix with audio rate calculations until they hit the filter. if you made them functions you'd be able to easily choose whether or not you run them at signal rate or param rate depending on what you feed them. gen~ (un?)helpfully works all this out for you depending on the chain.

sometimes i make audio rate & param rate internally, as well as max schedular rate and low priority (js) rate externally, for complete choice ;) yes, doing the coefficients in max (not msp!) and feeding them in as params directly is likely even cheeper. but for butt this seems not much of an issue at all.

by the way, is butt~ supposed to have 4 different noise declarations/instantiations or just one? just interested is all...

Pierre Alexandre Tremblay's icon

thanks for all this.

Yes 4 instances of noise. In the old C code I even vectorised the LC random algorithm to get 4 numbers per call ;-)

What is funny is that this old binary is twice slower in Max6 and 7 than the gen~ version. I presume the overhead of 64 bit to 32 bit conversion is taking the toll... I have to run about a hundred instances to see a significant figure, all in the main patcher to avoid other potential overheads (like poly~'s)

Next I'll port it to Java and see how it performs ;-)

p

Evan's icon

Sorry for the hijack, but I found this interesting:

if you made them functions you’d be able to easily choose whether or not you run them at signal rate or param rate depending on what you feed them.

Any chance you could post a small example? I'm lurking on every gen~ thread around trying to pick up different ways of doing things.

Graham Wakefield's icon

Hi there,

Nice! A few scattered thoughts:

- The reason you need to declare b0 etc. before the if()... block is because you use these variables in the main scope after the if/else block finishes. GenExpr doesn't scope variables like C (or js var), in which declarations are function-scoped; GenExpr scopes variables like C++ (or js let), i.e. at block-scope, which in plain terms means at the nearest containing set of {} brackets. If you want to use a variable outside the {} brackets, it needs to be declared there. Hope that helps to explain it!

- STKR's point is quite correct about param-only driven calculations being lifted to control rate (you can verify that by sending "exportcode" to the gen~).

- Turning up "dirt" above about 0.1 can easily blow up the feedback, I can see why you say "be careful"! I wonder if there's a way to keep the dirt but avoid the blowup? Maybe there's a way to redistribute the recirculating signals with noise that still preserves the overall energy? Or maybe something like a short envelope follower to suppress or it if the output magnitude gets too large?

- You might want to declare min & max for type. Also, you could adjust your conditions, so that instead of "type == 1" you could write "type > 0.5", etc., and for "type == 4" you could just use the else to capture all other cases -- that way any small floating point errors are not going to skip the coefficient calculation if someone passes in a value of 1.000000001 or -0.000000001 etc.

- You might also consider making these regular inputs rather than params. gen~ is smart enough to notice whether you hook up a signal or a message-rate input to its inlets. If there is no signal input, the processing happens at control rate, just like a param. You can still put default/min/max on them.

Max Patch
Copy patch and select New From Clipboard in Max.

E.g.:

Graham

Pierre Alexandre Tremblay's icon

Thanks for that Graham - I'll be inspecting in the next days, as well as some great contributions by STKR in private ;-)

Pierre Alexandre Tremblay's icon

Sorry to revive this old thread... I've used 'exportcode' and it seems that the coefficient calculations are inside the perform loop - while((__n--)){ ... }

Can you confirm that I understand correctly the code?

thanks

p

Graham Wakefield's icon

Hi Pierre,

The code used within Max is not necessarily the same as that exported via exportcode. When using exportcode, we have to assume that all inputs are signal rate, but within Max we can dynamically recompile the code whenever the connections to an object change, so inlets may appear outside the while() sample loop. Try sending the "full_source_code" message to the gen~ -- that shows what the code looks like within Max.

Graham

Pierre Alexandre Tremblay's icon

Thanks for this - this is all very useful for me! Surprisingly they are still in the perf loop, but it is so fast that this is all very academic.

I'll continue my port to Java and will see how that works...

thanks again for the quick reply

p