Page 1 of 1
Help With Ruby Normalization
Posted: Tue Jul 02, 2019 8:22 pm
by adamszabo
Hey everyone!
I am trying to figure out how to mimic the green "Norm" module with Ruby, so that it normalizes an incoming float array to -1 to 1 range. Its description says "Normalises a float array so that the values are between -1 and 1". I tried looking online for some examples but didnt manage to have the same results as the green module. Does anybody have an idea of a formula how it works?
Thanks!
Re: Help With Ruby Normalization
Posted: Tue Jul 02, 2019 8:56 pm
by RJHollins
Try looking for 'Rational Mapper', or 'Float Scaler'.
I know we had some from the SM toolboxes.
Re: Help With Ruby Normalization
Posted: Tue Jul 02, 2019 9:27 pm
by martinvicanek
if a[k] is the array divide each element by max_{k=0...K} |a[k]|
Re: Help With Ruby Normalization
Posted: Tue Jul 02, 2019 10:09 pm
by tulamide
martinvicanek wrote:if a[k] is the array divide each element by max_{k=0...K} |a[k]|
That's producing the range 0, 1 if the numbers are all positive (which for example midi numbers would be). So additionally add ... (- 0.5 * 2) in that case.
Adam, normalizing usually means to set the highest value equal to 1. You do that by dividing all numbers by the highest one. That's what Martin wrote above (in case you are like me and enjoy a little explanation over complicated math).
4/4 = 1
2/4 = 0.5
-2/4 = -0.5
-4/4 = -1
It can get more tricky if the range is somewhat odd, like -2 to 13, or 1 to 9.
EDIT: Ok, here's code in case you indeed want the numbers to be exactly matched to -1 and +1, and don't want to be frustrated by trying it for yourself
Code: Select all
#some array
a = [-4, 5, 8]
#range covered in array
amin = a.min
amax = a.max
arange = amax - amin
#range we want the array to map to
rmin = -1.0
rmax = 1.0
rrange = rmax - rmin
#map the numbers
r = a.map { |n| n = rmin + (n - amin) * (rrange / arange)}
##of course you can change rmin and rmax to your liking, should you need
##to normalize differently (like 0 to 1, for example)
Re: Help With Ruby Normalization
Posted: Tue Jul 02, 2019 11:44 pm
by adamszabo
Thanks guys very much appreciated!
Ive tried these but got different results from the Norm module and I started investigating more and with your guys help I figured it out. What the Norm does it finds the largest value, and multiplies it by the difference so that highest value will reach 1. So it does an ABS on all the values, finds the largest, divides 1 by that, and multiplies the entire array like so:
Code: Select all
a = @input
output 0, a.map { |n| n * 1 / ( (a.map { |n| n.abs }).max )}
Re: Help With Ruby Normalization
Posted: Wed Jul 03, 2019 1:59 am
by trogluddite
adamszabo wrote:So it does an ABS on all the values, finds the largest, divides 1 by that, and multiplies the entire array like so:
Yes; for normalising audio signals, that makes sense - you need to scale around zero so that a DC offset doesn't get introduced.
NB) The code would work much more efficiently like this...
Code: Select all
scale = 1.0 / array.max_by{|x| x.abs }
output(0, array.map{|x| x * scale })
Having the scale calculation inside the mapping code block gives you a loop within a loop - the number of iterations goes up with the square of the number of array elements. With scaling moved outside, it's only double the number of elements.
The method
max_by handily makes it even more efficient because it doesn't need to create a temporary mapped Array before calling
max on it; there's also a
min_by method, and even a
maxmin_by that gets both at once. They come from the
Enumerable module, which is a component of most Ruby containers - it's always worth checking if you don't see what you want under the listings for
Array or
Hash.)
Re: Help With Ruby Normalization
Posted: Wed Jul 03, 2019 5:43 am
by tulamide
trogluddite wrote:adamszabo wrote:So it does an ABS on all the values, finds the largest, divides 1 by that, and multiplies the entire array like so:
Yes; for normalising audio signals, that makes sense - you need to scale around zero so that a DC offset doesn't get introduced.
NB) The code would work much more efficiently like this...
Code: Select all
scale = 1.0 / array.max_by{|x| x.abs }
output(0, array.map{|x| x * scale })
Having the scale calculation inside the mapping code block gives you a loop within a loop - the number of iterations goes up with the square of the number of array elements. With scaling moved outside, it's only double the number of elements.
The method
max_by handily makes it even more efficient because it doesn't need to create a temporary mapped Array before calling
max on it; there's also a
min_by method, and even a
maxmin_by that gets both at once. They come from the
Enumerable module, which is a component of most Ruby containers - it's always worth checking if you don't see what you want under the listings for
Array or
Hash.)
Now that I see Adam's finding and a nice, clean code from Trog, I think I have to apologize to Martin. Doesn't |a[k]| mean the product of (or absolute of) a for k = 0 to array length? Or do I start to drift right now?
Re: Help With Ruby Normalization
Posted: Wed Jul 03, 2019 6:19 am
by martinvicanek
tulamide wrote:Doesn't |a[k]| mean the product of (or absolute of) a for k = 0 to array length?
Yes, I meant the maximum of all absolute values.

Re: Help With Ruby Normalization
Posted: Wed Jul 03, 2019 9:29 am
by adamszabo
Thanks you guys are awesome!
Trog your code is great, however I noticed that sometimes my graph was inverted and I noticed that your way of getting abs was givingdifferent results. So if I have a dataset like this:
0
-0.9
0.7
My one "(@input.map { |n| n.abs }).max" gives 0.9
And your one "@input.max_by{|x| x.abs }" gives -0.9
So we have to make another abs on the scale itself, and maybe add a small value to prevent it from getting #IND when dividing by zero?
scale = 1.0 / (@input.max_by{|x| x.abs } + 0.00001)
output(0, @input.map{|x| x * scale.abs })
Re: Help With Ruby Normalization
Posted: Wed Jul 03, 2019 9:44 pm
by trogluddite
Oops!
Yes, you're quite right, Adam. The
max_by method returns the original Array element, not the version transformed by the code block, so you need to abs what it returns.
You don't need the infinity check, though. Infinities are treated as valid number values by the CPU - wherever there is a defined mathematical result, the calculation will work; and dividing by infinity is defined to always return zero (as confirmed by stringing together a couple of float divide primitives.) BTW; strictly speaking, your code doesn't entirely exclude zero - you need to
abs before adding the offset, otherwise you get zero if the sample is exactly -0.00001!