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