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