use acorde_core::{Clef, Step};
use crate::RenderError;
pub(crate) fn step_idx(step: &Step) -> i32 {
match step {
Step::C => 0,
Step::D => 1,
Step::E => 2,
Step::F => 3,
Step::G => 4,
Step::A => 5,
Step::B => 6,
}
}
pub(crate) fn diatonic_index(step: &Step, octave: i8) -> i32 {
octave as i32 * 7 + step_idx(step)
}
pub(crate) fn clef_bottom_line(clef: &Clef) -> Result<i32, RenderError> {
match clef {
Clef::Treble => Ok(diatonic_index(&Step::E, 4)),
Clef::Bass => Ok(diatonic_index(&Step::G, 2)),
Clef::Alto => Ok(diatonic_index(&Step::F, 3)),
Clef::Tenor => Ok(diatonic_index(&Step::D, 3)),
Clef::Treble8vb => Ok(diatonic_index(&Step::E, 3)),
Clef::Treble8va => Ok(diatonic_index(&Step::E, 5)),
Clef::Bass8vb => Ok(diatonic_index(&Step::G, 1)),
Clef::Bass8va => Ok(diatonic_index(&Step::G, 3)),
Clef::Soprano => Ok(diatonic_index(&Step::C, 4)),
Clef::MezzoSoprano => Ok(diatonic_index(&Step::A, 3)),
Clef::Baritone => Ok(diatonic_index(&Step::B, 2)),
Clef::Percussion => Ok(diatonic_index(&Step::E, 4)),
}
}
pub(crate) fn staff_position(step: &Step, octave: i8, clef_bottom_line: i32) -> i32 {
diatonic_index(step, octave) - clef_bottom_line
}
pub(crate) fn position_y(position: i32, staff_size: f32) -> f32 {
-(position as f32) * (staff_size / 2.0)
}
pub(crate) fn ledger_positions(position: i32) -> Vec<i32> {
let mut out = Vec::new();
if position <= -2 {
let top = if position % 2 == 0 {
position
} else {
position + 1
};
let mut p = -2;
while p >= top {
out.push(p);
p -= 2;
}
} else if position >= 10 {
let bottom = if position % 2 == 0 {
position
} else {
position - 1
};
let mut p = 10;
while p <= bottom {
out.push(p);
p += 2;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn diatonic_index_matches_expected() {
assert_eq!(diatonic_index(&Step::C, 4), 28);
assert_eq!(diatonic_index(&Step::E, 4), 30);
}
#[test]
fn enharmonic_spellings_land_on_different_positions() {
let bottom = clef_bottom_line(&Clef::Treble).unwrap();
let pos = staff_position(&Step::C, 4, bottom);
assert_eq!(pos, staff_position(&Step::C, 4, bottom));
assert_ne!(pos, staff_position(&Step::B, 3, bottom));
}
#[test]
fn treble_bottom_line_is_e4() {
let bottom = clef_bottom_line(&Clef::Treble).unwrap();
assert_eq!(staff_position(&Step::E, 4, bottom), 0);
assert_eq!(staff_position(&Step::G, 4, bottom), 2);
assert_eq!(staff_position(&Step::B, 4, bottom), 4); assert_eq!(staff_position(&Step::F, 5, bottom), 8); }
#[test]
fn bass_bottom_line_is_g2() {
let bottom = clef_bottom_line(&Clef::Bass).unwrap();
assert_eq!(staff_position(&Step::G, 2, bottom), 0);
assert_eq!(staff_position(&Step::D, 3, bottom), 4); assert_eq!(staff_position(&Step::A, 3, bottom), 8); }
#[test]
fn middle_c_one_ledger_line_below_treble() {
let bottom = clef_bottom_line(&Clef::Treble).unwrap();
let pos = staff_position(&Step::C, 4, bottom);
assert_eq!(pos, -2);
assert_eq!(ledger_positions(pos), vec![-2]);
}
#[test]
fn b3_needs_one_ledger_line_hanging_below_it() {
let bottom = clef_bottom_line(&Clef::Treble).unwrap();
let pos = staff_position(&Step::B, 3, bottom);
assert_eq!(pos, -3);
assert_eq!(ledger_positions(pos), vec![-2]);
}
#[test]
fn a3_needs_two_ledger_lines_below_treble() {
let bottom = clef_bottom_line(&Clef::Treble).unwrap();
let pos = staff_position(&Step::A, 3, bottom);
assert_eq!(pos, -4);
assert_eq!(ledger_positions(pos), vec![-2, -4]);
}
#[test]
fn notes_within_staff_need_no_ledger_lines() {
assert!(ledger_positions(0).is_empty());
assert!(ledger_positions(4).is_empty());
assert!(ledger_positions(8).is_empty());
}
#[test]
fn a5_needs_one_ledger_line_above_treble() {
let bottom = clef_bottom_line(&Clef::Treble).unwrap();
let pos = staff_position(&Step::A, 5, bottom);
assert_eq!(pos, 10);
assert_eq!(ledger_positions(pos), vec![10]);
}
#[test]
fn c6_needs_two_ledger_lines_above_treble() {
let bottom = clef_bottom_line(&Clef::Treble).unwrap();
let pos = staff_position(&Step::C, 6, bottom);
assert_eq!(pos, 12);
assert_eq!(ledger_positions(pos), vec![10, 12]);
}
#[test]
fn percussion_clef_uses_the_canonical_display_staff() {
let bottom = clef_bottom_line(&Clef::Percussion).unwrap();
assert_eq!(staff_position(&Step::E, 4, bottom), 0);
assert_eq!(staff_position(&Step::B, 4, bottom), 4);
}
#[test]
fn position_y_bottom_line_is_zero() {
assert_eq!(position_y(0, 20.0), 0.0);
}
#[test]
fn position_y_increases_upward() {
assert!(position_y(2, 20.0) < position_y(0, 20.0));
}
}