use super::score::NoteAddr;
use serde::{Deserialize, Serialize};
use super::pitch::{Pitch, Step};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Clef {
Treble,
Bass,
Alto,
Tenor,
Percussion,
}
impl Clef {
pub fn to_musicxml_sign(&self) -> &'static str {
match self {
Clef::Treble => "G",
Clef::Bass => "F",
Clef::Alto => "C",
Clef::Tenor => "C",
Clef::Percussion => "percussion",
}
}
pub fn musicxml_line(&self) -> u8 {
match self {
Clef::Treble => 2,
Clef::Bass => 4,
Clef::Alto => 3,
Clef::Tenor => 4,
Clef::Percussion => 2,
}
}
pub fn middle_line_midi(&self) -> u8 {
match self {
Clef::Treble => 71, Clef::Bass => 50, Clef::Alto => 60, Clef::Tenor => 57, Clef::Percussion => 71, }
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KeySignature {
pub fifths: i8,
pub mode: String,
}
impl Default for KeySignature {
fn default() -> Self {
Self {
fifths: 0,
mode: "major".to_string(),
}
}
}
impl KeySignature {
const SHARP_ORDER: [Step; 7] = [
Step::F,
Step::C,
Step::G,
Step::D,
Step::A,
Step::E,
Step::B,
];
const FLAT_ORDER: [Step; 7] = [
Step::B,
Step::E,
Step::A,
Step::D,
Step::G,
Step::C,
Step::F,
];
pub fn alter_for_step(&self, step: &Step) -> i8 {
if self.fifths > 0 {
let count = self.fifths.min(7) as usize;
if Self::SHARP_ORDER[..count].contains(step) {
1
} else {
0
}
} else if self.fifths < 0 {
let count = (-self.fifths).min(7) as usize;
if Self::FLAT_ORDER[..count].contains(step) {
-1
} else {
0
}
} else {
0
}
}
pub fn contains_pitch(&self, pitch: &Pitch) -> bool {
pitch.alter == self.alter_for_step(&pitch.step)
}
pub fn tonic(&self) -> (Step, i8) {
if self.mode == "minor" {
let (maj_step, maj_alter) = Self::major_tonic_from_fifths(self.fifths);
let major_midi = Pitch::with_alter(maj_step, 4, maj_alter).to_midi();
let minor_midi = (major_midi - 3).clamp(0, 127) as u8;
let p = Pitch::from_midi(minor_midi, self.fifths < 0);
(p.step, p.alter)
} else {
Self::major_tonic_from_fifths(self.fifths)
}
}
pub fn display_name(&self) -> String {
let (step, alter) = self.tonic();
let acc = match alter {
1 => "#",
-1 => "b",
_ => "",
};
format!("{}{} {}", step.to_char(), acc, self.mode)
}
fn major_tonic_from_fifths(fifths: i8) -> (Step, i8) {
const TONICS: [(Step, i8); 15] = [
(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), ];
let idx = (fifths.clamp(-7, 7) + 7) as usize;
TONICS[idx].clone()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TimeSignature {
pub numerator: u8,
pub denominator: u8,
}
impl Default for TimeSignature {
fn default() -> Self {
Self {
numerator: 4,
denominator: 4,
}
}
}
impl TimeSignature {
pub fn beats_per_measure(&self) -> f64 {
self.numerator as f64
}
pub fn beat_unit_beats(&self) -> f64 {
4.0 / self.denominator as f64
}
pub fn total_beats(&self) -> f64 {
self.beats_per_measure() * self.beat_unit_beats()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Dynamic {
Pppp,
Ppp,
Pp,
P,
Mp,
Mf,
F,
Ff,
Fff,
Ffff,
Sfz,
Rfz,
Fz,
Sf,
Fp,
Sfp,
Sfpp,
Pf,
Sffz,
Sfzp,
N,
}
impl Dynamic {
pub const ALL: [Dynamic; 21] = [
Dynamic::Pppp,
Dynamic::Ppp,
Dynamic::Pp,
Dynamic::P,
Dynamic::Mp,
Dynamic::Mf,
Dynamic::F,
Dynamic::Ff,
Dynamic::Fff,
Dynamic::Ffff,
Dynamic::Sfz,
Dynamic::Rfz,
Dynamic::Fz,
Dynamic::Sf,
Dynamic::Fp,
Dynamic::Sfp,
Dynamic::Sfpp,
Dynamic::Pf,
Dynamic::Sffz,
Dynamic::Sfzp,
Dynamic::N,
];
pub fn from_musicxml_str(name: &str) -> Option<Dynamic> {
Some(match name {
"pppppp" | "ppppp" => Dynamic::Pppp,
"ffffff" | "fffff" => Dynamic::Ffff,
"rf" => Dynamic::Rfz,
other => *Self::ALL
.iter()
.find(|dynamic| dynamic.to_musicxml_str() == other)?,
})
}
pub fn sustained_level(&self) -> Option<Dynamic> {
match self {
Dynamic::Sfz | Dynamic::Rfz | Dynamic::Fz | Dynamic::Sf | Dynamic::Sffz => None,
Dynamic::Fp | Dynamic::Sfp | Dynamic::Sfzp => Some(Dynamic::P),
Dynamic::Sfpp => Some(Dynamic::Pp),
Dynamic::Pf => Some(Dynamic::F),
level => Some(*level),
}
}
pub fn to_musicxml_str(&self) -> &'static str {
match self {
Dynamic::Pppp => "pppp",
Dynamic::Ppp => "ppp",
Dynamic::Pp => "pp",
Dynamic::P => "p",
Dynamic::Mp => "mp",
Dynamic::Mf => "mf",
Dynamic::F => "f",
Dynamic::Ff => "ff",
Dynamic::Fff => "fff",
Dynamic::Ffff => "ffff",
Dynamic::Sfz => "sfz",
Dynamic::Rfz => "rfz",
Dynamic::Fz => "fz",
Dynamic::Sf => "sf",
Dynamic::Fp => "fp",
Dynamic::Sfp => "sfp",
Dynamic::Sfpp => "sfpp",
Dynamic::Pf => "pf",
Dynamic::Sffz => "sffz",
Dynamic::Sfzp => "sfzp",
Dynamic::N => "n",
}
}
pub fn to_velocity(&self) -> u8 {
match self {
Dynamic::Pppp => 16,
Dynamic::Ppp => 24,
Dynamic::Pp => 36,
Dynamic::P => 48,
Dynamic::Mp => 60,
Dynamic::Mf => 72,
Dynamic::F => 84,
Dynamic::Ff => 96,
Dynamic::Fff => 108,
Dynamic::Ffff => 120,
Dynamic::Sfz => 112,
Dynamic::Rfz => 104,
Dynamic::Fz => 100,
Dynamic::Sf => 96,
Dynamic::Fp => 84,
Dynamic::Sfp | Dynamic::Sfpp => 96,
Dynamic::Pf => 48,
Dynamic::Sffz => 120,
Dynamic::Sfzp => 112,
Dynamic::N => 8,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Articulation {
Staccato,
Staccatissimo,
Accent,
Tenuto,
Marcato,
Fermata,
Trill,
Mordent,
InvertedMordent,
Turn,
InvertedTurn,
Shake,
Tremolo(u8),
BreathMark,
Caesura,
UpBow,
DownBow,
Harmonic,
OpenString,
Stopped,
SnapPizzicato,
}
impl Articulation {
pub fn is_technical_mark(&self) -> bool {
matches!(
self,
Self::UpBow
| Self::DownBow
| Self::Harmonic
| Self::OpenString
| Self::Stopped
| Self::SnapPizzicato
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum GuitarTechnique {
Bend,
Slide,
HammerOn,
PullOff,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum FingeringSelectionPolicy {
#[default]
SourceOrder,
LowestNumber,
HighestNumber,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub enum Barline {
#[default]
Normal,
Double,
Final,
RepeatStart,
RepeatEnd,
RepeatBoth,
Dashed,
Dotted,
Invisible,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum HairpinKind {
Crescendo,
Decrescendo,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TupletInfo {
pub actual_notes: u8,
pub normal_notes: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
pub enum BeamState {
#[default]
None,
Begin,
Continue,
End,
BeginEnd,
BackwardHook,
ForwardHook,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum OttavaKind {
Va8,
Vb8,
Ma15,
Mb15,
}
impl OttavaKind {
pub fn musicxml_type(&self) -> &'static str {
match self {
OttavaKind::Va8 | OttavaKind::Ma15 => "down",
OttavaKind::Vb8 | OttavaKind::Mb15 => "up",
}
}
pub fn display_shift_steps(&self) -> i32 {
match self {
OttavaKind::Va8 => -7,
OttavaKind::Ma15 => -14,
OttavaKind::Vb8 => 7,
OttavaKind::Mb15 => 14,
}
}
pub fn musicxml_size(&self) -> u8 {
match self {
OttavaKind::Va8 | OttavaKind::Vb8 => 8,
OttavaKind::Ma15 | OttavaKind::Mb15 => 15,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub enum NoteHead {
#[default]
Normal,
Diamond, X, Slash, Cross, Triangle, }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Lyric {
pub text: String,
pub syllabic: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub extend: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VerseLyric {
pub verse: u8,
pub lyric: Lyric,
}
impl VerseLyric {
pub const MAX_VERSE: u8 = 32;
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StyledText {
pub style: TextStyle,
pub text: String,
#[serde(default)]
pub placement: Option<String>,
#[serde(default)]
pub offset_x: Option<f64>,
#[serde(default)]
pub offset_y: Option<f64>,
#[serde(default)]
pub relative_x: Option<f64>,
#[serde(default)]
pub relative_y: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TextStyle {
Expression,
Technique,
Lyrics,
ChordSymbol,
FiguredBass,
RehearsalMark,
Generic,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CrossStaff {
pub target_staff: usize,
#[serde(default)]
pub target_voice: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TabPosition {
pub string: u8,
pub fret: u8,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TablatureConfig {
pub lines: u8,
pub tuning_midi: Vec<i16>,
#[serde(default)]
pub capo: u8,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChordSymbol {
pub root: String,
pub kind: String,
pub bass: Option<String>,
#[serde(default)]
pub placement: Option<String>,
#[serde(default)]
pub extender: bool,
#[serde(default)]
pub harmonic_degree: Option<String>,
#[serde(default)]
pub harmony_function: Option<String>,
#[serde(default)]
pub harmony_type: Option<String>,
#[serde(default)]
pub chord_ref: Option<String>,
#[serde(default)]
pub range_end: Option<NoteAddr>,
#[serde(default)]
pub degrees: Vec<ChordDegree>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChordDefinition {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub kind: Option<String>,
#[serde(default)]
pub fret_position: Option<u32>,
#[serde(default)]
pub tab_strings: Option<String>,
#[serde(default)]
pub tab_courses: Option<String>,
#[serde(default)]
pub members: Vec<ChordDefinitionMember>,
#[serde(default)]
pub barres: Vec<ChordBarre>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChordDefinitionMember {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub pitch: Option<Pitch>,
#[serde(default)]
pub tab_string: Option<u8>,
#[serde(default)]
pub tab_course: Option<u8>,
#[serde(default)]
pub tab_fret: Option<u16>,
#[serde(default)]
pub fingering: Option<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChordBarre {
#[serde(default)]
pub start_member: Option<String>,
#[serde(default)]
pub end_member: Option<String>,
#[serde(default)]
pub fret: Option<u16>,
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub kind: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChordDegree {
pub value: u8,
pub alter: i8,
pub kind: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FiguredBassFigure {
pub number: String,
#[serde(default)]
pub alter: Option<String>,
#[serde(default)]
pub prefix: Option<String>,
#[serde(default)]
pub suffix: Option<String>,
#[serde(default)]
pub extender: bool,
}
impl ChordDegree {
pub fn display_text(&self) -> String {
let accidental = match self.alter {
-2 => "bb",
-1 => "b",
1 => "#",
2 => "##",
_ => "",
};
match self.kind.as_str() {
"subtract" => format!("no{}{}", accidental, self.value),
"alter" => format!("{}{}", accidental, self.value),
_ => format!("add{}{}", accidental, self.value),
}
}
}
pub const CHORD_KIND_SUFFIXES: &[(&str, &str)] = &[
("major", ""),
("minor", "m"),
("augmented", "aug"),
("diminished", "dim"),
("dominant", "7"),
("major-seventh", "maj7"),
("minor-seventh", "m7"),
("diminished-seventh", "dim7"),
("augmented-seventh", "aug7"),
("half-diminished", "m7b5"),
("major-minor", "mMaj7"),
("minor-major-seventh", "mMaj7"),
("minor-major", "mMaj7"),
("major-sixth", "6"),
("minor-sixth", "m6"),
("dominant-ninth", "9"),
("major-ninth", "maj9"),
("minor-ninth", "m9"),
("dominant-11th", "11"),
("major-11th", "maj11"),
("minor-11th", "m11"),
("dominant-13th", "13"),
("major-13th", "maj13"),
("minor-13th", "m13"),
("suspended-second", "sus2"),
("suspended-fourth", "sus4"),
("power", "5"),
("major-add9", "add9"),
("minor-add9", "madd9"),
("dominant-flat-five", "7b5"),
("dominant-sharp-five", "7#5"),
];
impl ChordSymbol {
pub fn kind_for_suffix(suffix: &str) -> Option<&'static str> {
CHORD_KIND_SUFFIXES
.iter()
.find(|(_, candidate)| *candidate == suffix)
.map(|(kind, _)| *kind)
}
pub fn display_text(&self) -> String {
let kind_str = CHORD_KIND_SUFFIXES
.iter()
.find(|(kind, _)| *kind == self.kind)
.map_or(self.kind.as_str(), |(_, suffix)| *suffix);
let bass_str = match &self.bass {
Some(b) => format!("/{}", b),
None => String::new(),
};
let degree_str = self
.degrees
.iter()
.map(ChordDegree::display_text)
.collect::<String>();
format!("{}{}{}{}", self.root, kind_str, degree_str, bass_str)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_alter_c_major_all_natural() {
let key = KeySignature {
fifths: 0,
mode: "major".into(),
};
for step in [
Step::C,
Step::D,
Step::E,
Step::F,
Step::G,
Step::A,
Step::B,
] {
assert_eq!(key.alter_for_step(&step), 0, "step {:?}", step);
}
}
#[test]
fn key_alter_g_major_fsharp() {
let key = KeySignature {
fifths: 1,
mode: "major".into(),
};
assert_eq!(key.alter_for_step(&Step::F), 1);
assert_eq!(key.alter_for_step(&Step::G), 0);
}
#[test]
fn key_alter_f_major_bflat() {
let key = KeySignature {
fifths: -1,
mode: "major".into(),
};
assert_eq!(key.alter_for_step(&Step::B), -1);
assert_eq!(key.alter_for_step(&Step::C), 0);
}
#[test]
fn key_alter_bb_major() {
let key = KeySignature {
fifths: -2,
mode: "major".into(),
};
assert_eq!(key.alter_for_step(&Step::B), -1);
assert_eq!(key.alter_for_step(&Step::E), -1);
assert_eq!(key.alter_for_step(&Step::A), 0);
}
#[test]
fn key_contains_pitch_g_major() {
let key = KeySignature {
fifths: 1,
mode: "major".into(),
};
assert!(key.contains_pitch(&Pitch::new(Step::G, 4)));
assert!(key.contains_pitch(&Pitch::new(Step::D, 4)));
assert!(key.contains_pitch(&Pitch::with_alter(Step::F, 4, 1))); assert!(!key.contains_pitch(&Pitch::new(Step::F, 4)));
}
#[test]
fn key_display_name_c_major() {
let key = KeySignature {
fifths: 0,
mode: "major".into(),
};
assert_eq!(key.display_name(), "C major");
}
#[test]
fn key_display_name_bb_major() {
let key = KeySignature {
fifths: -2,
mode: "major".into(),
};
assert_eq!(key.display_name(), "Bb major");
}
#[test]
fn key_display_name_fsharp_minor() {
let key = KeySignature {
fifths: 3,
mode: "minor".into(),
};
assert_eq!(key.display_name(), "F# minor");
}
#[test]
fn key_tonic_d_major() {
let key = KeySignature {
fifths: 2,
mode: "major".into(),
};
let (step, alter) = key.tonic();
assert_eq!(step, Step::D);
assert_eq!(alter, 0);
}
#[test]
fn key_tonic_a_minor() {
let key = KeySignature {
fifths: 0,
mode: "minor".into(),
};
let (step, alter) = key.tonic();
assert_eq!(step, Step::A);
assert_eq!(alter, 0);
}
#[test]
fn chord_display_major() {
let c = ChordSymbol {
root: "C".into(),
kind: "major".into(),
bass: None,
placement: None,
extender: false,
harmonic_degree: None,
harmony_function: None,
harmony_type: None,
chord_ref: None,
range_end: None,
degrees: Vec::new(),
};
assert_eq!(c.display_text(), "C");
}
#[test]
fn chord_display_minor_seventh_slash() {
let c = ChordSymbol {
root: "D".into(),
kind: "minor-seventh".into(),
bass: Some("F".into()),
placement: None,
extender: false,
harmonic_degree: None,
harmony_function: None,
harmony_type: None,
chord_ref: None,
range_end: None,
degrees: Vec::new(),
};
assert_eq!(c.display_text(), "Dm7/F");
}
#[test]
fn chord_display_structured_degrees() {
let c = ChordSymbol {
root: "C".into(),
kind: "dominant".into(),
bass: None,
placement: None,
extender: false,
harmonic_degree: None,
harmony_function: None,
harmony_type: None,
chord_ref: None,
range_end: None,
degrees: vec![
ChordDegree {
value: 9,
alter: 1,
kind: "add".into(),
},
ChordDegree {
value: 5,
alter: -1,
kind: "alter".into(),
},
ChordDegree {
value: 3,
alter: 0,
kind: "subtract".into(),
},
],
};
assert_eq!(c.display_text(), "C7add#9b5no3");
}
#[test]
fn chord_symbol_legacy_json_defaults_degrees() {
let chord: ChordSymbol =
serde_json::from_str(r#"{"root":"C","kind":"major","bass":null,"placement":null}"#)
.expect("legacy chord symbol JSON deserializes");
assert!(chord.degrees.is_empty());
assert!(!chord.extender);
assert!(chord.harmonic_degree.is_none());
assert!(chord.harmony_function.is_none());
assert!(chord.harmony_type.is_none());
}
#[test]
fn time_sig_total_beats_three_four() {
let ts = TimeSignature {
numerator: 3,
denominator: 4,
};
assert!((ts.total_beats() - 3.0).abs() < 1e-9);
}
#[test]
fn time_sig_total_beats_six_eight() {
let ts = TimeSignature {
numerator: 6,
denominator: 8,
};
assert!((ts.total_beats() - 3.0).abs() < 1e-9);
}
#[test]
fn clef_treble_middle_b4() {
assert_eq!(Clef::Treble.middle_line_midi(), 71);
}
#[test]
fn clef_bass_middle_d3() {
assert_eq!(Clef::Bass.middle_line_midi(), 50);
}
#[test]
fn clef_alto_middle_c4() {
assert_eq!(Clef::Alto.middle_line_midi(), 60);
}
#[test]
fn clef_tenor_middle_a3() {
assert_eq!(Clef::Tenor.middle_line_midi(), 57);
}
}