diff --git a/changes/changelog.d/1222.enhancement b/changes/changelog.d/1222.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..c896000e80f95507f95a934e740f1277ac566eb9 --- /dev/null +++ b/changes/changelog.d/1222.enhancement @@ -0,0 +1 @@ +Logarithmic scale for volume slider (#1222) \ No newline at end of file diff --git a/front/src/audio/volume.js b/front/src/audio/volume.js new file mode 100644 index 0000000000000000000000000000000000000000..25f6c0ed2d6d620e32a14eb8dbd76ac02ca1969a --- /dev/null +++ b/front/src/audio/volume.js @@ -0,0 +1,32 @@ + +// Provides functions to convert between linear and logarithmic volume scales. +// The logarithmic volume from the UI is converted to a linear volume with a +// logarithmic function like exp(b*x)/a. +// Compare https://www.dr-lex.be/info-stuff/volumecontrols.html for how the +// values for a and b got derived. + +const PARAM_A = 1000 +const PARAM_B = Math.log(1000) // ~ 6.908 + +function toLinearVolumeScale(v) { + // Or as approximation: + // return Math.pow(v, 4) + if (v == 0.0) { + return 0.0 + } + + return Math.min(Math.exp(PARAM_B * v) / PARAM_A, 1.0) +} + +function toLogarithmicVolumeScale(v) { + // Or as approximation: + // return Math.exp(Math.log(v) / 4) + if (v == 0.0) { + return 0.0 + } + + return Math.log(v * PARAM_A) / PARAM_B +} + +exports.toLinearVolumeScale = toLinearVolumeScale +exports.toLogarithmicVolumeScale = toLogarithmicVolumeScale diff --git a/front/src/components/audio/VolumeControl.vue b/front/src/components/audio/VolumeControl.vue index 4635d9763930554a3245abdfc6c38e8e33d0a3e7..47a2a9594e4a233fbbe903b8789ddbe751f6fe89 100644 --- a/front/src/components/audio/VolumeControl.vue +++ b/front/src/components/audio/VolumeControl.vue @@ -29,7 +29,7 @@ <input id="volume-slider" type="range" - step="0.05" + step="0.02" min="0" max="1" v-model="sliderVolume" /> @@ -38,6 +38,7 @@ </template> <script> import { mapState, mapGetters, mapActions } from "vuex" +import { toLinearVolumeScale, toLogarithmicVolumeScale } from '@/audio/volume' export default { data () { @@ -49,10 +50,10 @@ export default { computed: { sliderVolume: { get () { - return this.$store.state.player.volume + return toLogarithmicVolumeScale(this.$store.state.player.volume) }, set (v) { - this.$store.commit("player/volume", v) + this.$store.commit("player/volume", toLinearVolumeScale(v)) } }, labels () { diff --git a/front/tests/unit/specs/audio/volume.spec.js b/front/tests/unit/specs/audio/volume.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..61ac8e9c9cbdf718b933e5be3159a20e1c4db6f7 --- /dev/null +++ b/front/tests/unit/specs/audio/volume.spec.js @@ -0,0 +1,34 @@ +import { expect } from 'chai' + +import { toLinearVolumeScale, toLogarithmicVolumeScale } from '@/audio/volume' + +describe('store/auth', () => { + describe('toLinearVolumeScale', () => { + describe('it should return real 0', () => { + expect(toLinearVolumeScale(0.0)).to.equal(0.0) + }) + + describe('it should have logarithmic scale', () => { + expect(2 * toLinearVolumeScale(0.5)).to.be.closeTo(toLinearVolumeScale(0.6), 0.001) + }) + + describe('it should return full volume', () => { + expect(toLogarithmicVolumeScale(1.0)).to.be.closeTo(1.0, 0.001) + }) + }) + + describe('toLogarithmicVolumeScale', () => { + describe('it should return real 0', () => { + expect(toLogarithmicVolumeScale(0.0)).to.equal(0.0) + }) + + describe('it should have logarithmic scale', () => { + expect(toLogarithmicVolumeScale(0.6)).to.be.closeTo(0.9261, 0.001) + expect(toLogarithmicVolumeScale(0.7)).to.be.closeTo(0.9483, 0.001) + }) + + describe('it should return full volume', () => { + expect(toLogarithmicVolumeScale(1.0)).to.be.closeTo(1.0, 0.001) + }) + }) +}) \ No newline at end of file