1use std::fmt;
9
10use crate::{
11 defaults::FloatType,
12 display::Drawn,
13 error::{Error, Result},
14 notation::Placement,
15};
16
17pub const SHORT_NAMES: [&str; 16] = [
19 "pppppp", "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf", "f", "fp", "sf", "ff", "fff", "ffff",
20 "fffff", "ffffff",
21];
22
23const LONG_NAMES: [(&str, &str); 10] = [
25 ("ppp", "pianississimo"),
26 ("pp", "pianissimo"),
27 ("p", "piano"),
28 ("mp", "mezzopiano"),
29 ("mf", "mezzoforte"),
30 ("f", "forte"),
31 ("fp", "fortepiano"),
32 ("sf", "sforzando"),
33 ("ff", "fortissimo"),
34 ("fff", "fortississimo"),
35];
36
37const ENGLISH_NAMES: [(&str, &str); 8] = [
39 ("ppp", "extremely soft"),
40 ("pp", "very soft"),
41 ("p", "soft"),
42 ("mp", "moderately soft"),
43 ("mf", "moderately loud"),
44 ("f", "loud"),
45 ("ff", "very loud"),
46 ("fff", "extremely loud"),
47];
48
49const SCALARS: [(&str, FloatType); 13] = [
51 ("n", 0.0),
52 ("pppp", 0.1),
53 ("ppp", 0.15),
54 ("pp", 0.25),
55 ("p", 0.35),
56 ("mp", 0.45),
57 ("mf", 0.55),
58 ("f", 0.7),
59 ("fp", 0.75),
60 ("sf", 0.85),
61 ("ff", 0.85),
62 ("fff", 0.9),
63 ("ffff", 0.95),
64];
65
66const DEFAULT_SCALAR: FloatType = 0.5;
68
69fn looked_up<T: Copy>(table: &[(&str, T)], mark: &str) -> Option<T> {
70 table
71 .iter()
72 .find(|(name, _)| *name == mark)
73 .map(|(_, value)| *value)
74}
75
76pub fn dynamic_str_from_decimal(value: FloatType) -> &'static str {
80 match value {
81 value if value <= 0.0 => "n",
82 value if value < 0.11 => "pppp",
83 value if value < 0.16 => "ppp",
84 value if value < 0.26 => "pp",
85 value if value < 0.36 => "p",
86 value if value < 0.5 => "mp",
87 value if value < 0.65 => "mf",
88 value if value < 0.8 => "f",
89 value if value < 0.9 => "ff",
90 _ => "fff",
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114#[must_use]
115pub struct Dynamic {
116 value: String,
117 #[cfg_attr(feature = "serde", serde(default))]
119 volume_scalar: Option<FloatType>,
120 #[cfg_attr(feature = "serde", serde(default))]
121 placement: Drawn<Option<Placement>>,
122}
123
124impl Dynamic {
125 pub fn new(value: impl Into<String>) -> Self {
128 Self {
129 value: value.into(),
130 volume_scalar: None,
131 placement: Drawn(None),
132 }
133 }
134
135 pub fn placement(&self) -> Option<Placement> {
137 self.placement.0
138 }
139
140 pub fn set_placement(&mut self, placement: Option<Placement>) {
142 self.placement.0 = placement;
143 }
144
145 pub fn from_scalar(scalar: FloatType) -> Result<Self> {
148 let mut dynamic = Self::new(dynamic_str_from_decimal(scalar));
149 dynamic.set_volume_scalar(scalar)?;
150 Ok(dynamic)
151 }
152
153 pub fn value(&self) -> &str {
155 &self.value
156 }
157
158 pub fn set_value(&mut self, value: impl Into<String>) {
160 self.value = value.into();
161 }
162
163 pub fn long_name(&self) -> Option<&'static str> {
165 looked_up(&LONG_NAMES, &self.value)
166 }
167
168 pub fn english_name(&self) -> Option<&'static str> {
170 looked_up(&ENGLISH_NAMES, &self.value)
171 }
172
173 pub fn volume_scalar(&self) -> FloatType {
181 if let Some(scalar) = self.volume_scalar {
182 return scalar;
183 }
184 if let Some(scalar) = looked_up(&SCALARS, &self.value) {
185 return scalar;
186 }
187 let mut mark = self.value.as_str();
190 if mark.contains('s') {
191 let mut characters = mark.chars();
192 characters.next();
193 mark = characters.as_str();
194 }
195 let mark = mark.strip_suffix('z').unwrap_or(mark);
196 looked_up(&SCALARS, mark).unwrap_or(DEFAULT_SCALAR)
197 }
198
199 pub fn set_volume_scalar(&mut self, scalar: FloatType) -> Result<()> {
201 if !(0.0..=1.0).contains(&scalar) {
202 return Err(Error::Volume(format!(
203 "cannot set as volume scalar to: {scalar}"
204 )));
205 }
206 self.volume_scalar = Some(scalar);
207 Ok(())
208 }
209}
210
211impl fmt::Display for Dynamic {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 write!(f, "{}", self.value)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
224 fn a_mark_sounds_as_loud_as_music21_says() {
225 for (mark, scalar) in [
226 ("sf", 0.85),
227 ("sfz", 0.7),
228 ("fp", 0.75),
229 ("rfz", 0.5),
230 ("sffz", 0.85),
231 ("ppppp", 0.5),
232 ("n", 0.0),
233 ] {
234 assert_eq!(Dynamic::new(mark).volume_scalar(), scalar, "{mark}");
235 }
236 assert_eq!(Dynamic::new("sf").long_name(), Some("sforzando"));
237 assert_eq!(Dynamic::new("sf").english_name(), None);
238 assert_eq!(Dynamic::new("mf").english_name(), Some("moderately loud"));
239 assert!(Dynamic::from_scalar(1.5).is_err());
240 assert_eq!(dynamic_str_from_decimal(0.0), "n");
241 assert_eq!(dynamic_str_from_decimal(0.95), "fff");
242 }
243}