1use super::score::NoteAddr;
2use serde::{Deserialize, Serialize};
3
4use super::pitch::{Pitch, Step};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub enum Clef {
8 Treble,
9 Bass,
10 Alto,
11 Tenor,
12 Percussion,
13 Treble8vb,
15 Treble8va,
17 Bass8vb,
19 Bass8va,
21 Soprano,
23 MezzoSoprano,
25 Baritone,
27 AbcMiddle {
32 base: Box<Clef>,
34 middle: Pitch,
36 },
37}
38
39impl Clef {
40 pub fn to_musicxml_sign(&self) -> &'static str {
41 match self {
42 Clef::Treble | Clef::Treble8vb | Clef::Treble8va => "G",
43 Clef::Bass | Clef::Bass8vb | Clef::Bass8va => "F",
44 Clef::Alto | Clef::Tenor | Clef::Soprano | Clef::MezzoSoprano | Clef::Baritone => "C",
45 Clef::Percussion => "percussion",
46 Clef::AbcMiddle { base, .. } => base.to_musicxml_sign(),
47 }
48 }
49
50 pub fn musicxml_line(&self) -> u8 {
51 match self {
52 Clef::Treble | Clef::Treble8vb | Clef::Treble8va => 2,
53 Clef::Bass | Clef::Bass8vb | Clef::Bass8va => 4,
54 Clef::Alto => 3,
55 Clef::Tenor => 4,
56 Clef::Percussion => 2,
57 Clef::Soprano => 1,
58 Clef::MezzoSoprano => 2,
59 Clef::Baritone => 5,
60 Clef::AbcMiddle { base, .. } => base.musicxml_line(),
61 }
62 }
63
64 pub fn octave_change(&self) -> i8 {
67 match self {
68 Clef::Treble8vb | Clef::Bass8vb => -1,
69 Clef::Treble8va | Clef::Bass8va => 1,
70 Clef::AbcMiddle { base, .. } => base.octave_change(),
71 _ => 0,
72 }
73 }
74
75 pub fn without_octave(&self) -> Clef {
77 match self {
78 Clef::Treble8vb | Clef::Treble8va => Clef::Treble,
79 Clef::Bass8vb | Clef::Bass8va => Clef::Bass,
80 Clef::AbcMiddle { base, middle } => Clef::AbcMiddle {
81 base: Box::new(base.without_octave()),
82 middle: middle.clone(),
83 },
84 other => other.clone(),
85 }
86 }
87
88 pub fn with_abc_middle(base: Clef, middle: Pitch) -> Option<Clef> {
93 (!matches!(base, Clef::AbcMiddle { .. } | Clef::Percussion)).then_some(Clef::AbcMiddle {
94 base: Box::new(base),
95 middle,
96 })
97 }
98
99 pub fn abc_middle(&self) -> Option<&Pitch> {
101 match self {
102 Clef::AbcMiddle { middle, .. } => Some(middle),
103 _ => None,
104 }
105 }
106
107 pub fn from_sign(sign: &str, line: Option<u8>, octave_change: i8) -> Option<Clef> {
111 let clef = match (sign.trim().to_ascii_uppercase().as_str(), line) {
112 ("G", None | Some(2)) => Clef::Treble,
113 ("F", None | Some(4)) => Clef::Bass,
114 ("C", Some(1)) => Clef::Soprano,
115 ("C", Some(2)) => Clef::MezzoSoprano,
116 ("C", None | Some(3)) => Clef::Alto,
117 ("C", Some(4)) => Clef::Tenor,
118 ("C", Some(5)) | ("F", Some(3)) => Clef::Baritone,
119 ("PERCUSSION", _) => Clef::Percussion,
120 _ => return None,
121 };
122 Some(match (clef, octave_change) {
123 (clef, 0) => clef,
124 (Clef::Treble, -1) => Clef::Treble8vb,
125 (Clef::Treble, 1) => Clef::Treble8va,
126 (Clef::Bass, -1) => Clef::Bass8vb,
127 (Clef::Bass, 1) => Clef::Bass8va,
128 (Clef::Percussion, _) => Clef::Percussion,
129 _ => return None,
130 })
131 }
132
133 pub fn from_name(name: &str) -> Option<Clef> {
135 Some(match name {
136 "Treble" => Clef::Treble,
137 "Bass" => Clef::Bass,
138 "Alto" => Clef::Alto,
139 "Tenor" => Clef::Tenor,
140 "Percussion" => Clef::Percussion,
141 "Treble8vb" => Clef::Treble8vb,
142 "Treble8va" => Clef::Treble8va,
143 "Bass8vb" => Clef::Bass8vb,
144 "Bass8va" => Clef::Bass8va,
145 "Soprano" => Clef::Soprano,
146 "MezzoSoprano" => Clef::MezzoSoprano,
147 "Baritone" => Clef::Baritone,
148 _ => return None,
149 })
150 }
151
152 pub fn middle_line_midi(&self) -> u8 {
154 match self {
155 Clef::Treble => 71, Clef::Bass => 50, Clef::Alto => 60, Clef::Tenor => 57, Clef::Percussion => 71, Clef::Treble8vb => 59, Clef::Treble8va => 83, Clef::Bass8vb => 38, Clef::Bass8va => 62, Clef::Soprano => 67, Clef::MezzoSoprano => 64, Clef::Baritone => 53, Clef::AbcMiddle { middle, .. } => middle.to_midi().clamp(0, 127) as u8,
168 }
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub struct KeySignature {
174 pub fifths: i8,
176 pub mode: String,
178}
179
180impl Default for KeySignature {
181 fn default() -> Self {
182 Self {
183 fifths: 0,
184 mode: "major".to_string(),
185 }
186 }
187}
188
189impl KeySignature {
190 const SHARP_ORDER: [Step; 7] = [
192 Step::F,
193 Step::C,
194 Step::G,
195 Step::D,
196 Step::A,
197 Step::E,
198 Step::B,
199 ];
200 const FLAT_ORDER: [Step; 7] = [
202 Step::B,
203 Step::E,
204 Step::A,
205 Step::D,
206 Step::G,
207 Step::C,
208 Step::F,
209 ];
210
211 pub fn alter_for_step(&self, step: &Step) -> i8 {
213 if self.fifths > 0 {
214 let count = self.fifths.min(7) as usize;
215 if Self::SHARP_ORDER[..count].contains(step) {
216 1
217 } else {
218 0
219 }
220 } else if self.fifths < 0 {
221 let count = (-self.fifths).min(7) as usize;
222 if Self::FLAT_ORDER[..count].contains(step) {
223 -1
224 } else {
225 0
226 }
227 } else {
228 0
229 }
230 }
231
232 pub fn contains_pitch(&self, pitch: &Pitch) -> bool {
234 pitch.alter == self.alter_for_step(&pitch.step)
235 }
236
237 pub fn tonic(&self) -> (Step, i8) {
241 if self.mode == "minor" {
242 let (maj_step, maj_alter) = Self::major_tonic_from_fifths(self.fifths);
243 let major_midi = Pitch::with_alter(maj_step, 4, maj_alter).to_midi();
245 let minor_midi = (major_midi - 3).clamp(0, 127) as u8;
246 let p = Pitch::from_midi(minor_midi, self.fifths < 0);
247 (p.step, p.alter)
248 } else {
249 Self::major_tonic_from_fifths(self.fifths)
250 }
251 }
252
253 pub fn display_name(&self) -> String {
255 let (step, alter) = self.tonic();
256 let acc = match alter {
257 1 => "#",
258 -1 => "b",
259 _ => "",
260 };
261 format!("{}{} {}", step.to_char(), acc, self.mode)
262 }
263
264 fn major_tonic_from_fifths(fifths: i8) -> (Step, i8) {
265 const TONICS: [(Step, i8); 15] = [
268 (Step::C, -1), (Step::G, -1), (Step::D, -1), (Step::A, -1), (Step::E, -1), (Step::B, -1), (Step::F, 0), (Step::C, 0), (Step::G, 0), (Step::D, 0), (Step::A, 0), (Step::E, 0), (Step::B, 0), (Step::F, 1), (Step::C, 1), ];
284 let idx = (fifths.clamp(-7, 7) + 7) as usize;
285 TONICS[idx].clone()
286 }
287}
288
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
290pub struct TimeSignature {
291 pub numerator: u8,
292 pub denominator: u8,
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
297pub enum TimeSymbol {
298 Common,
299 Cut,
300}
301
302impl Default for TimeSignature {
303 fn default() -> Self {
304 Self {
305 numerator: 4,
306 denominator: 4,
307 }
308 }
309}
310
311impl TimeSignature {
312 pub fn beats_per_measure(&self) -> f64 {
313 self.numerator as f64
314 }
315
316 pub fn beat_unit_beats(&self) -> f64 {
317 4.0 / self.denominator as f64
318 }
319
320 pub fn total_beats(&self) -> f64 {
321 self.beats_per_measure() * self.beat_unit_beats()
322 }
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
326pub enum Dynamic {
327 Pppp,
328 Ppp,
329 Pp,
330 P,
331 Mp,
332 Mf,
333 F,
334 Ff,
335 Fff,
336 Ffff,
337 Sfz,
338 Rfz,
339 Fz,
340 Sf,
341 Fp,
343 Sfp,
345 Sfpp,
347 Pf,
349 Sffz,
351 Sfzp,
353 N,
355}
356
357impl Dynamic {
358 pub const ALL: [Dynamic; 21] = [
360 Dynamic::Pppp,
361 Dynamic::Ppp,
362 Dynamic::Pp,
363 Dynamic::P,
364 Dynamic::Mp,
365 Dynamic::Mf,
366 Dynamic::F,
367 Dynamic::Ff,
368 Dynamic::Fff,
369 Dynamic::Ffff,
370 Dynamic::Sfz,
371 Dynamic::Rfz,
372 Dynamic::Fz,
373 Dynamic::Sf,
374 Dynamic::Fp,
375 Dynamic::Sfp,
376 Dynamic::Sfpp,
377 Dynamic::Pf,
378 Dynamic::Sffz,
379 Dynamic::Sfzp,
380 Dynamic::N,
381 ];
382
383 pub fn from_musicxml_str(name: &str) -> Option<Dynamic> {
386 Some(match name {
387 "pppppp" | "ppppp" => Dynamic::Pppp,
388 "ffffff" | "fffff" => Dynamic::Ffff,
389 "rf" => Dynamic::Rfz,
390 other => *Self::ALL
391 .iter()
392 .find(|dynamic| dynamic.to_musicxml_str() == other)?,
393 })
394 }
395
396 pub fn sustained_level(&self) -> Option<Dynamic> {
400 match self {
401 Dynamic::Sfz | Dynamic::Rfz | Dynamic::Fz | Dynamic::Sf | Dynamic::Sffz => None,
402 Dynamic::Fp | Dynamic::Sfp | Dynamic::Sfzp => Some(Dynamic::P),
403 Dynamic::Sfpp => Some(Dynamic::Pp),
404 Dynamic::Pf => Some(Dynamic::F),
405 level => Some(*level),
406 }
407 }
408
409 pub fn to_musicxml_str(&self) -> &'static str {
410 match self {
411 Dynamic::Pppp => "pppp",
412 Dynamic::Ppp => "ppp",
413 Dynamic::Pp => "pp",
414 Dynamic::P => "p",
415 Dynamic::Mp => "mp",
416 Dynamic::Mf => "mf",
417 Dynamic::F => "f",
418 Dynamic::Ff => "ff",
419 Dynamic::Fff => "fff",
420 Dynamic::Ffff => "ffff",
421 Dynamic::Sfz => "sfz",
422 Dynamic::Rfz => "rfz",
423 Dynamic::Fz => "fz",
424 Dynamic::Sf => "sf",
425 Dynamic::Fp => "fp",
426 Dynamic::Sfp => "sfp",
427 Dynamic::Sfpp => "sfpp",
428 Dynamic::Pf => "pf",
429 Dynamic::Sffz => "sffz",
430 Dynamic::Sfzp => "sfzp",
431 Dynamic::N => "n",
432 }
433 }
434
435 pub fn to_velocity(&self) -> u8 {
436 match self {
437 Dynamic::Pppp => 16,
438 Dynamic::Ppp => 24,
439 Dynamic::Pp => 36,
440 Dynamic::P => 48,
441 Dynamic::Mp => 60,
442 Dynamic::Mf => 72,
443 Dynamic::F => 84,
444 Dynamic::Ff => 96,
445 Dynamic::Fff => 108,
446 Dynamic::Ffff => 120,
447 Dynamic::Sfz => 112,
448 Dynamic::Rfz => 104,
449 Dynamic::Fz => 100,
450 Dynamic::Sf => 96,
451 Dynamic::Fp => 84,
452 Dynamic::Sfp | Dynamic::Sfpp => 96,
453 Dynamic::Pf => 48,
454 Dynamic::Sffz => 120,
455 Dynamic::Sfzp => 112,
456 Dynamic::N => 8,
457 }
458 }
459}
460
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462pub enum Articulation {
463 Staccato,
464 Staccatissimo,
465 Accent,
466 Tenuto,
467 Marcato,
468 Fermata,
469 Trill,
470 Mordent,
471 InvertedMordent,
472 Turn,
473 InvertedTurn,
474 Shake,
475 Tremolo(u8),
476 BreathMark,
477 Caesura,
478 UpBow,
480 DownBow,
482 Harmonic,
484 OpenString,
486 Stopped,
488 SnapPizzicato,
490 Tap,
492 LeftHandTap,
494 Vibrato,
496 Scoop,
498 Plop,
500 Doit,
502 Falloff,
504}
505
506impl Articulation {
507 pub fn is_technical_mark(&self) -> bool {
510 matches!(
511 self,
512 Self::UpBow
513 | Self::DownBow
514 | Self::Harmonic
515 | Self::OpenString
516 | Self::Stopped
517 | Self::SnapPizzicato
518 | Self::Tap
519 | Self::LeftHandTap
520 | Self::Vibrato
521 )
522 }
523}
524
525#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
527#[serde(rename_all = "kebab-case")]
528pub enum GuitarTechnique {
529 Bend,
530 Slide,
531 HammerOn,
532 PullOff,
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
537pub enum FingeringSelectionPolicy {
538 #[default]
540 SourceOrder,
541 LowestNumber,
543 HighestNumber,
545}
546
547#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
548pub enum Barline {
549 #[default]
550 Normal,
551 Double,
552 Final,
553 RepeatStart,
554 RepeatEnd,
555 RepeatBoth,
556 Dashed,
557 Dotted,
558 Invisible,
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
562pub enum HairpinKind {
563 Crescendo,
564 Decrescendo,
565}
566
567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
568pub struct TupletInfo {
569 pub actual_notes: u8,
571 pub normal_notes: u8,
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
577#[serde(rename_all = "kebab-case")]
578pub enum TupletBoundary {
579 Start,
580 Stop,
581 StartStop,
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
585pub enum BeamState {
586 #[default]
587 None,
588 Begin,
589 Continue,
590 End,
591 BeginEnd,
592 BackwardHook,
593 ForwardHook,
594}
595
596#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
598pub enum OttavaKind {
599 Va8,
601 Vb8,
603 Ma15,
605 Mb15,
607}
608
609impl OttavaKind {
610 pub fn musicxml_type(&self) -> &'static str {
613 match self {
614 OttavaKind::Va8 | OttavaKind::Ma15 => "down",
615 OttavaKind::Vb8 | OttavaKind::Mb15 => "up",
616 }
617 }
618
619 pub fn display_shift_steps(&self) -> i32 {
621 match self {
622 OttavaKind::Va8 => -7,
623 OttavaKind::Ma15 => -14,
624 OttavaKind::Vb8 => 7,
625 OttavaKind::Mb15 => 14,
626 }
627 }
628
629 pub fn musicxml_size(&self) -> u8 {
630 match self {
631 OttavaKind::Va8 | OttavaKind::Vb8 => 8,
632 OttavaKind::Ma15 | OttavaKind::Mb15 => 15,
633 }
634 }
635}
636
637#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
641pub enum AccidentalDisplay {
642 #[default]
643 Auto,
644 Explicit,
646 Cautionary,
648 Parenthesized,
651 Editorial,
653}
654
655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
657pub enum NoteHead {
658 #[default]
659 Normal,
660 Diamond, X, Slash, Cross, Triangle, }
666
667#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
669pub struct Lyric {
670 pub text: String,
672 pub syllabic: String,
674 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
677 pub extend: bool,
678}
679
680#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
682pub struct VerseLyric {
683 pub verse: u8,
685 pub lyric: Lyric,
686}
687
688impl VerseLyric {
689 pub const MAX_VERSE: u8 = 32;
691}
692
693#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
696pub struct StyledText {
697 pub style: TextStyle,
698 pub text: String,
699 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub position_beats: Option<f64>,
703 #[serde(default)]
705 pub placement: Option<String>,
706 #[serde(default)]
708 pub offset_x: Option<f64>,
709 #[serde(default)]
711 pub offset_y: Option<f64>,
712 #[serde(default)]
714 pub relative_x: Option<f64>,
715 #[serde(default)]
717 pub relative_y: Option<f64>,
718}
719
720#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
721pub enum TextStyle {
722 Expression,
723 Technique,
724 Lyrics,
725 ChordSymbol,
726 FiguredBass,
727 RehearsalMark,
728 Generic,
729}
730
731#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
734pub struct CrossStaff {
735 pub target_staff: usize,
736 #[serde(default)]
737 pub target_voice: Option<usize>,
738}
739
740#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
742pub struct TabPosition {
743 pub string: u8,
745 pub fret: u8,
746}
747
748#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
750pub struct TablatureConfig {
751 pub lines: u8,
752 pub tuning_midi: Vec<i16>,
753 #[serde(default)]
754 pub capo: u8,
755}
756
757#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
759pub struct ChordSymbol {
760 pub root: String,
762 pub kind: String,
764 pub bass: Option<String>,
766 #[serde(default)]
768 pub placement: Option<String>,
769 #[serde(default)]
771 pub extender: bool,
772 #[serde(default)]
774 pub harmonic_degree: Option<String>,
775 #[serde(default)]
777 pub harmony_function: Option<String>,
778 #[serde(default)]
780 pub harmony_type: Option<String>,
781 #[serde(default)]
783 pub chord_ref: Option<String>,
784 #[serde(default)]
790 pub range_end: Option<NoteAddr>,
791 #[serde(default)]
793 pub degrees: Vec<ChordDegree>,
794}
795
796#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
802pub struct ChordDefinition {
803 #[serde(default)]
804 pub id: Option<String>,
805 #[serde(default)]
806 pub label: Option<String>,
807 #[serde(default)]
808 pub kind: Option<String>,
809 #[serde(default)]
810 pub fret_position: Option<u32>,
811 #[serde(default)]
812 pub tab_strings: Option<String>,
813 #[serde(default)]
814 pub tab_courses: Option<String>,
815 #[serde(default)]
816 pub members: Vec<ChordDefinitionMember>,
817 #[serde(default)]
818 pub barres: Vec<ChordBarre>,
819}
820
821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
823pub struct ChordDefinitionMember {
824 #[serde(default)]
825 pub id: Option<String>,
826 #[serde(default)]
827 pub pitch: Option<Pitch>,
828 #[serde(default)]
829 pub tab_string: Option<u8>,
830 #[serde(default)]
831 pub tab_course: Option<u8>,
832 #[serde(default)]
833 pub tab_fret: Option<u16>,
834 #[serde(default)]
835 pub fingering: Option<u8>,
836}
837
838#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
840pub struct ChordBarre {
841 #[serde(default)]
842 pub start_member: Option<String>,
843 #[serde(default)]
844 pub end_member: Option<String>,
845 #[serde(default)]
846 pub fret: Option<u16>,
847 #[serde(default)]
848 pub label: Option<String>,
849 #[serde(default)]
850 pub kind: Option<String>,
851}
852
853#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
855pub struct ChordDegree {
856 pub value: u8,
858 pub alter: i8,
860 pub kind: String,
862}
863
864#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
866pub struct FiguredBassFigure {
867 pub number: String,
869 #[serde(default)]
871 pub alter: Option<String>,
872 #[serde(default)]
874 pub prefix: Option<String>,
875 #[serde(default)]
877 pub suffix: Option<String>,
878 #[serde(default)]
880 pub extender: bool,
881}
882
883#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
885pub struct FiguredBassGroup {
886 #[serde(default)]
888 pub voice: usize,
889 pub position_beats: f64,
891 #[serde(default, skip_serializing_if = "Option::is_none")]
893 pub duration_beats: Option<f64>,
894 pub figures: Vec<FiguredBassFigure>,
896 #[serde(default, skip_serializing_if = "Vec::is_empty")]
898 pub extend_types: Vec<Option<String>>,
899}
900
901impl ChordDegree {
902 pub fn display_text(&self) -> String {
904 let accidental = match self.alter {
905 -2 => "bb",
906 -1 => "b",
907 1 => "#",
908 2 => "##",
909 _ => "",
910 };
911 match self.kind.as_str() {
912 "subtract" => format!("no{}{}", accidental, self.value),
913 "alter" => format!("{}{}", accidental, self.value),
914 _ => format!("add{}{}", accidental, self.value),
915 }
916 }
917}
918
919pub const CHORD_KIND_SUFFIXES: &[(&str, &str)] = &[
923 ("major", ""),
924 ("minor", "m"),
925 ("augmented", "aug"),
926 ("diminished", "dim"),
927 ("dominant", "7"),
928 ("major-seventh", "maj7"),
929 ("minor-seventh", "m7"),
930 ("diminished-seventh", "dim7"),
931 ("augmented-seventh", "aug7"),
932 ("half-diminished", "m7b5"),
933 ("major-minor", "mMaj7"),
934 ("minor-major-seventh", "mMaj7"),
935 ("minor-major", "mMaj7"),
936 ("major-sixth", "6"),
937 ("minor-sixth", "m6"),
938 ("dominant-ninth", "9"),
939 ("major-ninth", "maj9"),
940 ("minor-ninth", "m9"),
941 ("dominant-11th", "11"),
942 ("major-11th", "maj11"),
943 ("minor-11th", "m11"),
944 ("dominant-13th", "13"),
945 ("major-13th", "maj13"),
946 ("minor-13th", "m13"),
947 ("suspended-second", "sus2"),
948 ("suspended-fourth", "sus4"),
949 ("power", "5"),
950 ("major-add9", "add9"),
951 ("minor-add9", "madd9"),
952 ("dominant-flat-five", "7b5"),
953 ("dominant-sharp-five", "7#5"),
954];
955
956impl ChordSymbol {
957 pub fn kind_for_suffix(suffix: &str) -> Option<&'static str> {
959 CHORD_KIND_SUFFIXES
960 .iter()
961 .find(|(_, candidate)| *candidate == suffix)
962 .map(|(kind, _)| *kind)
963 }
964
965 pub fn display_text(&self) -> String {
966 let kind_str = CHORD_KIND_SUFFIXES
967 .iter()
968 .find(|(kind, _)| *kind == self.kind)
969 .map_or(self.kind.as_str(), |(_, suffix)| *suffix);
970 let bass_str = match &self.bass {
971 Some(b) => format!("/{}", b),
972 None => String::new(),
973 };
974 let degree_str = self
975 .degrees
976 .iter()
977 .map(ChordDegree::display_text)
978 .collect::<String>();
979 format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
980 }
981}
982
983#[cfg(test)]
984mod tests {
985 use super::*;
986
987 #[test]
988 fn key_alter_c_major_all_natural() {
989 let key = KeySignature {
990 fifths: 0,
991 mode: "major".into(),
992 };
993 for step in [
994 Step::C,
995 Step::D,
996 Step::E,
997 Step::F,
998 Step::G,
999 Step::A,
1000 Step::B,
1001 ] {
1002 assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
1003 }
1004 }
1005
1006 #[test]
1007 fn key_alter_g_major_fsharp() {
1008 let key = KeySignature {
1009 fifths: 1,
1010 mode: "major".into(),
1011 };
1012 assert_eq!(key.alter_for_step(&Step::F), 1);
1013 assert_eq!(key.alter_for_step(&Step::G), 0);
1014 }
1015
1016 #[test]
1017 fn key_alter_f_major_bflat() {
1018 let key = KeySignature {
1019 fifths: -1,
1020 mode: "major".into(),
1021 };
1022 assert_eq!(key.alter_for_step(&Step::B), -1);
1023 assert_eq!(key.alter_for_step(&Step::C), 0);
1024 }
1025
1026 #[test]
1027 fn key_alter_bb_major() {
1028 let key = KeySignature {
1029 fifths: -2,
1030 mode: "major".into(),
1031 };
1032 assert_eq!(key.alter_for_step(&Step::B), -1);
1033 assert_eq!(key.alter_for_step(&Step::E), -1);
1034 assert_eq!(key.alter_for_step(&Step::A), 0);
1035 }
1036
1037 #[test]
1038 fn key_contains_pitch_g_major() {
1039 let key = KeySignature {
1040 fifths: 1,
1041 mode: "major".into(),
1042 };
1043 assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
1045 assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
1046 assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
1049 }
1050
1051 #[test]
1052 fn key_display_name_c_major() {
1053 let key = KeySignature {
1054 fifths: 0,
1055 mode: "major".into(),
1056 };
1057 assert_eq!(key.display_name(), "C major");
1058 }
1059
1060 #[test]
1061 fn key_display_name_bb_major() {
1062 let key = KeySignature {
1063 fifths: -2,
1064 mode: "major".into(),
1065 };
1066 assert_eq!(key.display_name(), "Bb major");
1067 }
1068
1069 #[test]
1070 fn key_display_name_fsharp_minor() {
1071 let key = KeySignature {
1072 fifths: 3,
1073 mode: "minor".into(),
1074 };
1075 assert_eq!(key.display_name(), "F# minor");
1076 }
1077
1078 #[test]
1079 fn key_tonic_d_major() {
1080 let key = KeySignature {
1081 fifths: 2,
1082 mode: "major".into(),
1083 };
1084 let (step, alter) = key.tonic();
1085 assert_eq!(step, Step::D);
1086 assert_eq!(alter, 0);
1087 }
1088
1089 #[test]
1090 fn key_tonic_a_minor() {
1091 let key = KeySignature {
1093 fifths: 0,
1094 mode: "minor".into(),
1095 };
1096 let (step, alter) = key.tonic();
1097 assert_eq!(step, Step::A);
1098 assert_eq!(alter, 0);
1099 }
1100
1101 #[test]
1102 fn chord_display_major() {
1103 let c = ChordSymbol {
1104 root: "C".into(),
1105 kind: "major".into(),
1106 bass: None,
1107 placement: None,
1108 extender: false,
1109 harmonic_degree: None,
1110 harmony_function: None,
1111 harmony_type: None,
1112 chord_ref: None,
1113 range_end: None,
1114 degrees: Vec::new(),
1115 };
1116 assert_eq!(c.display_text(), "C");
1117 }
1118
1119 #[test]
1120 fn chord_display_minor_seventh_slash() {
1121 let c = ChordSymbol {
1122 root: "D".into(),
1123 kind: "minor-seventh".into(),
1124 bass: Some("F".into()),
1125 placement: None,
1126 extender: false,
1127 harmonic_degree: None,
1128 harmony_function: None,
1129 harmony_type: None,
1130 chord_ref: None,
1131 range_end: None,
1132 degrees: Vec::new(),
1133 };
1134 assert_eq!(c.display_text(), "Dm7/F");
1135 }
1136
1137 #[test]
1138 fn chord_display_structured_degrees() {
1139 let c = ChordSymbol {
1140 root: "C".into(),
1141 kind: "dominant".into(),
1142 bass: None,
1143 placement: None,
1144 extender: false,
1145 harmonic_degree: None,
1146 harmony_function: None,
1147 harmony_type: None,
1148 chord_ref: None,
1149 range_end: None,
1150 degrees: vec![
1151 ChordDegree {
1152 value: 9,
1153 alter: 1,
1154 kind: "add".into(),
1155 },
1156 ChordDegree {
1157 value: 5,
1158 alter: -1,
1159 kind: "alter".into(),
1160 },
1161 ChordDegree {
1162 value: 3,
1163 alter: 0,
1164 kind: "subtract".into(),
1165 },
1166 ],
1167 };
1168 assert_eq!(c.display_text(), "C7add#9b5no3");
1169 }
1170
1171 #[test]
1172 fn chord_symbol_legacy_json_defaults_degrees() {
1173 let chord: ChordSymbol =
1174 serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
1175 .expect("legacy chord symbol JSON deserializes");
1176 assert!(chord.degrees.is_empty());
1177 assert!(!chord.extender);
1178 assert!(chord.harmonic_degree.is_none());
1179 assert!(chord.harmony_function.is_none());
1180 assert!(chord.harmony_type.is_none());
1181 }
1182
1183 #[test]
1184 fn time_sig_total_beats_three_four() {
1185 let ts = TimeSignature {
1186 numerator: 3,
1187 denominator: 4,
1188 };
1189 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1190 }
1191
1192 #[test]
1193 fn time_sig_total_beats_six_eight() {
1194 let ts = TimeSignature {
1195 numerator: 6,
1196 denominator: 8,
1197 };
1198 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1199 }
1200
1201 #[test]
1202 fn clef_treble_middle_b4() {
1203 assert_eq!(Clef::Treble.middle_line_midi(), 71);
1204 }
1205
1206 #[test]
1207 fn clef_bass_middle_d3() {
1208 assert_eq!(Clef::Bass.middle_line_midi(), 50);
1209 }
1210
1211 #[test]
1212 fn clef_alto_middle_c4() {
1213 assert_eq!(Clef::Alto.middle_line_midi(), 60);
1214 }
1215
1216 #[test]
1217 fn clef_tenor_middle_a3() {
1218 assert_eq!(Clef::Tenor.middle_line_midi(), 57);
1219 }
1220}