use super::VolumeCtrl;
use crate::player::db_to_ratio;
pub trait MappedCtrl {
fn to_mapped(&self, volume: u16) -> f64;
fn as_unmapped(&self, mapped_volume: f64) -> u16;
fn db_range(&self) -> f64;
fn set_db_range(&mut self, new_db_range: f64);
fn range_ok(&self) -> bool;
}
impl MappedCtrl for VolumeCtrl {
fn to_mapped(&self, volume: u16) -> f64 {
if volume == 0 {
return 0.0;
} else if volume == Self::MAX_VOLUME {
return 1.0;
}
let normalized_volume = volume as f64 / Self::MAX_VOLUME as f64;
let mapped_volume = if self.range_ok() {
match *self {
Self::Cubic(db_range) => {
CubicMapping::linear_to_mapped(normalized_volume, db_range)
}
Self::Log(db_range) => LogMapping::linear_to_mapped(normalized_volume, db_range),
_ => normalized_volume,
}
} else {
error!("{self:?} does not work with 0 dB range, using linear mapping instead");
normalized_volume
};
debug!(
"Input volume {} mapped to: {:.2}%",
volume,
mapped_volume * 100.0
);
mapped_volume
}
fn as_unmapped(&self, mapped_volume: f64) -> u16 {
if f64::abs(mapped_volume - 0.0) <= f64::EPSILON {
return 0;
} else if f64::abs(mapped_volume - 1.0) <= f64::EPSILON {
return Self::MAX_VOLUME;
}
let unmapped_volume = if self.range_ok() {
match *self {
Self::Cubic(db_range) => CubicMapping::mapped_to_linear(mapped_volume, db_range),
Self::Log(db_range) => LogMapping::mapped_to_linear(mapped_volume, db_range),
_ => mapped_volume,
}
} else {
error!("{self:?} does not work with 0 dB range, using linear mapping instead");
mapped_volume
};
(unmapped_volume * Self::MAX_VOLUME as f64) as u16
}
fn db_range(&self) -> f64 {
match *self {
Self::Fixed => 0.0,
Self::Linear => Self::DEFAULT_DB_RANGE, Self::Log(db_range) | Self::Cubic(db_range) => db_range,
}
}
fn set_db_range(&mut self, new_db_range: f64) {
match self {
Self::Cubic(db_range) | Self::Log(db_range) => *db_range = new_db_range,
_ => error!("Invalid to set dB range for volume control type {self:?}"),
}
debug!("Volume control is now {self:?}")
}
fn range_ok(&self) -> bool {
self.db_range() > 0.0 || matches!(self, Self::Fixed | Self::Linear)
}
}
pub trait VolumeMapping {
fn linear_to_mapped(unmapped_volume: f64, db_range: f64) -> f64;
fn mapped_to_linear(mapped_volume: f64, db_range: f64) -> f64;
}
pub struct LogMapping {}
impl VolumeMapping for LogMapping {
fn linear_to_mapped(normalized_volume: f64, db_range: f64) -> f64 {
let (db_ratio, ideal_factor) = Self::coefficients(db_range);
f64::exp(ideal_factor * normalized_volume) / db_ratio
}
fn mapped_to_linear(mapped_volume: f64, db_range: f64) -> f64 {
let (db_ratio, ideal_factor) = Self::coefficients(db_range);
f64::ln(db_ratio * mapped_volume) / ideal_factor
}
}
impl LogMapping {
fn coefficients(db_range: f64) -> (f64, f64) {
let db_ratio = db_to_ratio(db_range);
let ideal_factor = f64::ln(db_ratio);
(db_ratio, ideal_factor)
}
}
pub struct CubicMapping {}
impl VolumeMapping for CubicMapping {
fn linear_to_mapped(normalized_volume: f64, db_range: f64) -> f64 {
let min_norm = Self::min_norm(db_range);
f64::powi(normalized_volume * (1.0 - min_norm) + min_norm, 3)
}
fn mapped_to_linear(mapped_volume: f64, db_range: f64) -> f64 {
let min_norm = Self::min_norm(db_range);
(mapped_volume.powf(1.0 / 3.0) - min_norm) / (1.0 - min_norm)
}
}
impl CubicMapping {
fn min_norm(db_range: f64) -> f64 {
f64::powf(10.0, -db_range / 60.0)
}
}