Skip to content

Commit

Permalink
piano-layout: Cleanup (#225)
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex authored Nov 26, 2024
1 parent 9a228bb commit 2a4a87d
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 123 deletions.
5 changes: 4 additions & 1 deletion neothesia-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ fn get_layout(
let neutral_width = width / white_count as f32;
let neutral_height = height * 0.2;

piano_layout::KeyboardLayout::from_range(neutral_width, neutral_height, range)
piano_layout::KeyboardLayout::from_range(
piano_layout::Sizing::new(neutral_width, neutral_height),
range,
)
}

fn time_without_lead_in(playback: &midi_file::PlaybackState) -> f32 {
Expand Down
6 changes: 4 additions & 2 deletions neothesia-iced-widgets/src/piano_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ impl<M, R: iced_core::Renderer> Widget<M, Theme, R> for PianoRange {
let neutral_width = bounds.width / white_count as f32;
let neutral_height = bounds.height;

let layout =
piano_layout::KeyboardLayout::from_range(neutral_width, neutral_height, range);
let layout = piano_layout::KeyboardLayout::from_range(
piano_layout::Sizing::new(neutral_width, neutral_height),
range,
);

let mut neutral = layout
.keys
Expand Down
5 changes: 4 additions & 1 deletion neothesia/src/scene/playing_scene/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ fn get_layout(
let neutral_width = width / white_count as f32;
let neutral_height = height * 0.2;

piano_layout::KeyboardLayout::from_range(neutral_width, neutral_height, range)
piano_layout::KeyboardLayout::from_range(
piano_layout::Sizing::new(neutral_width, neutral_height),
range,
)
}

impl Keyboard {
Expand Down
235 changes: 121 additions & 114 deletions piano-layout/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,23 @@ pub struct KeyboardLayout {
pub width: f32,
pub height: f32,

pub neutral_width: f32,
pub sharp_width: f32,

pub neutral_height: f32,
pub sharp_height: f32,

pub sizing: Sizing,
pub range: KeyboardRange,
}

impl KeyboardLayout {
pub fn from_range(neutral_width: f32, neutral_height: f32, range: KeyboardRange) -> Self {
let sharp_width = neutral_width * 0.625; // 62.5%
let sharp_height = neutral_height * 0.635;

let sizing = Sizing::new(neutral_width, neutral_height);

let octaves = range_to_octaves(&sizing, range.range());

pub fn from_range(sizing: Sizing, range: KeyboardRange) -> Self {
let mut keys = Vec::with_capacity(range.count());

let mut offset = 0.0;
let mut id = 0;

for octave in octaves {
for mut key in octave.keys {
let oct = Octave::new(&sizing);

for octave_range in split_range_by_octaves(range.range()) {
let (width, key_iter) = oct.sub_range(octave_range);

for mut key in key_iter {
key.id = id;
id += 1;

Expand All @@ -50,47 +42,43 @@ impl KeyboardLayout {
keys.push(key);
}

offset += octave.width;
offset += width;
}

// Board size
let width = neutral_width * range.white_count() as f32;
let height = neutral_height;
let width = sizing.neutral_width * range.white_count() as f32;
let height = sizing.neutral_height;

KeyboardLayout {
keys: keys.into(),

width,
height,

neutral_width,
sharp_width,

neutral_height,
sharp_height,

sizing,
range,
}
}
}

#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyKind {
#[default]
Neutral,
Sharp,
}

impl KeyKind {
pub fn is_neutral(&self) -> bool {
matches!(self, Self::Neutral)
*self == Self::Neutral
}

pub fn is_sharp(&self) -> bool {
matches!(self, Self::Sharp)
*self == Self::Sharp
}
}

#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone, Copy)]
pub struct Key {
x: f32,
width: f32,
Expand Down Expand Up @@ -130,131 +118,150 @@ impl Key {
}
}

struct Octave {
keys: Vec<Key>,
width: f32,
}

struct Sizing {
neutral_width: f32,
neutral_height: f32,

sharp_width: f32,
sharp_height: f32,
}

impl Sizing {
fn new(neutral_width: f32, neutral_height: f32) -> Self {
let sharp_width = neutral_width * 0.625; // 62.5%
let sharp_height = neutral_height * 0.635;

Self {
neutral_width,
neutral_height,
sharp_width,
sharp_height,
}
}
}

fn range_to_octaves(sizing: &Sizing, range: &std::ops::Range<u8>) -> Vec<Octave> {
fn split_range_by_octaves(
range: &std::ops::Range<u8>,
) -> impl Iterator<Item = std::ops::Range<usize>> {
let start = range.start as usize;
let end = range.end as usize;

let mut octaves = Vec::with_capacity(10);

let mut id = start;
while id < end {
let start_id = id % 12;
let end_id = if id + 12 > end { end - id } else { 12 };

let start_id = start_id as u8;
let end_id = end_id as u8;
std::iter::from_fn(move || {
if id < end {
let start_id = id % 12;
let end_id = if id + 12 > end { end - id } else { 12 };

let range = start_id..end_id;
let range = start_id..end_id;

id += range.len();
id += range.len();

octaves.push(partial_octave(sizing, range));
}
Some(range)
} else {
None
}
})
}

octaves
struct Octave {
keys: [Key; 12],
width: f32,
}

fn partial_octave(sizing: &Sizing, range: std::ops::Range<u8>) -> Octave {
let mut keys = Vec::with_capacity(12);
impl Octave {
fn new(sizing: &Sizing) -> Self {
let mut keys = [Key::default(); 12];

let width = sizing.neutral_width * 7.0;
let width = sizing.neutral_width * 7.0;

let neutral_ids: [u8; 7] = [0, 2, 4, 5, 7, 9, 11];
const C: u8 = 0;
const CS: u8 = 1;
const D: u8 = 2;
const DS: u8 = 3;
const E: u8 = 4;
const F: u8 = 5;
const FS: u8 = 6;
const G: u8 = 7;
const GS: u8 = 8;
const A: u8 = 9;
const AS: u8 = 10;
const B: u8 = 11;

for (id, note_id) in neutral_ids.iter().enumerate() {
let x = id as f32 * sizing.neutral_width;
let neutral_ids: [u8; 7] = [C, D, E, F, G, A, B];
let sharp_ids: [u8; 5] = [CS, DS, FS, GS, AS];

if range.contains(note_id) {
keys.push(Key {
for (id, note_id) in neutral_ids.into_iter().enumerate() {
let x = id as f32 * sizing.neutral_width;

keys[note_id as usize] = Key {
id: 0,
x,
width: sizing.neutral_width,
height: sizing.neutral_height,
kind: KeyKind::Neutral,
note_id: *note_id,
});
note_id,
};
}
}

let sharp_ids: [u8; 5] = [1, 3, 6, 8, 10];

#[inline(always)]
fn sharp_id_to_x(id: u8, cde_width: f32, cde_mult: f32, fgab_mult: f32) -> f32 {
let id = id + 1;
if matches!(id, 2 | 4) {
let mult = cde_mult;
id as f32 * mult - mult / 2.0
} else {
let mult = fgab_mult;
let id = id - 5;
cde_width + id as f32 * mult - mult / 2.0
#[inline(always)]
fn sharp_note_id_to_x(note_id: u8, cde_width: f32, cde_mult: f32, fgab_mult: f32) -> f32 {
if matches!(note_id, CS | DS) {
let mult = cde_mult;
(note_id + 1) as f32 * mult - mult / 2.0
} else {
let mult = fgab_mult;
let id = note_id - E;
cde_width + id as f32 * mult - mult / 2.0
}
}
}

// Mathematically there is no correct way to position keys, but doing it separately for cde and fgh
// is quite popular, and gives decently accurate results, so let's do that
let cde_width = sizing.neutral_width * 3.0;
let fgab_width = sizing.neutral_width * 4.0;
let cde_mult = cde_width / 5.0;
let fgab_mult = fgab_width / 7.0;
// Mathematically there is no correct way to position keys, but doing it separately for cde and fgh
// is quite popular, and gives decently accurate results, so let's do that
let cde_width = sizing.neutral_width * 3.0;
let fgab_width = sizing.neutral_width * 4.0;
let cde_mult = cde_width / 5.0;
let fgab_mult = fgab_width / 7.0;

for note_id in sharp_ids {
let x = sharp_id_to_x(note_id, cde_width, cde_mult, fgab_mult);
for note_id in sharp_ids {
let x = sharp_note_id_to_x(note_id, cde_width, cde_mult, fgab_mult);

let w = sizing.sharp_width;
let hw = w / 2.0;
let w = sizing.sharp_width;
let hw = w / 2.0;

let x = x - hw;
let x = x - hw;

if range.contains(&note_id) {
keys.push(Key {
keys[note_id as usize] = Key {
id: 0,
x,
width: sizing.sharp_width,
height: sizing.sharp_height,
kind: KeyKind::Sharp,
note_id,
});
};
}

Self { keys, width }
}

let start_offset = keys.first().map(|key| key.x());
fn sub_range(&self, range: std::ops::Range<usize>) -> (f32, impl Iterator<Item = Key> + '_) {
let keys = &self.keys[range.clone()];
let start_offset = keys.first().map(Key::x).unwrap_or(0.0);
let new_width = self.width - start_offset;

let mut iter = keys.iter();

if let Some(start_offset) = start_offset {
keys.iter_mut().for_each(|key| key.x -= start_offset);
(
new_width,
std::iter::from_fn(move || {
let key = iter.next()?;

Some(Key {
x: key.x - start_offset,
..*key
})
}),
)
}
}

#[derive(Debug, Clone, Copy)]
pub struct Sizing {
pub neutral_width: f32,
pub neutral_height: f32,

pub sharp_width: f32,
pub sharp_height: f32,
}

keys.sort_by_key(|key| key.note_id);
impl Sizing {
pub fn new(neutral_width: f32, neutral_height: f32) -> Self {
let sharp_width = neutral_width * 0.625; // 62.5%
let sharp_height = neutral_height * 0.635;

Octave {
keys,
width: width - start_offset.unwrap_or(0.0),
Self {
neutral_width,
neutral_height,
sharp_width,
sharp_height,
}
}
}
5 changes: 0 additions & 5 deletions piano-layout/src/range.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![allow(dead_code)]

use std::{
ops::{Range, RangeBounds},
rc::Rc,
Expand Down Expand Up @@ -129,6 +127,3 @@ impl Default for KeyboardRange {
Self::standard_88_keys()
}
}

#[cfg(test)]
mod tests {}

0 comments on commit 2a4a87d

Please sign in to comment.