use std::fmt;
use crate::core::Pitch;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ClefSign {
G,
F,
C,
Percussion,
Tab,
None,
Jianpu,
}
impl ClefSign {
pub fn char(&self) -> char {
match self {
ClefSign::G => 'G',
ClefSign::F => 'F',
ClefSign::C => 'C',
ClefSign::Percussion => 'P',
ClefSign::Tab => 'T',
ClefSign::None => 'N',
ClefSign::Jianpu => 'J',
}
}
}
impl fmt::Display for ClefSign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.char())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Clef {
sign: ClefSign,
line: u8,
octave_change: i8,
}
impl Clef {
pub fn new(sign: ClefSign, line: u8) -> Self {
Self {
sign,
line,
octave_change: 0,
}
}
pub fn with_octave(sign: ClefSign, line: u8, octave_change: i8) -> Self {
Self {
sign,
line,
octave_change,
}
}
pub fn treble() -> Self {
Self::new(ClefSign::G, 2)
}
pub fn bass() -> Self {
Self::new(ClefSign::F, 4)
}
pub fn alto() -> Self {
Self::new(ClefSign::C, 3)
}
pub fn tenor() -> Self {
Self::new(ClefSign::C, 4)
}
pub fn soprano() -> Self {
Self::new(ClefSign::C, 1)
}
pub fn mezzo_soprano() -> Self {
Self::new(ClefSign::C, 2)
}
pub fn baritone() -> Self {
Self::new(ClefSign::F, 3)
}
pub fn treble_8va() -> Self {
Self::with_octave(ClefSign::G, 2, 1)
}
pub fn treble_8vb() -> Self {
Self::with_octave(ClefSign::G, 2, -1)
}
pub fn bass_8vb() -> Self {
Self::with_octave(ClefSign::F, 4, -1)
}
pub fn percussion() -> Self {
Self::new(ClefSign::Percussion, 3)
}
pub fn tab() -> Self {
Self::new(ClefSign::Tab, 3)
}
pub fn french_violin() -> Self {
Self::new(ClefSign::G, 1)
}
pub fn sub_bass() -> Self {
Self::new(ClefSign::F, 5)
}
pub fn no_clef() -> Self {
Self::new(ClefSign::None, 3)
}
pub fn jianpu() -> Self {
Self::new(ClefSign::Jianpu, 3)
}
pub fn from_string(s: &str) -> Option<Clef> {
let trimmed = s.trim();
match trimmed.to_lowercase().as_str() {
"treble" | "g2" => return Some(Clef::treble()),
"treble8va" | "treble 8va" => return Some(Clef::treble_8va()),
"treble8vb" | "treble 8vb" => return Some(Clef::treble_8vb()),
"bass" | "f4" => return Some(Clef::bass()),
"bass8vb" | "bass 8vb" => return Some(Clef::bass_8vb()),
"alto" | "c3" => return Some(Clef::alto()),
"tenor" | "c4" => return Some(Clef::tenor()),
"soprano" | "c1" => return Some(Clef::soprano()),
"mezzo-soprano" | "mezzosoprano" | "c2" => return Some(Clef::mezzo_soprano()),
"baritone" | "f3" => return Some(Clef::baritone()),
"french violin" | "frenchviolin" | "g1" => return Some(Clef::french_violin()),
"sub-bass" | "subbass" | "f5" => return Some(Clef::sub_bass()),
"percussion" => return Some(Clef::percussion()),
"tab" | "tablature" => return Some(Clef::tab()),
"none" | "no clef" | "noclef" => return Some(Clef::no_clef()),
"jianpu" => return Some(Clef::jianpu()),
_ => {}
}
let mut chars = trimmed.chars();
let sign = match chars.next()?.to_ascii_uppercase() {
'G' => ClefSign::G,
'F' => ClefSign::F,
'C' => ClefSign::C,
'P' => ClefSign::Percussion,
'T' => ClefSign::Tab,
_ => return None,
};
let line: u8 = chars.as_str().parse().ok()?;
Some(Clef::new(sign, line))
}
pub fn best_clef_for_pitches(pitches: &[Pitch]) -> Clef {
if pitches.is_empty() {
return Clef::treble();
}
let average: f64 =
pitches.iter().map(|p| p.midi() as f64).sum::<f64>() / pitches.len() as f64;
if average >= 60.0 {
Clef::treble()
} else {
Clef::bass()
}
}
pub fn stem_direction_for_pitches(&self, pitches: &[Pitch]) -> crate::core::StemDirection {
if pitches.is_empty() {
return crate::core::StemDirection::Up;
}
let middle_line_pitch = self.pitch_at_position(8) as f64;
let average: f64 =
pitches.iter().map(|p| p.midi() as f64).sum::<f64>() / pitches.len() as f64;
if average < middle_line_pitch {
crate::core::StemDirection::Up
} else {
crate::core::StemDirection::Down
}
}
pub fn sign(&self) -> ClefSign {
self.sign
}
pub fn line(&self) -> u8 {
self.line
}
pub fn octave_change(&self) -> i8 {
self.octave_change
}
pub fn name(&self) -> &'static str {
match (self.sign, self.line, self.octave_change) {
(ClefSign::G, 2, 0) => "treble",
(ClefSign::G, 2, 1) => "treble 8va",
(ClefSign::G, 2, -1) => "treble 8vb",
(ClefSign::F, 4, 0) => "bass",
(ClefSign::F, 4, -1) => "bass 8vb",
(ClefSign::F, 3, 0) => "baritone",
(ClefSign::C, 3, 0) => "alto",
(ClefSign::C, 4, 0) => "tenor",
(ClefSign::C, 1, 0) => "soprano",
(ClefSign::C, 2, 0) => "mezzo-soprano",
(ClefSign::G, 1, 0) => "french violin",
(ClefSign::F, 5, 0) => "sub-bass",
(ClefSign::Percussion, _, _) => "percussion",
(ClefSign::Tab, _, _) => "tab",
(ClefSign::None, _, _) => "none",
(ClefSign::Jianpu, _, _) => "jianpu",
_ => "custom",
}
}
pub fn reference_pitch(&self) -> u8 {
let base = match self.sign {
ClefSign::G => 67, ClefSign::F => 53, ClefSign::C => 60, ClefSign::Percussion => 60,
ClefSign::Tab => 60,
ClefSign::None => 60,
ClefSign::Jianpu => 60,
};
(base as i8 + self.octave_change * 12) as u8
}
fn reference_degree(&self) -> i32 {
match self.sign {
ClefSign::G => 4, ClefSign::F => 3, ClefSign::C | ClefSign::Percussion | ClefSign::Tab | ClefSign::None | ClefSign::Jianpu => 0, }
}
pub fn pitch_at_position(&self, position: i8) -> u8 {
let clef_position = 4 + (self.line as i8 - 1) * 2;
let steps = (position - clef_position) as i32;
let semitones_from_c = [0, 2, 4, 5, 7, 9, 11];
let reference_degree = self.reference_degree();
let c_in_reference_octave = self.reference_pitch() as i32 - semitones_from_c[reference_degree as usize];
let total_degree = reference_degree + steps;
let octave_shift = total_degree.div_euclid(7);
let degree_in_octave = total_degree.rem_euclid(7) as usize;
(c_in_reference_octave + octave_shift * 12 + semitones_from_c[degree_in_octave]) as u8
}
}
impl Default for Clef {
fn default() -> Self {
Self::treble()
}
}
impl fmt::Display for Clef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clef_creation() {
let treble = Clef::treble();
assert_eq!(treble.sign(), ClefSign::G);
assert_eq!(treble.line(), 2);
assert_eq!(treble.name(), "treble");
}
#[test]
fn test_clef_types() {
assert_eq!(Clef::bass().name(), "bass");
assert_eq!(Clef::alto().name(), "alto");
assert_eq!(Clef::tenor().name(), "tenor");
}
#[test]
fn test_clef_octave() {
let treble_8vb = Clef::treble_8vb();
assert_eq!(treble_8vb.octave_change(), -1);
assert_eq!(treble_8vb.name(), "treble 8vb");
}
#[test]
fn test_reference_pitch() {
assert_eq!(Clef::treble().reference_pitch(), 67); assert_eq!(Clef::bass().reference_pitch(), 53); assert_eq!(Clef::alto().reference_pitch(), 60); }
#[test]
fn test_pitch_at_position_treble_staff_lines() {
let treble = Clef::treble();
assert_eq!(treble.pitch_at_position(4), 64); assert_eq!(treble.pitch_at_position(6), 67); assert_eq!(treble.pitch_at_position(8), 71); assert_eq!(treble.pitch_at_position(10), 74); assert_eq!(treble.pitch_at_position(12), 77); }
#[test]
fn test_pitch_at_position_bass_staff_lines() {
let bass = Clef::bass();
assert_eq!(bass.pitch_at_position(4), 43); assert_eq!(bass.pitch_at_position(10), 53); assert_eq!(bass.pitch_at_position(12), 57); }
#[test]
fn test_pitch_at_position_alto_staff_lines() {
let alto = Clef::alto();
assert_eq!(alto.pitch_at_position(4), 53); assert_eq!(alto.pitch_at_position(8), 60); assert_eq!(alto.pitch_at_position(12), 67); }
#[test]
fn test_pitch_at_position_tenor_staff_lines() {
let tenor = Clef::tenor();
assert_eq!(tenor.pitch_at_position(4), 50); assert_eq!(tenor.pitch_at_position(10), 60); }
#[test]
fn test_clef_from_string_named() {
assert_eq!(Clef::from_string("treble"), Some(Clef::treble()));
assert_eq!(Clef::from_string("Bass"), Some(Clef::bass()));
assert_eq!(Clef::from_string("ALTO"), Some(Clef::alto()));
assert_eq!(Clef::from_string("mezzo-soprano"), Some(Clef::mezzo_soprano()));
assert_eq!(Clef::from_string("french violin"), Some(Clef::french_violin()));
assert_eq!(Clef::from_string("sub-bass"), Some(Clef::sub_bass()));
assert_eq!(Clef::from_string("none"), Some(Clef::no_clef()));
assert_eq!(Clef::from_string("jianpu"), Some(Clef::jianpu()));
assert_eq!(Clef::from_string("not a clef"), None);
}
#[test]
fn test_clef_from_string_sign_line_form() {
assert_eq!(Clef::from_string("G2"), Some(Clef::treble()));
assert_eq!(Clef::from_string("F4"), Some(Clef::bass()));
assert_eq!(Clef::from_string("C3"), Some(Clef::alto()));
assert_eq!(Clef::from_string("C4"), Some(Clef::tenor()));
assert_eq!(Clef::from_string("X9"), None);
}
#[test]
fn test_best_clef_for_pitches() {
use crate::core::{Pitch, Step};
let high = vec![
Pitch::from_parts(Step::C, Some(5), None),
Pitch::from_parts(Step::E, Some(5), None),
];
assert_eq!(Clef::best_clef_for_pitches(&high), Clef::treble());
let low = vec![
Pitch::from_parts(Step::C, Some(2), None),
Pitch::from_parts(Step::E, Some(2), None),
];
assert_eq!(Clef::best_clef_for_pitches(&low), Clef::bass());
}
#[test]
fn test_stem_direction_for_pitches() {
use crate::core::{Pitch, Step, StemDirection};
let treble = Clef::treble();
let low_note = vec![Pitch::from_parts(Step::C, Some(4), None)];
assert_eq!(treble.stem_direction_for_pitches(&low_note), StemDirection::Up);
let high_note = vec![Pitch::from_parts(Step::C, Some(6), None)];
assert_eq!(treble.stem_direction_for_pitches(&high_note), StemDirection::Down);
}
#[test]
fn test_new_clef_variant_names() {
assert_eq!(Clef::french_violin().name(), "french violin");
assert_eq!(Clef::sub_bass().name(), "sub-bass");
assert_eq!(Clef::no_clef().name(), "none");
assert_eq!(Clef::jianpu().name(), "jianpu");
}
}