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 Cautionary,
646 Parenthesized,
649 Editorial,
651}
652
653#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
655pub enum NoteHead {
656 #[default]
657 Normal,
658 Diamond, X, Slash, Cross, Triangle, }
664
665#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
667pub struct Lyric {
668 pub text: String,
670 pub syllabic: String,
672 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
675 pub extend: bool,
676}
677
678#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
680pub struct VerseLyric {
681 pub verse: u8,
683 pub lyric: Lyric,
684}
685
686impl VerseLyric {
687 pub const MAX_VERSE: u8 = 32;
689}
690
691#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
694pub struct StyledText {
695 pub style: TextStyle,
696 pub text: String,
697 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub position_beats: Option<f64>,
701 #[serde(default)]
703 pub placement: Option<String>,
704 #[serde(default)]
706 pub offset_x: Option<f64>,
707 #[serde(default)]
709 pub offset_y: Option<f64>,
710 #[serde(default)]
712 pub relative_x: Option<f64>,
713 #[serde(default)]
715 pub relative_y: Option<f64>,
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
719pub enum TextStyle {
720 Expression,
721 Technique,
722 Lyrics,
723 ChordSymbol,
724 FiguredBass,
725 RehearsalMark,
726 Generic,
727}
728
729#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
732pub struct CrossStaff {
733 pub target_staff: usize,
734 #[serde(default)]
735 pub target_voice: Option<usize>,
736}
737
738#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
740pub struct TabPosition {
741 pub string: u8,
743 pub fret: u8,
744}
745
746#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
748pub struct TablatureConfig {
749 pub lines: u8,
750 pub tuning_midi: Vec<i16>,
751 #[serde(default)]
752 pub capo: u8,
753}
754
755#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
757pub struct ChordSymbol {
758 pub root: String,
760 pub kind: String,
762 pub bass: Option<String>,
764 #[serde(default)]
766 pub placement: Option<String>,
767 #[serde(default)]
769 pub extender: bool,
770 #[serde(default)]
772 pub harmonic_degree: Option<String>,
773 #[serde(default)]
775 pub harmony_function: Option<String>,
776 #[serde(default)]
778 pub harmony_type: Option<String>,
779 #[serde(default)]
781 pub chord_ref: Option<String>,
782 #[serde(default)]
788 pub range_end: Option<NoteAddr>,
789 #[serde(default)]
791 pub degrees: Vec<ChordDegree>,
792}
793
794#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
800pub struct ChordDefinition {
801 #[serde(default)]
802 pub id: Option<String>,
803 #[serde(default)]
804 pub label: Option<String>,
805 #[serde(default)]
806 pub kind: Option<String>,
807 #[serde(default)]
808 pub fret_position: Option<u32>,
809 #[serde(default)]
810 pub tab_strings: Option<String>,
811 #[serde(default)]
812 pub tab_courses: Option<String>,
813 #[serde(default)]
814 pub members: Vec<ChordDefinitionMember>,
815 #[serde(default)]
816 pub barres: Vec<ChordBarre>,
817}
818
819#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
821pub struct ChordDefinitionMember {
822 #[serde(default)]
823 pub id: Option<String>,
824 #[serde(default)]
825 pub pitch: Option<Pitch>,
826 #[serde(default)]
827 pub tab_string: Option<u8>,
828 #[serde(default)]
829 pub tab_course: Option<u8>,
830 #[serde(default)]
831 pub tab_fret: Option<u16>,
832 #[serde(default)]
833 pub fingering: Option<u8>,
834}
835
836#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
838pub struct ChordBarre {
839 #[serde(default)]
840 pub start_member: Option<String>,
841 #[serde(default)]
842 pub end_member: Option<String>,
843 #[serde(default)]
844 pub fret: Option<u16>,
845 #[serde(default)]
846 pub label: Option<String>,
847 #[serde(default)]
848 pub kind: Option<String>,
849}
850
851#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
853pub struct ChordDegree {
854 pub value: u8,
856 pub alter: i8,
858 pub kind: String,
860}
861
862#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
864pub struct FiguredBassFigure {
865 pub number: String,
867 #[serde(default)]
869 pub alter: Option<String>,
870 #[serde(default)]
872 pub prefix: Option<String>,
873 #[serde(default)]
875 pub suffix: Option<String>,
876 #[serde(default)]
878 pub extender: bool,
879}
880
881impl ChordDegree {
882 pub fn display_text(&self) -> String {
884 let accidental = match self.alter {
885 -2 => "bb",
886 -1 => "b",
887 1 => "#",
888 2 => "##",
889 _ => "",
890 };
891 match self.kind.as_str() {
892 "subtract" => format!("no{}{}", accidental, self.value),
893 "alter" => format!("{}{}", accidental, self.value),
894 _ => format!("add{}{}", accidental, self.value),
895 }
896 }
897}
898
899pub const CHORD_KIND_SUFFIXES: &[(&str, &str)] = &[
903 ("major", ""),
904 ("minor", "m"),
905 ("augmented", "aug"),
906 ("diminished", "dim"),
907 ("dominant", "7"),
908 ("major-seventh", "maj7"),
909 ("minor-seventh", "m7"),
910 ("diminished-seventh", "dim7"),
911 ("augmented-seventh", "aug7"),
912 ("half-diminished", "m7b5"),
913 ("major-minor", "mMaj7"),
914 ("minor-major-seventh", "mMaj7"),
915 ("minor-major", "mMaj7"),
916 ("major-sixth", "6"),
917 ("minor-sixth", "m6"),
918 ("dominant-ninth", "9"),
919 ("major-ninth", "maj9"),
920 ("minor-ninth", "m9"),
921 ("dominant-11th", "11"),
922 ("major-11th", "maj11"),
923 ("minor-11th", "m11"),
924 ("dominant-13th", "13"),
925 ("major-13th", "maj13"),
926 ("minor-13th", "m13"),
927 ("suspended-second", "sus2"),
928 ("suspended-fourth", "sus4"),
929 ("power", "5"),
930 ("major-add9", "add9"),
931 ("minor-add9", "madd9"),
932 ("dominant-flat-five", "7b5"),
933 ("dominant-sharp-five", "7#5"),
934];
935
936impl ChordSymbol {
937 pub fn kind_for_suffix(suffix: &str) -> Option<&'static str> {
939 CHORD_KIND_SUFFIXES
940 .iter()
941 .find(|(_, candidate)| *candidate == suffix)
942 .map(|(kind, _)| *kind)
943 }
944
945 pub fn display_text(&self) -> String {
946 let kind_str = CHORD_KIND_SUFFIXES
947 .iter()
948 .find(|(kind, _)| *kind == self.kind)
949 .map_or(self.kind.as_str(), |(_, suffix)| *suffix);
950 let bass_str = match &self.bass {
951 Some(b) => format!("/{}", b),
952 None => String::new(),
953 };
954 let degree_str = self
955 .degrees
956 .iter()
957 .map(ChordDegree::display_text)
958 .collect::<String>();
959 format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
960 }
961}
962
963#[cfg(test)]
964mod tests {
965 use super::*;
966
967 #[test]
968 fn key_alter_c_major_all_natural() {
969 let key = KeySignature {
970 fifths: 0,
971 mode: "major".into(),
972 };
973 for step in [
974 Step::C,
975 Step::D,
976 Step::E,
977 Step::F,
978 Step::G,
979 Step::A,
980 Step::B,
981 ] {
982 assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
983 }
984 }
985
986 #[test]
987 fn key_alter_g_major_fsharp() {
988 let key = KeySignature {
989 fifths: 1,
990 mode: "major".into(),
991 };
992 assert_eq!(key.alter_for_step(&Step::F), 1);
993 assert_eq!(key.alter_for_step(&Step::G), 0);
994 }
995
996 #[test]
997 fn key_alter_f_major_bflat() {
998 let key = KeySignature {
999 fifths: -1,
1000 mode: "major".into(),
1001 };
1002 assert_eq!(key.alter_for_step(&Step::B), -1);
1003 assert_eq!(key.alter_for_step(&Step::C), 0);
1004 }
1005
1006 #[test]
1007 fn key_alter_bb_major() {
1008 let key = KeySignature {
1009 fifths: -2,
1010 mode: "major".into(),
1011 };
1012 assert_eq!(key.alter_for_step(&Step::B), -1);
1013 assert_eq!(key.alter_for_step(&Step::E), -1);
1014 assert_eq!(key.alter_for_step(&Step::A), 0);
1015 }
1016
1017 #[test]
1018 fn key_contains_pitch_g_major() {
1019 let key = KeySignature {
1020 fifths: 1,
1021 mode: "major".into(),
1022 };
1023 assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
1025 assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
1026 assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
1029 }
1030
1031 #[test]
1032 fn key_display_name_c_major() {
1033 let key = KeySignature {
1034 fifths: 0,
1035 mode: "major".into(),
1036 };
1037 assert_eq!(key.display_name(), "C major");
1038 }
1039
1040 #[test]
1041 fn key_display_name_bb_major() {
1042 let key = KeySignature {
1043 fifths: -2,
1044 mode: "major".into(),
1045 };
1046 assert_eq!(key.display_name(), "Bb major");
1047 }
1048
1049 #[test]
1050 fn key_display_name_fsharp_minor() {
1051 let key = KeySignature {
1052 fifths: 3,
1053 mode: "minor".into(),
1054 };
1055 assert_eq!(key.display_name(), "F# minor");
1056 }
1057
1058 #[test]
1059 fn key_tonic_d_major() {
1060 let key = KeySignature {
1061 fifths: 2,
1062 mode: "major".into(),
1063 };
1064 let (step, alter) = key.tonic();
1065 assert_eq!(step, Step::D);
1066 assert_eq!(alter, 0);
1067 }
1068
1069 #[test]
1070 fn key_tonic_a_minor() {
1071 let key = KeySignature {
1073 fifths: 0,
1074 mode: "minor".into(),
1075 };
1076 let (step, alter) = key.tonic();
1077 assert_eq!(step, Step::A);
1078 assert_eq!(alter, 0);
1079 }
1080
1081 #[test]
1082 fn chord_display_major() {
1083 let c = ChordSymbol {
1084 root: "C".into(),
1085 kind: "major".into(),
1086 bass: None,
1087 placement: None,
1088 extender: false,
1089 harmonic_degree: None,
1090 harmony_function: None,
1091 harmony_type: None,
1092 chord_ref: None,
1093 range_end: None,
1094 degrees: Vec::new(),
1095 };
1096 assert_eq!(c.display_text(), "C");
1097 }
1098
1099 #[test]
1100 fn chord_display_minor_seventh_slash() {
1101 let c = ChordSymbol {
1102 root: "D".into(),
1103 kind: "minor-seventh".into(),
1104 bass: Some("F".into()),
1105 placement: None,
1106 extender: false,
1107 harmonic_degree: None,
1108 harmony_function: None,
1109 harmony_type: None,
1110 chord_ref: None,
1111 range_end: None,
1112 degrees: Vec::new(),
1113 };
1114 assert_eq!(c.display_text(), "Dm7/F");
1115 }
1116
1117 #[test]
1118 fn chord_display_structured_degrees() {
1119 let c = ChordSymbol {
1120 root: "C".into(),
1121 kind: "dominant".into(),
1122 bass: None,
1123 placement: None,
1124 extender: false,
1125 harmonic_degree: None,
1126 harmony_function: None,
1127 harmony_type: None,
1128 chord_ref: None,
1129 range_end: None,
1130 degrees: vec![
1131 ChordDegree {
1132 value: 9,
1133 alter: 1,
1134 kind: "add".into(),
1135 },
1136 ChordDegree {
1137 value: 5,
1138 alter: -1,
1139 kind: "alter".into(),
1140 },
1141 ChordDegree {
1142 value: 3,
1143 alter: 0,
1144 kind: "subtract".into(),
1145 },
1146 ],
1147 };
1148 assert_eq!(c.display_text(), "C7add#9b5no3");
1149 }
1150
1151 #[test]
1152 fn chord_symbol_legacy_json_defaults_degrees() {
1153 let chord: ChordSymbol =
1154 serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
1155 .expect("legacy chord symbol JSON deserializes");
1156 assert!(chord.degrees.is_empty());
1157 assert!(!chord.extender);
1158 assert!(chord.harmonic_degree.is_none());
1159 assert!(chord.harmony_function.is_none());
1160 assert!(chord.harmony_type.is_none());
1161 }
1162
1163 #[test]
1164 fn time_sig_total_beats_three_four() {
1165 let ts = TimeSignature {
1166 numerator: 3,
1167 denominator: 4,
1168 };
1169 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1170 }
1171
1172 #[test]
1173 fn time_sig_total_beats_six_eight() {
1174 let ts = TimeSignature {
1175 numerator: 6,
1176 denominator: 8,
1177 };
1178 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1179 }
1180
1181 #[test]
1182 fn clef_treble_middle_b4() {
1183 assert_eq!(Clef::Treble.middle_line_midi(), 71);
1184 }
1185
1186 #[test]
1187 fn clef_bass_middle_d3() {
1188 assert_eq!(Clef::Bass.middle_line_midi(), 50);
1189 }
1190
1191 #[test]
1192 fn clef_alto_middle_c4() {
1193 assert_eq!(Clef::Alto.middle_line_midi(), 60);
1194 }
1195
1196 #[test]
1197 fn clef_tenor_middle_a3() {
1198 assert_eq!(Clef::Tenor.middle_line_midi(), 57);
1199 }
1200}