use crate::prelude::*;
use bevy_ecs::component::Component;
use bevy_math::prelude::*;
#[derive(Debug, Component, Clone, Copy)]
pub struct PerceptualVolume {
pub pivot_pos: f32,
pub pivot_volume: Volume,
}
impl PerceptualVolume {
pub const fn new() -> Self {
Self {
pivot_volume: Volume::Decibels(-50.0),
pivot_pos: 0.01,
}
}
}
impl Default for PerceptualVolume {
fn default() -> Self {
Self::new()
}
}
impl PerceptualVolume {
pub fn perceptual_to_volume(&self, perceptual: f32) -> Volume {
let perceptual = perceptual.max(0f32);
if perceptual < self.pivot_pos {
let min = 0.0_f32;
let max = self.pivot_volume.linear();
let t = perceptual / self.pivot_pos;
Volume::Linear(min.lerp(max, t))
} else {
let min = self.pivot_volume.decibels();
let max = 0.0;
let t = (perceptual - self.pivot_pos) / (1.0 - self.pivot_pos);
Volume::Decibels(min.lerp(max, t))
}
}
pub fn volume_to_perceptual(&self, volume: Volume) -> f32 {
if volume.linear() <= self.pivot_volume.linear() {
let vol = volume.linear();
let pivot = self.pivot_volume.linear();
let t = vol / pivot;
t * self.pivot_pos
} else {
let vol = volume.decibels();
let pivot = self.pivot_volume.decibels();
let t = (vol - pivot) / (0.0 - pivot);
self.pivot_pos + t * (1.0 - self.pivot_pos)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip() {
let converter = PerceptualVolume::default();
for i in 0..100 {
let percent = -0.1 + i as f32 / 90.0;
let volume = converter.perceptual_to_volume(percent);
let perceptual = converter.volume_to_perceptual(volume);
if percent < 0.0 {
assert_eq!(perceptual, 0.0);
} else {
assert!(
(perceptual - percent).abs() < 0.0001,
"perceptual: {perceptual}, percent: {percent}"
);
}
}
}
}