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}
14
15impl Clef {
16 pub fn to_musicxml_sign(&self) -> &'static str {
17 match self {
18 Clef::Treble => "G",
19 Clef::Bass => "F",
20 Clef::Alto => "C",
21 Clef::Tenor => "C",
22 Clef::Percussion => "percussion",
23 }
24 }
25
26 pub fn musicxml_line(&self) -> u8 {
27 match self {
28 Clef::Treble => 2,
29 Clef::Bass => 4,
30 Clef::Alto => 3,
31 Clef::Tenor => 4,
32 Clef::Percussion => 2,
33 }
34 }
35
36 pub fn middle_line_midi(&self) -> u8 {
38 match self {
39 Clef::Treble => 71, Clef::Bass => 50, Clef::Alto => 60, Clef::Tenor => 57, Clef::Percussion => 71, }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49pub struct KeySignature {
50 pub fifths: i8,
52 pub mode: String,
54}
55
56impl Default for KeySignature {
57 fn default() -> Self {
58 Self {
59 fifths: 0,
60 mode: "major".to_string(),
61 }
62 }
63}
64
65impl KeySignature {
66 const SHARP_ORDER: [Step; 7] = [
68 Step::F,
69 Step::C,
70 Step::G,
71 Step::D,
72 Step::A,
73 Step::E,
74 Step::B,
75 ];
76 const FLAT_ORDER: [Step; 7] = [
78 Step::B,
79 Step::E,
80 Step::A,
81 Step::D,
82 Step::G,
83 Step::C,
84 Step::F,
85 ];
86
87 pub fn alter_for_step(&self, step: &Step) -> i8 {
89 if self.fifths > 0 {
90 let count = self.fifths.min(7) as usize;
91 if Self::SHARP_ORDER[..count].contains(step) {
92 1
93 } else {
94 0
95 }
96 } else if self.fifths < 0 {
97 let count = (-self.fifths).min(7) as usize;
98 if Self::FLAT_ORDER[..count].contains(step) {
99 -1
100 } else {
101 0
102 }
103 } else {
104 0
105 }
106 }
107
108 pub fn contains_pitch(&self, pitch: &Pitch) -> bool {
110 pitch.alter == self.alter_for_step(&pitch.step)
111 }
112
113 pub fn tonic(&self) -> (Step, i8) {
117 if self.mode == "minor" {
118 let (maj_step, maj_alter) = Self::major_tonic_from_fifths(self.fifths);
119 let major_midi = Pitch::with_alter(maj_step, 4, maj_alter).to_midi();
121 let minor_midi = (major_midi - 3).clamp(0, 127) as u8;
122 let p = Pitch::from_midi(minor_midi, self.fifths < 0);
123 (p.step, p.alter)
124 } else {
125 Self::major_tonic_from_fifths(self.fifths)
126 }
127 }
128
129 pub fn display_name(&self) -> String {
131 let (step, alter) = self.tonic();
132 let acc = match alter {
133 1 => "#",
134 -1 => "b",
135 _ => "",
136 };
137 format!("{}{} {}", step.to_char(), acc, self.mode)
138 }
139
140 fn major_tonic_from_fifths(fifths: i8) -> (Step, i8) {
141 const TONICS: [(Step, i8); 15] = [
144 (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), ];
160 let idx = (fifths.clamp(-7, 7) + 7) as usize;
161 TONICS[idx].clone()
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166pub struct TimeSignature {
167 pub numerator: u8,
168 pub denominator: u8,
169}
170
171impl Default for TimeSignature {
172 fn default() -> Self {
173 Self {
174 numerator: 4,
175 denominator: 4,
176 }
177 }
178}
179
180impl TimeSignature {
181 pub fn beats_per_measure(&self) -> f64 {
182 self.numerator as f64
183 }
184
185 pub fn beat_unit_beats(&self) -> f64 {
186 4.0 / self.denominator as f64
187 }
188
189 pub fn total_beats(&self) -> f64 {
190 self.beats_per_measure() * self.beat_unit_beats()
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
195pub enum Dynamic {
196 Pppp,
197 Ppp,
198 Pp,
199 P,
200 Mp,
201 Mf,
202 F,
203 Ff,
204 Fff,
205 Ffff,
206 Sfz,
207 Rfz,
208 Fz,
209 Sf,
210}
211
212impl Dynamic {
213 pub fn to_musicxml_str(&self) -> &'static str {
214 match self {
215 Dynamic::Pppp => "pppp",
216 Dynamic::Ppp => "ppp",
217 Dynamic::Pp => "pp",
218 Dynamic::P => "p",
219 Dynamic::Mp => "mp",
220 Dynamic::Mf => "mf",
221 Dynamic::F => "f",
222 Dynamic::Ff => "ff",
223 Dynamic::Fff => "fff",
224 Dynamic::Ffff => "ffff",
225 Dynamic::Sfz => "sfz",
226 Dynamic::Rfz => "rfz",
227 Dynamic::Fz => "fz",
228 Dynamic::Sf => "sf",
229 }
230 }
231
232 pub fn to_velocity(&self) -> u8 {
233 match self {
234 Dynamic::Pppp => 16,
235 Dynamic::Ppp => 24,
236 Dynamic::Pp => 36,
237 Dynamic::P => 48,
238 Dynamic::Mp => 60,
239 Dynamic::Mf => 72,
240 Dynamic::F => 84,
241 Dynamic::Ff => 96,
242 Dynamic::Fff => 108,
243 Dynamic::Ffff => 120,
244 Dynamic::Sfz => 112,
245 Dynamic::Rfz => 104,
246 Dynamic::Fz => 100,
247 Dynamic::Sf => 96,
248 }
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub enum Articulation {
254 Staccato,
255 Staccatissimo,
256 Accent,
257 Tenuto,
258 Marcato,
259 Fermata,
260 Trill,
261 Mordent,
262 InvertedMordent,
263 Turn,
264 InvertedTurn,
265 Shake,
266 Tremolo(u8),
267 BreathMark,
268 Caesura,
269 UpBow,
271 DownBow,
273 Harmonic,
275 OpenString,
277 Stopped,
279 SnapPizzicato,
281}
282
283impl Articulation {
284 pub fn is_technical_mark(&self) -> bool {
287 matches!(
288 self,
289 Self::UpBow
290 | Self::DownBow
291 | Self::Harmonic
292 | Self::OpenString
293 | Self::Stopped
294 | Self::SnapPizzicato
295 )
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
301#[serde(rename_all = "kebab-case")]
302pub enum GuitarTechnique {
303 Bend,
304 Slide,
305 HammerOn,
306 PullOff,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
311pub enum FingeringSelectionPolicy {
312 #[default]
314 SourceOrder,
315 LowestNumber,
317 HighestNumber,
319}
320
321#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
322pub enum Barline {
323 #[default]
324 Normal,
325 Double,
326 Final,
327 RepeatStart,
328 RepeatEnd,
329 RepeatBoth,
330 Dashed,
331 Dotted,
332 Invisible,
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
336pub enum HairpinKind {
337 Crescendo,
338 Decrescendo,
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
342pub struct TupletInfo {
343 pub actual_notes: u8,
345 pub normal_notes: u8,
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
350pub enum BeamState {
351 #[default]
352 None,
353 Begin,
354 Continue,
355 End,
356 BeginEnd,
357 BackwardHook,
358 ForwardHook,
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
363pub enum OttavaKind {
364 Va8,
366 Vb8,
368 Ma15,
370 Mb15,
372}
373
374impl OttavaKind {
375 pub fn musicxml_type(&self) -> &'static str {
376 match self {
377 OttavaKind::Va8 | OttavaKind::Ma15 => "up",
378 OttavaKind::Vb8 | OttavaKind::Mb15 => "down",
379 }
380 }
381
382 pub fn musicxml_size(&self) -> u8 {
383 match self {
384 OttavaKind::Va8 | OttavaKind::Vb8 => 8,
385 OttavaKind::Ma15 | OttavaKind::Mb15 => 15,
386 }
387 }
388}
389
390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
392pub enum NoteHead {
393 #[default]
394 Normal,
395 Diamond, X, Slash, Cross, Triangle, }
401
402#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
404pub struct Lyric {
405 pub text: String,
407 pub syllabic: String,
409}
410
411#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
413pub struct VerseLyric {
414 pub verse: u8,
416 pub lyric: Lyric,
417}
418
419impl VerseLyric {
420 pub const MAX_VERSE: u8 = 32;
422}
423
424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
427pub struct StyledText {
428 pub style: TextStyle,
429 pub text: String,
430 #[serde(default)]
432 pub placement: Option<String>,
433 #[serde(default)]
435 pub offset_x: Option<f64>,
436 #[serde(default)]
438 pub offset_y: Option<f64>,
439 #[serde(default)]
441 pub relative_x: Option<f64>,
442 #[serde(default)]
444 pub relative_y: Option<f64>,
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
448pub enum TextStyle {
449 Expression,
450 Technique,
451 Lyrics,
452 ChordSymbol,
453 FiguredBass,
454 RehearsalMark,
455 Generic,
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
461pub struct CrossStaff {
462 pub target_staff: usize,
463 #[serde(default)]
464 pub target_voice: Option<usize>,
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469pub struct TabPosition {
470 pub string: u8,
472 pub fret: u8,
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477pub struct TablatureConfig {
478 pub lines: u8,
479 pub tuning_midi: Vec<i16>,
480 #[serde(default)]
481 pub capo: u8,
482}
483
484#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
486pub struct ChordSymbol {
487 pub root: String,
489 pub kind: String,
491 pub bass: Option<String>,
493 #[serde(default)]
495 pub placement: Option<String>,
496 #[serde(default)]
498 pub extender: bool,
499 #[serde(default)]
501 pub harmonic_degree: Option<String>,
502 #[serde(default)]
504 pub harmony_function: Option<String>,
505 #[serde(default)]
507 pub harmony_type: Option<String>,
508 #[serde(default)]
510 pub chord_ref: Option<String>,
511 #[serde(default)]
517 pub range_end: Option<NoteAddr>,
518 #[serde(default)]
520 pub degrees: Vec<ChordDegree>,
521}
522
523#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
529pub struct ChordDefinition {
530 #[serde(default)]
531 pub id: Option<String>,
532 #[serde(default)]
533 pub label: Option<String>,
534 #[serde(default)]
535 pub kind: Option<String>,
536 #[serde(default)]
537 pub fret_position: Option<u32>,
538 #[serde(default)]
539 pub tab_strings: Option<String>,
540 #[serde(default)]
541 pub tab_courses: Option<String>,
542 #[serde(default)]
543 pub members: Vec<ChordDefinitionMember>,
544 #[serde(default)]
545 pub barres: Vec<ChordBarre>,
546}
547
548#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
550pub struct ChordDefinitionMember {
551 #[serde(default)]
552 pub id: Option<String>,
553 #[serde(default)]
554 pub pitch: Option<Pitch>,
555 #[serde(default)]
556 pub tab_string: Option<u8>,
557 #[serde(default)]
558 pub tab_course: Option<u8>,
559 #[serde(default)]
560 pub tab_fret: Option<u16>,
561 #[serde(default)]
562 pub fingering: Option<u8>,
563}
564
565#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
567pub struct ChordBarre {
568 #[serde(default)]
569 pub start_member: Option<String>,
570 #[serde(default)]
571 pub end_member: Option<String>,
572 #[serde(default)]
573 pub fret: Option<u16>,
574 #[serde(default)]
575 pub label: Option<String>,
576 #[serde(default)]
577 pub kind: Option<String>,
578}
579
580#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
582pub struct ChordDegree {
583 pub value: u8,
585 pub alter: i8,
587 pub kind: String,
589}
590
591#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
593pub struct FiguredBassFigure {
594 pub number: String,
596 #[serde(default)]
598 pub alter: Option<String>,
599 #[serde(default)]
601 pub prefix: Option<String>,
602 #[serde(default)]
604 pub suffix: Option<String>,
605 #[serde(default)]
607 pub extender: bool,
608}
609
610impl ChordDegree {
611 pub fn display_text(&self) -> String {
613 let accidental = match self.alter {
614 -2 => "bb",
615 -1 => "b",
616 1 => "#",
617 2 => "##",
618 _ => "",
619 };
620 match self.kind.as_str() {
621 "subtract" => format!("no{}{}", accidental, self.value),
622 "alter" => format!("{}{}", accidental, self.value),
623 _ => format!("add{}{}", accidental, self.value),
624 }
625 }
626}
627
628impl ChordSymbol {
629 pub fn display_text(&self) -> String {
630 let kind_str = match self.kind.as_str() {
631 "major" | "" => "",
632 "minor" => "m",
633 "dominant" => "7",
634 "major-seventh" => "maj7",
635 "minor-seventh" => "m7",
636 "diminished" => "dim",
637 "diminished-seventh" => "dim7",
638 "augmented" => "aug",
639 "suspended-second" => "sus2",
640 "suspended-fourth" => "sus4",
641 "half-diminished" => "m7b5",
642 "major-sixth" => "6",
643 "minor-sixth" => "m6",
644 "power" => "5",
645 "major-add9" => "add9",
646 "minor-add9" => "madd9",
647 "minor-major-seventh" => "mMaj7",
648 "dominant-flat-five" => "7b5",
649 "dominant-sharp-five" => "7#5",
650 "dominant-ninth" => "9",
651 "major-ninth" => "maj9",
652 "minor-ninth" => "m9",
653 other => other,
654 };
655 let bass_str = match &self.bass {
656 Some(b) => format!("/{}", b),
657 None => String::new(),
658 };
659 let degree_str = self
660 .degrees
661 .iter()
662 .map(ChordDegree::display_text)
663 .collect::<String>();
664 format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn key_alter_c_major_all_natural() {
674 let key = KeySignature {
675 fifths: 0,
676 mode: "major".into(),
677 };
678 for step in [
679 Step::C,
680 Step::D,
681 Step::E,
682 Step::F,
683 Step::G,
684 Step::A,
685 Step::B,
686 ] {
687 assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
688 }
689 }
690
691 #[test]
692 fn key_alter_g_major_fsharp() {
693 let key = KeySignature {
694 fifths: 1,
695 mode: "major".into(),
696 };
697 assert_eq!(key.alter_for_step(&Step::F), 1);
698 assert_eq!(key.alter_for_step(&Step::G), 0);
699 }
700
701 #[test]
702 fn key_alter_f_major_bflat() {
703 let key = KeySignature {
704 fifths: -1,
705 mode: "major".into(),
706 };
707 assert_eq!(key.alter_for_step(&Step::B), -1);
708 assert_eq!(key.alter_for_step(&Step::C), 0);
709 }
710
711 #[test]
712 fn key_alter_bb_major() {
713 let key = KeySignature {
714 fifths: -2,
715 mode: "major".into(),
716 };
717 assert_eq!(key.alter_for_step(&Step::B), -1);
718 assert_eq!(key.alter_for_step(&Step::E), -1);
719 assert_eq!(key.alter_for_step(&Step::A), 0);
720 }
721
722 #[test]
723 fn key_contains_pitch_g_major() {
724 let key = KeySignature {
725 fifths: 1,
726 mode: "major".into(),
727 };
728 assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
730 assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
731 assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
734 }
735
736 #[test]
737 fn key_display_name_c_major() {
738 let key = KeySignature {
739 fifths: 0,
740 mode: "major".into(),
741 };
742 assert_eq!(key.display_name(), "C major");
743 }
744
745 #[test]
746 fn key_display_name_bb_major() {
747 let key = KeySignature {
748 fifths: -2,
749 mode: "major".into(),
750 };
751 assert_eq!(key.display_name(), "Bb major");
752 }
753
754 #[test]
755 fn key_display_name_fsharp_minor() {
756 let key = KeySignature {
757 fifths: 3,
758 mode: "minor".into(),
759 };
760 assert_eq!(key.display_name(), "F# minor");
761 }
762
763 #[test]
764 fn key_tonic_d_major() {
765 let key = KeySignature {
766 fifths: 2,
767 mode: "major".into(),
768 };
769 let (step, alter) = key.tonic();
770 assert_eq!(step, Step::D);
771 assert_eq!(alter, 0);
772 }
773
774 #[test]
775 fn key_tonic_a_minor() {
776 let key = KeySignature {
778 fifths: 0,
779 mode: "minor".into(),
780 };
781 let (step, alter) = key.tonic();
782 assert_eq!(step, Step::A);
783 assert_eq!(alter, 0);
784 }
785
786 #[test]
787 fn chord_display_major() {
788 let c = ChordSymbol {
789 root: "C".into(),
790 kind: "major".into(),
791 bass: None,
792 placement: None,
793 extender: false,
794 harmonic_degree: None,
795 harmony_function: None,
796 harmony_type: None,
797 chord_ref: None,
798 range_end: None,
799 degrees: Vec::new(),
800 };
801 assert_eq!(c.display_text(), "C");
802 }
803
804 #[test]
805 fn chord_display_minor_seventh_slash() {
806 let c = ChordSymbol {
807 root: "D".into(),
808 kind: "minor-seventh".into(),
809 bass: Some("F".into()),
810 placement: None,
811 extender: false,
812 harmonic_degree: None,
813 harmony_function: None,
814 harmony_type: None,
815 chord_ref: None,
816 range_end: None,
817 degrees: Vec::new(),
818 };
819 assert_eq!(c.display_text(), "Dm7/F");
820 }
821
822 #[test]
823 fn chord_display_structured_degrees() {
824 let c = ChordSymbol {
825 root: "C".into(),
826 kind: "dominant".into(),
827 bass: None,
828 placement: None,
829 extender: false,
830 harmonic_degree: None,
831 harmony_function: None,
832 harmony_type: None,
833 chord_ref: None,
834 range_end: None,
835 degrees: vec![
836 ChordDegree {
837 value: 9,
838 alter: 1,
839 kind: "add".into(),
840 },
841 ChordDegree {
842 value: 5,
843 alter: -1,
844 kind: "alter".into(),
845 },
846 ChordDegree {
847 value: 3,
848 alter: 0,
849 kind: "subtract".into(),
850 },
851 ],
852 };
853 assert_eq!(c.display_text(), "C7add#9b5no3");
854 }
855
856 #[test]
857 fn chord_symbol_legacy_json_defaults_degrees() {
858 let chord: ChordSymbol =
859 serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
860 .expect("legacy chord symbol JSON deserializes");
861 assert!(chord.degrees.is_empty());
862 assert!(!chord.extender);
863 assert!(chord.harmonic_degree.is_none());
864 assert!(chord.harmony_function.is_none());
865 assert!(chord.harmony_type.is_none());
866 }
867
868 #[test]
869 fn time_sig_total_beats_three_four() {
870 let ts = TimeSignature {
871 numerator: 3,
872 denominator: 4,
873 };
874 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
875 }
876
877 #[test]
878 fn time_sig_total_beats_six_eight() {
879 let ts = TimeSignature {
880 numerator: 6,
881 denominator: 8,
882 };
883 assert!((ts.total_beats() - 3.0).abs() < 1e-9);
884 }
885
886 #[test]
887 fn clef_treble_middle_b4() {
888 assert_eq!(Clef::Treble.middle_line_midi(), 71);
889 }
890
891 #[test]
892 fn clef_bass_middle_d3() {
893 assert_eq!(Clef::Bass.middle_line_midi(), 50);
894 }
895
896 #[test]
897 fn clef_alto_middle_c4() {
898 assert_eq!(Clef::Alto.middle_line_midi(), 60);
899 }
900
901 #[test]
902 fn clef_tenor_middle_a3() {
903 assert_eq!(Clef::Tenor.middle_line_midi(), 57);
904 }
905}