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