Help With Ruby Normalization

For general discussion related FlowStone
Post Reply
adamszabo
Posts: 667
Joined: Sun Jul 11, 2010 7:21 am

Help With Ruby Normalization

Post 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!
RJHollins
Posts: 1573
Joined: Thu Mar 08, 2012 7:58 pm

Re: Help With Ruby Normalization

Post by RJHollins »

Try looking for 'Rational Mapper', or 'Float Scaler'.

I know we had some from the SM toolboxes.
User avatar
martinvicanek
Posts: 1334
Joined: Sat Jun 22, 2013 8:28 pm

Re: Help With Ruby Normalization

Post by martinvicanek »

if a[k] is the array divide each element by max_{k=0...K} |a[k]|
tulamide
Posts: 2714
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany

Re: Help With Ruby Normalization

Post 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)
"There lies the dog buried" (German saying translated literally)
adamszabo
Posts: 667
Joined: Sun Jul 11, 2010 7:21 am

Re: Help With Ruby Normalization

Post 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 )}
User avatar
trogluddite
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Help With Ruby Normalization

Post 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.)
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
tulamide
Posts: 2714
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany

Re: Help With Ruby Normalization

Post 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?
"There lies the dog buried" (German saying translated literally)
User avatar
martinvicanek
Posts: 1334
Joined: Sat Jun 22, 2013 8:28 pm

Re: Help With Ruby Normalization

Post 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. ;)
adamszabo
Posts: 667
Joined: Sun Jul 11, 2010 7:21 am

Re: Help With Ruby Normalization

Post 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 })
User avatar
trogluddite
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: Help With Ruby Normalization

Post by trogluddite »

Oops! :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!
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
Post Reply