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
883impl ChordDegree {
884 pub fn display_text(&self) -> String {
886 let accidental = match self.alter {
887 -2 => "bb",
888 -1 => "b",
889 1 => "#",
890 2 => "##",
891 _ => "",
892 };
893 match self.kind.as_str() {
894 "subtract" => format!("no{}{}", accidental, self.value),
895 "alter" => format!("{}{}", accidental, self.value),
896 _ => format!("add{}{}", accidental, self.value),
897 }
898 }
899}
900
901pub const CHORD_KIND_SUFFIXES: &[(&str, &str)] = &[
905 ("major", ""),
906 ("minor", "m"),
907 ("augmented", "aug"),
908 ("diminished", "dim"),
909 ("dominant", "7"),
910 ("major-seventh", "maj7"),
911 ("minor-seventh", "m7"),
912 ("diminished-seventh", "dim7"),
913 ("augmented-seventh", "aug7"),
914 ("half-diminished", "m7b5"),
915 ("major-minor", "mMaj7"),
916 ("minor-major-seventh", "mMaj7"),
917 ("minor-major", "mMaj7"),
918 ("major-sixth", "6"),
919 ("minor-sixth", "m6"),
920 ("dominant-ninth", "9"),
921 ("major-ninth", "maj9"),
922 ("minor-ninth", "m9"),
923 ("dominant-11th", "11"),
924 ("major-11th", "maj11"),
925 ("minor-11th", "m11"),
926 ("dominant-13th", "13"),
927 ("major-13th", "maj13"),
928 ("minor-13th", "m13"),
929 ("suspended-second", "sus2"),
930 ("suspended-fourth", "sus4"),
931 ("power", "5"),
932 ("major-add9", "add9"),
933 ("minor-add9", "madd9"),
934 ("dominant-flat-five", "7b5"),
935 ("dominant-sharp-five", "7#5"),
936];
937
938impl ChordSymbol {
939 pub fn kind_for_suffix(suffix: &str) -> Option<&'static str> {
941 CHORD_KIND_SUFFIXES
942 .iter()
943 .find(|(_, candidate)| *candidate == suffix)
944 .map(|(kind, _)| *kind)
945 }
946
947 pub fn display_text(&self) -> String {
948 let kind_str = CHORD_KIND_SUFFIXES
949 .iter()
950 .find(|(kind, _)| *kind == self.kind)
951 .map_or(self.kind.as_str(), |(_, suffix)| *suffix);
952 let bass_str = match &self.bass {
953 Some(b) => format!("/{}", b),
954 None => String::new(),
955 };
956 let degree_str = self
957 .degrees
958 .iter()
959 .map(ChordDegree::display_text)
960 .collect::<String>();
961 format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
962 }
963}
964
965#[cfg(test)]
966mod tests {
967 use super::*;
968
969 #[test]
970 fn key_alter_c_major_all_natural() {
971 let key = KeySignature {
972 fifths: 0,
973 mode: "major".into(),
974 };
975 for step in [
976 Step::C,
977 Step::D,
978 Step::E,
979 Step::F,
980 Step::G,
981 Step::A,
982 Step::B,
983 ] {
984 assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
985 }
986 }
987
988 #[test]
989 fn key_alter_g_major_fsharp() {
990 let key = KeySignature {
991 fifths: 1,
992 mode: "major".into(),
993 };
994 assert_eq!(key.alter_for_step(&Step::F), 1);
995 assert_eq!(key.alter_for_step(&Step::G), 0);
996 }
997
998 #[test]
999 fn key_alter_f_major_bflat() {
1000 let key = KeySignature {
1001 fifths: -1,
1002 mode: "major".into(),
1003 };
1004 assert_eq!(key.alter_for_step(&Step::B), -1);
1005 assert_eq!(key.alter_for_step(&Step::C), 0);
1006 }
1007
1008 #[test]
1009 fn key_alter_bb_major() {
1010 let key = KeySignature {
1011 fifths: -2,
1012 mode: "major".into(),
1013 };
1014 assert_eq!(key.alter_for_step(&Step::B), -1);
1015 assert_eq!(key.alter_for_step(&Step::E), -1);
1016 assert_eq!(key.alter_for_step(&Step::A), 0);
1017 }
1018
1019 #[test]
1020 fn key_contains_pitch_g_major() {
1021 let key = KeySignature {
1022 fifths: 1,
1023 mode: "major".into(),
1024 };
1025 assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
1027 assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
1028 assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
1031 }
1032
1033 #[test]
1034 fn key_display_name_c_major() {
1035 let key = KeySignature {
1036 fifths: 0,
1037 mode: "major".into(),
1038 };
1039 assert_eq!(key.display_name(), "C major");
1040 }
1041
1042 #[test]
1043 fn key_display_name_bb_major() {
1044 let key = KeySignature {
1045 fifths: -2,
1046 mode: "major".into(),
1047 };
1048 assert_eq!(key.display_name(), "Bb major");
1049 }
1050
1051 #[test]
1052 fn key_display_name_fsharp_minor() {
1053 let key = KeySignature {
1054 fifths: 3,
1055 mode: "minor".into(),
1056 };
1057 assert_eq!(key.display_name(), "F# minor");
1058 }
1059
1060 #[test]
1061 fn key_tonic_d_major() {
1062 let key = KeySignature {
1063 fifths: 2,
1064 mode: "major".into(),
1065 };
1066 let (step, alter) = key.tonic();
1067 assert_eq!(step, Step::D);
1068 assert_eq!(alter, 0);
1069 }
1070
1071 #[test]
1072 fn key_tonic_a_minor() {
1073 let key = KeySignature {
1075 fifths: 0,
1076 mode: "minor".into(),
1077 };
1078 let (step, alter) = key.tonic();
1079 assert_eq!(step, Step::A);
1080 assert_eq!(alter, 0);
1081 }
1082
1083 #[test]
1084 fn chord_display_major() {
1085 let c = ChordSymbol {
1086 root: "C".into(),
1087 kind: "major".into(),
1088 bass: None,
1089 placement: None,
1090 extender: false,
1091 harmonic_degree: None,
1092 harmony_function: None,
1093 harmony_type: None,
1094 chord_ref: None,
1095 range_end: None,
1096 degrees: Vec::new(),
1097 };
1098 assert_eq!(c.display_text(), "C");
1099 }
1100
1101 #[test]
1102 fn chord_display_minor_seventh_slash() {
1103 let c = ChordSymbol {
1104 root: "D".into(),
1105 kind: "minor-seventh".into(),
1106 bass: Some("F".into()),
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(), "Dm7/F");
1117 }
1118
1119 #[test]
1120 fn chord_display_structured_degrees() {
1121 let c = ChordSymbol {
1122 root: "C".into(),
1123 kind: "dominant".into(),
1124 bass: None,
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![
1133 ChordDegree {
1134 value: 9,
1135 alter: 1,
1136 kind: "add".into(),
1137 },
1138 ChordDegree {
1139 value: 5,
1140 alter: -1,
1141 kind: "alter".into(),
1142 },
1143 ChordDegree {
1144 value: 3,
1145 alter: 0,
1146 kind: "subtract".into(),
1147 },
1148 ],
1149 };
1150 assert_eq!(c.display_text(), "C7add#9b5no3");
1151 }
1152
1153 #[test]
1154 fn chord_symbol_legacy_json_defaults_degrees() {
1155 let chord: ChordSymbol =
1156 serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
1157 .expect("legacy chord symbol JSON deserializes");
1158 assert!(chord.degrees.is_empty());
1159 assert!(!chord.extender);
1160 assert!(chord.harmonic_degree.is_none());
1161 assert!(chord.harmony_function.is_none());
1162 assert!(chord.harmony_type.is_none());
1163 }
1164
1165 #[test]
1166 fn time_sig_total_beats_three_four() {
1167 let ts = TimeSignature {
1168 numerator: 3,
1169 denominator: 4,
1170 };
1171 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1172 }
1173
1174 #[test]
1175 fn time_sig_total_beats_six_eight() {
1176 let ts = TimeSignature {
1177 numerator: 6,
1178 denominator: 8,
1179 };
1180 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1181 }
1182
1183 #[test]
1184 fn clef_treble_middle_b4() {
1185 assert_eq!(Clef::Treble.middle_line_midi(), 71);
1186 }
1187
1188 #[test]
1189 fn clef_bass_middle_d3() {
1190 assert_eq!(Clef::Bass.middle_line_midi(), 50);
1191 }
1192
1193 #[test]
1194 fn clef_alto_middle_c4() {
1195 assert_eq!(Clef::Alto.middle_line_midi(), 60);
1196 }
1197
1198 #[test]
1199 fn clef_tenor_middle_a3() {
1200 assert_eq!(Clef::Tenor.middle_line_midi(), 57);
1201 }
1202}