32

Let's say I have a slider that can go between 0 and 1. The SoundTransform.volume also ranges between 0 (silent) and 1 (full volume), but if I use a linear function, let's say SoundTransform.volume = slider.volume, the result is rather not pleasing - the perception is that the volume dramatically changes in the lower half and does almost nothing in the upper half of the slider.

I really haven't studied the human ear, but I overheard once that human perception is logarithmic, or something similar. What algorithms should I use for setting the SoundTransform.volume?

Kromster
  • 7,181
  • 7
  • 63
  • 111
evilpenguin
  • 5,448
  • 6
  • 41
  • 49
  • The perception is that the volume dramatically changes in the lower half and does almost nothing in the upper half of the slider. – evilpenguin Jul 23 '09 at 09:10

8 Answers8

31

human perception in general is logarithmic, also when it comes to things as luminosity, etc. ... this enables us to register small changes to small "input signals" of our environement, or to put it another way: to always percieve a change of a perceivable physical quantity in relation to its value ...

thus, you should modify the volume to grow exponentially, like this:

y = (Math.exp(x)-1)/(Math.E-1)

you can try other bases as well:

y = (Math.pow(base,x)-1)/(base-1)

the bigger the value of base is, the stronger the effect, the slower volume starts growing in the beginning and the faster it grows in the end ...

a slighty simpler approach, giving you similar results (you are only in the interval between 0 and 1, so approximations are quite simple, actually), is to exponantiate the original value, as

y = Math.pow(x, exp);

for exp bigger than 1, the effect is, that the output (i.e. the volume in you case) first goes up slower, and then faster towards the end ... this is very similar to exponential functions ... the bigger exp, the stronger the effect ...

Kromster
  • 7,181
  • 7
  • 63
  • 111
back2dos
  • 15,588
  • 34
  • 50
9

Human hearing is logarithmic, so you want an exponential function (the inverse) to apply to the linear output of your slider. I don't know if human hearing is closer to ln or log:

For Ln:

e^x

For Log:

10^x

You could experiment with other bases too. You will then need to scale your output so that it covers the available range of values.

Update

After a bit of research it seems that base 2 would be appropriate since the power is related to the square of the pressure. If anyone knows better, please correct me.

I think what you want is:

v' = 2^v.a^v - 1
a  = ( 2^(log2(m+1)/n) )/2

v is your linear input value ranging from 0..n v' is your logarithmic value ranging from 0..m

The -1 in the first equation is to give you an output range from 0 instead of 1 (since k^0=1).

The m+1 is to compensate for this so you get 0..m not 0..m+1

You can of course get tweak this to suit your requirements.

Draemon
  • 33,955
  • 16
  • 77
  • 104
  • 1
    e^x and 10^x will essentially scale the same way. They are in fact just a constant factor away. Just play with a scaling factor and ignore the base. – ReaperUnreal Jul 22 '09 at 14:50
  • 3
    They're not a constant factor away: e^x = (e/10)^x . 10^x – Draemon Jul 22 '09 at 16:48
  • the idea of bringing in the 2 from the square seems very interesting to me, but the causality seems a little weak ... it would mean, that when a sound source with constant volume moves towards you, with linear speed, the resulting change perceived by you is also linear ... i personally think, i don't perceive it like that, but that may be due to the doppler effect ... also, there are rarely sources with constant volume, in nature, so i don't see, why evolution would chose this particular exponent ... but who knows ^^ – back2dos Jul 27 '09 at 13:08
  • @ReaperUnreal: You're thinking of logarithms: `ln(x) = log(x)/log(e)` – BlueRaja - Danny Pflughoeft Mar 06 '13 at 20:41
5

Hearing is complicated, the perceived loudness varies according to frequency, the duration of the sample, and from person to person. So this cannot be solved mathematically but by trying a variety of functions for the control and picking the one which 'feels' the best.

