Old 03-30-2017, 07:15 AM   #1
reapercurious
Human being with feelings
 
reapercurious's Avatar
 
Join Date: Jul 2007
Posts: 1,890
Default RMS from scratch (doesn't run)

Hi,
I'm trying to create an RMS counter off the top of my head. Got it running, I forgot to hit play. Dumbest mistake of all time.

I think what it does is the standard deviation... But am I right that the squaring and the sqare-rooting of the signal is a calculatory solution to getting an absolute value instead of summing positive and negative results?

Code:
desc:rms experiment 30 mar 2017

slider1:2<2,1000,2>length

@init
counter = 0;
acc = 0;
difference = 0;
rms = 0;
 
@slider

length = slider1;

@sample

acc += abs(spl0);  //acc == 'accumulator'
difference += abs(spl0 - mean);

counter >= length ? (
  mean = (acc / length); //dont d.b.z.
  rms = (difference / length);
  difference = 0;
  acc = 0;
  counter = 0;
);

counter += 1;

spl0=spl0;
spl1=spl1;

//@gfx

// print(rms) //pseudocode

Last edited by reapercurious; 03-30-2017 at 08:42 AM.
reapercurious is offline   Reply With Quote
Old 03-30-2017, 08:31 AM   #2
reapercurious
Human being with feelings
 
reapercurious's Avatar
 
Join Date: Jul 2007
Posts: 1,890
Default

i guess a general topic would be "non obvious bugs in reajs to watch for"
reapercurious is offline   Reply With Quote
Old 03-30-2017, 03:15 PM   #3
TryingToMakeMusic
Banned
 
Join Date: Nov 2016
Posts: 416
Default

Quote:
Originally Posted by reapercurious View Post
am I right that the squaring and the sqaure-rooting of the signal is a calculatory solution to getting an absolute value instead of summing positive and negative results?
The squaring makes everything positive, but if you replaced it with an absolute-value function, you'd get different results. Squared amplitude is power, then the sum gives you power averaged over some time-period, and then the final square-root isn't essential to the concept, but it's what people do, I guess to make the results more comparable to peak meters.
TryingToMakeMusic is offline   Reply With Quote
Old 03-30-2017, 10:22 PM   #4
reapercurious
Human being with feelings
 
reapercurious's Avatar
 
Join Date: Jul 2007
Posts: 1,890
Default

Quote:
Originally Posted by TryingToMakeMusic View Post
The squaring makes everything positive, but if you replaced it with an absolute-value function, you'd get different results. Squared amplitude is power, then the sum gives you power averaged over some time-period, and then the final square-root isn't essential to the concept, but it's what people do, I guess to make the results more comparable to peak meters.
That is so weird because I'm just doing substitution and plugging in numbers into (spl0 - X)^2 and the only thing it seems to do is make the difference positive once the sqrt is done. Unless the Summation is supposed to happen before the sqrt() is done. I guess it is my lack of knowledge on the order of operations with Calculus equations
reapercurious is offline   Reply With Quote
Old 03-30-2017, 10:34 PM   #5
TryingToMakeMusic
Banned
 
Join Date: Nov 2016
Posts: 416
Default

Quote:
Originally Posted by reapercurious View Post
Unless the Summation is supposed to happen before the sqrt() is done.
For RMS, the summation is supposed to happen before the sqrt() is done.

"root-mean-squared" -> root(mean(squared())) -> {first square, then mean, and then finally root}
TryingToMakeMusic is offline   Reply With Quote
Old 04-01-2017, 08:50 PM   #6
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 876
Default

I mean, I like the method that acts more like an IIR filter, what with the exp() coefficients and whatnot. If I was to take a hack at doing it this way I would do something like this. Seems to work correctly.


Code:
desc:RMS tester

slider1:2<2,2000,1># of samples
slider5:0<0,1>RMS (dB)

@init

buf = 100;
ptr = 0;

dbc = 20/log(10);
function ratio2db(r) ( log(abs(r))*dbc; );

@slider

size = slider1;
memset(buf,0,2000);
ptr = 0;
acc = 0;

@sample

val = spl0 + spl1;
val *= val;

acc -= buf[ptr];
acc += val;
buf[ptr] = val;

slider5 = ratio2db(sqrt(acc/size));

ptr += 1;
ptr >= size ? ptr -= size;
SaulT is offline   Reply With Quote
Old 04-02-2017, 04:37 AM   #7
reapercurious
Human being with feelings
 
reapercurious's Avatar
 
Join Date: Jul 2007
Posts: 1,890
Default

Quote:
Originally Posted by SaulT View Post
I mean, I like the method that acts more like an IIR filter, what with the exp() coefficients and whatnot. If I was to take a hack at doing it this way I would do something like this. Seems to work correctly.


Code:
desc:RMS tester

slider1:2<2,2000,1># of samples
slider5:0<0,1>RMS (dB)

@init

buf = 100;
ptr = 0;

dbc = 20/log(10);
function ratio2db(r) ( log(abs(r))*dbc; );

@slider

size = slider1;
memset(buf,0,2000);
ptr = 0;
acc = 0;

@sample

val = spl0 + spl1;
val *= val;

acc -= buf[ptr];
acc += val;
buf[ptr] = val;

slider5 = ratio2db(sqrt(acc/size));

ptr += 1;
ptr >= size ? ptr -= size;
" dbc = 20/log(10) "
that's the same as 20
not sure what that's about.

also summing spl0 and spl1 before squaring and rooting will turn that into mono with phase cancellation. i would try to process each channel independently or just make the plugin track either spl0 or spl1.
reapercurious is offline   Reply With Quote
Old 04-02-2017, 01:40 PM   #8
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 876
Default

Proof of concept. Summing the two is the mid channel, you can always take the abs() of each then sum, then multiply by 0.5, or whatever fits your application. It should be trivial to modify it for separate channels.

Log() in Reaper (in many languages) is ln().


Ratio = 10^(dB/20)
Ln(ratio) = (dB/20) ln(10)
Ln(ratio)/ln(10) = dB/20
20 * ln(ratio)/ln(10) = dB

dbc * ln(ratio) = dB


In Reaper js


dbc = 20/log(10)
SaulT is offline   Reply With Quote
Old 04-02-2017, 11:40 PM   #9
reapercurious
Human being with feelings
 
reapercurious's Avatar
 
Join Date: Jul 2007
Posts: 1,890
Default

Quote:
Originally Posted by SaulT View Post
Proof of concept. Summing the two is the mid channel, you can always take the abs() of each then sum, then multiply by 0.5, or whatever fits your application. It should be trivial to modify it for separate channels.

Log() in Reaper (in many languages) is ln().


Ratio = 10^(dB/20)
Ln(ratio) = (dB/20) ln(10)
Ln(ratio)/ln(10) = dB/20
20 * ln(ratio)/ln(10) = dB

dbc * ln(ratio) = dB


In Reaper js


dbc = 20/log(10)
No idea what that is but it looks cool.
Yeah I just got done reading about dB and the article went to great pains to Stress that I needed natural log() go figure. Hehe
reapercurious is offline   Reply With Quote
Old 04-05-2017, 11:45 PM   #10
reapercurious
Human being with feelings
 
reapercurious's Avatar
 
Join Date: Jul 2007
Posts: 1,890
Default

Oops not natural, log base 10
Also I'm getting conflicting examples above. do you square each sample before getting the mean? it makes more sense unless the purpose of squaring is to get the abs since otherwise a sum of positive and negative would average to zero.

Last edited by reapercurious; 04-06-2017 at 12:00 AM.
reapercurious is offline   Reply With Quote
Old 04-06-2017, 12:38 AM   #11
TryingToMakeMusic
Banned
 
Join Date: Nov 2016
Posts: 416
Default

Quote:
Originally Posted by reapercurious View Post
unless the purpose of squaring is to get the abs
If that were the purpose, everyone would use abs instead of squaring. But everyone squares. If you still think abs would make a good substitute for square, just do it?
TryingToMakeMusic is offline   Reply With Quote
Old 04-06-2017, 12:56 AM   #12
bezusheist
Human being with feelings
 
bezusheist's Avatar
 
Join Date: Nov 2010
Location: Mullet
Posts: 829
Default

samples += 1;
square = spl0*spl0;
sum += square;
rms = sqrt(sum/samples);
rms_dB = 20*log10(rms);
bezusheist is offline   Reply With Quote
Old 04-06-2017, 12:29 PM   #13
jcjr
Human being with feelings
 
Join Date: Dec 2015
Location: SE TN USA
Posts: 77
Default

One thing about the squaring-- If you for instance have a sine wave peaking +/- 1.0, and you square it-- You get a nice pure sine wave of double the original frequency, peaking +1.0 / 0.0.

You could sum the values, take the mean, then square root the mean and get the RMS.

But even if you would like to smooth the envelope some other way such as a lowpass filter-- The squaring has advantage because it hasn't added any "distortion series" of harmonics to the pure sine wave, and the output being double the frequency of the input makes it easier to filter/smooth the envelope with less envelope ripple for a given time-constant of the filter.

For instance if you use a smoothing filter that is -3 dB at 40 Hz and -24 dB at 80 Hz, then a squared 40 Hz sine wave would get filtered at -24 dB, rather than the -3 dB of filtering the original 40 Hz sine. So you could get a smoother envelope without making the envelope response "too slow".

Now, abs() of the signal would also give you double the fundamental frequency at +1.0 / 0.0, but the doubled tone would not be a pure sine wave. It would add lots of high harmonics because of the "pointy" artifacts at the pre-abs zero crossings, so your smoothing filter might need to be more aggressive to adequately filter the additional high harmonics out of the envelope. Depends of what purpose that you need the envelope.

I think squaring would also tend to add some "distortion components" to harmonically rich signals, but suspect there would be fewer added high harmonics compared to abs().

You can get RMS with an IIR lowpass filter (rather than summing the squared values)-- 1. Square each sample 2. Feed each squared sample to the lowpass filter (the lowpass filter performs the equivalent of taking the mean of the squared samples) 3. Square-root the filter output whenever you need to know the current RMS level. When you lower the cutoff frequency (raise the time constant) of the lowpass filter, it raises the number of samples which are "averaged" When you raise the cutoff frequency, it "averages" fewer samples and responds quicker and has higher ripple riding on top of the measurement.

https://www.allaboutcircuits.com/tex...-ac-magnitude/

For instance if you abs() the samples and low-pass filter them (with a long-enough filter time constant relative to the tone frequency), then a sine wave input of +1/- 1.0, would measure about 0.637 at the lowpass filter output.

If you square the samples and low-pass them with the same filter you used with the abs() example above, you would measure 0.5 at the lowpass filter output. That is because the squared sine has been doubled in frequency, cut in half of amplitude, and offset so that the bottom peaks kiss 0.0.

However after you square-root the filter ouput, it will measure 0.707 which is the expected RMS value of the sine wave input. The square->filter->square root outputs RMS, and the abs->filter outputs average level.

Another interesting difference is the time-interaction with the envelope smoother, regardless whether it is an IIR or FIR (sum the samples) smoother.

For instance if you have an IIR lowpass smoother which reaches 63% of the max value in 100 ms after the test signal starts, and the smoother reaches 37% of the max value in 100 ms after the test signal stops-- If you filter the squared values then do the square root, the output will attack to 63% in LESS THAN 100 ms, and the output will release to 37% in LONGER THAN 100 ms.

If you have a sum-the-squared-samples FIR smoother which reaches 50% of max value in 100 ms, for both attack and release, then after the square root it is the same deal-- Attack is now shorter than 100 ms and release is now longer than 100 ms. The square root of 0.5 is 0.707. So after the square root, the attack has reached an 0.707 of max after 100 ms, and obviously the attack reached the 0.5 of max sometime BEFORE 100 ms. Same deal for release-- If the filter reached 0.5 max value after 100 ms, then after the square root that is 0.707, and the 0.5 level is reached sometime AFTER 100 ms.

On the other hand, the same filters applied to abs would give different scaling (0.637 vs 0.707) and the time response of attack and release of the filtering would be different.
jcjr is offline   Reply With Quote
Old 04-07-2017, 08:28 PM   #14
SaulT
Human being with feelings
 
Join Date: Oct 2013
Location: Seattle, WA
Posts: 876
Default

Quote:
No idea what that is but it looks cool.
Yeah I just got done reading about dB and the article went to great pains to Stress that I needed natural log()log 10 go figure. Hehe
So what I wrote above is how to solve for dB if you have the ratio. From the top, the proper method of calculating ratio is

ratio = 10^(dB/20)

right? So one way of solving it is to take the natural log of both sides then use some arithmetic to solve. You can use log10 to arrive at basically the same answer.

By definition, and remembering that log10(10) = 1

log10(x) = ln(x)/ln(10)

So

ratio = 10^(dB/20)
log10 (ratio) = dB/20
20 * log10(ratio) = dB

to show that the two calculations are the same, make the substitution as above

20 * ln(ratio)/ln(10) = dB
20 / ln(10) * ln(ratio) = dB
dbc * ln(ratio) = dB

So you can use either function and arrive at the same conclusion

dB = 20 * log10(ratio)
dB = dbc * log(ratio)


FWIW I did a quick test, looping each function 1000 times. I didn't see a significant difference in CPU between log() vs log10(). I have to say that they are significantly slower than using an approximation, though. e.g. (again, looped 1000 times)

dB = 20 * log10(spl0); // 32% CPU

x = spl0;
xx = (x-1)*(x-1);
dB = dbc * (0.5*xx + x - 1)/(0.166667*xx + x); // 5.5%

Just a quick pade approximation, nothing super fancy, it definitely has its range where it is useful... but that gives you an idea.

Last edited by SaulT; 04-07-2017 at 08:44 PM.
SaulT 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 09:47 PM.


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