1use crate::defaults::{FloatType, IntegerType};
2use crate::display::{DisplayLocation, DisplaySize, DisplayStyle, DisplayType};
3use crate::error::{Error, Result};
4
5use std::fmt::{Display, Formatter};
6use std::str::FromStr;
7
8enum AccidentalEnum {
9 Natural,
10 HalfSharp,
11 Sharp,
12 OneAndAHalfSharp,
13 DoubleSharp,
14 TripleSharp,
15 QuadrupleSharp,
16 HalfFlat,
17 Flat,
18 OneAndAHalfFlat,
19 DoubleFlat,
20 TripleFlat,
21 QuadrupleFlat,
22}
23
24impl AccidentalEnum {
25 fn to_name(&self) -> &'static str {
26 match self {
27 AccidentalEnum::Natural => "natural",
28 AccidentalEnum::HalfSharp => "half-sharp",
29 AccidentalEnum::Sharp => "sharp",
30 AccidentalEnum::OneAndAHalfSharp => "one-and-a-half-sharp",
31 AccidentalEnum::DoubleSharp => "double-sharp",
32 AccidentalEnum::TripleSharp => "triple-sharp",
33 AccidentalEnum::QuadrupleSharp => "quadruple-sharp",
34 AccidentalEnum::HalfFlat => "half-flat",
35 AccidentalEnum::Flat => "flat",
36 AccidentalEnum::OneAndAHalfFlat => "one-and-a-half-flat",
37 AccidentalEnum::DoubleFlat => "double-flat",
38 AccidentalEnum::TripleFlat => "triple-flat",
39 AccidentalEnum::QuadrupleFlat => "quadruple-flat",
40 }
41 }
42
43 fn from_name(s: &str) -> Option<Self> {
44 match s {
45 "natural" => Some(AccidentalEnum::Natural),
46 "half-sharp" => Some(AccidentalEnum::HalfSharp),
47 "sharp" => Some(AccidentalEnum::Sharp),
48 "one-and-a-half-sharp" => Some(AccidentalEnum::OneAndAHalfSharp),
49 "double-sharp" => Some(AccidentalEnum::DoubleSharp),
50 "triple-sharp" => Some(AccidentalEnum::TripleSharp),
51 "quadruple-sharp" => Some(AccidentalEnum::QuadrupleSharp),
52 "half-flat" => Some(AccidentalEnum::HalfFlat),
53 "flat" => Some(AccidentalEnum::Flat),
54 "one-and-a-half-flat" => Some(AccidentalEnum::OneAndAHalfFlat),
55 "double-flat" => Some(AccidentalEnum::DoubleFlat),
56 "triple-flat" => Some(AccidentalEnum::TripleFlat),
57 "quadruple-flat" => Some(AccidentalEnum::QuadrupleFlat),
58 _ => None,
59 }
60 }
61
62 fn to_alter(&self) -> FloatType {
63 match self {
64 AccidentalEnum::Natural => 0.0,
65 AccidentalEnum::HalfSharp => 0.5,
66 AccidentalEnum::Sharp => 1.0,
67 AccidentalEnum::OneAndAHalfSharp => 1.5,
68 AccidentalEnum::DoubleSharp => 2.0,
69 AccidentalEnum::TripleSharp => 3.0,
70 AccidentalEnum::QuadrupleSharp => 4.0,
71 AccidentalEnum::HalfFlat => -0.5,
72 AccidentalEnum::Flat => -1.0,
73 AccidentalEnum::OneAndAHalfFlat => -1.5,
74 AccidentalEnum::DoubleFlat => -2.0,
75 AccidentalEnum::TripleFlat => -3.0,
76 AccidentalEnum::QuadrupleFlat => -4.0,
77 }
78 }
79
80 fn from_alter_str(s: &str) -> Option<Self> {
81 match s {
82 "-4.0" => Some(AccidentalEnum::QuadrupleFlat),
83 "-3.0" => Some(AccidentalEnum::TripleFlat),
84 "-2.0" => Some(AccidentalEnum::DoubleFlat),
85 "-1.5" => Some(AccidentalEnum::OneAndAHalfFlat),
86 "-1.0" => Some(AccidentalEnum::Flat),
87 "-0.5" => Some(AccidentalEnum::HalfFlat),
88 "0.0" => Some(AccidentalEnum::Natural),
89 "0.5" => Some(AccidentalEnum::HalfSharp),
90 "1.0" => Some(AccidentalEnum::Sharp),
91 "1.5" => Some(AccidentalEnum::OneAndAHalfSharp),
92 "2.0" => Some(AccidentalEnum::DoubleSharp),
93 "3.0" => Some(AccidentalEnum::TripleSharp),
94 "4.0" => Some(AccidentalEnum::QuadrupleSharp),
95 _ => None,
96 }
97 }
98
99 fn to_modifier(&self) -> &'static str {
100 match self {
101 AccidentalEnum::Natural => "",
102 AccidentalEnum::HalfSharp => "~",
103 AccidentalEnum::Sharp => "#",
104 AccidentalEnum::OneAndAHalfSharp => "#~",
105 AccidentalEnum::DoubleSharp => "##",
106 AccidentalEnum::TripleSharp => "###",
107 AccidentalEnum::QuadrupleSharp => "####",
108 AccidentalEnum::HalfFlat => "`",
109 AccidentalEnum::Flat => "-",
110 AccidentalEnum::OneAndAHalfFlat => "-`",
111 AccidentalEnum::DoubleFlat => "--",
112 AccidentalEnum::TripleFlat => "---",
113 AccidentalEnum::QuadrupleFlat => "----",
114 }
115 }
116
117 fn from_modifier(s: &str) -> Option<Self> {
118 match s {
119 "" => Some(AccidentalEnum::Natural),
120 "~" => Some(AccidentalEnum::HalfSharp),
121 "#" => Some(AccidentalEnum::Sharp),
122 "#~" => Some(AccidentalEnum::OneAndAHalfSharp),
123 "##" => Some(AccidentalEnum::DoubleSharp),
124 "###" => Some(AccidentalEnum::TripleSharp),
125 "####" => Some(AccidentalEnum::QuadrupleSharp),
126 "`" => Some(AccidentalEnum::HalfFlat),
127 "-" => Some(AccidentalEnum::Flat),
128 "-`" => Some(AccidentalEnum::OneAndAHalfFlat),
129 "--" => Some(AccidentalEnum::DoubleFlat),
130 "---" => Some(AccidentalEnum::TripleFlat),
131 "----" => Some(AccidentalEnum::QuadrupleFlat),
132 _ => None,
133 }
134 }
135
136 const fn to_unicode(&self) -> &'static str {
137 match self {
138 AccidentalEnum::QuadrupleSharp => "\u{1d12a}\u{1d12a}",
139 AccidentalEnum::TripleSharp => "\u{266f}\u{1d12a}",
140 AccidentalEnum::DoubleSharp => "\u{1d12a}",
141 AccidentalEnum::OneAndAHalfSharp => "\u{266f}\u{1d132}",
142 AccidentalEnum::Sharp => "\u{266f}",
143 AccidentalEnum::HalfSharp => "\u{1d132}",
144 AccidentalEnum::QuadrupleFlat => "\u{1d12b}\u{1d12b}",
145 AccidentalEnum::TripleFlat => "\u{266d}\u{1d12b}",
151 AccidentalEnum::DoubleFlat => "\u{1d12b}",
152 AccidentalEnum::OneAndAHalfFlat => "\u{266d}\u{1d133}",
153 AccidentalEnum::Flat => "\u{266d}",
154 AccidentalEnum::HalfFlat => "\u{1d133}",
155 AccidentalEnum::Natural => "\u{266e}",
156 }
157 }
158
159 fn from_unicode(s: &str) -> Option<Self> {
160 match s {
161 "\u{1d12a}\u{1d12a}" => Some(AccidentalEnum::QuadrupleSharp),
162 "\u{266f}\u{1d12a}" => Some(AccidentalEnum::TripleSharp),
163 "\u{1d12a}" => Some(AccidentalEnum::DoubleSharp),
164 "\u{266f}\u{1d132}" => Some(AccidentalEnum::OneAndAHalfSharp),
165 "\u{266f}" => Some(AccidentalEnum::Sharp),
166 "\u{1d12b}\u{1d12b}" => Some(AccidentalEnum::QuadrupleFlat),
167 "\u{266d}\u{1d12b}" => Some(AccidentalEnum::TripleFlat),
168 "\u{1d12b}" => Some(AccidentalEnum::DoubleFlat),
169 "\u{266d}\u{1d133}" => Some(AccidentalEnum::OneAndAHalfFlat),
170 "\u{266d}" => Some(AccidentalEnum::Flat),
171 "\u{1d133}" => Some(AccidentalEnum::HalfFlat),
172 "\u{1d132}" => Some(AccidentalEnum::HalfSharp),
173 "\u{266e}" => Some(AccidentalEnum::Natural),
174 _ => None,
175 }
176 }
177
178 fn from_alternate_name(s: &str) -> Option<Self> {
179 match s {
180 "n" => Some(AccidentalEnum::Natural),
181 "is" => Some(AccidentalEnum::Sharp),
182 "isis" => Some(AccidentalEnum::DoubleSharp),
183 "isisis" => Some(AccidentalEnum::TripleSharp),
184 "isisisis" => Some(AccidentalEnum::QuadrupleSharp),
185 "ih" | "quarter-sharp" | "semisharp" => Some(AccidentalEnum::HalfSharp),
186 "isih" | "three-quarter-sharp" | "three-quarters-sharp" | "sesquisharp" => {
187 Some(AccidentalEnum::OneAndAHalfSharp)
188 }
189 "b" | "es" => Some(AccidentalEnum::Flat),
190 "eses" => Some(AccidentalEnum::DoubleFlat),
191 "eseses" => Some(AccidentalEnum::TripleFlat),
192 "eseseses" => Some(AccidentalEnum::QuadrupleFlat),
193 "eh" | "quarter-flat" | "semiflat" => Some(AccidentalEnum::HalfFlat),
194 "eseh" | "three-quarter-flat" | "three-quarters-flat" | "sesquiflat" => {
195 Some(AccidentalEnum::OneAndAHalfFlat)
196 }
197 _ => None,
198 }
199 }
200
201 fn from_float(f: FloatType) -> Option<Self> {
202 match f {
203 -4.0 => Some(AccidentalEnum::QuadrupleFlat),
204 -3.0 => Some(AccidentalEnum::TripleFlat),
205 -2.0 => Some(AccidentalEnum::DoubleFlat),
206 -1.5 => Some(AccidentalEnum::OneAndAHalfFlat),
207 -1.0 => Some(AccidentalEnum::Flat),
208 -0.5 => Some(AccidentalEnum::HalfFlat),
209 0.0 => Some(AccidentalEnum::Natural),
210 0.5 => Some(AccidentalEnum::HalfSharp),
211 1.0 => Some(AccidentalEnum::Sharp),
212 1.5 => Some(AccidentalEnum::OneAndAHalfSharp),
213 2.0 => Some(AccidentalEnum::DoubleSharp),
214 3.0 => Some(AccidentalEnum::TripleSharp),
215 4.0 => Some(AccidentalEnum::QuadrupleSharp),
216 _ => None,
217 }
218 }
219
220 fn from_string(s: &str) -> Option<Self> {
221 AccidentalEnum::from_name(s)
222 .or_else(|| AccidentalEnum::from_modifier(s))
223 .or_else(|| AccidentalEnum::from_alter_str(s))
224 .or_else(|| AccidentalEnum::from_unicode(s))
225 .or_else(|| AccidentalEnum::from_alternate_name(s))
226 }
227}
228
229#[derive(Clone, Debug, PartialEq)]
231#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
232pub enum AccidentalSpecifier {
233 Name(String),
235 Alter(FloatType),
237 Accidental(Accidental),
239}
240
241impl From<&str> for AccidentalSpecifier {
242 fn from(value: &str) -> Self {
243 Self::Name(value.to_string())
244 }
245}
246
247impl From<String> for AccidentalSpecifier {
248 fn from(value: String) -> Self {
249 Self::Name(value)
250 }
251}
252
253impl From<i8> for AccidentalSpecifier {
254 fn from(value: i8) -> Self {
255 Self::Alter(value as FloatType)
256 }
257}
258
259impl From<IntegerType> for AccidentalSpecifier {
260 fn from(value: IntegerType) -> Self {
261 Self::Alter(value as FloatType)
262 }
263}
264
265impl From<FloatType> for AccidentalSpecifier {
266 fn from(value: FloatType) -> Self {
267 Self::Alter(value)
268 }
269}
270
271impl From<Accidental> for AccidentalSpecifier {
272 fn from(value: Accidental) -> Self {
273 Self::Accidental(value)
274 }
275}
276
277impl Display for AccidentalSpecifier {
278 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
279 match self {
280 Self::Name(name) => write!(f, "{name}"),
281 Self::Alter(alter) => write!(f, "{alter}"),
282 Self::Accidental(accidental) => write!(f, "{accidental}"),
283 }
284 }
285}
286
287#[derive(Clone, Debug, PartialEq)]
291pub enum AccidentalAttribute {
292 Name(String),
294 Alter(FloatType),
296 Modifier(String),
298}
299
300#[derive(Clone, Debug)]
301#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
302#[must_use]
308pub struct Accidental {
309 display_type: DisplayType,
310 display_status: Option<bool>,
311 display_style: DisplayStyle,
312 display_size: DisplaySize,
313 display_location: DisplayLocation,
314 #[cfg_attr(feature = "serde", serde(default))]
315 color: Option<String>,
316 name: String,
317 modifier: String,
318 pub(crate) alter: FloatType,
319}
320
321impl PartialEq for Accidental {
322 fn eq(&self, other: &Self) -> bool {
323 self.name == other.name
324 }
325}
326
327impl PartialOrd for Accidental {
328 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
329 self.alter.partial_cmp(&other.alter)
330 }
331}
332
333impl FromStr for Accidental {
334 type Err = Error;
335
336 fn from_str(value: &str) -> Result<Self> {
337 Self::new(value)
338 }
339}
340
341impl TryFrom<&str> for Accidental {
342 type Error = Error;
343
344 fn try_from(value: &str) -> Result<Self> {
345 Self::new(value)
346 }
347}
348
349impl TryFrom<String> for Accidental {
350 type Error = Error;
351
352 fn try_from(value: String) -> Result<Self> {
353 Self::new(value)
354 }
355}
356
357impl TryFrom<IntegerType> for Accidental {
358 type Error = Error;
359
360 fn try_from(value: IntegerType) -> Result<Self> {
361 Self::new(value)
362 }
363}
364
365impl TryFrom<i8> for Accidental {
366 type Error = Error;
367
368 fn try_from(value: i8) -> Result<Self> {
369 Self::new(value)
370 }
371}
372
373impl TryFrom<FloatType> for Accidental {
374 type Error = Error;
375
376 fn try_from(value: FloatType) -> Result<Self> {
377 Self::new(value)
378 }
379}
380
381impl Accidental {
382 pub fn new(specifier: impl Into<AccidentalSpecifier>) -> Result<Self> {
385 let specifier = specifier.into();
386 if let AccidentalSpecifier::Accidental(accidental) = specifier {
387 return Ok(accidental);
388 }
389
390 let mut acci = Self {
391 display_type: DisplayType::Normal,
392 display_status: None,
393 display_style: DisplayStyle::Normal,
394 display_size: DisplaySize::Full,
395 display_location: DisplayLocation::Normal,
396 color: None,
397 name: "".to_string(),
398 modifier: "".to_string(),
399 alter: 0.0,
400 };
401
402 acci.set_specifier(specifier, false)?;
403 Ok(acci)
404 }
405
406 pub fn list_names() -> Vec<&'static str> {
408 let mut names = [
409 "double-flat",
410 "double-sharp",
411 "flat",
412 "half-flat",
413 "half-sharp",
414 "natural",
415 "one-and-a-half-flat",
416 "one-and-a-half-sharp",
417 "quadruple-flat",
418 "quadruple-sharp",
419 "sharp",
420 "triple-flat",
421 "triple-sharp",
422 ];
423 names.sort_unstable();
424 names.to_vec()
425 }
426
427 pub fn is_valid_name(name: &str) -> bool {
430 AccidentalEnum::from_string(&name.to_lowercase()).is_some()
431 }
432
433 pub fn standardize_name(name: &str) -> Result<String> {
435 AccidentalEnum::from_string(&name.to_lowercase())
436 .map(|accidental| accidental.to_name().to_string())
437 .ok_or_else(|| {
438 Error::Accidental(format!("'{name}' is not a supported accidental type"))
439 })
440 }
441
442 pub fn set(&mut self, specifier: impl Into<AccidentalSpecifier>) -> Result<()> {
444 self.set_specifier(specifier.into(), false)
445 }
446
447 pub fn set_allowing_non_standard_value(
451 &mut self,
452 specifier: impl Into<AccidentalSpecifier>,
453 ) -> Result<()> {
454 self.set_specifier(specifier.into(), true)
455 }
456
457 fn set_specifier(
458 &mut self,
459 specifier: AccidentalSpecifier,
460 allow_non_standard_value: bool,
461 ) -> Result<()> {
462 if let AccidentalSpecifier::Accidental(accidental) = specifier {
463 *self = accidental;
464 return Ok(());
465 }
466
467 if let Some(accidental) = Self::specifier_to_standard(&specifier) {
468 self.name = accidental.to_name().to_string();
469 self.alter = accidental.to_alter();
470 self.modifier = accidental.to_modifier().to_string();
471 return Ok(());
472 }
473
474 if !allow_non_standard_value {
475 return Err(Error::Accidental(format!(
476 "{specifier} is not a supported accidental type"
477 )));
478 }
479
480 match specifier {
481 AccidentalSpecifier::Name(name) => self.name = name.to_lowercase(),
482 AccidentalSpecifier::Alter(alter) => self.alter = alter,
483 AccidentalSpecifier::Accidental(_) => unreachable!(),
484 }
485 Ok(())
486 }
487
488 fn specifier_to_standard(specifier: &AccidentalSpecifier) -> Option<AccidentalEnum> {
489 match specifier {
490 AccidentalSpecifier::Name(name) => AccidentalEnum::from_string(&name.to_lowercase()),
491 AccidentalSpecifier::Alter(alter) => AccidentalEnum::from_float(*alter),
492 AccidentalSpecifier::Accidental(accidental) => {
493 AccidentalEnum::from_name(accidental.name())
494 }
495 }
496 }
497
498 pub fn name(&self) -> &str {
500 &self.name
501 }
502
503 pub fn set_name(&mut self, name: impl Into<String>) -> Result<()> {
506 self.set_allowing_non_standard_value(name.into())
507 }
508
509 pub fn alter(&self) -> FloatType {
511 self.alter
512 }
513
514 pub fn set_alter(&mut self, alter: FloatType) -> Result<()> {
517 self.set_allowing_non_standard_value(alter)
518 }
519
520 pub fn modifier(&self) -> &str {
522 &self.modifier
523 }
524
525 pub fn set_modifier(&mut self, modifier: impl Into<String>) {
528 let modifier = modifier.into();
529 if let Some(accidental) = AccidentalEnum::from_modifier(&modifier) {
530 self.name = accidental.to_name().to_string();
531 self.alter = accidental.to_alter();
532 self.modifier = accidental.to_modifier().to_string();
533 } else {
534 self.modifier = modifier;
535 }
536 }
537
538 pub fn unicode(&self) -> String {
540 AccidentalEnum::from_modifier(&self.modifier)
541 .map(|accidental| accidental.to_unicode().to_string())
542 .unwrap_or_else(|| self.modifier.clone())
543 }
544
545 pub fn full_name(&self) -> &str {
548 self.name()
549 }
550
551 pub fn is_twelve_tone(&self) -> bool {
553 !matches!(
554 self.name.as_str(),
555 "half-sharp" | "one-and-a-half-sharp" | "half-flat" | "one-and-a-half-flat"
556 )
557 }
558
559 pub fn set_name_independently(&mut self, name: impl Into<String>) {
561 self.name = name.into();
562 }
563
564 pub fn set_alter_independently(&mut self, alter: FloatType) {
566 self.alter = alter;
567 }
568
569 pub fn set_modifier_independently(&mut self, modifier: impl Into<String>) {
571 self.modifier = modifier.into();
572 }
573
574 pub fn set_attribute_independently(&mut self, attribute: AccidentalAttribute) {
579 match attribute {
580 AccidentalAttribute::Name(name) => self.set_name_independently(name),
581 AccidentalAttribute::Alter(alter) => self.set_alter_independently(alter),
582 AccidentalAttribute::Modifier(modifier) => self.set_modifier_independently(modifier),
583 }
584 }
585
586 pub fn color(&self) -> Option<&str> {
589 self.color.as_deref()
590 }
591
592 pub fn set_color(&mut self, color: Option<String>) {
594 self.color = color;
595 }
596
597 pub fn inherit_display(&mut self, other: &Accidental) {
599 self.display_type = other.display_type.clone();
600 self.display_status = other.display_status;
601 self.display_style = other.display_style.clone();
602 self.display_size = other.display_size.clone();
603 self.display_location = other.display_location.clone();
604 self.color = other.color.clone();
605 }
606
607 pub fn display_type(&self) -> &'static str {
609 display_type_to_str(&self.display_type)
610 }
611
612 pub fn set_display_type(&mut self, value: &str) -> Result<()> {
614 self.display_type = display_type_from_str(value).ok_or_else(|| {
615 Error::Accidental(format!("Supplied display type is not supported: '{value}'"))
616 })?;
617 Ok(())
618 }
619
620 pub fn display_status(&self) -> Option<bool> {
623 self.display_status
624 }
625
626 pub fn set_display_status(&mut self, value: Option<bool>) {
628 self.display_status = value;
629 }
630
631 pub fn display_style(&self) -> &'static str {
633 display_style_to_str(&self.display_style)
634 }
635
636 pub fn set_display_style(&mut self, value: &str) -> Result<()> {
638 self.display_style = display_style_from_str(value).ok_or_else(|| {
639 Error::Accidental(format!(
640 "Supplied display style is not supported: {value:?}"
641 ))
642 })?;
643 Ok(())
644 }
645
646 pub fn display_size(&self) -> String {
648 display_size_to_string(&self.display_size)
649 }
650
651 pub fn set_display_size(&mut self, value: &str) -> Result<()> {
653 self.display_size = display_size_from_str(value)?;
654 Ok(())
655 }
656
657 pub fn display_location(&self) -> &'static str {
659 display_location_to_str(&self.display_location)
660 }
661
662 pub fn set_display_location(&mut self, value: &str) -> Result<()> {
664 self.display_location = display_location_from_str(value).ok_or_else(|| {
665 Error::Accidental(format!(
666 "Supplied display location is not supported: {value:?}"
667 ))
668 })?;
669 Ok(())
670 }
671
672 pub fn natural() -> Accidental {
674 let x = Accidental::new("natural");
675 assert!(x.is_ok());
676 match x {
677 Ok(val) => val,
678 Err(err) => panic!("creating a natural Accidental should never fail: {err}"),
679 }
680 }
681
682 pub fn flat() -> Accidental {
684 let x = Accidental::new("flat");
685 assert!(x.is_ok());
686 match x {
687 Ok(val) => val,
688 Err(err) => panic!("creating a flat Accidental should never fail: {err}"),
689 }
690 }
691
692 pub fn sharp() -> Accidental {
694 let x = Accidental::new("sharp");
695 assert!(x.is_ok());
696 match x {
697 Ok(val) => val,
698 Err(err) => panic!("creating a sharp Accidental should never fail: {err}"),
699 }
700 }
701}
702
703fn display_type_to_str(value: &DisplayType) -> &'static str {
704 match value {
705 DisplayType::Normal => "normal",
706 DisplayType::Always => "always",
707 DisplayType::Never => "never",
708 DisplayType::UnlessRepeated => "unless-repeated",
709 DisplayType::EvenTied => "even-tied",
710 DisplayType::IfAbsolutelyNecessary => "if-absolutely-necessary",
711 }
712}
713
714fn display_type_from_str(value: &str) -> Option<DisplayType> {
715 match value {
716 "normal" => Some(DisplayType::Normal),
717 "always" => Some(DisplayType::Always),
718 "never" => Some(DisplayType::Never),
719 "unless-repeated" => Some(DisplayType::UnlessRepeated),
720 "even-tied" => Some(DisplayType::EvenTied),
721 "if-absolutely-necessary" => Some(DisplayType::IfAbsolutelyNecessary),
722 _ => None,
723 }
724}
725
726fn display_style_to_str(value: &DisplayStyle) -> &'static str {
727 match value {
728 DisplayStyle::Normal => "normal",
729 DisplayStyle::Parentheses => "parentheses",
730 DisplayStyle::Bracket => "bracket",
731 DisplayStyle::Both => "both",
732 }
733}
734
735fn display_style_from_str(value: &str) -> Option<DisplayStyle> {
736 match value {
737 "normal" => Some(DisplayStyle::Normal),
738 "parentheses" => Some(DisplayStyle::Parentheses),
739 "bracket" => Some(DisplayStyle::Bracket),
740 "both" => Some(DisplayStyle::Both),
741 _ => None,
742 }
743}
744
745fn display_size_to_string(value: &DisplaySize) -> String {
746 match value {
747 DisplaySize::Full => "full".to_string(),
748 DisplaySize::Cue => "cue".to_string(),
749 DisplaySize::Large => "large".to_string(),
750 DisplaySize::Percentage(percentage) => percentage.to_string(),
751 }
752}
753
754fn display_size_from_str(value: &str) -> Result<DisplaySize> {
755 match value {
756 "full" => Ok(DisplaySize::Full),
757 "cue" => Ok(DisplaySize::Cue),
758 "large" => Ok(DisplaySize::Large),
759 _ => value
760 .parse::<FloatType>()
761 .map(DisplaySize::Percentage)
762 .map_err(|_| {
763 Error::Accidental(format!("Supplied display size is not supported: {value:?}"))
764 }),
765 }
766}
767
768fn display_location_to_str(value: &DisplayLocation) -> &'static str {
769 match value {
770 DisplayLocation::Normal => "normal",
771 DisplayLocation::Above => "above",
772 DisplayLocation::Ficta => "ficta",
773 DisplayLocation::Below => "below",
774 }
775}
776
777fn display_location_from_str(value: &str) -> Option<DisplayLocation> {
778 match value {
779 "normal" => Some(DisplayLocation::Normal),
780 "above" => Some(DisplayLocation::Above),
781 "ficta" => Some(DisplayLocation::Ficta),
782 "below" => Some(DisplayLocation::Below),
783 _ => None,
784 }
785}
786
787impl Default for Accidental {
788 fn default() -> Self {
789 Self::natural()
790 }
791}
792
793impl std::fmt::Display for Accidental {
794 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
795 write!(f, "{}", self.name)
796 }
797}
798
799#[cfg(test)]
800mod tests {
801
802 #[test]
803 fn an_accidental_is_built_from_a_number_or_an_owned_name_and_recoloured() {
804 use super::{Accidental, AccidentalAttribute, AccidentalSpecifier};
805
806 assert_eq!(
807 Accidental::try_from("#".to_string()).unwrap().name(),
808 "sharp"
809 );
810 assert_eq!(Accidental::try_from(-1).unwrap().name(), "flat");
811 assert_eq!(Accidental::try_from(2i8).unwrap().name(), "double-sharp");
812 assert!(Accidental::try_from(9).is_err());
813 assert!(matches!(
814 AccidentalSpecifier::from(1i8),
815 AccidentalSpecifier::Alter(_)
816 ));
817
818 let mut accidental = Accidental::natural();
819 assert_eq!(accidental.color(), None);
820 accidental.set_color(Some("red".to_string()));
821 assert_eq!(accidental.color(), Some("red"));
822 accidental.set_attribute_independently(AccidentalAttribute::Name("sharp".to_string()));
823 assert_eq!(accidental.name(), "sharp");
824 accidental.set_attribute_independently(AccidentalAttribute::Alter(-1.0));
825 assert_eq!(accidental.alter(), -1.0);
826 accidental.set_attribute_independently(AccidentalAttribute::Modifier("--".to_string()));
827 assert_eq!(accidental.modifier(), "--");
828 }
829
830 use super::{Accidental, AccidentalSpecifier};
831
832 #[test]
833 fn test_natural() {
834 let acc = Accidental::natural();
835 assert_eq!(acc.name, "natural");
836 assert_eq!(acc.alter, 0.0);
837 assert_eq!(acc.modifier(), "");
838 }
839
840 #[test]
841 fn test_sharp() {
842 let acc = Accidental::sharp();
843 assert_eq!(acc.name, "sharp");
844 assert_eq!(acc.alter, 1.0);
845 assert_eq!(acc.modifier(), "#");
846 }
847
848 #[test]
849 fn test_flat() {
850 let acc = Accidental::flat();
851 assert_eq!(acc.name, "flat");
852 assert_eq!(acc.alter, -1.0);
853 assert_eq!(acc.modifier(), "-");
854 }
855
856 #[test]
857 fn test_creation_from_int() {
858 let acc_sharp = Accidental::new(1).unwrap();
859 assert_eq!(acc_sharp.name, "sharp");
860 assert_eq!(acc_sharp.alter, 1.0);
861
862 let acc_flat = Accidental::new(-1).unwrap();
863 assert_eq!(acc_flat.name, "flat");
864 assert_eq!(acc_flat.alter, -1.0);
865
866 let acc_natural = Accidental::new(0).unwrap();
867 assert_eq!(acc_natural.name, "natural");
868 assert_eq!(acc_natural.alter, 0.0);
869 }
870
871 #[test]
872 fn accidental_supports_rust_conversion_traits() {
873 let parsed: Accidental = "#".parse().unwrap();
874 assert_eq!(parsed.name(), "sharp");
875
876 let from_str = Accidental::try_from("flat").unwrap();
877 assert_eq!(from_str.modifier(), "-");
878
879 let from_alter = Accidental::try_from(-0.5).unwrap();
880 assert_eq!(from_alter.name(), "half-flat");
881 }
882
883 #[test]
884 fn test_creation_from_float() {
885 let acc_double_sharp: Accidental = Accidental::new(2.0).unwrap();
886 assert_eq!(acc_double_sharp.name, "double-sharp");
887 assert_eq!(acc_double_sharp.alter, 2.0);
888
889 let acc_half_flat: Accidental = Accidental::new(-0.5).unwrap();
890 assert_eq!(acc_half_flat.name, "half-flat");
891 assert_eq!(acc_half_flat.alter, -0.5);
892 }
893
894 #[test]
895 fn test_creation_from_str() {
896 let acc1: Accidental = Accidental::new("sharp").unwrap();
897 assert_eq!(acc1.name, "sharp");
898 assert_eq!(acc1.alter, 1.0);
899
900 let acc2: Accidental = Accidental::new("Flat").unwrap();
902 assert_eq!(acc2.name, "flat");
903 assert_eq!(acc2.alter, -1.0);
904 }
905
906 #[test]
907 fn test_creation_from_string() {
908 let acc: Accidental = Accidental::new("double-flat".to_string()).unwrap();
909 assert_eq!(acc.name, "double-flat");
910 assert_eq!(acc.alter, -2.0);
911 }
912
913 #[test]
914 fn test_invalid_accidental() {
915 let result = Accidental::new("invalid");
916 assert!(
917 result.is_err(),
918 "An invalid accidental should return an error"
919 );
920 }
921
922 #[test]
923 fn test_display_trait() {
924 let acc = Accidental::sharp();
925 assert_eq!(format!("{acc}"), "sharp");
926 }
927
928 #[test]
929 fn test_equality() {
930 let acc1 = Accidental::sharp();
931 let acc2 = Accidental::sharp();
932 let acc3 = Accidental::flat();
933 assert_eq!(acc1, acc2);
934 assert_ne!(acc1, acc3);
935 }
936
937 #[test]
938 fn test_alternate_names() {
939 let acc_sharp: Accidental = Accidental::new("is".to_string()).unwrap();
941 assert_eq!(acc_sharp.name, "sharp");
942 assert_eq!(acc_sharp.alter, 1.0);
943
944 let acc_double_sharp: Accidental = Accidental::new("isis".to_string()).unwrap();
945 assert_eq!(acc_double_sharp.name, "double-sharp");
946 assert_eq!(acc_double_sharp.alter, 2.0);
947
948 let acc_triple_sharp: Accidental = Accidental::new("isisis".to_string()).unwrap();
949 assert_eq!(acc_triple_sharp.name, "triple-sharp");
950 assert_eq!(acc_triple_sharp.alter, 3.0);
951
952 let acc_double_flat: Accidental = Accidental::new("eses".to_string()).unwrap();
953 assert_eq!(acc_double_flat.name, "double-flat");
954 assert_eq!(acc_double_flat.alter, -2.0);
955 }
956
957 #[test]
958 fn public_api_matches_music21_accidental_basics() {
959 let mut accidental = Accidental::new("sharp").unwrap();
960 assert_eq!(accidental.name(), "sharp");
961 assert_eq!(accidental.alter(), 1.0);
962 assert_eq!(accidental.modifier(), "#");
963 assert_eq!(accidental.unicode(), "\u{266f}");
964 assert_eq!(accidental.full_name(), "sharp");
965 assert!(accidental.is_twelve_tone());
966
967 accidental.set("--").unwrap();
968 assert_eq!(accidental.name(), "double-flat");
969 assert_eq!(accidental.alter(), -2.0);
970 assert_eq!(accidental.modifier(), "--");
971
972 accidental.set("quarter-sharp").unwrap();
973 assert_eq!(accidental.name(), "half-sharp");
974 assert_eq!(accidental.alter(), 0.5);
975 assert!(!accidental.is_twelve_tone());
976 }
977
978 #[test]
979 fn public_api_preserves_non_standard_values_when_requested() {
980 let mut accidental = Accidental::new("flat").unwrap();
981 accidental
982 .set_allowing_non_standard_value("flat-flat-up")
983 .unwrap();
984 assert_eq!(accidental.name(), "flat-flat-up");
985 assert_eq!(accidental.alter(), -1.0);
986 assert_eq!(accidental.modifier(), "-");
987
988 accidental.set_alter(-0.9).unwrap();
989 assert_eq!(accidental.name(), "flat-flat-up");
990 assert_eq!(accidental.alter(), -0.9);
991 assert_eq!(accidental.modifier(), "-");
992
993 accidental.set_modifier("&");
994 assert_eq!(accidental.name(), "flat-flat-up");
995 assert_eq!(accidental.alter(), -0.9);
996 assert_eq!(accidental.modifier(), "&");
997 }
998
999 #[test]
1000 fn public_api_supports_display_metadata() {
1001 let mut source = Accidental::new("double-flat").unwrap();
1002 source.set_display_type("always").unwrap();
1003 source.set_display_status(Some(true));
1004 source.set_display_style("parentheses").unwrap();
1005 source.set_display_size("cue").unwrap();
1006 source.set_display_location("above").unwrap();
1007
1008 let mut target = Accidental::new("sharp").unwrap();
1009 target.inherit_display(&source);
1010 assert_eq!(target.display_type(), "always");
1011 assert_eq!(target.display_status(), Some(true));
1012 assert_eq!(target.display_style(), "parentheses");
1013 assert_eq!(target.display_size(), "cue");
1014 assert_eq!(target.display_location(), "above");
1015 }
1016
1017 #[test]
1018 fn public_api_covers_name_helpers_and_standardization_errors() {
1019 let names = Accidental::list_names();
1020 assert_eq!(names.first(), Some(&"double-flat"));
1021 assert!(names.contains(&"quadruple-sharp"));
1022
1023 assert!(Accidental::is_valid_name("quarter-sharp"));
1024 assert!(Accidental::is_valid_name("es"));
1025 assert!(!Accidental::is_valid_name("not-an-accidental"));
1026
1027 assert_eq!(Accidental::standardize_name("##").unwrap(), "double-sharp");
1028 let err = Accidental::standardize_name("not-an-accidental").unwrap_err();
1029 assert!(err.to_string().contains("not a supported accidental type"));
1030 }
1031
1032 #[test]
1033 fn public_api_covers_setter_branches_and_independent_updates() {
1034 let mut accidental = Accidental::default();
1035 assert_eq!(accidental.name(), "natural");
1036
1037 accidental.set(Accidental::flat()).unwrap();
1038 assert_eq!(accidental.name(), "flat");
1039 assert_eq!(accidental.unicode(), "\u{266d}");
1040
1041 accidental.set_name("sharp").unwrap();
1042 assert_eq!(accidental.alter(), 1.0);
1043 assert_eq!(accidental.modifier(), "#");
1044
1045 accidental.set_modifier("~~");
1046 assert_eq!(accidental.name(), "sharp");
1047 assert_eq!(accidental.modifier(), "~~");
1048 assert_eq!(accidental.unicode(), "~~");
1049
1050 accidental.set_name_independently("custom");
1051 accidental.set_alter_independently(0.25);
1052 accidental.set_modifier_independently("^");
1053 assert_eq!(accidental.full_name(), "custom");
1054 assert_eq!(accidental.alter(), 0.25);
1055 assert_eq!(accidental.modifier(), "^");
1056
1057 let cloned = Accidental::new(AccidentalSpecifier::from(accidental.clone())).unwrap();
1058 assert_eq!(cloned, accidental);
1059 }
1060
1061 #[test]
1062 fn public_api_rejects_invalid_display_metadata() {
1063 let mut accidental = Accidental::new("natural").unwrap();
1064 assert!(accidental.set_display_type("sometimes").is_err());
1065 assert!(accidental.set_display_style("curly").is_err());
1066 assert!(accidental.set_display_size("huge").is_err());
1067 assert!(accidental.set_display_location("beside").is_err());
1068
1069 accidental.set_display_type("never").unwrap();
1070 accidental.set_display_style("both").unwrap();
1071 accidental.set_display_size("125.5").unwrap();
1072 accidental.set_display_location("below").unwrap();
1073
1074 assert_eq!(accidental.display_type(), "never");
1075 assert_eq!(accidental.display_style(), "both");
1076 assert_eq!(accidental.display_size(), "125.5");
1077 assert_eq!(accidental.display_location(), "below");
1078 }
1079
1080 #[test]
1081 fn accidental_ordering_follows_alter() {
1082 assert!(Accidental::flat() < Accidental::natural());
1083 assert!(Accidental::sharp() > Accidental::natural());
1084 }
1085}