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, Serialize, Deserialize, Default)]
569pub enum BeamState {
570 #[default]
571 None,
572 Begin,
573 Continue,
574 End,
575 BeginEnd,
576 BackwardHook,
577 ForwardHook,
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
582pub enum OttavaKind {
583 Va8,
585 Vb8,
587 Ma15,
589 Mb15,
591}
592
593impl OttavaKind {
594 pub fn musicxml_type(&self) -> &'static str {
597 match self {
598 OttavaKind::Va8 | OttavaKind::Ma15 => "down",
599 OttavaKind::Vb8 | OttavaKind::Mb15 => "up",
600 }
601 }
602
603 pub fn display_shift_steps(&self) -> i32 {
605 match self {
606 OttavaKind::Va8 => -7,
607 OttavaKind::Ma15 => -14,
608 OttavaKind::Vb8 => 7,
609 OttavaKind::Mb15 => 14,
610 }
611 }
612
613 pub fn musicxml_size(&self) -> u8 {
614 match self {
615 OttavaKind::Va8 | OttavaKind::Vb8 => 8,
616 OttavaKind::Ma15 | OttavaKind::Mb15 => 15,
617 }
618 }
619}
620
621#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
625pub enum AccidentalDisplay {
626 #[default]
627 Auto,
628 Cautionary,
630 Parenthesized,
633 Editorial,
635}
636
637#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
639pub enum NoteHead {
640 #[default]
641 Normal,
642 Diamond, X, Slash, Cross, Triangle, }
648
649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
651pub struct Lyric {
652 pub text: String,
654 pub syllabic: String,
656 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
659 pub extend: bool,
660}
661
662#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
664pub struct VerseLyric {
665 pub verse: u8,
667 pub lyric: Lyric,
668}
669
670impl VerseLyric {
671 pub const MAX_VERSE: u8 = 32;
673}
674
675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
678pub struct StyledText {
679 pub style: TextStyle,
680 pub text: String,
681 #[serde(default)]
683 pub placement: Option<String>,
684 #[serde(default)]
686 pub offset_x: Option<f64>,
687 #[serde(default)]
689 pub offset_y: Option<f64>,
690 #[serde(default)]
692 pub relative_x: Option<f64>,
693 #[serde(default)]
695 pub relative_y: Option<f64>,
696}
697
698#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
699pub enum TextStyle {
700 Expression,
701 Technique,
702 Lyrics,
703 ChordSymbol,
704 FiguredBass,
705 RehearsalMark,
706 Generic,
707}
708
709#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
712pub struct CrossStaff {
713 pub target_staff: usize,
714 #[serde(default)]
715 pub target_voice: Option<usize>,
716}
717
718#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
720pub struct TabPosition {
721 pub string: u8,
723 pub fret: u8,
724}
725
726#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
728pub struct TablatureConfig {
729 pub lines: u8,
730 pub tuning_midi: Vec<i16>,
731 #[serde(default)]
732 pub capo: u8,
733}
734
735#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
737pub struct ChordSymbol {
738 pub root: String,
740 pub kind: String,
742 pub bass: Option<String>,
744 #[serde(default)]
746 pub placement: Option<String>,
747 #[serde(default)]
749 pub extender: bool,
750 #[serde(default)]
752 pub harmonic_degree: Option<String>,
753 #[serde(default)]
755 pub harmony_function: Option<String>,
756 #[serde(default)]
758 pub harmony_type: Option<String>,
759 #[serde(default)]
761 pub chord_ref: Option<String>,
762 #[serde(default)]
768 pub range_end: Option<NoteAddr>,
769 #[serde(default)]
771 pub degrees: Vec<ChordDegree>,
772}
773
774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
780pub struct ChordDefinition {
781 #[serde(default)]
782 pub id: Option<String>,
783 #[serde(default)]
784 pub label: Option<String>,
785 #[serde(default)]
786 pub kind: Option<String>,
787 #[serde(default)]
788 pub fret_position: Option<u32>,
789 #[serde(default)]
790 pub tab_strings: Option<String>,
791 #[serde(default)]
792 pub tab_courses: Option<String>,
793 #[serde(default)]
794 pub members: Vec<ChordDefinitionMember>,
795 #[serde(default)]
796 pub barres: Vec<ChordBarre>,
797}
798
799#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
801pub struct ChordDefinitionMember {
802 #[serde(default)]
803 pub id: Option<String>,
804 #[serde(default)]
805 pub pitch: Option<Pitch>,
806 #[serde(default)]
807 pub tab_string: Option<u8>,
808 #[serde(default)]
809 pub tab_course: Option<u8>,
810 #[serde(default)]
811 pub tab_fret: Option<u16>,
812 #[serde(default)]
813 pub fingering: Option<u8>,
814}
815
816#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
818pub struct ChordBarre {
819 #[serde(default)]
820 pub start_member: Option<String>,
821 #[serde(default)]
822 pub end_member: Option<String>,
823 #[serde(default)]
824 pub fret: Option<u16>,
825 #[serde(default)]
826 pub label: Option<String>,
827 #[serde(default)]
828 pub kind: Option<String>,
829}
830
831#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
833pub struct ChordDegree {
834 pub value: u8,
836 pub alter: i8,
838 pub kind: String,
840}
841
842#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
844pub struct FiguredBassFigure {
845 pub number: String,
847 #[serde(default)]
849 pub alter: Option<String>,
850 #[serde(default)]
852 pub prefix: Option<String>,
853 #[serde(default)]
855 pub suffix: Option<String>,
856 #[serde(default)]
858 pub extender: bool,
859}
860
861impl ChordDegree {
862 pub fn display_text(&self) -> String {
864 let accidental = match self.alter {
865 -2 => "bb",
866 -1 => "b",
867 1 => "#",
868 2 => "##",
869 _ => "",
870 };
871 match self.kind.as_str() {
872 "subtract" => format!("no{}{}", accidental, self.value),
873 "alter" => format!("{}{}", accidental, self.value),
874 _ => format!("add{}{}", accidental, self.value),
875 }
876 }
877}
878
879pub const CHORD_KIND_SUFFIXES: &[(&str, &str)] = &[
883 ("major", ""),
884 ("minor", "m"),
885 ("augmented", "aug"),
886 ("diminished", "dim"),
887 ("dominant", "7"),
888 ("major-seventh", "maj7"),
889 ("minor-seventh", "m7"),
890 ("diminished-seventh", "dim7"),
891 ("augmented-seventh", "aug7"),
892 ("half-diminished", "m7b5"),
893 ("major-minor", "mMaj7"),
894 ("minor-major-seventh", "mMaj7"),
895 ("minor-major", "mMaj7"),
896 ("major-sixth", "6"),
897 ("minor-sixth", "m6"),
898 ("dominant-ninth", "9"),
899 ("major-ninth", "maj9"),
900 ("minor-ninth", "m9"),
901 ("dominant-11th", "11"),
902 ("major-11th", "maj11"),
903 ("minor-11th", "m11"),
904 ("dominant-13th", "13"),
905 ("major-13th", "maj13"),
906 ("minor-13th", "m13"),
907 ("suspended-second", "sus2"),
908 ("suspended-fourth", "sus4"),
909 ("power", "5"),
910 ("major-add9", "add9"),
911 ("minor-add9", "madd9"),
912 ("dominant-flat-five", "7b5"),
913 ("dominant-sharp-five", "7#5"),
914];
915
916impl ChordSymbol {
917 pub fn kind_for_suffix(suffix: &str) -> Option<&'static str> {
919 CHORD_KIND_SUFFIXES
920 .iter()
921 .find(|(_, candidate)| *candidate == suffix)
922 .map(|(kind, _)| *kind)
923 }
924
925 pub fn display_text(&self) -> String {
926 let kind_str = CHORD_KIND_SUFFIXES
927 .iter()
928 .find(|(kind, _)| *kind == self.kind)
929 .map_or(self.kind.as_str(), |(_, suffix)| *suffix);
930 let bass_str = match &self.bass {
931 Some(b) => format!("/{}", b),
932 None => String::new(),
933 };
934 let degree_str = self
935 .degrees
936 .iter()
937 .map(ChordDegree::display_text)
938 .collect::<String>();
939 format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
940 }
941}
942
943#[cfg(test)]
944mod tests {
945 use super::*;
946
947 #[test]
948 fn key_alter_c_major_all_natural() {
949 let key = KeySignature {
950 fifths: 0,
951 mode: "major".into(),
952 };
953 for step in [
954 Step::C,
955 Step::D,
956 Step::E,
957 Step::F,
958 Step::G,
959 Step::A,
960 Step::B,
961 ] {
962 assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
963 }
964 }
965
966 #[test]
967 fn key_alter_g_major_fsharp() {
968 let key = KeySignature {
969 fifths: 1,
970 mode: "major".into(),
971 };
972 assert_eq!(key.alter_for_step(&Step::F), 1);
973 assert_eq!(key.alter_for_step(&Step::G), 0);
974 }
975
976 #[test]
977 fn key_alter_f_major_bflat() {
978 let key = KeySignature {
979 fifths: -1,
980 mode: "major".into(),
981 };
982 assert_eq!(key.alter_for_step(&Step::B), -1);
983 assert_eq!(key.alter_for_step(&Step::C), 0);
984 }
985
986 #[test]
987 fn key_alter_bb_major() {
988 let key = KeySignature {
989 fifths: -2,
990 mode: "major".into(),
991 };
992 assert_eq!(key.alter_for_step(&Step::B), -1);
993 assert_eq!(key.alter_for_step(&Step::E), -1);
994 assert_eq!(key.alter_for_step(&Step::A), 0);
995 }
996
997 #[test]
998 fn key_contains_pitch_g_major() {
999 let key = KeySignature {
1000 fifths: 1,
1001 mode: "major".into(),
1002 };
1003 assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
1005 assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
1006 assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
1009 }
1010
1011 #[test]
1012 fn key_display_name_c_major() {
1013 let key = KeySignature {
1014 fifths: 0,
1015 mode: "major".into(),
1016 };
1017 assert_eq!(key.display_name(), "C major");
1018 }
1019
1020 #[test]
1021 fn key_display_name_bb_major() {
1022 let key = KeySignature {
1023 fifths: -2,
1024 mode: "major".into(),
1025 };
1026 assert_eq!(key.display_name(), "Bb major");
1027 }
1028
1029 #[test]
1030 fn key_display_name_fsharp_minor() {
1031 let key = KeySignature {
1032 fifths: 3,
1033 mode: "minor".into(),
1034 };
1035 assert_eq!(key.display_name(), "F# minor");
1036 }
1037
1038 #[test]
1039 fn key_tonic_d_major() {
1040 let key = KeySignature {
1041 fifths: 2,
1042 mode: "major".into(),
1043 };
1044 let (step, alter) = key.tonic();
1045 assert_eq!(step, Step::D);
1046 assert_eq!(alter, 0);
1047 }
1048
1049 #[test]
1050 fn key_tonic_a_minor() {
1051 let key = KeySignature {
1053 fifths: 0,
1054 mode: "minor".into(),
1055 };
1056 let (step, alter) = key.tonic();
1057 assert_eq!(step, Step::A);
1058 assert_eq!(alter, 0);
1059 }
1060
1061 #[test]
1062 fn chord_display_major() {
1063 let c = ChordSymbol {
1064 root: "C".into(),
1065 kind: "major".into(),
1066 bass: None,
1067 placement: None,
1068 extender: false,
1069 harmonic_degree: None,
1070 harmony_function: None,
1071 harmony_type: None,
1072 chord_ref: None,
1073 range_end: None,
1074 degrees: Vec::new(),
1075 };
1076 assert_eq!(c.display_text(), "C");
1077 }
1078
1079 #[test]
1080 fn chord_display_minor_seventh_slash() {
1081 let c = ChordSymbol {
1082 root: "D".into(),
1083 kind: "minor-seventh".into(),
1084 bass: Some("F".into()),
1085 placement: None,
1086 extender: false,
1087 harmonic_degree: None,
1088 harmony_function: None,
1089 harmony_type: None,
1090 chord_ref: None,
1091 range_end: None,
1092 degrees: Vec::new(),
1093 };
1094 assert_eq!(c.display_text(), "Dm7/F");
1095 }
1096
1097 #[test]
1098 fn chord_display_structured_degrees() {
1099 let c = ChordSymbol {
1100 root: "C".into(),
1101 kind: "dominant".into(),
1102 bass: None,
1103 placement: None,
1104 extender: false,
1105 harmonic_degree: None,
1106 harmony_function: None,
1107 harmony_type: None,
1108 chord_ref: None,
1109 range_end: None,
1110 degrees: vec![
1111 ChordDegree {
1112 value: 9,
1113 alter: 1,
1114 kind: "add".into(),
1115 },
1116 ChordDegree {
1117 value: 5,
1118 alter: -1,
1119 kind: "alter".into(),
1120 },
1121 ChordDegree {
1122 value: 3,
1123 alter: 0,
1124 kind: "subtract".into(),
1125 },
1126 ],
1127 };
1128 assert_eq!(c.display_text(), "C7add#9b5no3");
1129 }
1130
1131 #[test]
1132 fn chord_symbol_legacy_json_defaults_degrees() {
1133 let chord: ChordSymbol =
1134 serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
1135 .expect("legacy chord symbol JSON deserializes");
1136 assert!(chord.degrees.is_empty());
1137 assert!(!chord.extender);
1138 assert!(chord.harmonic_degree.is_none());
1139 assert!(chord.harmony_function.is_none());
1140 assert!(chord.harmony_type.is_none());
1141 }
1142
1143 #[test]
1144 fn time_sig_total_beats_three_four() {
1145 let ts = TimeSignature {
1146 numerator: 3,
1147 denominator: 4,
1148 };
1149 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1150 }
1151
1152 #[test]
1153 fn time_sig_total_beats_six_eight() {
1154 let ts = TimeSignature {
1155 numerator: 6,
1156 denominator: 8,
1157 };
1158 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1159 }
1160
1161 #[test]
1162 fn clef_treble_middle_b4() {
1163 assert_eq!(Clef::Treble.middle_line_midi(), 71);
1164 }
1165
1166 #[test]
1167 fn clef_bass_middle_d3() {
1168 assert_eq!(Clef::Bass.middle_line_midi(), 50);
1169 }
1170
1171 #[test]
1172 fn clef_alto_middle_c4() {
1173 assert_eq!(Clef::Alto.middle_line_midi(), 60);
1174 }
1175
1176 #[test]
1177 fn clef_tenor_middle_a3() {
1178 assert_eq!(Clef::Tenor.middle_line_midi(), 57);
1179 }
1180}