Old 12-01-2021, 01:37 AM   #1
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default How does this IIR code work?

Hi guys

I dont get how this code works, specifically this bit ”(n0+=((spl0-n0)*weight))”. Its clearly an IIR/EMA filter as Ive tested it. But I dont get how the variable n0 works with spl0 and weight. Whats the math behind it?

The way Ive learned IIR/EMA is ”spl0*weight + spl0*1-weight”. So whats up with this? Are there any another examples of code that does this?

The thing is it looks neat and tidy wich I like so I really wanna know

Best regards /danerius

Code:
desc:lowpass / weighted average

slider1:0.5<0,1,0.001>weight

@slider
weight = slider1*slider1;

@init
  n0 = 0;
  n1 = 0;

@sample
  spl0 =  (n0+=((spl0-n0)*weight)); // lowpass
  spl1 -= (n1+=((spl1-n1)*weight)); // highpass
danerius is offline   Reply With Quote
Old 12-01-2021, 03:08 AM   #2
nitsuj
Human being with feelings
 
nitsuj's Avatar
 
Join Date: Nov 2017
Posts: 292
Default

Quote:
Originally Posted by danerius View Post
Hi guys

I dont get how this code works, specifically this bit ”(n0+=((spl0-n0)*weight))”. Its clearly an IIR/EMA filter as Ive tested it. But I dont get how the variable n0 works with spl0 and weight. Whats the math behind it?

The way Ive learned IIR/EMA is ”spl0*weight + spl0*1-weight”. So whats up with this? Are there any another examples of code that does this?

The thing is it looks neat and tidy wich I like so I really wanna know

Best regards /danerius

Code:
desc:lowpass / weighted average

slider1:0.5<0,1,0.001>weight

@slider
weight = slider1*slider1;

@init
  n0 = 0;
  n1 = 0;

@sample
  spl0 =  (n0+=((spl0-n0)*weight)); // lowpass
  spl1 -= (n1+=((spl1-n1)*weight)); // highpass
It's a one pole filter (6dB) where n0 and n1 are the z-1 values. Go check out one pole low pass filters and you'll see that this is all it is. Maybe write a little code to put some values through it and see what happens to them.
nitsuj is offline   Reply With Quote
Old 12-01-2021, 04:35 AM   #3
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default

Hi Justin. Thanks for replying

I know its a 6dB one pole, Ive tested it with white noise + ReSpectrum. And Ive coded such filters but not in this way.

