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}",
146 AccidentalEnum::DoubleFlat => "\u{1d12b}",
147 AccidentalEnum::OneAndAHalfFlat => "\u{266d}\u{1d132}",
148 AccidentalEnum::Flat => "\u{266d}",
149 AccidentalEnum::HalfFlat => "\u{1d132}",
150 AccidentalEnum::Natural => "\u{266e}",
151 }
152 }
153
154 fn from_unicode(s: &str) -> Option<Self> {
155 match s {
156 "\u{1d12a}\u{1d12a}" => Some(AccidentalEnum::QuadrupleSharp),
157 "\u{266f}\u{1d12a}" => Some(AccidentalEnum::TripleSharp),
158 "\u{1d12a}" => Some(AccidentalEnum::DoubleSharp),
159 "\u{266f}\u{1d132}" => Some(AccidentalEnum::OneAndAHalfSharp),
160 "\u{266f}" => Some(AccidentalEnum::Sharp),
161 "\u{1d12b}\u{1d12b}" => Some(AccidentalEnum::QuadrupleFlat),
162 "\u{266d}" => Some(AccidentalEnum::Flat),
163 "\u{1d12b}" => Some(AccidentalEnum::DoubleFlat),
164 "\u{266d}\u{1d132}" => Some(AccidentalEnum::OneAndAHalfFlat),
165 "\u{1d132}" => Some(AccidentalEnum::HalfSharp),
166 "\u{266e}" => Some(AccidentalEnum::Natural),
167 _ => None,
168 }
169 }
170
171 fn from_alternate_name(s: &str) -> Option<Self> {
172 match s {
173 "n" => Some(AccidentalEnum::Natural),
174 "is" => Some(AccidentalEnum::Sharp),
175 "isis" => Some(AccidentalEnum::DoubleSharp),
176 "isisis" => Some(AccidentalEnum::TripleSharp),
177 "isisisis" => Some(AccidentalEnum::QuadrupleSharp),
178 "ih" | "quarter-sharp" | "semisharp" => Some(AccidentalEnum::HalfSharp),
179 "isih" | "three-quarter-sharp" | "three-quarters-sharp" | "sesquisharp" => {
180 Some(AccidentalEnum::OneAndAHalfSharp)
181 }
182 "b" | "es" => Some(AccidentalEnum::Flat),
183 "eses" => Some(AccidentalEnum::DoubleFlat),
184 "eseses" => Some(AccidentalEnum::TripleFlat),
185 "eseseses" => Some(AccidentalEnum::QuadrupleFlat),
186 "eh" | "quarter-flat" | "semiflat" => Some(AccidentalEnum::HalfFlat),
187 "eseh" | "three-quarter-flat" | "three-quarters-flat" | "sesquiflat" => {
188 Some(AccidentalEnum::OneAndAHalfFlat)
189 }
190 _ => None,
191 }
192 }
193
194 fn from_float(f: FloatType) -> Option<Self> {
195 match f {
196 -4.0 => Some(AccidentalEnum::QuadrupleFlat),
197 -3.0 => Some(AccidentalEnum::TripleFlat),
198 -2.0 => Some(AccidentalEnum::DoubleFlat),
199 -1.5 => Some(AccidentalEnum::OneAndAHalfFlat),
200 -1.0 => Some(AccidentalEnum::Flat),
201 -0.5 => Some(AccidentalEnum::HalfFlat),
202 0.0 => Some(AccidentalEnum::Natural),
203 0.5 => Some(AccidentalEnum::HalfSharp),
204 1.0 => Some(AccidentalEnum::Sharp),
205 1.5 => Some(AccidentalEnum::OneAndAHalfSharp),
206 2.0 => Some(AccidentalEnum::DoubleSharp),
207 3.0 => Some(AccidentalEnum::TripleSharp),
208 4.0 => Some(AccidentalEnum::QuadrupleSharp),
209 _ => None,
210 }
211 }
212
213 fn from_string(s: &str) -> Option<Self> {
214 AccidentalEnum::from_name(s)
215 .or_else(|| AccidentalEnum::from_modifier(s))
216 .or_else(|| AccidentalEnum::from_alter_str(s))
217 .or_else(|| AccidentalEnum::from_unicode(s))
218 .or_else(|| AccidentalEnum::from_alternate_name(s))
219 }
220}
221
222#[derive(Clone, Debug, PartialEq)]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225pub enum AccidentalSpecifier {
226 Name(String),
228 Alter(FloatType),
230 Accidental(Accidental),
232}
233
234impl From<&str> for AccidentalSpecifier {
235 fn from(value: &str) -> Self {
236 Self::Name(value.to_string())
237 }
238}
239
240impl From<String> for AccidentalSpecifier {
241 fn from(value: String) -> Self {
242 Self::Name(value)
243 }
244}
245
246impl From<i8> for AccidentalSpecifier {
247 fn from(value: i8) -> Self {
248 Self::Alter(value as FloatType)
249 }
250}
251
252impl From<IntegerType> for AccidentalSpecifier {
253 fn from(value: IntegerType) -> Self {
254 Self::Alter(value as FloatType)
255 }
256}
257
258impl From<FloatType> for AccidentalSpecifier {
259 fn from(value: FloatType) -> Self {
260 Self::Alter(value)
261 }
262}
263
264impl From<Accidental> for AccidentalSpecifier {
265 fn from(value: Accidental) -> Self {
266 Self::Accidental(value)
267 }
268}
269
270impl Display for AccidentalSpecifier {
271 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
272 match self {
273 Self::Name(name) => write!(f, "{name}"),
274 Self::Alter(alter) => write!(f, "{alter}"),
275 Self::Accidental(accidental) => write!(f, "{accidental}"),
276 }
277 }
278}
279
280#[derive(Clone, Debug)]
281#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
282pub struct Accidental {
288 _display_type: DisplayType,
289 _display_status: Option<bool>,
290 display_style: DisplayStyle,
291 display_size: DisplaySize,
292 display_location: DisplayLocation,
293 _name: String,
294 _modifier: String,
295 pub(crate) _alter: FloatType,
296}
297
298impl PartialEq for Accidental {
299 fn eq(&self, other: &Self) -> bool {
300 self._name == other._name
301 }
302}
303
304impl PartialOrd for Accidental {
305 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
306 self._alter.partial_cmp(&other._alter)
307 }
308}
309
310impl FromStr for Accidental {
311 type Err = Error;
312
313 fn from_str(value: &str) -> Result<Self> {
314 Self::new(value)
315 }
316}
317
318impl TryFrom<&str> for Accidental {
319 type Error = Error;
320
321 fn try_from(value: &str) -> Result<Self> {
322 Self::new(value)
323 }
324}
325
326impl TryFrom<String> for Accidental {
327 type Error = Error;
328
329 fn try_from(value: String) -> Result<Self> {
330 Self::new(value)
331 }
332}
333
334impl TryFrom<IntegerType> for Accidental {
335 type Error = Error;
336
337 fn try_from(value: IntegerType) -> Result<Self> {
338 Self::new(value)
339 }
340}
341
342impl TryFrom<i8> for Accidental {
343 type Error = Error;
344
345 fn try_from(value: i8) -> Result<Self> {
346 Self::new(value)
347 }
348}
349
350impl TryFrom<FloatType> for Accidental {
351 type Error = Error;
352
353 fn try_from(value: FloatType) -> Result<Self> {
354 Self::new(value)
355 }
356}
357
358impl Accidental {
359 pub fn new(specifier: impl Into<AccidentalSpecifier>) -> Result<Self> {
362 let specifier = specifier.into();
363 if let AccidentalSpecifier::Accidental(accidental) = specifier {
364 return Ok(accidental);
365 }
366
367 let mut acci = Self {
368 _display_type: DisplayType::Normal,
369 _display_status: None,
370 display_style: DisplayStyle::Normal,
371 display_size: DisplaySize::Full,
372 display_location: DisplayLocation::Normal,
373 _name: "".to_string(),
374 _modifier: "".to_string(),
375 _alter: 0.0,
376 };
377
378 acci.set_specifier(specifier, false)?;
379 Ok(acci)
380 }
381
382 pub fn list_names() -> Vec<&'static str> {
384 let mut names = [
385 "double-flat",
386 "double-sharp",
387 "flat",
388 "half-flat",
389 "half-sharp",
390 "natural",
391 "one-and-a-half-flat",
392 "one-and-a-half-sharp",
393 "quadruple-flat",
394 "quadruple-sharp",
395 "sharp",
396 "triple-flat",
397 "triple-sharp",
398 ];
399 names.sort_unstable();
400 names.to_vec()
401 }
402
403 pub fn is_valid_name(name: &str) -> bool {
406 AccidentalEnum::from_string(&name.to_lowercase()).is_some()
407 }
408
409 pub fn standardize_name(name: &str) -> Result<String> {
411 AccidentalEnum::from_string(&name.to_lowercase())
412 .map(|accidental| accidental.to_name().to_string())
413 .ok_or_else(|| {
414 Error::Accidental(format!("{name:?} is not a supported accidental type"))
415 })
416 }
417
418 pub fn set(&mut self, specifier: impl Into<AccidentalSpecifier>) -> Result<()> {
420 self.set_specifier(specifier.into(), false)
421 }
422
423 pub fn set_allowing_non_standard_value(
427 &mut self,
428 specifier: impl Into<AccidentalSpecifier>,
429 ) -> Result<()> {
430 self.set_specifier(specifier.into(), true)
431 }
432
433 fn set_specifier(
434 &mut self,
435 specifier: AccidentalSpecifier,
436 allow_non_standard_value: bool,
437 ) -> Result<()> {
438 if let AccidentalSpecifier::Accidental(accidental) = specifier {
439 *self = accidental;
440 return Ok(());
441 }
442
443 if let Some(accidental) = Self::specifier_to_standard(&specifier) {
444 self._name = accidental.to_name().to_string();
445 self._alter = accidental.to_alter();
446 self._modifier = accidental.to_modifier().to_string();
447 return Ok(());
448 }
449
450 if !allow_non_standard_value {
451 return Err(Error::Accidental(format!(
452 "{specifier} is not a supported accidental type"
453 )));
454 }
455
456 match specifier {
457 AccidentalSpecifier::Name(name) => self._name = name.to_lowercase(),
458 AccidentalSpecifier::Alter(alter) => self._alter = alter,
459 AccidentalSpecifier::Accidental(_) => unreachable!(),
460 }
461 Ok(())
462 }
463
464 fn specifier_to_standard(specifier: &AccidentalSpecifier) -> Option<AccidentalEnum> {
465 match specifier {
466 AccidentalSpecifier::Name(name) => AccidentalEnum::from_string(&name.to_lowercase()),
467 AccidentalSpecifier::Alter(alter) => AccidentalEnum::from_float(*alter),
468 AccidentalSpecifier::Accidental(accidental) => {
469 AccidentalEnum::from_name(accidental.name())
470 }
471 }
472 }
473
474 pub fn name(&self) -> &str {
476 &self._name
477 }
478
479 pub fn set_name(&mut self, name: impl Into<String>) -> Result<()> {
482 self.set_allowing_non_standard_value(name.into())
483 }
484
485 pub fn alter(&self) -> FloatType {
487 self._alter
488 }
489
490 pub fn set_alter(&mut self, alter: FloatType) -> Result<()> {
493 self.set_allowing_non_standard_value(alter)
494 }
495
496 pub fn modifier(&self) -> &str {
498 &self._modifier
499 }
500
501 pub fn set_modifier(&mut self, modifier: impl Into<String>) {
504 let modifier = modifier.into();
505 if let Some(accidental) = AccidentalEnum::from_modifier(&modifier) {
506 self._name = accidental.to_name().to_string();
507 self._alter = accidental.to_alter();
508 self._modifier = accidental.to_modifier().to_string();
509 } else {
510 self._modifier = modifier;
511 }
512 }
513
514 pub fn unicode(&self) -> String {
516 AccidentalEnum::from_modifier(&self._modifier)
517 .map(|accidental| accidental.to_unicode().to_string())
518 .unwrap_or_else(|| self._modifier.clone())
519 }
520
521 pub fn full_name(&self) -> &str {
524 self.name()
525 }
526
527 pub fn is_twelve_tone(&self) -> bool {
529 !matches!(
530 self._name.as_str(),
531 "half-sharp" | "one-and-a-half-sharp" | "half-flat" | "one-and-a-half-flat"
532 )
533 }
534
535 pub fn set_name_independently(&mut self, name: impl Into<String>) {
537 self._name = name.into();
538 }
539
540 pub fn set_alter_independently(&mut self, alter: FloatType) {
542 self._alter = alter;
543 }
544
545 pub fn set_modifier_independently(&mut self, modifier: impl Into<String>) {
547 self._modifier = modifier.into();
548 }
549
550 pub fn inherit_display(&mut self, other: &Accidental) {
552 self._display_type = other._display_type.clone();
553 self._display_status = other._display_status;
554 self.display_style = other.display_style.clone();
555 self.display_size = other.display_size.clone();
556 self.display_location = other.display_location.clone();
557 }
558
559 pub fn display_type(&self) -> &'static str {
561 display_type_to_str(&self._display_type)
562 }
563
564 pub fn set_display_type(&mut self, value: &str) -> Result<()> {
566 self._display_type = display_type_from_str(value).ok_or_else(|| {
567 Error::Accidental(format!("Supplied display type is not supported: {value:?}"))
568 })?;
569 Ok(())
570 }
571
572 pub fn display_status(&self) -> Option<bool> {
575 self._display_status
576 }
577
578 pub fn set_display_status(&mut self, value: Option<bool>) {
580 self._display_status = value;
581 }
582
583 pub fn display_style(&self) -> &'static str {
585 display_style_to_str(&self.display_style)
586 }
587
588 pub fn set_display_style(&mut self, value: &str) -> Result<()> {
590 self.display_style = display_style_from_str(value).ok_or_else(|| {
591 Error::Accidental(format!(
592 "Supplied display style is not supported: {value:?}"
593 ))
594 })?;
595 Ok(())
596 }
597
598 pub fn display_size(&self) -> String {
600 display_size_to_string(&self.display_size)
601 }
602
603 pub fn set_display_size(&mut self, value: &str) -> Result<()> {
605 self.display_size = display_size_from_str(value)?;
606 Ok(())
607 }
608
609 pub fn display_location(&self) -> &'static str {
611 display_location_to_str(&self.display_location)
612 }
613
614 pub fn set_display_location(&mut self, value: &str) -> Result<()> {
616 self.display_location = display_location_from_str(value).ok_or_else(|| {
617 Error::Accidental(format!(
618 "Supplied display location is not supported: {value:?}"
619 ))
620 })?;
621 Ok(())
622 }
623
624 pub fn natural() -> Accidental {
626 let x = Accidental::new("natural");
627 assert!(x.is_ok());
628 match x {
629 Ok(val) => val,
630 Err(err) => panic!("creating a natural Accidental should never fail: {err}"),
631 }
632 }
633
634 pub fn flat() -> Accidental {
636 let x = Accidental::new("flat");
637 assert!(x.is_ok());
638 match x {
639 Ok(val) => val,
640 Err(err) => panic!("creating a flat Accidental should never fail: {err}"),
641 }
642 }
643
644 pub fn sharp() -> Accidental {
646 let x = Accidental::new("sharp");
647 assert!(x.is_ok());
648 match x {
649 Ok(val) => val,
650 Err(err) => panic!("creating a sharp Accidental should never fail: {err}"),
651 }
652 }
653}
654
655fn display_type_to_str(value: &DisplayType) -> &'static str {
656 match value {
657 DisplayType::Normal => "normal",
658 DisplayType::Always => "always",
659 DisplayType::Never => "never",
660 DisplayType::UnlessRepeated => "unless-repeated",
661 DisplayType::EvenTied => "even-tied",
662 DisplayType::IfAbsolutelyNecessary => "if-absolutely-necessary",
663 }
664}
665
666fn display_type_from_str(value: &str) -> Option<DisplayType> {
667 match value {
668 "normal" => Some(DisplayType::Normal),
669 "always" => Some(DisplayType::Always),
670 "never" => Some(DisplayType::Never),
671 "unless-repeated" => Some(DisplayType::UnlessRepeated),
672 "even-tied" => Some(DisplayType::EvenTied),
673 "if-absolutely-necessary" => Some(DisplayType::IfAbsolutelyNecessary),
674 _ => None,
675 }
676}
677
678fn display_style_to_str(value: &DisplayStyle) -> &'static str {
679 match value {
680 DisplayStyle::Normal => "normal",
681 DisplayStyle::Parentheses => "parentheses",
682 DisplayStyle::Bracket => "bracket",
683 DisplayStyle::Both => "both",
684 }
685}
686
687fn display_style_from_str(value: &str) -> Option<DisplayStyle> {
688 match value {
689 "normal" => Some(DisplayStyle::Normal),
690 "parentheses" => Some(DisplayStyle::Parentheses),
691 "bracket" => Some(DisplayStyle::Bracket),
692 "both" => Some(DisplayStyle::Both),
693 _ => None,
694 }
695}
696
697fn display_size_to_string(value: &DisplaySize) -> String {
698 match value {
699 DisplaySize::Full => "full".to_string(),
700 DisplaySize::Cue => "cue".to_string(),
701 DisplaySize::Large => "large".to_string(),
702 DisplaySize::Percentage(percentage) => percentage.to_string(),
703 }
704}
705
706fn display_size_from_str(value: &str) -> Result<DisplaySize> {
707 match value {
708 "full" => Ok(DisplaySize::Full),
709 "cue" => Ok(DisplaySize::Cue),
710 "large" => Ok(DisplaySize::Large),
711 _ => value
712 .parse::<FloatType>()
713 .map(DisplaySize::Percentage)
714 .map_err(|_| {
715 Error::Accidental(format!("Supplied display size is not supported: {value:?}"))
716 }),
717 }
718}
719
720fn display_location_to_str(value: &DisplayLocation) -> &'static str {
721 match value {
722 DisplayLocation::Normal => "normal",
723 DisplayLocation::Above => "above",
724 DisplayLocation::Ficta => "ficta",
725 DisplayLocation::Below => "below",
726 }
727}
728
729fn display_location_from_str(value: &str) -> Option<DisplayLocation> {
730 match value {
731 "normal" => Some(DisplayLocation::Normal),
732 "above" => Some(DisplayLocation::Above),
733 "ficta" => Some(DisplayLocation::Ficta),
734 "below" => Some(DisplayLocation::Below),
735 _ => None,
736 }
737}
738
739impl Default for Accidental {
740 fn default() -> Self {
741 Self::natural()
742 }
743}
744
745impl std::fmt::Display for Accidental {
746 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
747 write!(f, "{}", self._name)
748 }
749}
750
751pub(crate) trait IntoAccidental: Display + Clone {
752 fn is_accidental(&self) -> bool;
753 fn into_accidental(self) -> Result<Accidental>;
754 fn accidental(self) -> Accidental;
755}
756
757impl IntoAccidental for i8 {
758 fn is_accidental(&self) -> bool {
759 false
760 }
761
762 fn into_accidental(self) -> Result<Accidental> {
763 Accidental::new(self)
764 }
765
766 fn accidental(self) -> Accidental {
767 panic!("call into_accidental instead")
768 }
769}
770
771impl IntoAccidental for IntegerType {
772 fn is_accidental(&self) -> bool {
773 false
774 }
775
776 fn into_accidental(self) -> Result<Accidental> {
777 Accidental::new(self)
778 }
779
780 fn accidental(self) -> Accidental {
781 panic!("call into_accidental instead")
782 }
783}
784
785impl IntoAccidental for FloatType {
786 fn is_accidental(&self) -> bool {
787 false
788 }
789
790 fn into_accidental(self) -> Result<Accidental> {
791 Accidental::new(self)
792 }
793
794 fn accidental(self) -> Accidental {
795 panic!("call into_accidental instead")
796 }
797}
798
799impl IntoAccidental for &str {
800 fn is_accidental(&self) -> bool {
801 false
802 }
803
804 fn into_accidental(self) -> Result<Accidental> {
805 Accidental::new(self)
806 }
807
808 fn accidental(self) -> Accidental {
809 panic!("call into_accidental instead")
810 }
811}
812
813impl IntoAccidental for String {
814 fn is_accidental(&self) -> bool {
815 false
816 }
817
818 fn into_accidental(self) -> Result<Accidental> {
819 Accidental::new(self)
820 }
821
822 fn accidental(self) -> Accidental {
823 panic!("call into_accidental instead")
824 }
825}
826
827impl IntoAccidental for Accidental {
828 fn is_accidental(&self) -> bool {
829 true
830 }
831
832 fn into_accidental(self) -> Result<Accidental> {
833 panic!("don't call into_accidental on an accidental");
834 }
835
836 fn accidental(self) -> Accidental {
837 self
838 }
839}
840
841impl IntoAccidental for AccidentalSpecifier {
842 fn is_accidental(&self) -> bool {
843 matches!(self, AccidentalSpecifier::Accidental(_))
844 }
845
846 fn into_accidental(self) -> Result<Accidental> {
847 Accidental::new(self)
848 }
849
850 fn accidental(self) -> Accidental {
851 match self {
852 AccidentalSpecifier::Accidental(accidental) => accidental,
853 _ => panic!("call into_accidental instead"),
854 }
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use super::{Accidental, AccidentalSpecifier, IntoAccidental};
861
862 #[test]
863 fn test_natural() {
864 let acc = Accidental::natural();
865 assert_eq!(acc._name, "natural");
866 assert_eq!(acc._alter, 0.0);
867 assert_eq!(acc.modifier(), "");
868 }
869
870 #[test]
871 fn test_sharp() {
872 let acc = Accidental::sharp();
873 assert_eq!(acc._name, "sharp");
874 assert_eq!(acc._alter, 1.0);
875 assert_eq!(acc.modifier(), "#");
876 }
877
878 #[test]
879 fn test_flat() {
880 let acc = Accidental::flat();
881 assert_eq!(acc._name, "flat");
882 assert_eq!(acc._alter, -1.0);
883 assert_eq!(acc.modifier(), "-");
884 }
885
886 #[test]
887 fn test_creation_from_int() {
888 let acc_sharp = 1.into_accidental().unwrap();
889 assert_eq!(acc_sharp._name, "sharp");
890 assert_eq!(acc_sharp._alter, 1.0);
891
892 let acc_flat = (-1).into_accidental().unwrap();
893 assert_eq!(acc_flat._name, "flat");
894 assert_eq!(acc_flat._alter, -1.0);
895
896 let acc_natural = 0.into_accidental().unwrap();
897 assert_eq!(acc_natural._name, "natural");
898 assert_eq!(acc_natural._alter, 0.0);
899 }
900
901 #[test]
902 fn accidental_supports_rust_conversion_traits() {
903 let parsed: Accidental = "#".parse().unwrap();
904 assert_eq!(parsed.name(), "sharp");
905
906 let from_str = Accidental::try_from("flat").unwrap();
907 assert_eq!(from_str.modifier(), "-");
908
909 let from_alter = Accidental::try_from(-0.5).unwrap();
910 assert_eq!(from_alter.name(), "half-flat");
911 }
912
913 #[test]
914 fn test_creation_from_float() {
915 let acc_double_sharp: Accidental = 2.0.into_accidental().unwrap();
916 assert_eq!(acc_double_sharp._name, "double-sharp");
917 assert_eq!(acc_double_sharp._alter, 2.0);
918
919 let acc_half_flat: Accidental = (-0.5).into_accidental().unwrap();
920 assert_eq!(acc_half_flat._name, "half-flat");
921 assert_eq!(acc_half_flat._alter, -0.5);
922 }
923
924 #[test]
925 fn test_creation_from_str() {
926 let acc1: Accidental = <&str>::into_accidental("sharp").unwrap();
927 assert_eq!(acc1._name, "sharp");
928 assert_eq!(acc1._alter, 1.0);
929
930 let acc2: Accidental = <&str>::into_accidental("Flat").unwrap();
932 assert_eq!(acc2._name, "flat");
933 assert_eq!(acc2._alter, -1.0);
934 }
935
936 #[test]
937 fn test_creation_from_string() {
938 let acc: Accidental = String::into_accidental("double-flat".to_string()).unwrap();
939 assert_eq!(acc._name, "double-flat");
940 assert_eq!(acc._alter, -2.0);
941 }
942
943 #[test]
944 fn test_invalid_accidental() {
945 let result = Accidental::new("invalid");
946 assert!(
947 result.is_err(),
948 "An invalid accidental should return an error"
949 );
950 }
951
952 #[test]
953 fn test_display_trait() {
954 let acc = Accidental::sharp();
955 assert_eq!(format!("{acc}"), "sharp");
956 }
957
958 #[test]
959 fn test_equality() {
960 let acc1 = Accidental::sharp();
961 let acc2 = Accidental::sharp();
962 let acc3 = Accidental::flat();
963 assert_eq!(acc1, acc2);
964 assert_ne!(acc1, acc3);
965 }
966
967 #[test]
968 fn test_alternate_names() {
969 let acc_sharp: Accidental = String::into_accidental("is".to_string()).unwrap();
971 assert_eq!(acc_sharp._name, "sharp");
972 assert_eq!(acc_sharp._alter, 1.0);
973
974 let acc_double_sharp: Accidental = String::into_accidental("isis".to_string()).unwrap();
975 assert_eq!(acc_double_sharp._name, "double-sharp");
976 assert_eq!(acc_double_sharp._alter, 2.0);
977
978 let acc_triple_sharp: Accidental = String::into_accidental("isisis".to_string()).unwrap();
979 assert_eq!(acc_triple_sharp._name, "triple-sharp");
980 assert_eq!(acc_triple_sharp._alter, 3.0);
981
982 let acc_double_flat: Accidental = String::into_accidental("eses".to_string()).unwrap();
983 assert_eq!(acc_double_flat._name, "double-flat");
984 assert_eq!(acc_double_flat._alter, -2.0);
985 }
986
987 #[test]
988 fn public_api_matches_music21_accidental_basics() {
989 let mut accidental = Accidental::new("sharp").unwrap();
990 assert_eq!(accidental.name(), "sharp");
991 assert_eq!(accidental.alter(), 1.0);
992 assert_eq!(accidental.modifier(), "#");
993 assert_eq!(accidental.unicode(), "\u{266f}");
994 assert_eq!(accidental.full_name(), "sharp");
995 assert!(accidental.is_twelve_tone());
996
997 accidental.set("--").unwrap();
998 assert_eq!(accidental.name(), "double-flat");
999 assert_eq!(accidental.alter(), -2.0);
1000 assert_eq!(accidental.modifier(), "--");
1001
1002 accidental.set("quarter-sharp").unwrap();
1003 assert_eq!(accidental.name(), "half-sharp");
1004 assert_eq!(accidental.alter(), 0.5);
1005 assert!(!accidental.is_twelve_tone());
1006 }
1007
1008 #[test]
1009 fn public_api_preserves_non_standard_values_when_requested() {
1010 let mut accidental = Accidental::new("flat").unwrap();
1011 accidental
1012 .set_allowing_non_standard_value("flat-flat-up")
1013 .unwrap();
1014 assert_eq!(accidental.name(), "flat-flat-up");
1015 assert_eq!(accidental.alter(), -1.0);
1016 assert_eq!(accidental.modifier(), "-");
1017
1018 accidental.set_alter(-0.9).unwrap();
1019 assert_eq!(accidental.name(), "flat-flat-up");
1020 assert_eq!(accidental.alter(), -0.9);
1021 assert_eq!(accidental.modifier(), "-");
1022
1023 accidental.set_modifier("&");
1024 assert_eq!(accidental.name(), "flat-flat-up");
1025 assert_eq!(accidental.alter(), -0.9);
1026 assert_eq!(accidental.modifier(), "&");
1027 }
1028
1029 #[test]
1030 fn public_api_supports_display_metadata() {
1031 let mut source = Accidental::new("double-flat").unwrap();
1032 source.set_display_type("always").unwrap();
1033 source.set_display_status(Some(true));
1034 source.set_display_style("parentheses").unwrap();
1035 source.set_display_size("cue").unwrap();
1036 source.set_display_location("above").unwrap();
1037
1038 let mut target = Accidental::new("sharp").unwrap();
1039 target.inherit_display(&source);
1040 assert_eq!(target.display_type(), "always");
1041 assert_eq!(target.display_status(), Some(true));
1042 assert_eq!(target.display_style(), "parentheses");
1043 assert_eq!(target.display_size(), "cue");
1044 assert_eq!(target.display_location(), "above");
1045 }
1046
1047 #[test]
1048 fn public_api_covers_name_helpers_and_standardization_errors() {
1049 let names = Accidental::list_names();
1050 assert_eq!(names.first(), Some(&"double-flat"));
1051 assert!(names.contains(&"quadruple-sharp"));
1052
1053 assert!(Accidental::is_valid_name("quarter-sharp"));
1054 assert!(Accidental::is_valid_name("es"));
1055 assert!(!Accidental::is_valid_name("not-an-accidental"));
1056
1057 assert_eq!(Accidental::standardize_name("##").unwrap(), "double-sharp");
1058 let err = Accidental::standardize_name("not-an-accidental").unwrap_err();
1059 assert!(err.to_string().contains("not a supported accidental type"));
1060 }
1061
1062 #[test]
1063 fn public_api_covers_setter_branches_and_independent_updates() {
1064 let mut accidental = Accidental::default();
1065 assert_eq!(accidental.name(), "natural");
1066
1067 accidental.set(Accidental::flat()).unwrap();
1068 assert_eq!(accidental.name(), "flat");
1069 assert_eq!(accidental.unicode(), "\u{266d}");
1070
1071 accidental.set_name("sharp").unwrap();
1072 assert_eq!(accidental.alter(), 1.0);
1073 assert_eq!(accidental.modifier(), "#");
1074
1075 accidental.set_modifier("~~");
1076 assert_eq!(accidental.name(), "sharp");
1077 assert_eq!(accidental.modifier(), "~~");
1078 assert_eq!(accidental.unicode(), "~~");
1079
1080 accidental.set_name_independently("custom");
1081 accidental.set_alter_independently(0.25);
1082 accidental.set_modifier_independently("^");
1083 assert_eq!(accidental.full_name(), "custom");
1084 assert_eq!(accidental.alter(), 0.25);
1085 assert_eq!(accidental.modifier(), "^");
1086
1087 let cloned = Accidental::new(AccidentalSpecifier::from(accidental.clone())).unwrap();
1088 assert_eq!(cloned, accidental);
1089 }
1090
1091 #[test]
1092 fn public_api_rejects_invalid_display_metadata() {
1093 let mut accidental = Accidental::new("natural").unwrap();
1094 assert!(accidental.set_display_type("sometimes").is_err());
1095 assert!(accidental.set_display_style("curly").is_err());
1096 assert!(accidental.set_display_size("huge").is_err());
1097 assert!(accidental.set_display_location("beside").is_err());
1098
1099 accidental.set_display_type("never").unwrap();
1100 accidental.set_display_style("both").unwrap();
1101 accidental.set_display_size("125.5").unwrap();
1102 accidental.set_display_location("below").unwrap();
1103
1104 assert_eq!(accidental.display_type(), "never");
1105 assert_eq!(accidental.display_style(), "both");
1106 assert_eq!(accidental.display_size(), "125.5");
1107 assert_eq!(accidental.display_location(), "below");
1108 }
1109
1110 #[test]
1111 fn accidental_ordering_follows_alter() {
1112 assert!(Accidental::flat() < Accidental::natural());
1113 assert!(Accidental::sharp() > Accidental::natural());
1114 }
1115}