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}
28
29impl Clef {
30 pub fn to_musicxml_sign(&self) -> &'static str {
31 match self {
32 Clef::Treble | Clef::Treble8vb | Clef::Treble8va => "G",
33 Clef::Bass | Clef::Bass8vb | Clef::Bass8va => "F",
34 Clef::Alto | Clef::Tenor | Clef::Soprano | Clef::MezzoSoprano | Clef::Baritone => "C",
35 Clef::Percussion => "percussion",
36 }
37 }
38
39 pub fn musicxml_line(&self) -> u8 {
40 match self {
41 Clef::Treble | Clef::Treble8vb | Clef::Treble8va => 2,
42 Clef::Bass | Clef::Bass8vb | Clef::Bass8va => 4,
43 Clef::Alto => 3,
44 Clef::Tenor => 4,
45 Clef::Percussion => 2,
46 Clef::Soprano => 1,
47 Clef::MezzoSoprano => 2,
48 Clef::Baritone => 5,
49 }
50 }
51
52 pub fn octave_change(&self) -> i8 {
55 match self {
56 Clef::Treble8vb | Clef::Bass8vb => -1,
57 Clef::Treble8va | Clef::Bass8va => 1,
58 _ => 0,
59 }
60 }
61
62 pub fn without_octave(&self) -> Clef {
64 match self {
65 Clef::Treble8vb | Clef::Treble8va => Clef::Treble,
66 Clef::Bass8vb | Clef::Bass8va => Clef::Bass,
67 other => other.clone(),
68 }
69 }
70
71 pub fn from_sign(sign: &str, line: Option<u8>, octave_change: i8) -> Option<Clef> {
75 let clef = match (sign.trim().to_ascii_uppercase().as_str(), line) {
76 ("G", None | Some(2)) => Clef::Treble,
77 ("F", None | Some(4)) => Clef::Bass,
78 ("C", Some(1)) => Clef::Soprano,
79 ("C", Some(2)) => Clef::MezzoSoprano,
80 ("C", None | Some(3)) => Clef::Alto,
81 ("C", Some(4)) => Clef::Tenor,
82 ("C", Some(5)) | ("F", Some(3)) => Clef::Baritone,
83 ("PERCUSSION", _) => Clef::Percussion,
84 _ => return None,
85 };
86 Some(match (clef, octave_change) {
87 (clef, 0) => clef,
88 (Clef::Treble, -1) => Clef::Treble8vb,
89 (Clef::Treble, 1) => Clef::Treble8va,
90 (Clef::Bass, -1) => Clef::Bass8vb,
91 (Clef::Bass, 1) => Clef::Bass8va,
92 (Clef::Percussion, _) => Clef::Percussion,
93 _ => return None,
94 })
95 }
96
97 pub fn from_name(name: &str) -> Option<Clef> {
99 Some(match name {
100 "Treble" => Clef::Treble,
101 "Bass" => Clef::Bass,
102 "Alto" => Clef::Alto,
103 "Tenor" => Clef::Tenor,
104 "Percussion" => Clef::Percussion,
105 "Treble8vb" => Clef::Treble8vb,
106 "Treble8va" => Clef::Treble8va,
107 "Bass8vb" => Clef::Bass8vb,
108 "Bass8va" => Clef::Bass8va,
109 "Soprano" => Clef::Soprano,
110 "MezzoSoprano" => Clef::MezzoSoprano,
111 "Baritone" => Clef::Baritone,
112 _ => return None,
113 })
114 }
115
116 pub fn middle_line_midi(&self) -> u8 {
118 match self {
119 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, }
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136pub struct KeySignature {
137 pub fifths: i8,
139 pub mode: String,
141}
142
143impl Default for KeySignature {
144 fn default() -> Self {
145 Self {
146 fifths: 0,
147 mode: "major".to_string(),
148 }
149 }
150}
151
152impl KeySignature {
153 const SHARP_ORDER: [Step; 7] = [
155 Step::F,
156 Step::C,
157 Step::G,
158 Step::D,
159 Step::A,
160 Step::E,
161 Step::B,
162 ];
163 const FLAT_ORDER: [Step; 7] = [
165 Step::B,
166 Step::E,
167 Step::A,
168 Step::D,
169 Step::G,
170 Step::C,
171 Step::F,
172 ];
173
174 pub fn alter_for_step(&self, step: &Step) -> i8 {
176 if self.fifths > 0 {
177 let count = self.fifths.min(7) as usize;
178 if Self::SHARP_ORDER[..count].contains(step) {
179 1
180 } else {
181 0
182 }
183 } else if self.fifths < 0 {
184 let count = (-self.fifths).min(7) as usize;
185 if Self::FLAT_ORDER[..count].contains(step) {
186 -1
187 } else {
188 0
189 }
190 } else {
191 0
192 }
193 }
194
195 pub fn contains_pitch(&self, pitch: &Pitch) -> bool {
197 pitch.alter == self.alter_for_step(&pitch.step)
198 }
199
200 pub fn tonic(&self) -> (Step, i8) {
204 if self.mode == "minor" {
205 let (maj_step, maj_alter) = Self::major_tonic_from_fifths(self.fifths);
206 let major_midi = Pitch::with_alter(maj_step, 4, maj_alter).to_midi();
208 let minor_midi = (major_midi - 3).clamp(0, 127) as u8;
209 let p = Pitch::from_midi(minor_midi, self.fifths < 0);
210 (p.step, p.alter)
211 } else {
212 Self::major_tonic_from_fifths(self.fifths)
213 }
214 }
215
216 pub fn display_name(&self) -> String {
218 let (step, alter) = self.tonic();
219 let acc = match alter {
220 1 => "#",
221 -1 => "b",
222 _ => "",
223 };
224 format!("{}{} {}", step.to_char(), acc, self.mode)
225 }
226
227 fn major_tonic_from_fifths(fifths: i8) -> (Step, i8) {
228 const TONICS: [(Step, i8); 15] = [
231 (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), ];
247 let idx = (fifths.clamp(-7, 7) + 7) as usize;
248 TONICS[idx].clone()
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub struct TimeSignature {
254 pub numerator: u8,
255 pub denominator: u8,
256}
257
258impl Default for TimeSignature {
259 fn default() -> Self {
260 Self {
261 numerator: 4,
262 denominator: 4,
263 }
264 }
265}
266
267impl TimeSignature {
268 pub fn beats_per_measure(&self) -> f64 {
269 self.numerator as f64
270 }
271
272 pub fn beat_unit_beats(&self) -> f64 {
273 4.0 / self.denominator as f64
274 }
275
276 pub fn total_beats(&self) -> f64 {
277 self.beats_per_measure() * self.beat_unit_beats()
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
282pub enum Dynamic {
283 Pppp,
284 Ppp,
285 Pp,
286 P,
287 Mp,
288 Mf,
289 F,
290 Ff,
291 Fff,
292 Ffff,
293 Sfz,
294 Rfz,
295 Fz,
296 Sf,
297 Fp,
299 Sfp,
301 Sfpp,
303 Pf,
305 Sffz,
307 Sfzp,
309 N,
311}
312
313impl Dynamic {
314 pub const ALL: [Dynamic; 21] = [
316 Dynamic::Pppp,
317 Dynamic::Ppp,
318 Dynamic::Pp,
319 Dynamic::P,
320 Dynamic::Mp,
321 Dynamic::Mf,
322 Dynamic::F,
323 Dynamic::Ff,
324 Dynamic::Fff,
325 Dynamic::Ffff,
326 Dynamic::Sfz,
327 Dynamic::Rfz,
328 Dynamic::Fz,
329 Dynamic::Sf,
330 Dynamic::Fp,
331 Dynamic::Sfp,
332 Dynamic::Sfpp,
333 Dynamic::Pf,
334 Dynamic::Sffz,
335 Dynamic::Sfzp,
336 Dynamic::N,
337 ];
338
339 pub fn from_musicxml_str(name: &str) -> Option<Dynamic> {
342 Some(match name {
343 "pppppp" | "ppppp" => Dynamic::Pppp,
344 "ffffff" | "fffff" => Dynamic::Ffff,
345 "rf" => Dynamic::Rfz,
346 other => *Self::ALL
347 .iter()
348 .find(|dynamic| dynamic.to_musicxml_str() == other)?,
349 })
350 }
351
352 pub fn sustained_level(&self) -> Option<Dynamic> {
356 match self {
357 Dynamic::Sfz | Dynamic::Rfz | Dynamic::Fz | Dynamic::Sf | Dynamic::Sffz => None,
358 Dynamic::Fp | Dynamic::Sfp | Dynamic::Sfzp => Some(Dynamic::P),
359 Dynamic::Sfpp => Some(Dynamic::Pp),
360 Dynamic::Pf => Some(Dynamic::F),
361 level => Some(*level),
362 }
363 }
364
365 pub fn to_musicxml_str(&self) -> &'static str {
366 match self {
367 Dynamic::Pppp => "pppp",
368 Dynamic::Ppp => "ppp",
369 Dynamic::Pp => "pp",
370 Dynamic::P => "p",
371 Dynamic::Mp => "mp",
372 Dynamic::Mf => "mf",
373 Dynamic::F => "f",
374 Dynamic::Ff => "ff",
375 Dynamic::Fff => "fff",
376 Dynamic::Ffff => "ffff",
377 Dynamic::Sfz => "sfz",
378 Dynamic::Rfz => "rfz",
379 Dynamic::Fz => "fz",
380 Dynamic::Sf => "sf",
381 Dynamic::Fp => "fp",
382 Dynamic::Sfp => "sfp",
383 Dynamic::Sfpp => "sfpp",
384 Dynamic::Pf => "pf",
385 Dynamic::Sffz => "sffz",
386 Dynamic::Sfzp => "sfzp",
387 Dynamic::N => "n",
388 }
389 }
390
391 pub fn to_velocity(&self) -> u8 {
392 match self {
393 Dynamic::Pppp => 16,
394 Dynamic::Ppp => 24,
395 Dynamic::Pp => 36,
396 Dynamic::P => 48,
397 Dynamic::Mp => 60,
398 Dynamic::Mf => 72,
399 Dynamic::F => 84,
400 Dynamic::Ff => 96,
401 Dynamic::Fff => 108,
402 Dynamic::Ffff => 120,
403 Dynamic::Sfz => 112,
404 Dynamic::Rfz => 104,
405 Dynamic::Fz => 100,
406 Dynamic::Sf => 96,
407 Dynamic::Fp => 84,
408 Dynamic::Sfp | Dynamic::Sfpp => 96,
409 Dynamic::Pf => 48,
410 Dynamic::Sffz => 120,
411 Dynamic::Sfzp => 112,
412 Dynamic::N => 8,
413 }
414 }
415}
416
417#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
418pub enum Articulation {
419 Staccato,
420 Staccatissimo,
421 Accent,
422 Tenuto,
423 Marcato,
424 Fermata,
425 Trill,
426 Mordent,
427 InvertedMordent,
428 Turn,
429 InvertedTurn,
430 Shake,
431 Tremolo(u8),
432 BreathMark,
433 Caesura,
434 UpBow,
436 DownBow,
438 Harmonic,
440 OpenString,
442 Stopped,
444 SnapPizzicato,
446 Tap,
448 LeftHandTap,
450 Vibrato,
452 Scoop,
454 Plop,
456 Doit,
458 Falloff,
460}
461
462impl Articulation {
463 pub fn is_technical_mark(&self) -> bool {
466 matches!(
467 self,
468 Self::UpBow
469 | Self::DownBow
470 | Self::Harmonic
471 | Self::OpenString
472 | Self::Stopped
473 | Self::SnapPizzicato
474 | Self::Tap
475 | Self::LeftHandTap
476 | Self::Vibrato
477 )
478 }
479}
480
481#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
483#[serde(rename_all = "kebab-case")]
484pub enum GuitarTechnique {
485 Bend,
486 Slide,
487 HammerOn,
488 PullOff,
489}
490
491#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
493pub enum FingeringSelectionPolicy {
494 #[default]
496 SourceOrder,
497 LowestNumber,
499 HighestNumber,
501}
502
503#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
504pub enum Barline {
505 #[default]
506 Normal,
507 Double,
508 Final,
509 RepeatStart,
510 RepeatEnd,
511 RepeatBoth,
512 Dashed,
513 Dotted,
514 Invisible,
515}
516
517#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
518pub enum HairpinKind {
519 Crescendo,
520 Decrescendo,
521}
522
523#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
524pub struct TupletInfo {
525 pub actual_notes: u8,
527 pub normal_notes: u8,
529}
530
531#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
532pub enum BeamState {
533 #[default]
534 None,
535 Begin,
536 Continue,
537 End,
538 BeginEnd,
539 BackwardHook,
540 ForwardHook,
541}
542
543#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
545pub enum OttavaKind {
546 Va8,
548 Vb8,
550 Ma15,
552 Mb15,
554}
555
556impl OttavaKind {
557 pub fn musicxml_type(&self) -> &'static str {
560 match self {
561 OttavaKind::Va8 | OttavaKind::Ma15 => "down",
562 OttavaKind::Vb8 | OttavaKind::Mb15 => "up",
563 }
564 }
565
566 pub fn display_shift_steps(&self) -> i32 {
568 match self {
569 OttavaKind::Va8 => -7,
570 OttavaKind::Ma15 => -14,
571 OttavaKind::Vb8 => 7,
572 OttavaKind::Mb15 => 14,
573 }
574 }
575
576 pub fn musicxml_size(&self) -> u8 {
577 match self {
578 OttavaKind::Va8 | OttavaKind::Vb8 => 8,
579 OttavaKind::Ma15 | OttavaKind::Mb15 => 15,
580 }
581 }
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
588pub enum AccidentalDisplay {
589 #[default]
590 Auto,
591 Cautionary,
593 Parenthesized,
596 Editorial,
598}
599
600#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
602pub enum NoteHead {
603 #[default]
604 Normal,
605 Diamond, X, Slash, Cross, Triangle, }
611
612#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
614pub struct Lyric {
615 pub text: String,
617 pub syllabic: String,
619 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
622 pub extend: bool,
623}
624
625#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
627pub struct VerseLyric {
628 pub verse: u8,
630 pub lyric: Lyric,
631}
632
633impl VerseLyric {
634 pub const MAX_VERSE: u8 = 32;
636}
637
638#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
641pub struct StyledText {
642 pub style: TextStyle,
643 pub text: String,
644 #[serde(default)]
646 pub placement: Option<String>,
647 #[serde(default)]
649 pub offset_x: Option<f64>,
650 #[serde(default)]
652 pub offset_y: Option<f64>,
653 #[serde(default)]
655 pub relative_x: Option<f64>,
656 #[serde(default)]
658 pub relative_y: Option<f64>,
659}
660
661#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
662pub enum TextStyle {
663 Expression,
664 Technique,
665 Lyrics,
666 ChordSymbol,
667 FiguredBass,
668 RehearsalMark,
669 Generic,
670}
671
672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
675pub struct CrossStaff {
676 pub target_staff: usize,
677 #[serde(default)]
678 pub target_voice: Option<usize>,
679}
680
681#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
683pub struct TabPosition {
684 pub string: u8,
686 pub fret: u8,
687}
688
689#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
691pub struct TablatureConfig {
692 pub lines: u8,
693 pub tuning_midi: Vec<i16>,
694 #[serde(default)]
695 pub capo: u8,
696}
697
698#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
700pub struct ChordSymbol {
701 pub root: String,
703 pub kind: String,
705 pub bass: Option<String>,
707 #[serde(default)]
709 pub placement: Option<String>,
710 #[serde(default)]
712 pub extender: bool,
713 #[serde(default)]
715 pub harmonic_degree: Option<String>,
716 #[serde(default)]
718 pub harmony_function: Option<String>,
719 #[serde(default)]
721 pub harmony_type: Option<String>,
722 #[serde(default)]
724 pub chord_ref: Option<String>,
725 #[serde(default)]
731 pub range_end: Option<NoteAddr>,
732 #[serde(default)]
734 pub degrees: Vec<ChordDegree>,
735}
736
737#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
743pub struct ChordDefinition {
744 #[serde(default)]
745 pub id: Option<String>,
746 #[serde(default)]
747 pub label: Option<String>,
748 #[serde(default)]
749 pub kind: Option<String>,
750 #[serde(default)]
751 pub fret_position: Option<u32>,
752 #[serde(default)]
753 pub tab_strings: Option<String>,
754 #[serde(default)]
755 pub tab_courses: Option<String>,
756 #[serde(default)]
757 pub members: Vec<ChordDefinitionMember>,
758 #[serde(default)]
759 pub barres: Vec<ChordBarre>,
760}
761
762#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
764pub struct ChordDefinitionMember {
765 #[serde(default)]
766 pub id: Option<String>,
767 #[serde(default)]
768 pub pitch: Option<Pitch>,
769 #[serde(default)]
770 pub tab_string: Option<u8>,
771 #[serde(default)]
772 pub tab_course: Option<u8>,
773 #[serde(default)]
774 pub tab_fret: Option<u16>,
775 #[serde(default)]
776 pub fingering: Option<u8>,
777}
778
779#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
781pub struct ChordBarre {
782 #[serde(default)]
783 pub start_member: Option<String>,
784 #[serde(default)]
785 pub end_member: Option<String>,
786 #[serde(default)]
787 pub fret: Option<u16>,
788 #[serde(default)]
789 pub label: Option<String>,
790 #[serde(default)]
791 pub kind: Option<String>,
792}
793
794#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
796pub struct ChordDegree {
797 pub value: u8,
799 pub alter: i8,
801 pub kind: String,
803}
804
805#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
807pub struct FiguredBassFigure {
808 pub number: String,
810 #[serde(default)]
812 pub alter: Option<String>,
813 #[serde(default)]
815 pub prefix: Option<String>,
816 #[serde(default)]
818 pub suffix: Option<String>,
819 #[serde(default)]
821 pub extender: bool,
822}
823
824impl ChordDegree {
825 pub fn display_text(&self) -> String {
827 let accidental = match self.alter {
828 -2 => "bb",
829 -1 => "b",
830 1 => "#",
831 2 => "##",
832 _ => "",
833 };
834 match self.kind.as_str() {
835 "subtract" => format!("no{}{}", accidental, self.value),
836 "alter" => format!("{}{}", accidental, self.value),
837 _ => format!("add{}{}", accidental, self.value),
838 }
839 }
840}
841
842pub const CHORD_KIND_SUFFIXES: &[(&str, &str)] = &[
846 ("major", ""),
847 ("minor", "m"),
848 ("augmented", "aug"),
849 ("diminished", "dim"),
850 ("dominant", "7"),
851 ("major-seventh", "maj7"),
852 ("minor-seventh", "m7"),
853 ("diminished-seventh", "dim7"),
854 ("augmented-seventh", "aug7"),
855 ("half-diminished", "m7b5"),
856 ("major-minor", "mMaj7"),
857 ("minor-major-seventh", "mMaj7"),
858 ("minor-major", "mMaj7"),
859 ("major-sixth", "6"),
860 ("minor-sixth", "m6"),
861 ("dominant-ninth", "9"),
862 ("major-ninth", "maj9"),
863 ("minor-ninth", "m9"),
864 ("dominant-11th", "11"),
865 ("major-11th", "maj11"),
866 ("minor-11th", "m11"),
867 ("dominant-13th", "13"),
868 ("major-13th", "maj13"),
869 ("minor-13th", "m13"),
870 ("suspended-second", "sus2"),
871 ("suspended-fourth", "sus4"),
872 ("power", "5"),
873 ("major-add9", "add9"),
874 ("minor-add9", "madd9"),
875 ("dominant-flat-five", "7b5"),
876 ("dominant-sharp-five", "7#5"),
877];
878
879impl ChordSymbol {
880 pub fn kind_for_suffix(suffix: &str) -> Option<&'static str> {
882 CHORD_KIND_SUFFIXES
883 .iter()
884 .find(|(_, candidate)| *candidate == suffix)
885 .map(|(kind, _)| *kind)
886 }
887
888 pub fn display_text(&self) -> String {
889 let kind_str = CHORD_KIND_SUFFIXES
890 .iter()
891 .find(|(kind, _)| *kind == self.kind)
892 .map_or(self.kind.as_str(), |(_, suffix)| *suffix);
893 let bass_str = match &self.bass {
894 Some(b) => format!("/{}", b),
895 None => String::new(),
896 };
897 let degree_str = self
898 .degrees
899 .iter()
900 .map(ChordDegree::display_text)
901 .collect::<String>();
902 format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
903 }
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909
910 #[test]
911 fn key_alter_c_major_all_natural() {
912 let key = KeySignature {
913 fifths: 0,
914 mode: "major".into(),
915 };
916 for step in [
917 Step::C,
918 Step::D,
919 Step::E,
920 Step::F,
921 Step::G,
922 Step::A,
923 Step::B,
924 ] {
925 assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
926 }
927 }
928
929 #[test]
930 fn key_alter_g_major_fsharp() {
931 let key = KeySignature {
932 fifths: 1,
933 mode: "major".into(),
934 };
935 assert_eq!(key.alter_for_step(&Step::F), 1);
936 assert_eq!(key.alter_for_step(&Step::G), 0);
937 }
938
939 #[test]
940 fn key_alter_f_major_bflat() {
941 let key = KeySignature {
942 fifths: -1,
943 mode: "major".into(),
944 };
945 assert_eq!(key.alter_for_step(&Step::B), -1);
946 assert_eq!(key.alter_for_step(&Step::C), 0);
947 }
948
949 #[test]
950 fn key_alter_bb_major() {
951 let key = KeySignature {
952 fifths: -2,
953 mode: "major".into(),
954 };
955 assert_eq!(key.alter_for_step(&Step::B), -1);
956 assert_eq!(key.alter_for_step(&Step::E), -1);
957 assert_eq!(key.alter_for_step(&Step::A), 0);
958 }
959
960 #[test]
961 fn key_contains_pitch_g_major() {
962 let key = KeySignature {
963 fifths: 1,
964 mode: "major".into(),
965 };
966 assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
968 assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
969 assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
972 }
973
974 #[test]
975 fn key_display_name_c_major() {
976 let key = KeySignature {
977 fifths: 0,
978 mode: "major".into(),
979 };
980 assert_eq!(key.display_name(), "C major");
981 }
982
983 #[test]
984 fn key_display_name_bb_major() {
985 let key = KeySignature {
986 fifths: -2,
987 mode: "major".into(),
988 };
989 assert_eq!(key.display_name(), "Bb major");
990 }
991
992 #[test]
993 fn key_display_name_fsharp_minor() {
994 let key = KeySignature {
995 fifths: 3,
996 mode: "minor".into(),
997 };
998 assert_eq!(key.display_name(), "F# minor");
999 }
1000
1001 #[test]
1002 fn key_tonic_d_major() {
1003 let key = KeySignature {
1004 fifths: 2,
1005 mode: "major".into(),
1006 };
1007 let (step, alter) = key.tonic();
1008 assert_eq!(step, Step::D);
1009 assert_eq!(alter, 0);
1010 }
1011
1012 #[test]
1013 fn key_tonic_a_minor() {
1014 let key = KeySignature {
1016 fifths: 0,
1017 mode: "minor".into(),
1018 };
1019 let (step, alter) = key.tonic();
1020 assert_eq!(step, Step::A);
1021 assert_eq!(alter, 0);
1022 }
1023
1024 #[test]
1025 fn chord_display_major() {
1026 let c = ChordSymbol {
1027 root: "C".into(),
1028 kind: "major".into(),
1029 bass: None,
1030 placement: None,
1031 extender: false,
1032 harmonic_degree: None,
1033 harmony_function: None,
1034 harmony_type: None,
1035 chord_ref: None,
1036 range_end: None,
1037 degrees: Vec::new(),
1038 };
1039 assert_eq!(c.display_text(), "C");
1040 }
1041
1042 #[test]
1043 fn chord_display_minor_seventh_slash() {
1044 let c = ChordSymbol {
1045 root: "D".into(),
1046 kind: "minor-seventh".into(),
1047 bass: Some("F".into()),
1048 placement: None,
1049 extender: false,
1050 harmonic_degree: None,
1051 harmony_function: None,
1052 harmony_type: None,
1053 chord_ref: None,
1054 range_end: None,
1055 degrees: Vec::new(),
1056 };
1057 assert_eq!(c.display_text(), "Dm7/F");
1058 }
1059
1060 #[test]
1061 fn chord_display_structured_degrees() {
1062 let c = ChordSymbol {
1063 root: "C".into(),
1064 kind: "dominant".into(),
1065 bass: None,
1066 placement: None,
1067 extender: false,
1068 harmonic_degree: None,
1069 harmony_function: None,
1070 harmony_type: None,
1071 chord_ref: None,
1072 range_end: None,
1073 degrees: vec![
1074 ChordDegree {
1075 value: 9,
1076 alter: 1,
1077 kind: "add".into(),
1078 },
1079 ChordDegree {
1080 value: 5,
1081 alter: -1,
1082 kind: "alter".into(),
1083 },
1084 ChordDegree {
1085 value: 3,
1086 alter: 0,
1087 kind: "subtract".into(),
1088 },
1089 ],
1090 };
1091 assert_eq!(c.display_text(), "C7add#9b5no3");
1092 }
1093
1094 #[test]
1095 fn chord_symbol_legacy_json_defaults_degrees() {
1096 let chord: ChordSymbol =
1097 serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
1098 .expect("legacy chord symbol JSON deserializes");
1099 assert!(chord.degrees.is_empty());
1100 assert!(!chord.extender);
1101 assert!(chord.harmonic_degree.is_none());
1102 assert!(chord.harmony_function.is_none());
1103 assert!(chord.harmony_type.is_none());
1104 }
1105
1106 #[test]
1107 fn time_sig_total_beats_three_four() {
1108 let ts = TimeSignature {
1109 numerator: 3,
1110 denominator: 4,
1111 };
1112 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1113 }
1114
1115 #[test]
1116 fn time_sig_total_beats_six_eight() {
1117 let ts = TimeSignature {
1118 numerator: 6,
1119 denominator: 8,
1120 };
1121 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
1122 }
1123
1124 #[test]
1125 fn clef_treble_middle_b4() {
1126 assert_eq!(Clef::Treble.middle_line_midi(), 71);
1127 }
1128
1129 #[test]
1130 fn clef_bass_middle_d3() {
1131 assert_eq!(Clef::Bass.middle_line_midi(), 50);
1132 }
1133
1134 #[test]
1135 fn clef_alto_middle_c4() {
1136 assert_eq!(Clef::Alto.middle_line_midi(), 60);
1137 }
1138
1139 #[test]
1140 fn clef_tenor_middle_a3() {
1141 assert_eq!(Clef::Tenor.middle_line_midi(), 57);
1142 }
1143}