1use std::fmt;
13
14use crate::{
15 defaults::IntegerType,
16 error::{Error, Result},
17 pitch::{Accidental, Pitch},
18};
19
20pub const EXTENDER: IntegerType = -1;
23
24const SHORTHAND: &[(&[Option<IntegerType>], &[IntegerType])] = &[
29 (&[None], &[5, 3]),
32 (&[Some(5)], &[5, 3]),
33 (&[Some(6)], &[6, 3]),
34 (&[Some(7)], &[7, 5, 3]),
35 (&[Some(9)], &[9, 7, 5, 3]),
36 (&[Some(11)], &[11, 9, 7, 5, 3]),
37 (&[Some(13)], &[13, 11, 9, 7, 5, 3]),
38 (&[Some(6), Some(5)], &[6, 5, 3]),
39 (&[Some(4), Some(3)], &[6, 4, 3]),
40 (&[Some(4), Some(2)], &[6, 4, 2]),
41 (&[Some(2)], &[6, 4, 2]),
42];
43
44const SPECIAL: &[(&str, &str)] = &[
47 ("+", "#"),
48 ("/", "-"),
49 ("\\", "#"),
50 ("b", "-"),
51 ("bb", "--"),
52 ("bbb", "---"),
53 ("bbbb", "-----"),
54 ("++", "##"),
55 ("+++", "###"),
56 ("++++", "####"),
57 ("\u{266f}", "#"),
58 ("\u{266e}", "n"),
59 ("\u{266d}", "-"),
60];
61
62#[derive(Clone, Debug, Default, PartialEq)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68#[must_use]
69pub struct Modifier {
70 written: Option<String>,
71 accidental: Option<Accidental>,
72}
73
74impl Modifier {
75 pub fn new(written: Option<&str>) -> Result<Self> {
78 let Some(mark) = written.filter(|mark| !mark.is_empty()) else {
79 return Ok(Self {
80 written: written.map(str::to_string),
81 accidental: None,
82 });
83 };
84 let accidental = match Accidental::new(mark) {
85 Ok(accidental) => accidental,
86 Err(_) => {
87 let Some((_, spelled)) = SPECIAL.iter().find(|(special, _)| *special == mark)
88 else {
89 return Err(Error::Notation(format!(
90 "Figure modifier unsupported in music21: {mark}"
91 )));
92 };
93 Accidental::new(*spelled)?
94 }
95 };
96 Ok(Self {
97 written: Some(mark.to_string()),
98 accidental: Some(accidental),
99 })
100 }
101
102 pub fn written(&self) -> Option<&str> {
104 self.written.as_deref()
105 }
106
107 pub fn accidental(&self) -> Option<&Accidental> {
109 self.accidental.as_ref()
110 }
111
112 pub fn modify(&self, pitch: &Pitch) -> Result<Pitch> {
117 let Some(accidental) = &self.accidental else {
118 return Ok(pitch.clone());
119 };
120 let mut modified = pitch.clone();
121 let alter = if accidental.alter() == 0.0 || !pitch.has_accidental() {
122 accidental.alter()
123 } else {
124 pitch.accidental().alter() + accidental.alter()
125 };
126 modified.set_accidental(Some(Accidental::new(alter)?));
127 Ok(modified)
128 }
129
130 pub fn modify_pitch_name(&self, name: &str) -> Result<String> {
133 Ok(self.modify(&Pitch::from_name(name)?)?.name())
134 }
135}
136
137pub fn convert_to_pitch(name: &str) -> Result<Pitch> {
140 Pitch::from_name(name)
141 .map_err(|_| Error::Value(format!("Cannot convert string {name} to a music21 Pitch.")))
142}
143
144impl fmt::Display for Modifier {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 let written = match &self.written {
148 Some(written) => written.as_str(),
149 None => "None",
150 };
151 match &self.accidental {
152 Some(accidental) => write!(f, "{written} {}", accidental.name()),
153 None => write!(f, "{written} None"),
154 }
155 }
156}
157
158#[derive(Clone, Debug, PartialEq)]
161#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
162#[must_use]
163pub struct Figure {
164 number: Option<IntegerType>,
165 modifier: Modifier,
166 extender: bool,
167}
168
169impl Figure {
170 pub fn new(number: Option<IntegerType>, modifier: Modifier, extender: bool) -> Self {
173 Self {
174 number,
175 modifier,
176 extender,
177 }
178 }
179
180 pub fn number(&self) -> Option<IntegerType> {
182 self.number
183 }
184
185 pub fn set_number(&mut self, number: Option<IntegerType>) {
187 self.number = number;
188 }
189
190 pub fn modifier(&self) -> &Modifier {
192 &self.modifier
193 }
194
195 pub fn has_extender(&self) -> bool {
197 self.extender
198 }
199
200 pub fn is_pure_extender(&self) -> bool {
204 self.number == Some(1) && self.extender
205 }
206}
207
208impl fmt::Display for Figure {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 if self.is_pure_extender() {
212 return write!(f, "pure-extender <Modifier {}>", self.modifier);
213 }
214 let number = match self.number {
215 Some(EXTENDER) => "_".to_string(),
216 Some(number) => number.to_string(),
217 None => "None".to_string(),
218 };
219 let extender = if self.extender { "(extender)" } else { "" };
220 write!(f, "{number}{extender} <Modifier {}>", self.modifier)
221 }
222}
223
224#[derive(Clone, Debug, Default, PartialEq)]
229#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
230#[must_use]
231pub struct Notation {
232 column: String,
233 figure_strings: Vec<String>,
234 original_numbers: Vec<Option<IntegerType>>,
235 original_modifiers: Vec<Option<String>>,
236 numbers: Vec<Option<IntegerType>>,
237 modifier_strings: Vec<Option<String>>,
238 extenders: Vec<bool>,
239 figures: Vec<Figure>,
240 figures_as_written: Vec<Figure>,
241}
242
243impl Notation {
244 pub fn parse(column: &str) -> Result<Self> {
246 let mut notation = Self {
247 column: column.to_string(),
248 ..Self::default()
249 };
250 notation.read_column()?;
251 notation.expand();
252 notation.build_figures()?;
253 Ok(notation)
254 }
255
256 fn read_column(&mut self) -> Result<()> {
259 for written in self.column.split(',') {
260 let written = written.trim();
261 self.figure_strings.push(written.to_string());
262 let digits: String = written
263 .chars()
264 .filter(|letter| letter.is_ascii_digit() || *letter == '_')
265 .collect();
266 let marks: String = written
267 .chars()
268 .filter(|letter| !letter.is_ascii_digit() && *letter != '_')
269 .collect();
270
271 let mut number = None;
272 let mut extender = false;
273 if !digits.is_empty() {
274 if digits == "_" {
275 number = Some(EXTENDER);
276 extender = true;
277 } else if digits.contains('_') {
278 extender = true;
279 number = digits.trim_matches('_').parse().ok();
280 } else {
281 number = digits.parse().ok();
282 }
283 }
284 self.original_numbers.push(number);
285 self.original_modifiers
286 .push((!marks.is_empty()).then_some(marks));
287 self.extenders.push(extender);
288 }
289 self.numbers = self.original_numbers.clone();
290 self.modifier_strings = self.original_modifiers.clone();
291 Ok(())
292 }
293
294 fn expand(&mut self) {
298 let longhand = SHORTHAND
299 .iter()
300 .find(|(shorthand, _)| *shorthand == self.numbers.as_slice())
301 .map(|(_, longhand)| longhand.to_vec());
302 let Some(longhand) = longhand else {
303 self.numbers = self
305 .numbers
306 .iter()
307 .map(|number| Some(number.unwrap_or(3)))
308 .collect();
309 return;
310 };
311 let against: Vec<IntegerType> = self
312 .numbers
313 .iter()
314 .map(|number| number.unwrap_or(3))
315 .collect();
316 self.modifier_strings = longhand
317 .iter()
318 .map(|number| {
319 against
320 .iter()
321 .position(|written| written == number)
322 .and_then(|index| self.modifier_strings[index].clone())
323 })
324 .collect();
325 self.numbers = longhand.into_iter().map(Some).collect();
326 }
327
328 fn build_figures(&mut self) -> Result<()> {
330 for (index, number) in self.numbers.iter().enumerate() {
331 let modifier = Modifier::new(self.modifier_strings[index].as_deref())?;
332 let extender = self.extenders.get(index).copied().unwrap_or(false);
333 self.figures.push(Figure::new(*number, modifier, extender));
334 }
335 for (index, number) in self.original_numbers.iter().enumerate() {
336 let modifier = Modifier::new(self.original_modifiers[index].as_deref())?;
337 self.figures_as_written
338 .push(Figure::new(*number, modifier, false));
339 }
340 Ok(())
341 }
342
343 pub fn column(&self) -> &str {
345 &self.column
346 }
347
348 pub fn figure_strings(&self) -> &[String] {
350 &self.figure_strings
351 }
352
353 pub fn original_numbers(&self) -> &[Option<IntegerType>] {
355 &self.original_numbers
356 }
357
358 pub fn original_modifiers(&self) -> &[Option<String>] {
360 &self.original_modifiers
361 }
362
363 pub fn numbers(&self) -> &[Option<IntegerType>] {
365 &self.numbers
366 }
367
368 pub fn modifier_strings(&self) -> &[Option<String>] {
370 &self.modifier_strings
371 }
372
373 pub fn has_extenders(&self) -> bool {
375 self.extenders.iter().any(|extender| *extender)
376 }
377
378 pub fn extenders(&self) -> &[bool] {
380 &self.extenders
381 }
382
383 pub fn figures(&self) -> &[Figure] {
385 &self.figures
386 }
387
388 pub fn figures_as_written(&self) -> &[Figure] {
390 &self.figures_as_written
391 }
392
393 pub fn modifiers(&self) -> Vec<&Modifier> {
395 self.figures.iter().map(Figure::modifier).collect()
396 }
397}
398
399impl fmt::Display for Notation {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 f.write_str(&self.column)
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn a_column_keeps_what_it_was_written_with_beside_what_it_means() {
411 let notation = Notation::parse("6,#").unwrap();
412 assert_eq!(notation.column(), "6,#");
413 assert_eq!(notation.figures_as_written().len(), 2);
414 assert_eq!(notation.original_modifiers(), [None, Some("#".to_string())]);
415 assert_eq!(notation.extenders(), [false, false]);
416 assert_eq!(notation.modifiers().len(), notation.figures().len());
417 assert_eq!(notation.modifiers()[1].written(), Some("#"));
418 assert_eq!(notation.modifiers()[0].written(), None);
419 assert!(notation.to_string().contains("6,#"));
420
421 let mut figure = Figure::new(Some(6), Modifier::new(None).unwrap(), true);
422 assert!(figure.has_extender());
423 assert!(!figure.is_pure_extender());
424 figure.set_number(Some(1));
425 assert!(figure.is_pure_extender());
426 figure.set_number(None);
427 assert_eq!(figure.number(), None);
428 }
429
430 #[test]
432 fn a_modifier_respells_a_pitch_name() {
433 assert_eq!(
434 Modifier::new(Some("#"))
435 .unwrap()
436 .modify_pitch_name("D")
437 .unwrap(),
438 "D#"
439 );
440 assert_eq!(
441 Modifier::new(Some("-"))
442 .unwrap()
443 .modify_pitch_name("F")
444 .unwrap(),
445 "F-"
446 );
447 assert_eq!(
448 Modifier::new(Some("n"))
449 .unwrap()
450 .modify_pitch_name("C#")
451 .unwrap(),
452 "C"
453 );
454 assert_eq!(
455 Modifier::new(None)
456 .unwrap()
457 .modify_pitch_name("B-")
458 .unwrap(),
459 "B-"
460 );
461 assert!(
462 Modifier::new(Some("#"))
463 .unwrap()
464 .modify_pitch_name("H")
465 .is_err()
466 );
467
468 assert_eq!(convert_to_pitch("C5").unwrap().to_string(), "C5");
469 assert!(matches!(convert_to_pitch("nonsense"), Err(Error::Value(_))));
470 }
471
472 #[test]
473 fn a_column_expands_out_of_its_shorthand() {
474 let notation = Notation::parse("4+,2").unwrap();
475 assert_eq!(notation.figure_strings(), ["4+", "2"]);
476 assert_eq!(notation.original_numbers(), [Some(4), Some(2)]);
477 assert_eq!(notation.numbers(), [Some(6), Some(4), Some(2)]);
478 assert_eq!(
479 notation.modifier_strings(),
480 [None, Some("+".to_string()), None]
481 );
482 assert_eq!(
483 notation.figures()[1].to_string(),
484 "4 <Modifier + sharp>",
485 "the mark stays with the number it was written against"
486 );
487 }
488
489 #[test]
490 fn a_bare_mark_is_the_third() {
491 let notation = Notation::parse("-6, -").unwrap();
492 assert_eq!(notation.original_numbers(), [Some(6), None]);
493 assert_eq!(notation.numbers(), [Some(6), Some(3)]);
494 assert_eq!(notation.figures()[1].to_string(), "3 <Modifier - flat>");
495 }
496
497 #[test]
498 fn figured_bass_reads_its_own_marks() {
499 assert_eq!(
501 Modifier::new(Some("+"))
502 .unwrap()
503 .accidental()
504 .map(|accidental| accidental.name()),
505 Some("sharp")
506 );
507 assert_eq!(
508 Modifier::new(Some("/"))
509 .unwrap()
510 .accidental()
511 .map(|accidental| accidental.name()),
512 Some("flat")
513 );
514 assert!(Modifier::new(Some("")).unwrap().accidental().is_none());
515 assert!(Modifier::new(None).unwrap().accidental().is_none());
516 assert!(Modifier::new(Some("zzz")).is_err());
517 }
518
519 #[test]
520 fn an_extender_carries_the_figure_on() {
521 let notation = Notation::parse("7_").unwrap();
522 assert!(notation.has_extenders());
523 let pure = Figure::new(Some(1), Modifier::new(Some("#")).unwrap(), true);
524 assert!(pure.is_pure_extender());
525 assert_eq!(pure.to_string(), "pure-extender <Modifier # sharp>");
526 }
527}