Move logarithmic volume transformation to AudioMixer

Previously, the volume transformation to adapt the volume for
logarithmic hearing has been performed in the VolumeControls Rust struct
and TypeScript class each.
Since this calculation is the same on desktop and web and should be
implemented in the audio backend, it has been moved into the
AudioMixer::mix_audio method.
The VolumeControls struct and class now only calculate the linear volume
out of the checkbox and the slider.
Player::set_volume and Player::volume now don't take and return the
adapted volume, but use the linear volume (which gets saved internally).
This commit is contained in:
Kornelius Rohrschneider 2023-08-20 13:33:34 +02:00 committed by Adrian Wielgosik
parent f04470247e
commit 53ba75d587
4 changed files with 27 additions and 31 deletions

View File

@ -446,7 +446,8 @@ impl AudioMixer {
}; };
use std::ops::DerefMut; use std::ops::DerefMut;
let volume = volume.to_sample(); // Adapt the volume for logarithmic hearing.
let volume = ((10_f32.powf(81_f32.log10() * volume) - 1.0) / 80.0).to_sample();
// For each sample, mix the samples from all active sound instances. // For each sample, mix the samples from all active sound instances.
for buf_frame in output_buffer for buf_frame in output_buffer

View File

@ -596,11 +596,15 @@ impl Player {
} }
/// Returns the master volume of the player. 1.0 is 100% volume. /// Returns the master volume of the player. 1.0 is 100% volume.
///
/// The volume is linear and not adapted for logarithmic hearing.
pub fn volume(&self) -> f32 { pub fn volume(&self) -> f32 {
self.audio.volume() self.audio.volume()
} }
/// Sets the master volume of the player. 1.0 is 100% volume. /// Sets the master volume of the player. 1.0 is 100% volume.
///
/// The volume should be linear and not adapted for logarithmic hearing.
pub fn set_volume(&mut self, volume: f32) { pub fn set_volume(&mut self, volume: f32) {
self.audio.set_volume(volume) self.audio.set_volume(volume)
} }

View File

@ -190,7 +190,7 @@ impl RuffleGui {
self.locale.clone(), self.locale.clone(),
); );
player.set_volume(self.volume_controls.get_real_volume()); player.set_volume(self.volume_controls.get_volume());
} }
/// Renders the main menu bar at the top of the window. /// Renders the main menu bar at the top of the window.
@ -440,7 +440,7 @@ impl RuffleGui {
if changed_checkbox || changed_slider { if changed_checkbox || changed_slider {
if let Some(player) = player { if let Some(player) = player {
player.set_volume(self.volume_controls.get_real_volume()); player.set_volume(self.volume_controls.get_volume());
} }
} }
}); });
@ -555,15 +555,11 @@ impl VolumeControls {
Self { is_muted, volume } Self { is_muted, volume }
} }
/// Calculates the real volume (between 0 and 1) out of the entered volume /// Returns the volume between 0 and 1 (calculated out of the
/// (between 0 and 100) and the mute checkbox. /// checkbox and the slider).
/// fn get_volume(&self) -> f32 {
/// This is also necessary because human hearing is logarithmic:
/// What sounds like half as loud (and should be 50% on the scale) is actually
/// about 1/10th of the real volume.
pub fn get_real_volume(&self) -> f32 {
if !self.is_muted { if !self.is_muted {
(10_f32.powf(81_f32.log10() * self.volume / 100.0) - 1.0) / 80.0 self.volume / 100.0
} else { } else {
0.0 0.0
} }

View File

@ -268,7 +268,7 @@ export class RufflePlayer extends HTMLElement {
this.addModalJavaScript(this.volumeControls); this.addModalJavaScript(this.volumeControls);
this.addModalJavaScript(this.videoModal); this.addModalJavaScript(this.videoModal);
this.volumeSettings = new VolumeControls(false, 1.0); this.volumeSettings = new VolumeControls(false, 100);
this.addVolumeControlsJavaScript(this.volumeControls); this.addVolumeControlsJavaScript(this.volumeControls);
const backupSaves = <HTMLElement>( const backupSaves = <HTMLElement>(
@ -413,12 +413,12 @@ export class RufflePlayer extends HTMLElement {
? "grey" ? "grey"
: "black"; : "black";
this.volumeSettings.isMuted = muteCheckbox.checked; this.volumeSettings.isMuted = muteCheckbox.checked;
this.instance?.set_volume(this.volumeSettings.get_real_volume()); this.instance?.set_volume(this.volumeSettings.get_volume());
}); });
volumeSlider.addEventListener("input", () => { volumeSlider.addEventListener("input", () => {
volumeSliderText.textContent = volumeSlider.value; volumeSliderText.textContent = volumeSlider.value;
this.volumeSettings.volume = volumeSlider.valueAsNumber; this.volumeSettings.volume = volumeSlider.valueAsNumber;
this.instance?.set_volume(this.volumeSettings.get_real_volume()); this.instance?.set_volume(this.volumeSettings.get_volume());
}); });
} }
@ -649,7 +649,8 @@ export class RufflePlayer extends HTMLElement {
this, this,
this.loadedConfig, this.loadedConfig,
); );
this.instance!.set_volume(this.volumeSettings.get_real_volume()); this.instance!.set_volume(this.volumeSettings.get_volume());
this.rendererDebugInfo = this.instance!.renderer_debug_info(); this.rendererDebugInfo = this.instance!.renderer_debug_info();
const actuallyUsedRendererName = this.instance!.renderer_name(); const actuallyUsedRendererName = this.instance!.renderer_name();
@ -910,6 +911,8 @@ export class RufflePlayer extends HTMLElement {
/** /**
* Returns the master volume of the player. * Returns the master volume of the player.
* *
* The volume is linear and not adapted for logarithmic hearing.
*
* @returns The volume. 1.0 is 100% volume. * @returns The volume. 1.0 is 100% volume.
*/ */
get volume(): number { get volume(): number {
@ -922,6 +925,8 @@ export class RufflePlayer extends HTMLElement {
/** /**
* Sets the master volume of the player. * Sets the master volume of the player.
* *
* The volume should be linear and not adapted for logarithmic hearing.
*
* @param value The volume. 1.0 is 100% volume. * @param value The volume. 1.0 is 100% volume.
*/ */
set volume(value: number) { set volume(value: number) {
@ -2371,26 +2376,16 @@ class VolumeControls {
constructor(isMuted: boolean, volume: number) { constructor(isMuted: boolean, volume: number) {
this.isMuted = isMuted; this.isMuted = isMuted;
this.volume = volume * 100; this.volume = volume;
} }
/** /**
* Calculates the real volume (between 0 and 1) out of the entered volume * Returns the volume between 0 and 1 (calculated out of the
* (between 0 and 100) and the mute checkbox. * checkbox and the slider).
* *
* This is also necessary because human hearing is logarithmic: * @returns The volume between 0 and 1.
* What sounds like half as loud (and should be 50% on the scale) is actually
* about 1/10th of the real volume.
*
* @returns The real volume.
*/ */
public get_real_volume(): number { public get_volume(): number {
if (!this.isMuted) { return !this.isMuted ? this.volume / 100 : 0;
return (
(Math.pow(10, (Math.log10(81) * this.volume) / 100) - 1) / 80
);
} else {
return 0;
}
} }
} }