To break it down... (n0+=((spl0-n0)*weight):
- In @init n0 = zero.
- So I read the code as zero + sample - zero * weight
- Wich in my head would be sample * weight... ie a gain control...!
- Isnt there supposed to be a variable one sample ago that has that added?

Best regards /Danerius

Quote:
Originally Posted by nitsuj View Post
It's a one pole filter (6dB) where n0 and n1 are the z-1 values. Go check out one pole low pass filters and you'll see that this is all it is. Maybe write a little code to put some values through it and see what happens to them.
danerius is offline   Reply With Quote
Old 12-01-2021, 09:48 AM   #4
nitsuj
Human being with feelings
 
nitsuj's Avatar
 
Join Date: Nov 2017
Posts: 292
Default

Quote:
Originally Posted by danerius View Post
Hi Justin. Thanks for replying

I know its a 6dB one pole, Ive tested it with white noise + ReSpectrum. And Ive coded such filters but not in this way.

To break it down... (n0+=((spl0-n0)*weight):
- In @init n0 = zero.
- So I read the code as zero + sample - zero * weight
- Wich in my head would be sample * weight... ie a gain control...!
- Isnt there supposed to be a variable one sample ago that has that added?

Best regards /Danerius
N0 and N1 are modified in the sample section - check out the '+=' operator used there. For example x += 1 is the same as x = x + 1.

Hopefully that should make sense now.
nitsuj is offline   Reply With Quote
Old 12-02-2021, 12:15 AM   #5
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,652
Default

BTW, the weight is usually calculated like this:

Code:
weight = 1 - exp(-2*$pi * cutoff / srate);
Tale is offline   Reply With Quote
Old 12-02-2021, 11:29 AM   #6
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default

Hi + thanks

Ive done a bit of homework and get what youre saying now. Will be using assignment operators more from now on

Best regards/danerius

Quote:
Originally Posted by nitsuj View Post
N0 and N1 are modified in the sample section - check out the '+=' operator used there. For example x += 1 is the same as x = x + 1.

Hopefully that should make sense now.
danerius is offline   Reply With Quote
Old 12-02-2021, 11:38 AM   #7
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default

Hi Tale + thanks

Thats the formula for tau? Right?

On another note. Me + a couple of friends are doing live streams once a week talking about JSFX + Reascript. Geraint Luff + nitsuj have been guests the stream. Heres a link to the channel:
https://www.youtube.com/results?sear...ry=iddqd+sound

Let me know if you wanna be on Thanks + best regards /danerius

Quote:
Originally Posted by Tale View Post
BTW, the weight is usually calculated like this:

Code:
weight = 1 - exp(-2*$pi * cutoff / srate);
danerius is offline   Reply With Quote
Old 12-02-2021, 12:23 PM   #8
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 876
Default

With a bit of arithmetic you can see that

n += (s-n)*weight

is the same as

n = n*(1-weight) + s*weight

which if you may have seen elsewhere. I use single pole smoothers a lot, for instance, I just write them a little different. Here are some utility functions that I use a lot, and a simple way to implement RMS as an example of how I might use them.

Code:
function singlepole(in,target,coeff) ( in*coeff + target*(1-coeff); );
function get_coeff(ms) ( exp(-1/(0.001 * ms * srate)); );
function set_rms(ms) ( this.coeff = get_coeff(ms); );
function rms(in) instance(rms,coeff,out) 
	( rms = singlepole(rms,in*in,coeff); out = sqrt(rms); );
SaulT is offline   Reply With Quote
Old 12-04-2021, 01:38 AM   #9
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default

Hi SaulT

Thanks for explaining. Ive actually used your RMS code already in another project. Found it while searching the forums

Best regards /danerius

Quote:
Originally Posted by SaulT View Post
With a bit of arithmetic you can see that

n += (s-n)*weight

is the same as

n = n*(1-weight) + s*weight

which if you may have seen elsewhere. I use single pole smoothers a lot, for instance, I just write them a little different. Here are some utility functions that I use a lot, and a simple way to implement RMS as an example of how I might use them.

Code:
function singlepole(in,target,coeff) ( in*coeff + target*(1-coeff); );
function get_coeff(ms) ( exp(-1/(0.001 * ms * srate)); );
function set_rms(ms) ( this.coeff = get_coeff(ms); );
function rms(in) instance(rms,coeff,out) 
	( rms = singlepole(rms,in*in,coeff); out = sqrt(rms); );
danerius is offline   Reply With Quote
Old 12-04-2021, 04:08 AM   #10
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,652
Default

FWIW, "in*coeff + target*(1-coeff)" is likely slightly slower than "n += (s-n)*weight", mostly because the latter only does a single multiplication, but also because it doesn't need to load the "1". Whether or not you will notice this depends i.e. it won't really matter if you use the code only once or twice in @sample.
Tale is offline   Reply With Quote
Old 12-08-2021, 12:24 PM   #11
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default

Hi Tale. Pardon the late response

Ive been meaning to try a Holt version of this IIR, ie you keep the n variable from one sample ago and add a percentage of that to the current one.

So ”n += (s-n)*weight1 + n-1*weight2” in my pseudo code. What would be a CPU efficient way to write that?

Thanks /danerius

Quote:
Originally Posted by Tale View Post
FWIW, "in*coeff + target*(1-coeff)" is likely slightly slower than "n += (s-n)*weight", mostly because the latter only does a single multiplication, but also because it doesn't need to load the "1". Whether or not you will notice this depends i.e. it won't really matter if you use the code only once or twice in @sample.
danerius is offline   Reply With Quote
Old 12-08-2021, 08:16 PM   #12
ashcat_lt
Human being with feelings
 
Join Date: Dec 2012
Posts: 7,293
Default

There are other implementations which precalculate the (1-coeff) into another variable (often icoeff), but I guess that puts us back to two multiplications. Not sure if that’s better or worse.
ashcat_lt is offline   Reply With Quote
Old 12-09-2021, 01:12 AM   #13
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,652
Default

Quote:
Originally Posted by danerius View Post
Ive been meaning to try a Holt version of this IIR, ie you keep the n variable from one sample ago and add a percentage of that to the current one.

So ”n += (s-n)*weight1 + n-1*weight2” in my pseudo code. What would be a CPU efficient way to write that?
Are you sure you want to do that? Because the "n" in "(s - n)" already is the value from 1 sample ago, so I guess you want to add the value from 2 sample ago? If so, then I guess something like this:

Code:
n2 = n1;
n1 = n;
n += (s - n1)*weight1 + n2*weight2;
Note that this looks kinda like a 2nd order filter biquad, except that a biquad also takes the previous input samples into account.
Tale is offline   Reply With Quote
Old 12-09-2021, 01:18 AM   #14
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,652
Default

Quote:
Originally Posted by ashcat_lt View Post
There are other implementations which precalculate the (1-coeff) into another variable (often icoeff), but I guess that puts us back to two multiplications. Not sure if that’s better or worse.
In C/C++ that could be more efficient, because it might be able to do the multiplications in parallel. But I think in JSFX it will be less efficient, not only because of the multiplications, but again also because it needs to load two values rather than one.
Tale is offline   Reply With Quote
Old 12-09-2021, 05:18 PM   #15
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default

Hi Tale + thx

Looking at the code and reading your comment I get that its a somewhat regular lowpass using two stored samples.

However. Im looking to make a smoothing/lowpass that stores the latest smoothed/lowpassed value to be added to the new one by a different factor/weight. Maybe this makes more sense?

Code:
lowpassed [n0] + lowpassed [n1];
Best regards /danerius


Quote:
Originally Posted by Tale View Post
Are you sure you want to do that? Because the "n" in "(s - n)" already is the value from 1 sample ago, so I guess you want to add the value from 2 sample ago? If so, then I guess something like this:

Code:
n2 = n1;
n1 = n;
n += (s - n1)*weight1 + n2*weight2;
Note that this looks kinda like a 2nd order filter biquad, except that a biquad also takes the previous input samples into account.
danerius is offline   Reply With Quote
Old 12-10-2021, 03:19 PM   #16
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 876
Default

What are you trying to accomplish?

It is better to use a biquad if you want a steeper and more accurate filter.

It seems like what you are describing (although I may be misunderstanding) is combining a smoother and a comb filter. For instance, in this example weight2 controls the amount of the first delayed output added back in, weight3 controls the amount of the second delayed output added back in. If you change the signs to negative you create a highpass effect, btw.

Anyways, as you raise slider3 you will see a notch at Nyquist and as you raise slider4 you will see a notch that's a little lower than srate/3, etc.

I played around with it a little and you can do things like change the code to

Code:
function smoothandcomb2(x,coeff1,coeff2,coeff3,coeff4)
 instance(s1,s2,z1,z2)
(
  z2 = z1;
  z1 = s1;
  s1 = singlepole(s1,x,coeff1);
  s2 = singlepole(s2,z1+s1,coeff4);
);
and add z1+s1 or z2+s1 or other such things, just put slider2 back in, it's maybe an interesting way to put a zero at Nyquist or whatever, but again, maybe just use a biquad.

Code:
desc:sault :: dany test halfquad

slider1:10000<1000,20000,1>weight 1
//slider2:20000<1000,20000,1>weight 4
slider3:-90<-90,0,0.1>delayed weight 1
slider4:-90<-90,0,0.1>delayed weight 2

@init

function singlepole(in,target,coeff) ( in*coeff + target*(1-coeff); );
function getfc(fc) ( exp(-2*$pi * fc / srate); );

function smoothandcomb(x,coeff1,coeff2,coeff3)
 instance(s1,z1,z2)
(
  z2 = z1;
  z1 = s1;
  s1 = singlepole(s1,x,coeff1);
  s1 + coeff2 * z1 + coeff3 * z2;
);

@slider

weight1 = getfc(slider1);
//weight4 = getfc(slider2);
weight2 = 10^(slider3/20);
weight3 = 10^(slider4/20);

@sample

spl0 = f0.smoothandcomb(spl0,weight1,weight2,weight3);
spl1 = f1.smoothandcomb(spl1,weight1,weight2,weight3);

Last edited by SaulT; 12-10-2021 at 03:41 PM.
SaulT is offline   Reply With Quote
Old 12-11-2021, 12:43 PM   #17
danerius
Human being with feelings
 
Join Date: Oct 2018
Posts: 173
Default

Quote:
Originally Posted by SaulT View Post
What are you trying to accomplish?

It is better to use a biquad if you want a steeper and more accurate filter.
Hi SaulT. Thanks for replying

Im not trying to create a regular filter. Theres a bunch of them out there already
Im borrowing other forms of smoothing filters available in other disciplines. So far Ive coded (badly) a one pole Hull filter. It does sound different, softer, smoother etc, up to a point where its starts behaving like any other low pass.

The formula Im trying to find a nice code for Ive come up with myself. So far its been tested in Excel with different forms of data. It smooths things nicely and looks to be able to handle higher values better than the Hull does. I could translate the Excel formula into JSFX but it wouldnt be that efficient. Hence me asking about the one pole earlier in this thread.

And to clarify... Its not storing two samples to get processed. Its using the current sample added to the last calculation as in a regular IIR. And storing the prior calculation and adds a portion of that to the current calculation.

Heres a couple of examples. Gray is data, yellow is a one pole low pass, blue is my smoothing formula. Both have the same smoothing value and blue clearly follows the data more closely and has nicer rounder curves . Plus Ive added a screen shot of the Excel formula.
https://work.danerius.se/wp-content/...eSmoothing.png

And for reference. Below is my Hull Low Pass...

Best regards /danerius

Code:
desc:Hull Moving Average Test 0.1
slider1:10<50,0,0.01>CutOff

@slider
//Converts slider1 to more managable numbers 
sli_conv = 2 ^ ( slider1 / 6 );
//Converts lsider1 values to the values needed in the Hull Smoothing
slow = 1/sli_conv; fast = (1/sli_conv)/2; smooth = 1/(sqrt(sli_conv));

@init

@sample
//To-Do. Add tau

//a, b, etc are for storing values. 0 = left 1 = right
hmaSum0 = (hmaFast0 + hmaSlow0) * 0.5;
hmaSmooth0 = (hmaSum0 * smooth) + (c0 * (1-smooth)); 
c0 = hmaSmooth0; 

hmaSlow0 = (spl0 * slow) + (a0 * (1-slow)); 
a0 = hmaSlow0; 
spl0 = hmaSlow0;

hmaFast0 = (spl0 * fast) + (b0 * (1-fast)); 
b0 = hmaFast0; 
spl0 = hmaFast0;

spl0 = hmaSmooth0; 

//a, b, etc are for storing values. 0 = left 1 = right
hmaSum1 = (hmaFast1 + hmaSlow1) * 0.5;
hmaSmooth1 = (hmaSum1 * smooth) + (c1 * (1-smooth)); 
c1 = hmaSmooth1; 

hmaSlow1 = (spl1 * slow) + (a1 * (1-slow)); 
a1 = hmaSlow1; 
spl1 = hmaSlow1;

hmaFast1 = (spl1 * fast) + (b1 * (1-fast)); 
b1 = hmaFast1; 
spl1 = hmaFast1;

spl1 = hmaSmooth1;
danerius is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 10:33 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.