1use std::fmt;
11
12use crate::{
13 defaults::{FloatType, IntegerType},
14 error::{Error, Result},
15};
16
17const UNSET_VELOCITY_SHIFT: FloatType = 0.20866;
21
22const BASE_LEVEL: FloatType = 0.5;
24
25#[derive(Clone, Debug, PartialEq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[must_use]
29pub struct Volume {
30 velocity_scalar: Option<FloatType>,
31 velocity_is_relative: bool,
32}
33
34impl Default for Volume {
35 fn default() -> Self {
38 Self {
39 velocity_scalar: None,
40 velocity_is_relative: true,
41 }
42 }
43}
44
45impl Volume {
46 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn merge_attributes(&mut self, other: &Volume) {
54 self.velocity_scalar = other.velocity_scalar;
55 self.velocity_is_relative = other.velocity_is_relative;
56 }
57
58 pub fn from_velocity(velocity: IntegerType) -> Self {
60 let mut volume = Self::new();
61 volume.set_velocity(Some(velocity));
62 volume
63 }
64
65 pub fn from_velocity_scalar(scalar: FloatType) -> Result<Self> {
67 let mut volume = Self::new();
68 volume.set_velocity_scalar(Some(scalar))?;
69 Ok(volume)
70 }
71
72 pub fn velocity(&self) -> Option<IntegerType> {
74 let scalar = self.velocity_scalar?;
75 let velocity = (scalar * 127.0).clamp(0.0, 127.0);
76 Some(velocity.round_ties_even() as IntegerType)
77 }
78
79 pub fn set_velocity(&mut self, velocity: Option<IntegerType>) {
81 self.velocity_scalar = velocity.map(|velocity| {
82 if velocity <= 0 {
83 0.0
84 } else if velocity >= 127 {
85 1.0
86 } else {
87 FloatType::from(velocity) / 127.0
88 }
89 });
90 }
91
92 pub fn velocity_scalar(&self) -> Option<FloatType> {
95 self.velocity_scalar
96 }
97
98 pub fn set_velocity_scalar(&mut self, scalar: Option<FloatType>) -> Result<()> {
101 match scalar {
102 None => self.velocity_scalar = None,
103 Some(scalar) if scalar.is_nan() => {
104 return Err(Error::Volume(
105 "value provided for velocityScalar must be a number, not NaN".to_string(),
106 ));
107 }
108 Some(scalar) => self.velocity_scalar = Some(scalar.clamp(0.0, 1.0)),
109 }
110 Ok(())
111 }
112
113 pub fn velocity_is_relative(&self) -> bool {
117 self.velocity_is_relative
118 }
119
120 pub fn set_velocity_is_relative(&mut self, relative: bool) {
122 self.velocity_is_relative = relative;
123 }
124
125 pub fn has_velocity_information(&self) -> bool {
128 self.velocity_scalar.is_some()
129 }
130
131 pub fn realized(&self) -> FloatType {
134 self.realized_with(None, 0.0, BASE_LEVEL, true)
135 }
136
137 pub fn realized_with(
147 &self,
148 dynamic_scalar: Option<FloatType>,
149 articulation_shift: FloatType,
150 base_level: FloatType,
151 clip: bool,
152 ) -> FloatType {
153 let mut value = base_level;
154 match self.velocity_scalar {
155 Some(scalar) if !self.velocity_is_relative => value = scalar,
156 Some(scalar) => value *= scalar * 2.0,
157 None => value += UNSET_VELOCITY_SHIFT,
158 }
159 if self.velocity_is_relative {
160 if let Some(dynamic_scalar) = dynamic_scalar {
161 value *= dynamic_scalar * 2.0;
162 }
163 value += articulation_shift;
164 }
165 if clip {
166 value = value.clamp(0.0, 1.0);
167 }
168 value
169 }
170
171 pub fn realized_dynamic(&self) -> &'static str {
174 dynamic_name(self.realized())
175 }
176
177 pub fn realized_str(&self) -> String {
180 rounded_str(self.realized())
181 }
182}
183
184pub fn rounded_str(value: FloatType) -> String {
187 let rounded = (value * 100.0).round() / 100.0;
188 let written = format!("{rounded}");
189 if written.contains('.') {
190 written
191 } else {
192 format!("{written}.0")
193 }
194}
195
196fn dynamic_name(value: FloatType) -> &'static str {
199 match value {
200 value if value <= 0.0 => "n",
201 value if value < 0.11 => "pppp",
202 value if value < 0.16 => "ppp",
203 value if value < 0.26 => "pp",
204 value if value < 0.36 => "p",
205 value if value < 0.5 => "mp",
206 value if value < 0.65 => "mf",
207 value if value < 0.8 => "f",
208 value if value < 0.9 => "ff",
209 _ => "fff",
210 }
211}
212
213impl fmt::Display for Volume {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 let mut rounded = format!("{:.2}", self.realized());
221 while rounded.ends_with('0') && !rounded.ends_with(".0") {
222 rounded.pop();
223 }
224 write!(f, "realized={rounded}")
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn merging_takes_the_other_volumes_velocity() {
234 let mut loud = Volume::from_velocity(111);
235 loud.set_velocity_is_relative(false);
236 let mut volume = Volume::new();
237 volume.merge_attributes(&loud);
238 assert_eq!(volume.velocity(), Some(111));
239 assert!(!volume.velocity_is_relative());
240 }
241
242 #[test]
243 fn velocity_and_scalar_are_the_same_number() {
244 let mut volume = Volume::new();
245 assert_eq!(volume.velocity(), None);
246 assert_eq!(volume.velocity_scalar(), None);
247 assert!(!volume.has_velocity_information());
248
249 volume.set_velocity(Some(64));
250 assert_eq!(volume.velocity(), Some(64));
251 assert!(volume.has_velocity_information());
252 assert!((volume.velocity_scalar().unwrap() - 64.0 / 127.0).abs() < 1e-12);
253
254 volume.set_velocity(Some(200));
255 assert_eq!(volume.velocity(), Some(127));
256 volume.set_velocity(Some(-5));
257 assert_eq!(volume.velocity(), Some(0));
258 volume.set_velocity(None);
259 assert_eq!(volume.velocity(), None);
260 }
261
262 #[test]
263 fn scalars_clamp_and_round_trip() {
264 let volume = Volume::from_velocity_scalar(0.5).unwrap();
265 assert_eq!(volume.velocity(), Some(64));
266 assert_eq!(
267 Volume::from_velocity_scalar(2.0).unwrap().velocity_scalar(),
268 Some(1.0)
269 );
270 assert_eq!(
271 Volume::from_velocity_scalar(-1.0)
272 .unwrap()
273 .velocity_scalar(),
274 Some(0.0)
275 );
276 assert!(Volume::from_velocity_scalar(FloatType::NAN).is_err());
277 assert_eq!(Volume::from_velocity(127).velocity_scalar(), Some(1.0));
278 }
279
280 #[test]
281 fn realized_matches_music21() {
282 let unset = Volume::new();
285 assert!((unset.realized() - 0.70866).abs() < 1e-9);
286 assert_eq!(unset.realized_dynamic(), "f");
287 assert_eq!(unset.realized_str(), "0.71");
288
289 let half = Volume::from_velocity_scalar(0.5).unwrap();
292 assert!((half.realized() - 0.5).abs() < 1e-9);
293 assert_eq!(half.realized_dynamic(), "mf");
294 assert_eq!(half.realized_str(), "0.5");
295
296 let mut absolute = Volume::from_velocity_scalar(0.5).unwrap();
298 absolute.set_velocity_is_relative(false);
299 assert!((absolute.realized() - 0.5).abs() < 1e-9);
300 let mut loud = Volume::from_velocity_scalar(1.0).unwrap();
301 loud.set_velocity_is_relative(false);
302 assert!((loud.realized() - 1.0).abs() < 1e-9);
303
304 assert!((half.realized_with(Some(0.5), 0.0, 0.5, true) - 0.5).abs() < 1e-9);
306 assert!((half.realized_with(Some(1.0), 0.0, 0.5, true) - 1.0).abs() < 1e-9);
307 assert!((half.realized_with(Some(1.0), 0.0, 0.5, false) - 1.0).abs() < 1e-9);
308 assert!(loud.realized_with(Some(1.0), 0.0, 0.5, false) > 0.99);
309
310 let unmarked = Volume::new();
313 assert!((unmarked.realized_with(None, 0.1, 0.5, true) - 0.80866).abs() < 1e-9);
314 }
315
316 #[test]
317 fn realized_names_cover_the_dynamic_range() {
318 assert_eq!(dynamic_name(0.0), "n");
319 assert_eq!(dynamic_name(0.05), "pppp");
320 assert_eq!(dynamic_name(0.12), "ppp");
321 assert_eq!(dynamic_name(0.2), "pp");
322 assert_eq!(dynamic_name(0.3), "p");
323 assert_eq!(dynamic_name(0.4), "mp");
324 assert_eq!(dynamic_name(0.6), "mf");
325 assert_eq!(dynamic_name(0.7), "f");
326 assert_eq!(dynamic_name(0.85), "ff");
327 assert_eq!(dynamic_name(1.0), "fff");
328 assert_eq!(Volume::from_velocity(64).to_string(), "realized=0.5");
329 assert_eq!(Volume::new().to_string(), "realized=0.71");
330 let mut silent = Volume::new();
331 silent.set_velocity(Some(0));
332 silent.set_velocity_is_relative(false);
333 assert_eq!(silent.to_string(), "realized=0.0");
334 }
335}