mod staff;
mod note;
mod clef;
mod measure;
mod config;
mod elements;
pub use staff::StaffElement;
pub use note::NoteElement;
pub use clef::ClefElement;
pub use measure::MeasureElement;
pub use config::{RenderConfig, StaffConfig};
pub use elements::{ScoreElement, render_score_to_image};
use mkgraphic::support::canvas::Canvas;
use crate::stream::Score;
use crate::notation::Clef;
pub const STAFF_SPACE: f32 = 8.0;
pub const STAFF_HEIGHT: f32 = STAFF_SPACE * 4.0;
pub const LEDGER_EXTENSION: f32 = 4.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StaffPosition {
pub position: i8,
pub accidental: i8,
}
impl StaffPosition {
pub fn new(position: i8, accidental: i8) -> Self {
Self { position, accidental }
}
pub fn to_y(&self, staff_space: f32) -> f32 {
-(self.position as f32) * (staff_space / 2.0)
}
}
pub struct ScoreRenderer {
config: RenderConfig,
}
impl ScoreRenderer {
pub fn new() -> Self {
Self {
config: RenderConfig::default(),
}
}
pub fn with_config(config: RenderConfig) -> Self {
Self { config }
}
pub fn config(&self) -> &RenderConfig {
&self.config
}
pub fn config_mut(&mut self) -> &mut RenderConfig {
&mut self.config
}
pub fn render(&self, score: &Score) -> ScoreElement {
ScoreElement::new(score, self.config.clone())
}
pub fn render_to_canvas(&self, score: &Score, canvas: &mut Canvas) {
let element = self.render(score);
element.draw_to_canvas(canvas, &self.config);
}
pub fn calculate_size(&self, score: &Score) -> (u32, u32) {
let num_parts = score.parts().len();
let num_measures = score.parts().first().map(|p| p.measures().len()).unwrap_or(0);
let width = self.config.margin_left
+ self.config.clef_width
+ self.config.key_sig_width
+ self.config.time_sig_width
+ (num_measures as f32 * self.config.measure_width)
+ self.config.margin_right;
let staff_with_spacing = self.config.staff.height + self.config.staff_spacing;
let height = self.config.margin_top
+ (num_parts as f32 * staff_with_spacing)
+ self.config.margin_bottom;
(width as u32, height as u32)
}
}
impl Default for ScoreRenderer {
fn default() -> Self {
Self::new()
}
}
pub fn midi_to_staff_position(midi: u8, clef: &Clef) -> StaffPosition {
let reference_midi = clef.reference_pitch() as i8;
let reference_position = (clef.line() as i8 - 3) * 2;
let midi_diff = midi as i8 - reference_midi;
let octaves = midi_diff / 12;
let semitones_in_octave = (midi_diff % 12 + 12) % 12;
let diatonic_step = match semitones_in_octave {
0 => 0,
1 | 2 => 1,
3 | 4 => 2,
5 => 3,
6 | 7 => 4,
8 | 9 => 5,
10 | 11 => 6,
_ => 0,
};
let total_steps = if midi_diff >= 0 {
octaves * 7 + diatonic_step as i8
} else {
octaves * 7 - if diatonic_step > 0 { 7 - diatonic_step as i8 } else { 0 }
};
let position = reference_position + total_steps;
let expected_semitones = match diatonic_step {
0 => 0,
1 => 2,
2 => 4,
3 => 5,
4 => 7,
5 => 9,
6 => 11,
_ => 0,
};
let accidental = (semitones_in_octave - expected_semitones) as i8;
StaffPosition::new(position, accidental)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_staff_position_to_y() {
let pos = StaffPosition::new(0, 0);
assert_eq!(pos.to_y(STAFF_SPACE), 0.0);
let pos = StaffPosition::new(2, 0);
assert_eq!(pos.to_y(STAFF_SPACE), -STAFF_SPACE);
let pos = StaffPosition::new(-2, 0);
assert_eq!(pos.to_y(STAFF_SPACE), STAFF_SPACE);
}
#[test]
fn test_midi_to_staff_position() {
let treble = Clef::treble();
let pos = midi_to_staff_position(60, &treble);
assert!(pos.position < -4); }
}