Do you find at the moment that varying the control at the low end of the range has little effect on the apparent volume, but that the volume increases rapidly at the upper end of the range? Or do you hear the reverse, the volume varies too quickly at the low end and not enough at the high end? Or would you like finer control over the volume at medium levels?

Increased low-volume sensitivity:

SoundTransform.volume = Math.sin(x * Math.PI / 2);

Increased high-volume sensitivity:

SoundTransform.volume = (Math.pow(base,x) - 1)/(base-1);

or

SoundTransform.volume = Math.pow(x, base);

Where base > 1, try different values and see how it feels. Or more drastically, a 90 degree circular arc:

SoundTransform.volume = 1 - Math.sqrt(1-(x * x));

Where x is slider.volume and is between 0 and 1.

Please do let us know how you get on!

Matt Howells
  • 40,310
  • 20
  • 83
  • 102
2

Yes, human perception is logarithmic. Considering this, you should adjust a volume exponentially, so that the percieved increase becomes linear. See decibel on Wikipedia

Tamás Szelei
  • 23,169
  • 18
  • 105
  • 180
2

Android already do such things from Audio Framework.It use decibels to adjust the volumes. User can use steps such as from 1 to 7 for ringtone or 1 to 15 for music. The formula is:

User call set volume API linearly but get amplitude exponentially. graph as below: enter image description here

Community
  • 1
  • 1
beetlej
  • 1,841
  • 4
  • 13
  • 27
1

A 3db increase means you are doubling the volume, but the human ear requires ~6db increase to perceive a doubling in volume.

However, a strictly logarithmic curve, while accurately modeling the human perception of volume, has a usability problem.

When people want a loud volume, the knob becomes too sensitive at the upper end, making it difficult to find the "right" volume.

You've probably had this problem before... 7 is too soft, 8 is too loud, meanwhile 1-3 are inaudible over background noise.

So, I recommend a logarithmic scale, but with a floor at the low end and a soft knee at the top to allow a more linear response, especially in the "loud" part of the knob.

Oh, and make sure the knob goes up to 11. ;)

richardtallent
  • 34,724
  • 14
  • 83
  • 123
  • Is this a well-known principal? Does it have a name or standard? I know a linear scale is wrong/common mistake (quiet volume too sensitive) and a log/exponential scale has the problem you describe (loud volume is too sensitive). Apple headphone jacks and other established electronics use something in the middle (e.g. 2dB steps for loud and 3dB steps for quiet) – aandroyd Oct 30 '20 at 20:08
0

The human ear indeed perceives sounds on a logarithmic scale of increasing intensity, and because of that, the unit generally used to measure acoustic intensity is the decibel (which is actually used for all sorts of intensities and powers, not just those of sound, and also happens to be a dimensionless unit). The reference level, 0 dB, is usually set to the lower bound of human hearing, and every ten-decibel increase above that is equivalent to an increase in power by a factor of 10.

Note, however, that you should first check with other people and see what they think, just in case; what sounds odd to you may not sound odd to others. If they agree with you, then go right ahead and do it exponentially, but if you're in the minority, then it might just be your own ears that are the problem.

EDIT: Ignore my previous third paragraph. Refer to back2dos's answer if you decide to do it exponentially.

JAB
  • 20,783
  • 6
  • 71
  • 80
  • There are many types of function where f(0) = 0 and f(1) = 1. – Matt Howells Jul 22 '09 at 14:25
  • Indeed, and if I had actually worked it out then (using f(x) = a * 10^x - b, with f(0) = 0 and f(1) = 1), I would have realized that. Instead I was careless and didn't even try calculating it out. – JAB Jul 22 '09 at 15:33
0

This is a javascript function i have for a logarithmic scale for dbm. The input is a percentage (0.00 to 1.00) and the max value (my implementation uses 12db)

The mid point is set to 0.5 and that will be 0db.

When the percentage is zero, the output is negative Infinity.

function percentageToDb(p, max) {
     return max * (1 - (Math.log(p) / Math.log(0.5)));
};
san1t1
  • 41
  • 1