#![allow(clippy::wrong_self_convention)]
use crate::{
note::{Note, NoteLetter},
pitch::{Pitch, Pitched, Ratio},
};
pub trait Tuning<K> {
fn pitch_of(&self, key: K) -> Pitch;
fn find_by_pitch(&self, pitch: Pitch) -> Approximation<K>;
fn as_linear_mapping(self) -> LinearMapping<Self>
where
Self: Sized,
{
LinearMapping { inner: self }
}
}
impl<K, T: Tuning<K> + ?Sized> Tuning<K> for &T {
fn pitch_of(&self, key: K) -> Pitch {
T::pitch_of(self, key)
}
fn find_by_pitch(&self, pitch: Pitch) -> Approximation<K> {
T::find_by_pitch(self, pitch)
}
}
impl<K: Copy, T: Tuning<K> + ?Sized> Pitched for (K, T) {
fn pitch(&self) -> Pitch {
self.1.pitch_of(self.0)
}
}
pub trait Scale {
fn sorted_pitch_of(&self, degree: i32) -> Pitch;
fn find_by_pitch_sorted(&self, pitch: Pitch) -> Approximation<i32>;
fn as_sorted_tuning(self) -> SortedTuning<Self>
where
Self: Sized,
{
SortedTuning { inner: self }
}
}
impl<S: Scale + ?Sized> Scale for &S {
fn sorted_pitch_of(&self, degree: i32) -> Pitch {
S::sorted_pitch_of(self, degree)
}
fn find_by_pitch_sorted(&self, pitch: Pitch) -> Approximation<i32> {
S::find_by_pitch_sorted(self, pitch)
}
}
pub struct SortedTuning<S> {
inner: S,
}
impl<S: Scale> Tuning<i32> for SortedTuning<S> {
fn pitch_of(&self, key: i32) -> Pitch {
self.inner.sorted_pitch_of(key)
}
fn find_by_pitch(&self, pitch: Pitch) -> Approximation<i32> {
self.inner.find_by_pitch_sorted(pitch)
}
}
pub trait KeyboardMapping<K> {
fn maybe_pitch_of(&self, key: K) -> Option<Pitch>;
}
impl<K, T: KeyboardMapping<K> + ?Sized> KeyboardMapping<K> for &T {
fn maybe_pitch_of(&self, key: K) -> Option<Pitch> {
T::maybe_pitch_of(self, key)
}
}
pub struct LinearMapping<T> {
inner: T,
}
impl<K, T: Tuning<K>> KeyboardMapping<K> for LinearMapping<T> {
fn maybe_pitch_of(&self, key: K) -> Option<Pitch> {
Some(self.inner.pitch_of(key))
}
}
#[derive(Copy, Clone, Debug)]
pub struct Approximation<K> {
pub approx_value: K,
pub deviation: Ratio,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct ConcertPitch {
a4_pitch: Pitch,
}
impl ConcertPitch {
pub fn from_a4_pitch(a4_pitch: impl Pitched) -> Self {
Self {
a4_pitch: a4_pitch.pitch(),
}
}
pub fn from_note_and_pitch(note: Note, pitched: impl Pitched) -> Self {
Self {
a4_pitch: pitched.pitch()
* Ratio::from_semitones(f64::from(
note.num_semitones_before(NoteLetter::A.in_octave(4)),
)),
}
}
pub fn a4_pitch(self) -> Pitch {
self.a4_pitch
}
}
impl Default for ConcertPitch {
fn default() -> Self {
Self::from_a4_pitch(Pitch::from_hz(440.0))
}
}
impl Tuning<Note> for ConcertPitch {
fn pitch_of(&self, note: Note) -> Pitch {
self.a4_pitch * Ratio::from_semitones(NoteLetter::A.in_octave(4).num_semitones_before(note))
}
fn find_by_pitch(&self, pitch: Pitch) -> Approximation<Note> {
let semitones_above_a4 = Ratio::between_pitches(self.a4_pitch, pitch).as_semitones();
let round_to_lower_step = Ratio::from_float(1.000001);
let approx_semitones_above_a4 =
(semitones_above_a4 - round_to_lower_step.as_semitones()).round();
Approximation {
approx_value: Note::from_midi_number(
approx_semitones_above_a4 as i32 + NoteLetter::A.in_octave(4).midi_number(),
),
deviation: Ratio::from_semitones(semitones_above_a4 - approx_semitones_above_a4),
}
}
}
impl Tuning<Note> for () {
fn pitch_of(&self, note: Note) -> Pitch {
ConcertPitch::default().pitch_of(note)
}
fn find_by_pitch(&self, pitch: Pitch) -> Approximation<Note> {
ConcertPitch::default().find_by_pitch(pitch)
}
}