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