librespot_playback/mixer/
mappings.rs1use super::VolumeCtrl;
2use crate::player::db_to_ratio;
3
4pub trait MappedCtrl {
5 fn to_mapped(&self, volume: u16) -> f64;
6 fn as_unmapped(&self, mapped_volume: f64) -> u16;
7
8 fn db_range(&self) -> f64;
9 fn set_db_range(&mut self, new_db_range: f64);
10 fn range_ok(&self) -> bool;
11}
12
13impl MappedCtrl for VolumeCtrl {
14 fn to_mapped(&self, volume: u16) -> f64 {
15 if volume == 0 {
19 return 0.0;
20 } else if volume == Self::MAX_VOLUME {
21 return 1.0;
23 }
24
25 let normalized_volume = volume as f64 / Self::MAX_VOLUME as f64;
26 let mapped_volume = if self.range_ok() {
27 match *self {
28 Self::Cubic(db_range) => {
29 CubicMapping::linear_to_mapped(normalized_volume, db_range)
30 }
31 Self::Log(db_range) => LogMapping::linear_to_mapped(normalized_volume, db_range),
32 _ => normalized_volume,
33 }
34 } else {
35 error!("{self:?} does not work with 0 dB range, using linear mapping instead");
37 normalized_volume
38 };
39
40 debug!(
41 "Input volume {} mapped to: {:.2}%",
42 volume,
43 mapped_volume * 100.0
44 );
45
46 mapped_volume
47 }
48
49 fn as_unmapped(&self, mapped_volume: f64) -> u16 {
50 if f64::abs(mapped_volume - 0.0) <= f64::EPSILON {
54 return 0;
55 } else if f64::abs(mapped_volume - 1.0) <= f64::EPSILON {
56 return Self::MAX_VOLUME;
57 }
58
59 let unmapped_volume = if self.range_ok() {
60 match *self {
61 Self::Cubic(db_range) => CubicMapping::mapped_to_linear(mapped_volume, db_range),
62 Self::Log(db_range) => LogMapping::mapped_to_linear(mapped_volume, db_range),
63 _ => mapped_volume,
64 }
65 } else {
66 error!("{self:?} does not work with 0 dB range, using linear mapping instead");
68 mapped_volume
69 };
70
71 (unmapped_volume * Self::MAX_VOLUME as f64) as u16
72 }
73
74 fn db_range(&self) -> f64 {
75 match *self {
76 Self::Fixed => 0.0,
77 Self::Linear => Self::DEFAULT_DB_RANGE, Self::Log(db_range) | Self::Cubic(db_range) => db_range,
79 }
80 }
81
82 fn set_db_range(&mut self, new_db_range: f64) {
83 match self {
84 Self::Cubic(db_range) | Self::Log(db_range) => *db_range = new_db_range,
85 _ => error!("Invalid to set dB range for volume control type {self:?}"),
86 }
87
88 debug!("Volume control is now {self:?}")
89 }
90
91 fn range_ok(&self) -> bool {
92 self.db_range() > 0.0 || matches!(self, Self::Fixed | Self::Linear)
93 }
94}
95
96pub trait VolumeMapping {
97 fn linear_to_mapped(unmapped_volume: f64, db_range: f64) -> f64;
98 fn mapped_to_linear(mapped_volume: f64, db_range: f64) -> f64;
99}
100
101pub struct LogMapping {}
106impl VolumeMapping for LogMapping {
107 fn linear_to_mapped(normalized_volume: f64, db_range: f64) -> f64 {
108 let (db_ratio, ideal_factor) = Self::coefficients(db_range);
109 f64::exp(ideal_factor * normalized_volume) / db_ratio
110 }
111
112 fn mapped_to_linear(mapped_volume: f64, db_range: f64) -> f64 {
113 let (db_ratio, ideal_factor) = Self::coefficients(db_range);
114 f64::ln(db_ratio * mapped_volume) / ideal_factor
115 }
116}
117
118impl LogMapping {
119 fn coefficients(db_range: f64) -> (f64, f64) {
120 let db_ratio = db_to_ratio(db_range);
121 let ideal_factor = f64::ln(db_ratio);
122 (db_ratio, ideal_factor)
123 }
124}
125
126pub struct CubicMapping {}
139impl VolumeMapping for CubicMapping {
140 fn linear_to_mapped(normalized_volume: f64, db_range: f64) -> f64 {
141 let min_norm = Self::min_norm(db_range);
142 f64::powi(normalized_volume * (1.0 - min_norm) + min_norm, 3)
143 }
144
145 fn mapped_to_linear(mapped_volume: f64, db_range: f64) -> f64 {
146 let min_norm = Self::min_norm(db_range);
147 (mapped_volume.powf(1.0 / 3.0) - min_norm) / (1.0 - min_norm)
148 }
149}
150
151impl CubicMapping {
152 fn min_norm(db_range: f64) -> f64 {
153 f64::powf(10.0, -db_range / 60.0)
156 }
157}