use crate::midi::MidiEvent;
use crate::SampleRate;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(u8)]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(rename_all = "camelCase")
)]
pub enum PitchClass {
#[default]
C,
CSharp,
D,
DSharp,
E,
F,
FSharp,
G,
GSharp,
A,
ASharp,
B,
}
impl PitchClass {
pub const ALL: [Self; 12] = [
Self::C,
Self::CSharp,
Self::D,
Self::DSharp,
Self::E,
Self::F,
Self::FSharp,
Self::G,
Self::GSharp,
Self::A,
Self::ASharp,
Self::B,
];
pub const fn semitone(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(rename_all = "camelCase")
)]
pub enum CorrectionScale {
Major,
Minor,
#[default]
Chromatic,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(transparent)
)]
pub struct NoteMask(pub [bool; 12]);
impl Default for NoteMask {
fn default() -> Self {
Self::CHROMATIC
}
}
impl NoteMask {
pub const CHROMATIC: Self = Self([true; 12]);
pub const NONE: Self = Self([false; 12]);
pub const fn new(notes: [bool; 12]) -> Self {
Self(notes)
}
pub fn from_key_scale(key: PitchClass, scale: CorrectionScale) -> Self {
if scale == CorrectionScale::Chromatic {
return Self::CHROMATIC;
}
let offset = usize::from(key.semitone())
+ if scale == CorrectionScale::Minor {
3
} else {
0
};
let major = [
true, false, true, false, true, true, false, true, false, true, false, true,
];
Self(std::array::from_fn(|i| major[(i + 24 - offset) % 12]))
}
}
pub(crate) struct CorrectionTable {
pub count: usize,
pub periods: [i32; 12],
pub midpoints: [i32; 13],
pub notes: [i32; 12],
base_periods: [i32; 12],
pub keyboard: [bool; 128],
}
impl CorrectionTable {
pub(crate) fn new(rate: SampleRate, mask: NoteMask) -> Self {
let mut result = Self {
count: 0,
periods: [0; 12],
midpoints: [0; 13],
notes: [0; 12],
base_periods: [0; 12],
keyboard: [false; 128],
};
result.set_sample_rate(rate);
result.set_notes(mask);
result
}
pub(crate) fn set_sample_rate(&mut self, rate: SampleRate) {
let frequency = ((-9.0 * std::f64::consts::LN_2 / 12.0).exp() * 440.0) as f32;
let mut base = rate.hz() / frequency;
while base < 10820.625 {
base += base;
}
self.base_periods = std::array::from_fn(|n| {
(f64::from(base) * 2.0_f64.powf((100.0 + n as f64 * 100.0) / 1200.0) + 0.5) as i32
});
}
pub(crate) fn set_notes(&mut self, mask: NoteMask) {
self.count = 0;
for i in 0..12 {
if mask.0[11 - i] {
self.periods[self.count] = self.base_periods[i];
self.notes[self.count] = (11 - i) as i32;
self.count += 1;
}
}
if self.count > 0 {
for i in 1..self.count {
self.midpoints[i] = (self.periods[i - 1] + self.periods[i]) / 2;
}
self.midpoints[self.count] = (self.periods[self.count - 1] + self.periods[0] * 2) / 2;
self.midpoints[0] = self.midpoints[self.count] / 2;
}
}
pub(crate) fn reset(&mut self) {
self.keyboard[..127].fill(false);
}
pub(crate) fn apply_validated_midi(&mut self, event: MidiEvent) {
match event {
MidiEvent::NoteOn { note, velocity, .. } => {
self.keyboard[note as usize] = velocity != 0
}
MidiEvent::NoteOff { note, .. } => self.keyboard[note as usize] = false,
MidiEvent::AllNotesOff => self.reset(),
}
let mut mask = [false; 12];
for i in 0..127 {
mask[i % 12] |= self.keyboard[i];
}
self.set_notes(NoteMask(mask));
}
}