1use crate::{
4 defaults::FloatType,
5 duration::Duration,
6 error::{Error, Result},
7};
8
9pub const DEFAULT_TEMPO_VALUES: [(&str, FloatType); 30] = [
12 ("larghissimo", 16.0),
13 ("largamente", 32.0),
14 ("grave", 40.0),
15 ("molto adagio", 40.0),
16 ("largo", 46.0),
17 ("lento", 52.0),
18 ("adagio", 56.0),
19 ("slow", 56.0),
20 ("langsam", 56.0),
21 ("larghetto", 60.0),
22 ("adagietto", 66.0),
23 ("andante", 72.0),
24 ("andantino", 80.0),
25 ("andante moderato", 83.0),
26 ("maestoso", 88.0),
27 ("moderato", 92.0),
28 ("moderate", 92.0),
29 ("allegretto", 108.0),
30 ("animato", 120.0),
31 ("allegro moderato", 128.0),
32 ("allegro", 132.0),
33 ("fast", 132.0),
34 ("schnell", 132.0),
35 ("allegrissimo", 140.0),
36 ("molto allegro", 144.0),
37 ("très vite", 144.0),
38 ("vivace", 160.0),
39 ("vivacissimo", 168.0),
40 ("presto", 184.0),
41 ("prestissimo", 208.0),
42];
43
44pub fn convert_tempo_by_referent(
49 number: FloatType,
50 source_quarter_length: FloatType,
51 destination_quarter_length: FloatType,
52) -> FloatType {
53 let seconds_per_source_beat = 60.0 / number;
54 let seconds_per_quarter = seconds_per_source_beat / source_quarter_length;
55 60.0 / (seconds_per_quarter * destination_quarter_length)
56}
57
58pub fn default_text_for_number(number: FloatType) -> Option<&'static str> {
62 let mut sorted = DEFAULT_TEMPO_VALUES;
63 sorted.sort_by(|left, right| {
64 left.1
65 .partial_cmp(&right.1)
66 .unwrap_or(std::cmp::Ordering::Equal)
67 .then_with(|| left.0.cmp(right.0))
68 });
69 sorted
70 .iter()
71 .find(|(_, value)| (value - 2.0..=value + 2.0).contains(&number))
72 .map(|(text, _)| *text)
73}
74
75pub fn default_number_for_text(text: &str) -> Option<FloatType> {
78 let lowered = text.to_lowercase();
79 let lookup = |candidate: &str| {
80 DEFAULT_TEMPO_VALUES
81 .iter()
82 .find(|(name, _)| *name == candidate)
83 .map(|(_, value)| *value)
84 };
85 lookup(&lowered).or_else(|| lookup(text)).or_else(|| {
86 text.split(' ')
87 .filter_map(|word| lookup(&word.to_lowercase()))
88 .next_back()
89 })
90}
91
92#[derive(Clone, Debug, PartialEq)]
98#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
99#[must_use]
100pub struct MetronomeMark {
101 number: Option<FloatType>,
102 text: Option<String>,
103 referent: Duration,
104 number_implicit: bool,
105 text_implicit: bool,
106 #[cfg_attr(feature = "serde", serde(default))]
108 number_sounding: Option<FloatType>,
109}
110
111impl Default for MetronomeMark {
112 fn default() -> Self {
115 Self::build(None, None, Duration::quarter())
116 }
117}
118
119impl MetronomeMark {
120 pub fn new(number: FloatType) -> Self {
123 Self::build(Some(number), None, Duration::quarter())
124 }
125
126 pub fn from_text(text: impl Into<String>) -> Self {
129 Self::build(None, Some(text.into()), Duration::quarter())
130 }
131
132 pub fn with_number_and_text(number: FloatType, text: impl Into<String>) -> Self {
134 Self::build(Some(number), Some(text.into()), Duration::quarter())
135 }
136
137 pub fn with_referent(mut self, referent: Duration) -> Self {
140 self.referent = referent;
141 self
142 }
143
144 fn build(number: Option<FloatType>, text: Option<String>, referent: Duration) -> Self {
145 let number_implicit = number.is_none();
146 let text_implicit = text.is_none();
147 let number = number.or_else(|| text.as_deref().and_then(default_number_for_text));
148 let text = text.or_else(|| number.and_then(default_text_for_number).map(String::from));
149 Self {
150 number_implicit: number_implicit && number.is_some(),
151 text_implicit: text_implicit && text.is_some(),
152 number,
153 text,
154 referent,
155 number_sounding: None,
156 }
157 }
158
159 pub fn equivalent_by_referent(&self, referent: Duration) -> MetronomeMark {
163 let number = self.number.map(|number| {
164 convert_tempo_by_referent(
165 number,
166 self.referent.quarter_length(),
167 referent.quarter_length(),
168 )
169 });
170 Self::build(number, self.text.clone(), referent)
171 }
172
173 pub fn maintained_number_with_referent(&self, referent: Duration) -> MetronomeMark {
176 Self::build(self.number, self.text.clone(), referent)
177 }
178
179 pub fn number(&self) -> Option<FloatType> {
181 self.number
182 }
183
184 pub fn set_number(&mut self, number: Option<FloatType>) {
187 self.number = number;
188 self.number_implicit = false;
189 if self.text.is_none()
190 && let Some(number) = number
191 && let Some(text) = default_text_for_number(number)
192 {
193 self.text = Some(text.to_string());
194 self.text_implicit = true;
195 }
196 }
197
198 pub fn set_text(&mut self, text: impl Into<String>) {
201 let text = text.into();
202 self.text_implicit = false;
203 if self.number.is_none()
204 && let Some(number) = default_number_for_text(&text)
205 {
206 self.number = Some(number);
207 self.number_implicit = true;
208 }
209 self.text = Some(text);
210 }
211
212 pub fn set_referent(&mut self, referent: Duration) {
215 self.referent = referent;
216 }
217
218 pub fn text(&self) -> Option<&str> {
220 self.text.as_deref()
221 }
222
223 pub fn text_expression(&self, return_implicit: bool) -> Option<&str> {
227 if self.text_implicit && !return_implicit {
228 return None;
229 }
230 self.text.as_deref()
231 }
232
233 pub fn referent(&self) -> &Duration {
235 &self.referent
236 }
237
238 pub fn number_implicit(&self) -> bool {
240 self.number_implicit
241 }
242
243 pub fn set_number_implicit(&mut self, implicit: bool) {
247 self.number_implicit = implicit;
248 }
249
250 pub fn text_implicit(&self) -> bool {
252 self.text_implicit
253 }
254
255 pub fn number_sounding(&self) -> Option<FloatType> {
264 self.number_sounding
265 }
266
267 pub fn set_number_sounding(&mut self, number: Option<FloatType>) {
269 self.number_sounding = number;
270 }
271
272 pub fn with_number_sounding(mut self, number: FloatType) -> Self {
278 self.number_sounding = Some(number);
279 self
280 }
281
282 pub fn quarter_bpm(&self) -> Option<FloatType> {
284 self.number
285 .map(|number| convert_tempo_by_referent(number, self.referent.quarter_length(), 1.0))
286 }
287
288 pub fn sounding_quarter_bpm(&self) -> Option<FloatType> {
291 match self.number_sounding {
292 Some(number) => Some(convert_tempo_by_referent(
293 number,
294 self.referent.quarter_length(),
295 1.0,
296 )),
297 None => self.quarter_bpm(),
298 }
299 }
300
301 pub fn seconds_per_quarter(&self) -> Result<FloatType> {
307 let quarter_bpm = self.quarter_bpm().ok_or_else(|| {
308 Error::Tempo("cannot derive seconds without a tempo number".to_string())
309 })?;
310 if quarter_bpm == 0.0 {
311 return Err(Error::Tempo(
312 "a tempo of no beats a minute lasts no seconds a beat".to_string(),
313 ));
314 }
315 Ok(60.0 / quarter_bpm)
316 }
317
318 pub fn quarter_length_to_seconds(&self, quarter_length: FloatType) -> Result<FloatType> {
320 Ok(self.seconds_per_quarter()? * quarter_length)
321 }
322
323 pub fn duration_to_seconds(&self, duration: &Duration) -> Result<FloatType> {
325 self.quarter_length_to_seconds(duration.quarter_length())
326 }
327
328 pub fn seconds_to_duration(&self, seconds: FloatType) -> Result<Duration> {
330 if seconds.is_nan() || seconds <= 0.0 {
331 return Err(Error::Tempo(
332 "seconds must be a number greater than zero".to_string(),
333 ));
334 }
335 Duration::new(seconds / self.seconds_per_quarter()?)
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn a_mark_can_be_rewritten_one_half_at_a_time() {
345 let mut mark = MetronomeMark::from_text("adagio");
346 assert!(mark.number_implicit());
347 mark.set_number(Some(60.0));
348 assert!(!mark.number_implicit());
349 assert_eq!(mark.number(), Some(60.0));
350 mark.set_number_implicit(true);
351 assert!(mark.number_implicit());
352 mark.set_referent(Duration::half());
353 assert_eq!(mark.referent().quarter_length(), 2.0);
354 assert_eq!(mark.quarter_bpm(), Some(120.0));
355
356 let mut numbered = MetronomeMark::new(90.0);
357 assert!(numbered.text_implicit());
358 numbered.set_text("largo");
359 assert!(!numbered.text_implicit());
360 assert_eq!(numbered.text(), Some("largo"));
361 assert_eq!(numbered.number(), Some(90.0));
362 let mut wordless = MetronomeMark::default();
363 wordless.set_number(Some(184.0));
364 assert_eq!(wordless.text(), Some("presto"));
365 assert!(wordless.text_implicit());
366 wordless.set_number(None);
367 assert_eq!(wordless.number(), None);
368 }
369
370 #[test]
372 fn an_implied_word_is_only_answered_when_asked_for() {
373 let presto = MetronomeMark::from_text("presto");
374 assert_eq!(presto.number(), Some(184.0));
375 assert_eq!(presto.text_expression(false), Some("presto"));
376
377 let ninety = MetronomeMark::new(90.0);
378 assert!(ninety.text_implicit());
379 assert_eq!(ninety.text_expression(false), None);
380 assert_eq!(ninety.text_expression(true), Some("maestoso"));
381 }
382
383 #[test]
384 fn a_mark_may_be_played_faster_than_it_is_written() {
385 let playback = MetronomeMark::default().with_number_sounding(168.0);
387 assert_eq!(playback.number_sounding(), Some(168.0));
388 assert_eq!(playback.number(), None);
390 assert_eq!(playback.text(), None);
391 assert_eq!(playback.quarter_bpm(), None);
392 assert_eq!(playback.sounding_quarter_bpm(), Some(168.0));
393
394 let mut written = MetronomeMark::new(60.0).with_referent(Duration::half());
397 assert_eq!(written.quarter_bpm(), Some(120.0));
398 assert_eq!(written.sounding_quarter_bpm(), Some(120.0));
399 written.set_number_sounding(Some(90.0));
400 assert_eq!(written.number(), Some(60.0));
401 assert_eq!(written.quarter_bpm(), Some(120.0));
402 assert_eq!(written.sounding_quarter_bpm(), Some(180.0));
403 }
404
405 #[test]
406 fn referent_changes_match_music21() {
407 let quarter_sixty = MetronomeMark::new(60.0);
408 let eighths = quarter_sixty.equivalent_by_referent(Duration::eighth());
409 assert_eq!(eighths.number(), Some(120.0));
410 assert_eq!(eighths.referent().quarter_length(), 0.5);
411 assert_eq!(eighths.text(), Some("larghetto"));
412 let halves = quarter_sixty.equivalent_by_referent(Duration::half());
413 assert_eq!(halves.number(), Some(30.0));
414
415 let andante = MetronomeMark::with_number_and_text(72.0, "andante")
416 .with_referent(Duration::new(1.5).unwrap());
417 let quarters = andante.equivalent_by_referent(Duration::quarter());
418 assert_eq!(quarters.number(), Some(108.0));
419 assert_eq!(quarters.text(), Some("andante"));
420 assert_eq!(
421 andante.equivalent_by_referent(Duration::half()).number(),
422 Some(54.0)
423 );
424
425 let kept = andante.maintained_number_with_referent(Duration::eighth());
426 assert_eq!(kept.number(), Some(72.0));
427 assert_eq!(kept.referent().quarter_length(), 0.5);
428 assert_eq!(kept.text(), Some("andante"));
429 }
430
431 #[test]
432 fn numbers_imply_the_tempo_words_music21_picks() {
433 let cases = [
434 (120.0, Some("animato")),
435 (60.0, Some("larghetto")),
436 (61.0, Some("larghetto")),
437 (62.0, Some("larghetto")),
438 (63.0, None),
439 (90.0, Some("maestoso")),
440 (92.0, Some("moderate")),
441 (100.0, None),
442 (118.0, Some("animato")),
443 (122.0, Some("animato")),
444 (123.0, None),
445 (16.0, Some("larghissimo")),
446 (12.0, None),
447 (56.5, Some("adagio")),
448 (300.0, None),
449 ];
450 for (number, text) in cases {
451 let mark = MetronomeMark::new(number);
452 assert_eq!(mark.text(), text, "{number}");
453 assert_eq!(mark.text_implicit(), text.is_some(), "{number}");
454 assert!(!mark.number_implicit());
455 }
456 }
457
458 #[test]
459 fn words_imply_their_conventional_numbers() {
460 let cases = [
461 ("allegro", Some(132.0)),
462 ("Allegro", Some(132.0)),
463 ("ALLEGRO ", Some(132.0)),
464 ("très vite", Some(144.0)),
465 ("andante moderato", Some(83.0)),
466 ("unknown words", None),
467 ];
468 for (text, number) in cases {
469 let mark = MetronomeMark::from_text(text);
470 assert_eq!(mark.number(), number, "{text}");
471 assert_eq!(mark.number_implicit(), number.is_some(), "{text}");
472 assert_eq!(mark.text(), Some(text));
473 assert!(!mark.text_implicit());
474 }
475 let both = MetronomeMark::with_number_and_text(120.0, "fast");
476 assert_eq!(both.number(), Some(120.0));
477 assert_eq!(both.text(), Some("fast"));
478 assert!(!both.number_implicit() && !both.text_implicit());
479 }
480
481 #[test]
482 fn referents_convert_to_quarter_bpm_and_seconds() {
483 let quarter = MetronomeMark::new(120.0);
484 assert_eq!(quarter.quarter_bpm(), Some(120.0));
485 assert_eq!(quarter.quarter_length_to_seconds(1.0).unwrap(), 0.5);
486 assert_eq!(
487 quarter.seconds_to_duration(0.75).unwrap().quarter_length(),
488 1.5
489 );
490
491 let half = MetronomeMark::new(60.0).with_referent(Duration::half());
492 assert_eq!(half.quarter_bpm(), Some(120.0));
493 assert_eq!(half.text(), Some("larghetto"));
494 assert_eq!(half.quarter_length_to_seconds(1.0).unwrap(), 0.5);
495 assert_eq!(
496 half.duration_to_seconds(&Duration::new(3.0).unwrap())
497 .unwrap(),
498 1.5
499 );
500 assert_eq!(half.seconds_to_duration(1.0).unwrap().quarter_length(), 2.0);
501
502 let eighth = MetronomeMark::new(120.0).with_referent(Duration::eighth());
503 assert_eq!(eighth.quarter_bpm(), Some(60.0));
504 assert_eq!(eighth.seconds_per_quarter().unwrap(), 1.0);
505
506 let dotted = MetronomeMark::new(56.5).with_referent(Duration::half());
507 assert!((dotted.quarter_bpm().unwrap() - 113.0).abs() < 1e-9);
508
509 assert_eq!(convert_tempo_by_referent(60.0, 1.0, 2.0), 30.0);
510 assert_eq!(convert_tempo_by_referent(60.0, 2.0, 1.0), 120.0);
511
512 let unknown = MetronomeMark::from_text("unknown words");
513 assert_eq!(unknown.quarter_bpm(), None);
514 assert!(unknown.seconds_per_quarter().is_err());
515 assert!(quarter.seconds_to_duration(0.0).is_err());
516
517 let motionless = MetronomeMark::new(0.0);
518 assert_eq!(motionless.quarter_bpm(), Some(0.0));
519 assert!(motionless.seconds_per_quarter().is_err());
520 assert!(
521 motionless
522 .duration_to_seconds(&Duration::quarter())
523 .is_err()
524 );
525 assert!(motionless.quarter_length_to_seconds(1.0).is_err());
526 }
527}