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
295impl Default for TimeSignature {
296 fn default() -> Self {
297 Self {
298 numerator: 4,
299 denominator: 4,
300 }
301 }
302}
303
304impl TimeSignature {
305 pub fn beats_per_measure(&self) -> f64 {
306 self.numerator as f64
307 }
308
309 pub fn beat_unit_beats(&self) -> f64 {
310 4.0 / self.denominator as f64
311 }
312
313 pub fn total_beats(&self) -> f64 {
314 self.beats_per_measure() * self.beat_unit_beats()
315 }
316}
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
319pub enum Dynamic {
320 Pppp,
321 Ppp,
322 Pp,
323 P,
324 Mp,
325 Mf,
326 F,
327 Ff,
328 Fff,
329 Ffff,
330 Sfz,
331 Rfz,
332 Fz,
333 Sf,
334 Fp,
336 Sfp,
338 Sfpp,
340 Pf,
342 Sffz,
344 Sfzp,
346 N,
348}
349
350impl Dynamic {
351 pub const ALL: [Dynamic; 21] = [
353 Dynamic::Pppp,
354 Dynamic::Ppp,
355 Dynamic::Pp,
356 Dynamic::P,
357 Dynamic::Mp,
358 Dynamic::Mf,
359 Dynamic::F,
360 Dynamic::Ff,
361 Dynamic::Fff,
362 Dynamic::Ffff,
363 Dynamic::Sfz,
364 Dynamic::Rfz,
365 Dynamic::Fz,
366 Dynamic::Sf,
367 Dynamic::Fp,
368 Dynamic::Sfp,
369 Dynamic::Sfpp,
370 Dynamic::Pf,
371 Dynamic::Sffz,
372 Dynamic::Sfzp,
373 Dynamic::N,
374 ];
375
376 pub fn from_musicxml_str(name: &str) -> Option<Dynamic> {
379 Some(match name {
380 "pppppp" | "ppppp" => Dynamic::Pppp,
381 "ffffff" | "fffff" => Dynamic::Ffff,
382 "rf" => Dynamic::Rfz,
383 other => *Self::ALL
384 .iter()
385 .find(|dynamic| dynamic.to_musicxml_str() == other)?,
386 })
387 }
388
389 pub fn sustained_level(&self) -> Option<Dynamic> {
393 match self {
394 Dynamic::Sfz | Dynamic::Rfz | Dynamic::Fz | Dynamic::Sf | Dynamic::Sffz => None,
395 Dynamic::Fp | Dynamic::Sfp | Dynamic::Sfzp => Some(Dynamic::P),
396 Dynamic::Sfpp => Some(Dynamic::Pp),
397 Dynamic::Pf => Some(Dynamic::F),
398 level => Some(*level),
399 }
400 }
401
402 pub fn to_musicxml_str(&self) -> &'static str {
403 match self {
404 Dynamic::Pppp => "pppp",
405 Dynamic::Ppp => "ppp",
406 Dynamic::Pp => "pp",
407 Dynamic::P => "p",
408 Dynamic::Mp => "mp",
409 Dynamic::Mf => "mf",
410 Dynamic::F => "f",
411 Dynamic::Ff => "ff",
412 Dynamic::Fff => "fff",
413 Dynamic::Ffff => "ffff",
414 Dynamic::Sfz => "sfz",
415 Dynamic::Rfz => "rfz",
416 Dynamic::Fz => "fz",
417 Dynamic::Sf => "sf",
418 Dynamic::Fp => "fp",
419 Dynamic::Sfp => "sfp",
420 Dynamic::Sfpp => "sfpp",
421 Dynamic::Pf => "pf",
422 Dynamic::Sffz => "sffz",
423 Dynamic::Sfzp => "sfzp",
424 Dynamic::N => "n",
425 }
426 }
427
428 pub fn to_velocity(&self) -> u8 {
429 match self {
430 Dynamic::Pppp => 16,
431 Dynamic::Ppp => 24,
432 Dynamic::Pp => 36,
433 Dynamic::P => 48,
434 Dynamic::Mp => 60,
435 Dynamic::Mf => 72,
436 Dynamic::F => 84,
437 Dynamic::Ff => 96,
438 Dynamic::Fff => 108,
439 Dynamic::Ffff => 120,
440 Dynamic::Sfz => 112,
441 Dynamic::Rfz => 104,
442 Dynamic::Fz => 100,
443 Dynamic::Sf => 96,
444 Dynamic::Fp => 84,
445 Dynamic::Sfp | Dynamic::Sfpp => 96,
446 Dynamic::Pf => 48,
447 Dynamic::Sffz => 120,
448 Dynamic::Sfzp => 112,
449 Dynamic::N => 8,
450 }
451 }
452}
453
454#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
455pub enum Articulation {
456 Staccato,
457 Staccatissimo,
458 Accent,
459 Tenuto,
460 Marcato,
461 Fermata,
462 Trill,
463 Mordent,
464 InvertedMordent,
465 Turn,
466 InvertedTurn,
467 Shake,
468 Tremolo(u8),
469 BreathMark,
470 Caesura,
471 UpBow,
473 DownBow,
475 Harmonic,
477 OpenString,
479 Stopped,
481 SnapPizzicato,
483 Tap,
485 LeftHandTap,
487 Vibrato,
489 Scoop,
491 Plop,
493 Doit,
495 Falloff,
497}
498
499impl Articulation {
500 pub fn is_technical_mark(&self) -> bool {
503 matches!(
504 self,
505 Self::UpBow
506 | Self::DownBow
507 | Self::Harmonic
508 | Self::OpenString
509 | Self::Stopped
510 | Self::SnapPizzicato
511 | Self::Tap
512 | Self::LeftHandTap
513 | Self::Vibrato
514 )
515 }
516}
517
518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
520#[serde(rename_all = "kebab-case")]
521pub enum GuitarTechnique {
522 Bend,
523 Slide,
524 HammerOn,
525 PullOff,
526}
527
528#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
530pub enum FingeringSelectionPolicy {
531 #[default]
533 SourceOrder,
534 LowestNumber,
536 HighestNumber,
538}
539
540#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
541pub enum Barline {
542 #[default]
543 Normal,
544 Double,
545 Final,
546 RepeatStart,
547 RepeatEnd,
548 RepeatBoth,
549 Dashed,
550 Dotted,
551 Invisible,
552}
553
554#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
555pub enum HairpinKind {
556 Crescendo,
557 Decrescendo,
558}
559
560#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
561pub struct TupletInfo {
562 pub actual_notes: u8,
564 pub normal_notes: u8,
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
570#[serde(rename_all = "kebab-case")]
571pub enum TupletBoundary {
572 Start,
573 Stop,
574 StartStop,
575}
576
577#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
578pub enum BeamState {
579 #[default]
580 None,
581 Begin,
582 Continue,
583 End,
584 BeginEnd,
585 BackwardHook,
586 ForwardHook,
587}
588
589#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
591pub enum OttavaKind {
592 Va8,
594 Vb8,
596 Ma15,
598 Mb15,
600}
601
602impl OttavaKind {
603 pub fn musicxml_type(&self) -> &'static str {
606 match self {
607 OttavaKind::Va8 | OttavaKind::Ma15 => "down",
608 OttavaKind::Vb8 | OttavaKind::Mb15 => "up",
609 }
610 }
611
612 pub fn display_shift_steps(&self) -> i32 {
614 match self {
615 OttavaKind::Va8 => -7,
616 OttavaKind::Ma15 => -14,
617 OttavaKind::Vb8 => 7,
618 OttavaKind::Mb15 => 14,
619 }
620 }
621
622 pub fn musicxml_size(&self) -> u8 {
623 match self {
624 OttavaKind::Va8 | OttavaKind::Vb8 => 8,
625 OttavaKind::Ma15 | OttavaKind::Mb15 => 15,
626 }
627 }
628}
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
634pub enum AccidentalDisplay {
635 #[default]
636 Auto,
637 Cautionary,
639 Parenthesized,
642 Editorial,
644}
645
646#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
648pub enum NoteHead {
649 #[default]
650 Normal,
651 Diamond, X, Slash, Cross, Triangle, }
657
658#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
660pub struct Lyric {
661 pub text: String,
663 pub syllabic: String,
665 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
668 pub extend: bool,
669}
670
671#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
673pub struct VerseLyric {
674 pub verse: u8,
676 pub lyric: Lyric,
677}
678
679impl VerseLyric {
680 pub const MAX_VERSE: u8 = 32;
682}
683
684#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
687pub struct StyledText {
688 pub style: TextStyle,
689 pub text: String,
690 #[serde(default)]
692 pub placement: Option<String>,
693 #[serde(default)]
695 pub offset_x: Option<f64>,
696 #[serde(default)]
698 pub offset_y: Option<f64>,
699 #[serde(default)]
701 pub relative_x: Option<f64>,
702 #[serde(default)]
704 pub relative_y: Option<f64>,
705}
706
707#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
708pub enum TextStyle {
709 Expression,
710 Technique,
711 Lyrics,
712 ChordSymbol,
713 FiguredBass,
714 RehearsalMark,
715 Generic,
716}
717
718#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
721pub struct CrossStaff {
722 pub target_staff: usize,
723 #[serde(default)]
724 pub target_voice: Option<usize>,
725}
726
727#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
729pub struct TabPosition {
730 pub string: u8,
732 pub fret: u8,
733}
734
735#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
737pub struct TablatureConfig {
738 pub lines: u8,
739 pub tuning_midi: Vec<i16>,
740 #[serde(default)]
741 pub capo: u8,
742}
743
744#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
746pub struct ChordSymbol {
747 pub root: String,
749 pub kind: String,
751 pub bass: Option<String>,
753 #[serde(default)]
755 pub placement: Option<String>,
756 #[serde(default)]
758 pub extender: bool,
759 #[serde(default)]
761 pub harmonic_degree: Option<String>,
762 #[serde(default)]
764 pub harmony_function: Option<String>,
765 #[serde(default)]
767 pub harmony_type: Option<String>,
768 #[serde(default)]
770 pub chord_ref: Option<String>,
771 #[serde(default)]
777 pub range_end: Option<NoteAddr>,
778 #[serde(default)]
780 pub degrees: Vec<ChordDegree>,
781}
782
783#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
789pub struct ChordDefinition {
790 #[serde(default)]
791 pub id: Option<String>,
792 #[serde(default)]
793 pub label: Option<String>,
794 #[serde(default)]
795 pub kind: Option<String>,
796 #[serde(default)]
797 pub fret_position: Option<u32>,
798 #[serde(default)]
799 pub tab_strings: Option<String>,
800 #[serde(default)]
801 pub tab_courses: Option<String>,
802 #[serde(default)]
803 pub members: Vec<ChordDefinitionMember>,
804 #[serde(default)]
805 pub barres: Vec<ChordBarre>,
806}
807
808#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
810pub struct ChordDefinitionMember {
811 #[serde(default)]
812 pub id: Option<String>,
813 #[serde(default)]
814 pub pitch: Option<Pitch>,
815 #[serde(default)]
816 pub tab_string: Option<u8>,
817 #[serde(default)]
818 pub tab_course: Option<u8>,
819 #[serde(default)]
820 pub tab_fret: Option<u16>,
821 #[serde(default)]
822 pub fingering: Option<u8>,
823}
824
825#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
827pub struct ChordBarre {
828 #[serde(default)]
829 pub start_member: Option<String>,
830 #[serde(default)]
831 pub end_member: Option<String>,
832 #[serde(default)]
833 pub fret: Option<u16>,
834 #[serde(default)]
835 pub label: Option<String>,
836 #[serde(default)]
837 pub kind: Option<String>,
838}
839
840#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
842pub struct ChordDegree {
843 pub value: u8,
845 pub alter: i8,
847 pub kind: String,
849}
850
851#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
853pub struct FiguredBassFigure {
854 pub number: String,
856 #[serde(default)]
858 pub alter: Option<String>,
859 #[serde(default)]
861 pub prefix: Option<String>,
862 #[serde(default)]
864 pub suffix: Option<String>,
865 #[serde(default)]
867 pub extender: bool,
868}
869
870impl ChordDegree {
871 pub fn display_text(&self) -> String {
873 let accidental = match self.alter {
874 -2 => "bb",
875 -1 => "b",
876 1 => "#",
877 2 => "##",
878 _ => "",
879 };
880 match self.kind.as_str() {
881 "subtract" => format!("no{}{}", accidental, self.value),
882 "alter" => format!("{}{}", accidental, self.value),
883 _ => format!("add{}{}", accidental, self.value),
884 }
885 }
886}
887
888pub const CHORD_KIND_SUFFIXES: &[(&str, &str)] = &[
892 ("major", ""),
893 ("minor", "m"),
894 ("augmented", "aug"),
895 ("diminished", "dim"),
896 ("dominant", "7"),
897 ("major-seventh", "maj7"),
898 ("minor-seventh", "m7"),
899 ("diminished-seventh", "dim7"),
900 ("augmented-seventh", "aug7"),
901 ("half-diminished", "m7b5"),
902 ("major-minor", "mMaj7"),
903 ("minor-major-seventh", "mMaj7"),
904 ("minor-major", "mMaj7"),
905 ("major-sixth", "6"),
906 ("minor-sixth", "m6"),
907 ("dominant-ninth", "9"),
908 ("major-ninth", "maj9"),
909 ("minor-ninth", "m9"),
910 ("dominant-11th", "11"),
911 ("major-11th", "maj11"),
912 ("minor-11th", "m11"),
913 ("dominant-13th", "13"),
914 ("major-13th", "maj13"),
915 ("minor-13th", "m13"),
916 ("suspended-second", "sus2"),
917 ("suspended-fourth", "sus4"),
918 ("power", "5"),
919 ("major-add9", "add9"),
920 ("minor-add9", "madd9"),
921 ("dominant-flat-five", "7b5"),
922 ("dominant-sharp-five", "7#5"),
923];
924
925impl ChordSymbol {
926 pub fn kind_for_suffix(suffix: &str) -> Option<&'static str> {
928 CHORD_KIND_SUFFIXES
929 .iter()
930 .find(|(_, candidate)| *candidate == suffix)
931 .map(|(kind, _)| *kind)
932 }
933
934 pub fn display_text(&self) -> String {
935 let kind_str = CHORD_KIND_SUFFIXES
936 .iter()
937 .find(|(kind, _)| *kind == self.kind)
938 .map_or(self.kind.as_str(), |(_, suffix)| *suffix);
939 let bass_str = match &self.bass {
940 Some(b) => format!("/{}", b),
941 None => String::new(),
942 };
943 let degree_str = self
944 .degrees
945 .iter()
946 .map(ChordDegree::display_text)
947 .collect::<String>();
948 format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
949 }
950}
951
952#[cfg(test)]
953mod tests {
954 use super::*;
955
956 #[test]
957 fn key_alter_c_major_all_natural() {
958 let key = KeySignature {
959 fifths: 0,
960 mode: "major".into(),
961 };
962 for step in [
963 Step::C,
964 Step::D,
965 Step::E,
966 Step::F,
967 Step::G,
968 Step::A,
969 Step::B,
970 ] {
971 assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
972 }
973 }
974
975 #[test]
976 fn key_alter_g_major_fsharp() {
977 let key = KeySignature {
978 fifths: 1,
979 mode: "major".into(),
980 };
981 assert_eq!(key.alter_for_step(&Step::F), 1);
982 assert_eq!(key.alter_for_step(&Step::G), 0);
983 }
984
985 #[test]
986 fn key_alter_f_major_bflat() {
987 let key = KeySignature {
988 fifths: -1,
989 mode: "major".into(),
990 };
991 assert_eq!(key.alter_for_step(&Step::B), -1);
992 assert_eq!(key.alter_for_step(&Step::C), 0);
993 }
994
995 #[test]
996 fn key_alter_bb_major() {
997 let key = KeySignature {
998 fifths: -2,
999 mode: "major".into(),
1000 };
1001 assert_eq!(key.alter_for_step(&Step::B), -1);
1002 assert_eq!(key.alter_for_step(&Step::E), -1);
1003 assert_eq!(key.alter_for_step(&Step::A), 0);
1004 }
1005
1006 #[test]
1007 fn key_contains_pitch_g_major() {
1008 let key = KeySignature {
1009 fifths: 1,
1010 mode: "major".into(),
1011 };
1012 assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
1014 assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
1015 assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
1018 }
1019
1020 #[test]
1021 fn key_display_name_c_major() {
1022 let key = KeySignature {
1023 fifths: 0,
1024 mode: "major".into(),
1025 };
1026 assert_eq!(key.display_name(), "C major");
1027 }
1028
1029 #[test]
1030 fn key_display_name_bb_major() {
1031 let key = KeySignature {
1032 fifths: -2,
1033 mode: "major".into(),
1034 };
1035 assert_eq!(key.display_name(), "Bb major");
1036 }
1037
1038 #[test]
1039 fn key_display_name_fsharp_minor() {
1040 let key = KeySignature {
1041 fifths: 3,
1042 mode: "minor".into(),
1043 };
1044 assert_eq!(key.display_name(), "F# minor");
1045 }
1046
1047 #[test]
1048 fn key_tonic_d_major() {
1049 let key = KeySignature {
1050 fifths: 2,
1051 mode: "major".into(),
1052 };
1053 let (step, alter) = key.tonic();
1054 assert_eq!(step, Step::D);
1055 assert_eq!(alter, 0);
1056 }
1057
1058 #[test]
1059 fn key_tonic_a_minor() {
1060 let key = KeySignature {
1062 fifths: 0,
1063 mode: "minor".into(),
1064 };
1065 let (step, alter) = key.tonic();
1066 assert_eq!(step, Step::A);
1067 assert_eq!(alter, 0);
1068 }
1069
1070 #[test]
1071 fn chord_display_major() {
1072 let c = ChordSymbol {
1073 root: "C".into(),
1074 kind: "major".into(),
1075 bass: None,
1076 placement: None,
1077 extender: false,
1078 harmonic_degree: None,
1079 harmony_function: None,
1080 harmony_type: None,
1081 chord_ref: None,
1082 range_end: None,
1083 degrees: Vec::new(),
1084 };
1085 assert_eq!(c.display_text(), "C");
1086 }
1087
1088 #[test]
1089 fn chord_display_minor_seventh_slash() {
1090 let c = ChordSymbol {
1091 root: "D".into(),
1092 kind: "minor-seventh".into(),
1093 bass: Some("F".into()),
1094 placement: None,
1095 extender: false,
1096 harmonic_degree: None,
1097 harmony_function: None,
1098 harmony_type: None,
1099 chord_ref: None,
1100 range_end: None,
1101 degrees: Vec::new(),
1102 };
1103 assert_eq!(c.display_text(), "Dm7/F");
1104 }
1105
1106 #[test]
1107 fn chord_display_structured_degrees() {
1108 let c = ChordSymbol {
1109 root: "C".into(),
1110 kind: "dominant".into(),
1111 bass: None,
1112 placement: None,
1113 extender: false,
1114 harmonic_degree: None,
1115 harmony_function: None,
1116 harmony_type: None,
1117 chord_ref: None,
1118 range_end: None,
1119 degrees: vec![
1120 ChordDegree {
1121 value: 9,
1122 alter: 1,
1123 kind: "add".into(),
1124 },
1125 ChordDegree {
1126 value: 5,
1127 alter: -1,
1128 kind: "alter".into(),
1129 },
1130 ChordDegree {
1131 value: 3,
1132 alter: 0,
1133 kind: "subtract".into(),
1134 },
1135 ],
1136 };
1137 assert_eq!(c.display_text(), "C7add#9b5no3");
1138 }
1139
1140 #[test]
1141 fn chord_symbol_legacy_json_defaults_degrees() {
1142 let chord: ChordSymbol =
1143 serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
1144 .expect("legacy chord symbol JSON deserializes");
1145 assert!(chord.degrees.is_empty());
1146 assert!(!chord.extender);
1147 assert!(chord.harmonic_degree.is_none());
1148 assert!(chord.harmony_function.is_none());
1149 assert!(chord.harmony_type.is_none());
1150 }
1151
1152 #[test]
1153 fn time_sig_total_beats_three_four() {
1154 let ts = TimeSignature {
1155 numerator: 3,
1156 denominator: 4,
1157 };
1158 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1159 }
1160
1161 #[test]
1162 fn time_sig_total_beats_six_eight() {
1163 let ts = TimeSignature {
1164 numerator: 6,
1165 denominator: 8,
1166 };
1167 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1168 }
1169
1170 #[test]
1171 fn clef_treble_middle_b4() {
1172 assert_eq!(Clef::Treble.middle_line_midi(), 71);
1173 }
1174
1175 #[test]
1176 fn clef_bass_middle_d3() {
1177 assert_eq!(Clef::Bass.middle_line_midi(), 50);
1178 }
1179
1180 #[test]
1181 fn clef_alto_middle_c4() {
1182 assert_eq!(Clef::Alto.middle_line_midi(), 60);
1183 }
1184
1185 #[test]
1186 fn clef_tenor_middle_a3() {
1187 assert_eq!(Clef::Tenor.middle_line_midi(), 57);
1188 }
1189}