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