[Enhancement] Logarithmic Volume Control
Description
Human perception of loudness is logarithmic, not linear. As such, most volume controls cater to the human ear using logarithmic scaling. (source, and an other source)
Could this be added to video.js?
Reduced test case
all
Steps to reproduce
n/a
Errors
No response
What version of Video.js are you using?
7.12.1
Video.js plugins used.
https://github.com/iv-org/invidious/blob/master/videojs-dependencies.yml
What browser(s) including version(s) does this occur with?
firefox 119.0.1
What OS(es) and version(s) does this occur with?
Linux (nixos and manjaro)
👋 Thanks for opening your first issue here! 👋
If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can. To help make it easier for us to investigate your issue, please follow the contributing guidelines.
Discord have released an MIT licensed library for doing the translation between perceptual and linear volume space, which looks like it should be a drop-in solution here. https://github.com/discord/perceptual
A quick way would be to use a middleware to override what is sent to / returned from the tech (video el) when volume is set or got. Although this impacts anything using Video.js's API, not just the UI control
videojs.use('*', function() {
return {
volume: (v) => {
return Math.sqrt(v);
},
setVolume: (v) => {
return v ** 2;
}
}
});
Power of two isn't the correct transfer function for perceptual volume; the RMS deviation from the proper perceptual curve is about 20%, and in the worst case results in the linearised volume being more than twice what it should be at the ~75% mark.
Better transfer functions would be:
function perceptualToLinear(p)
{
const dbRange = 50;
const offset = Math.pow(10, -dbRange / 20);
return Math.pow(10, (p * dbRange - dbRange) / 20) * (1 + dbRange);
}
function linearToPerceptual(v)
{
const dbRange = 50;
const offset = Math.pow(10, -dbRange / 20);
return 20 * Math.log10((x / (1 + offset) + offset) + 50);
}
These functions implement normalised transfer functions which pass through (0,0) and (1,1).
My suggestion would be to implement the transfer functions within the VolumeBar control, since it's the physical slider control which is primarily affected due to the increased dexterity required to make small perceptual volume modifications at the quiet end of the scale. This leaves the API unaffected, allowing 3rd party code to implement whatever transfer function they like.
From an architecture and maintenance perspective it may be prudent to implement the volume transfer functions as their own classes, i.e. one for linear and one for log perceptual, in a module, to separate them from the UI code. This also then allows 3rd party code to take advantage of the exact implementation being used if they want to, by importing the same classes.