use crate::fixed::fixed::{Q15, Q24_8, Q7_25, Q8_8};
use serde::{Deserialize, Serialize};
macro_rules! impl_q15_readability {
($t:ident) => {
impl $t {
#[inline]
pub const fn as_q15_i16(self) -> i16 {
self.0.raw()
}
#[inline(always)]
pub const fn as_q15_i32(self) -> i32 {
self.0.raw() as i32
}
#[inline]
pub const fn from_q15_i16(v: i16) -> Self {
Self(Q15::from_raw(v))
}
#[inline(always)]
pub const fn from_q15_i32_sat(v: i32) -> Self {
Self(Q15::from_i32_sat(v))
}
}
};
}
macro_rules! impl_q8_8_readability {
($t:ident) => {
impl $t {
#[inline]
pub const fn as_q8_8_i16(self) -> i16 {
self.0.raw()
}
#[inline]
pub const fn as_q8_8_i32(self) -> i32 {
self.0.raw() as i32
}
#[inline]
pub const fn from_q8_8_i16(v: i16) -> Self {
Self(Q8_8::from_raw(v))
}
#[inline]
pub const fn from_q8_8_i32_sat(v: i32) -> Self {
Self(Q8_8::from_i32_sat(v))
}
}
};
}
macro_rules! impl_q15_ops {
($t:ident) => {
impl core::ops::Neg for $t {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self(-self.0)
}
}
impl core::ops::Add for $t {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl core::ops::Sub for $t {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
};
}
macro_rules! impl_q8_8_ops {
($t:ident) => {
impl core::ops::Neg for $t {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self(-self.0)
}
}
impl core::ops::Add for $t {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl core::ops::Sub for $t {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
};
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Amp(Q15);
impl Amp {
pub const SILENCE: Self = Self(Q15::ZERO);
#[inline]
pub const fn from_q15(q: Q15) -> Self {
Self(q)
}
#[inline]
pub const fn into_q15(self) -> Q15 {
self.0
}
#[inline(always)]
pub const fn lerp(self, other: Self, t: Q15) -> Self {
let u = self.0.raw() as i32;
let v = other.0.raw() as i32;
let diff = v - u; let scaled = (t.raw() as i32 * diff + (1 << 14)) >> 15;
let r = u + scaled;
let r = if r > 0x7FFF {
0x7FFF
} else if r < -0x8000 {
-0x8000
} else {
r
};
Self(Q15::from_raw(r as i16))
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
#[inline(always)]
pub const fn widen_q15_16(self) -> i32 {
self.0.widen_q15_16()
}
#[inline(always)]
pub fn accumulate_into(self, acc: &mut i32) {
*acc = acc.saturating_add(self.as_q15_i32());
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Volume(Q15);
impl Volume {
pub const SILENT: Self = Self(Q15::ZERO);
pub const FULL: Self = Self(Q15::ONE);
#[inline]
pub const fn from_q15(q: Q15) -> Self {
Self(q)
}
#[inline(always)]
pub const fn raw(self) -> Q15 {
self.0
}
#[inline(always)]
pub const fn scaled_by(self, other: Volume) -> Volume {
Volume(self.0.mul(other.0))
}
#[inline(always)]
pub const fn apply(self, s: Amp) -> Amp {
Amp(self.0.mul(s.into_q15()))
}
#[inline]
pub const fn with_tremolo(self, modulation: Q15) -> Volume {
let s = self.0.saturating_add(modulation);
Volume(s.clamp(Q15::ZERO, Q15::ONE))
}
#[inline]
pub const fn faded_by(self, step: Q15) -> Volume {
let s = self.0.saturating_sub(step);
Volume(s.clamp(Q15::ZERO, Q15::ONE))
}
#[inline]
pub const fn from_byte_64(byte: u8) -> Self {
if byte >= 64 {
Self::FULL
} else {
Self(Q15::from_raw((byte as i16) << 9))
}
}
#[inline]
pub const fn to_byte_64(self) -> u8 {
let raw = self.0.raw() as i32;
let byte = (raw + 256) >> 9;
if byte < 0 {
0
} else if byte > 64 {
64
} else {
byte as u8
}
}
#[inline]
pub const fn from_ratio(num: i32, den: i32) -> Self {
Self(Q15::from_ratio(num, den))
}
#[inline]
pub const fn to_ratio_u16(self, scale: u32) -> u16 {
let raw = self.0.raw() as i64;
if raw <= 0 {
return 0;
}
let scaled = raw * scale as i64;
let rounded = (scaled + 0x4000) >> 15;
if rounded > u16::MAX as i64 {
u16::MAX
} else {
rounded as u16
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct ChannelVolume(Q15);
impl ChannelVolume {
pub const FULL: Self = Self(Q15::ONE);
pub const SILENT: Self = Self(Q15::ZERO);
#[inline]
pub const fn from_q15(q: Q15) -> Self {
Self(q)
}
#[inline]
pub const fn applied_to(self, v: Volume) -> Volume {
Volume(self.0.mul(v.0))
}
#[inline(always)]
pub const fn raw(self) -> Q15 {
self.0
}
#[inline]
pub const fn from_byte_64(byte: u8) -> Self {
if byte >= 64 {
Self::FULL
} else {
Self(Q15::from_raw((byte as i16) << 9))
}
}
#[inline]
pub const fn to_byte_64(self) -> u8 {
let raw = self.0.raw() as i32;
let byte = (raw + 256) >> 9;
if byte < 0 {
0
} else if byte > 64 {
64
} else {
byte as u8
}
}
#[inline]
pub const fn shifted_by(self, delta: Q15) -> Self {
let s = self.0.saturating_add(delta);
Self(s.clamp(Q15::ZERO, Q15::ONE))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct EnvValue(Q15);
impl EnvValue {
pub const VOLUME_DEFAULT: Self = Self(Q15::ONE);
pub const PAN_DEFAULT: Self = Self(Q15::HALF);
#[inline]
pub const fn from_q15(q: Q15) -> Self {
Self(q)
}
#[inline]
pub const fn from_byte_64(byte: u8) -> Self {
if byte >= 64 {
Self(Q15::ONE)
} else {
Self(Q15::from_raw((byte as i16) << 9))
}
}
#[inline]
pub const fn from_signed_byte_64(byte: i8) -> Self {
let centred = byte as i16 + 32;
if centred <= 0 {
Self(Q15::ZERO)
} else if centred >= 64 {
Self(Q15::ONE)
} else {
Self(Q15::from_raw(centred << 9))
}
}
#[inline]
pub const fn to_byte_64(self) -> u8 {
let raw = self.0.raw() as i32;
let byte = (raw + 256) >> 9;
if byte < 0 {
0
} else if byte > 64 {
64
} else {
byte as u8
}
}
#[inline]
pub const fn applied_to(self, v: Volume) -> Volume {
Volume(self.0.mul(v.0))
}
#[inline(always)]
pub const fn raw_q15(self) -> Q15 {
self.0
}
#[inline(always)]
pub const fn is_silent(self) -> bool {
self.0.raw() == 0
}
#[inline(always)]
pub const fn lerp(self, other: Self, t: Q15) -> Self {
let u = self.0.raw() as i32;
let v = other.0.raw() as i32;
let diff = v - u;
let scaled = (t.raw() as i32 * diff + (1 << 14)) >> 15;
let r = u + scaled;
let r = if r > 0x7FFF {
0x7FFF
} else if r < -0x8000 {
-0x8000
} else {
r
};
Self(Q15::from_raw(r as i16))
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct GlobalVolume(Q15);
impl GlobalVolume {
pub const FULL: Self = Self(Q15::ONE);
pub const SILENT: Self = Self(Q15::ZERO);
#[inline]
pub const fn from_q15(q: Q15) -> Self {
Self(q)
}
#[inline(always)]
pub const fn raw_q15(self) -> Q15 {
self.0
}
#[inline]
pub const fn applied_to(self, s: Amp) -> Amp {
Amp(self.0.mul(s.into_q15()))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Amplification(i16);
impl Amplification {
pub const UNITY: Self = Self(0x1000);
#[inline]
pub const fn from_raw_q4_12(raw: i16) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw_q4_12(self) -> i16 {
self.0
}
#[inline]
pub const fn from_int(n: i8) -> Self {
Self((n as i16) << 12)
}
#[inline]
pub const fn applied_to(self, s: Amp) -> Amp {
let prod = (self.0 as i32) * (s.into_q15().raw() as i32);
let prod = prod + (1 << 11);
let r = prod >> 12;
let r = if r > 0x7FFF {
0x7FFF
} else if r < -0x8000 {
-0x8000
} else {
r
};
Amp(Q15::from_raw(r as i16))
}
#[inline]
pub const fn as_q4_12_i32(self) -> i32 {
self.0 as i32
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Panning(Q15);
impl Panning {
pub const LEFT: Self = Self(Q15::ZERO);
pub const CENTER: Self = Self(Q15::HALF);
pub const RIGHT: Self = Self(Q15::ONE);
#[inline]
pub const fn from_q15(q: Q15) -> Self {
Self(q)
}
#[inline(always)]
pub const fn raw(self) -> Q15 {
self.0
}
#[inline]
pub const fn from_byte_64(byte: u8) -> Self {
if byte >= 64 {
Self::RIGHT
} else {
Self(Q15::from_raw((byte as i16) << 9))
}
}
#[inline]
pub const fn to_byte_64(self) -> u8 {
let raw = self.0.raw() as i32;
let byte = (raw + 256) >> 9;
if byte < 0 {
0
} else if byte > 64 {
64
} else {
byte as u8
}
}
#[inline]
pub const fn from_byte_255(byte: u8) -> Self {
Self(Q15::from_ratio(byte as i32, 255))
}
#[inline]
pub const fn to_byte_255(self) -> u8 {
let raw = self.0.raw() as i64;
if raw <= 0 {
return 0;
}
let scaled = raw * 255;
let rounded = (scaled + 0x4000) >> 15;
if rounded > 255 {
255
} else {
rounded as u8
}
}
#[inline]
pub const fn from_ratio(num: i32, den: i32) -> Self {
Self(Q15::from_ratio(num, den))
}
#[inline]
pub const fn shifted_by(self, delta: Q15) -> Self {
let s = self.0.saturating_add(delta);
Self(s.clamp(Q15::ZERO, Q15::ONE))
}
#[inline]
pub fn stereo_gains(self) -> (Volume, Volume) {
let p = self.0;
let one_minus = Q15::ONE.saturating_sub(p);
let l = crate::fixed::tables::pan_sqrt(one_minus);
let r = crate::fixed::tables::pan_sqrt(p);
(Volume(l), Volume(r))
}
#[inline(always)]
pub const fn stereo_split_linear(self) -> (Volume, Volume) {
let p = self.0.raw();
let pan_left = 0x7FFF_i16 - p;
(Volume(Q15::from_raw(pan_left)), Volume(Q15::from_raw(p)))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Pitch(Q8_8);
impl Pitch {
pub const C0: Self = Self(Q8_8::from_int(0));
pub const C4: Self = Self(Q8_8::from_int(48));
pub const A4: Self = Self(Q8_8::from_int(57));
pub const B9: Self = Self(Q8_8::from_int(119));
#[inline]
pub const fn from_semitone(n: i16) -> Self {
Self(Q8_8::from_int(n))
}
#[inline]
pub const fn from_q8_8(q: Q8_8) -> Self {
Self(q)
}
#[inline(always)]
pub const fn raw(self) -> Q8_8 {
self.0
}
#[inline]
pub const fn quantized(self) -> Self {
Self(Q8_8::from_int(self.0.round()))
}
#[inline]
pub const fn semitone_trunc(self) -> i16 {
self.0.trunc()
}
#[inline]
pub const fn semitone_round(self) -> i16 {
self.0.round()
}
#[inline]
pub const fn semitone_ceil(self) -> i16 {
self.0.ceil_int()
}
#[inline]
pub const fn sub_semitone_q4(self) -> u8 {
self.0.frac_byte() >> 4
}
#[inline]
pub const fn shift(self, delta: PitchDelta) -> Self {
Self(self.0.saturating_add(delta.0))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct PitchDelta(Q8_8);
impl PitchDelta {
pub const ZERO: Self = Self(Q8_8::ZERO);
#[inline]
pub const fn from_semitones(n: i16) -> Self {
Self(Q8_8::from_int(n))
}
#[inline]
pub const fn from_q8_8(q: Q8_8) -> Self {
Self(q)
}
#[inline(always)]
pub const fn raw(self) -> Q8_8 {
self.0
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
#[inline]
pub const fn from_ratio(num: i16, den: i16) -> Self {
Self(Q8_8::from_ratio(num, den))
}
#[inline]
pub const fn to_ratio_byte(self, den: u32) -> u8 {
self.0.to_ratio_byte(den)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Finetune(Q15);
impl Finetune {
pub const ZERO: Self = Self(Q15::ZERO);
#[inline]
pub const fn from_q15(q: Q15) -> Self {
Self(q)
}
#[inline]
pub const fn into_q15(self) -> Q15 {
self.0
}
#[inline]
pub const fn from_ratio(num: i32, den: i32) -> Self {
Self(Q15::from_ratio(num, den))
}
#[inline]
pub const fn to_signed_byte(self, scale: u32) -> i8 {
let raw = self.0.raw() as i64;
let scaled = raw * (scale as i64);
let rounded = if raw >= 0 {
(scaled + 0x4000) >> 15
} else {
-(((-scaled) + 0x4000) >> 15)
};
if rounded > 127 {
127
} else if rounded < -128 {
-128
} else {
rounded as i8
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct PitchAcc(Q24_8);
impl PitchAcc {
#[inline]
pub const fn from_pitch(p: Pitch) -> Self {
Self(Q24_8::from_q8_8(p.raw()))
}
#[inline]
pub const fn add_delta(self, d: PitchDelta) -> Self {
Self(self.0.saturating_add(Q24_8::from_q8_8(d.raw())))
}
#[inline]
pub const fn add_finetune(self, ft: Finetune) -> Self {
Self(self.0.add_finetune_q15(ft.into_q15()))
}
#[inline]
pub const fn into_pitch(self) -> Pitch {
let r = self.0.raw();
let r = if r < 0 {
0
} else if r > 119 << 8 {
119 << 8
} else {
r
};
Pitch::from_q8_8(Q8_8::from_raw(r as i16))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Period(u16);
impl Period {
pub const ZERO: Self = Self(0);
#[inline]
pub const fn from_raw(raw: u16) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw(self) -> u16 {
self.0
}
#[inline]
pub const fn saturating_add_signed(self, delta: i16) -> Self {
let s = self.0 as i32 + delta as i32;
if s < 0 {
Self(0)
} else if s > 0xFFFF {
Self(0xFFFF)
} else {
Self(s as u16)
}
}
#[inline]
pub const fn clamp(self, min: Self, max: Self) -> Self {
if self.0 < min.0 {
min
} else if self.0 > max.0 {
max
} else {
self
}
}
#[inline]
pub const fn slide_towards(self, goal: Self, incr: i16) -> Self {
let step = if incr < 0 {
-(incr as i32)
} else {
incr as i32
};
if step == 0 {
return goal;
}
if self.0 < goal.0 {
let new = self.0 as i32 + step;
if new >= goal.0 as i32 {
goal
} else {
Self(new as u16)
}
} else if self.0 > goal.0 {
let new = self.0 as i32 - step;
if new <= goal.0 as i32 {
goal
} else {
Self(new as u16)
}
} else {
self
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Frequency(u32);
impl Frequency {
pub const ZERO: Self = Self(0);
pub const C4_REFERENCE: Self = Self(8372u32 << 8);
pub const C4_REFERENCE_LEGACY: Self = Self(8363u32 << 8);
#[inline]
pub const fn from_hz(hz: u32) -> Self {
if hz > (u32::MAX >> 8) {
Self(u32::MAX)
} else {
Self(hz << 8)
}
}
#[inline]
pub const fn from_raw_q24_8(raw: u32) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw_q24_8(self) -> u32 {
self.0
}
#[inline]
pub const fn shift_octave(self, oct: i8) -> Self {
if oct >= 0 {
let shift = oct as u32;
if shift >= 32 {
Self(u32::MAX)
} else {
let candidate = (self.0 as u64) << shift;
if candidate > u32::MAX as u64 {
Self(u32::MAX)
} else {
Self(candidate as u32)
}
}
} else {
let shift = (-oct) as u32;
if shift >= 32 {
Self(0)
} else {
Self(self.0 >> shift)
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(transparent)]
pub struct SampleRate(pub u32);
impl SampleRate {
pub const CD: Self = Self(44_100);
pub const PRO_48K: Self = Self(48_000);
#[inline]
pub const fn from_hz(hz: u32) -> Self {
Self(hz)
}
#[inline]
pub const fn hz(self) -> u32 {
self.0
}
}
impl Default for SampleRate {
fn default() -> Self {
Self::CD
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct SampleStep(Q7_25);
impl SampleStep {
pub const ZERO: Self = Self(Q7_25::ZERO);
#[inline]
pub const fn from_raw_q7_25(q: Q7_25) -> Self {
Self(q)
}
#[inline]
pub const fn into_q7_25(self) -> Q7_25 {
self.0
}
#[inline]
pub fn for_frequency(freq: Frequency, rate: SampleRate) -> Self {
if rate.0 == 0 {
return Self::ZERO;
}
let numerator = (freq.raw_q24_8() as u64) << 17;
let q = numerator / rate.0 as u64;
let q = if q > u32::MAX as u64 {
u32::MAX
} else {
q as u32
};
Self(Q7_25::from_raw(q))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct RetrigMul(i16);
impl RetrigMul {
pub const UNITY: Self = Self(0x2000);
#[inline]
pub const fn from_raw_q3_13(raw: i16) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw_q3_13(self) -> i16 {
self.0
}
#[inline]
pub const fn from_ratio(num: i16, den: i16) -> Self {
if den == 0 {
return Self::UNITY;
}
let scaled = ((num as i32) << 13) / den as i32;
let scaled = if scaled > i16::MAX as i32 {
i16::MAX as i32
} else if scaled < i16::MIN as i32 {
i16::MIN as i32
} else {
scaled
};
Self(scaled as i16)
}
#[inline]
pub const fn applied_to(self, v: Volume) -> Volume {
let prod = (self.0 as i32) * (v.raw().raw() as i32);
let prod = prod + (1 << 12);
let r = prod >> 13;
let r = if r > 0x7FFF {
0x7FFF
} else if r < 0 {
0
} else {
r
};
Volume(Q15::from_raw(r as i16))
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Phase(u16);
impl Phase {
pub const ZERO: Self = Self(0);
pub const QUARTER: Self = Self(0x4000);
pub const HALF: Self = Self(0x8000);
pub const THREE_QUARTERS: Self = Self(0xC000);
#[inline]
pub const fn from_raw(raw: u16) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw(self) -> u16 {
self.0
}
#[inline(always)]
pub const fn raw_as_i16(self) -> i16 {
self.0 as i16
}
#[inline]
pub const fn shifted(self, by: Self) -> Self {
Self(self.0.wrapping_add(by.0))
}
#[inline]
pub const fn halved(self) -> Self {
Self(self.0 >> 1)
}
#[inline]
pub const fn is_first_half(self) -> bool {
self.0 < Self::HALF.0
}
#[inline]
pub const fn to_q15_unsigned(self) -> Q15 {
Q15::from_raw((self.0 >> 1) as i16)
}
}
impl_q15_readability!(Amp);
impl_q15_readability!(Volume);
impl_q15_readability!(ChannelVolume);
impl_q15_readability!(EnvValue);
impl_q15_readability!(GlobalVolume);
impl_q15_readability!(Panning);
impl_q15_readability!(Finetune);
impl_q8_8_readability!(Pitch);
impl_q8_8_readability!(PitchDelta);
impl_q15_ops!(Amp);
impl_q15_ops!(Finetune);
impl_q8_8_ops!(PitchDelta);
impl core::ops::Add for Volume {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl core::ops::Add for ChannelVolume {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl core::ops::Add for GlobalVolume {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl core::ops::Add for Panning {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl core::ops::Add<PitchDelta> for Pitch {
type Output = Pitch;
#[inline]
fn add(self, rhs: PitchDelta) -> Pitch {
Pitch(self.0 + rhs.0)
}
}
impl core::ops::Sub<PitchDelta> for Pitch {
type Output = Pitch;
#[inline]
fn sub(self, rhs: PitchDelta) -> Pitch {
Pitch(self.0 - rhs.0)
}
}
impl core::ops::Sub for Pitch {
type Output = PitchDelta;
#[inline]
fn sub(self, rhs: Pitch) -> PitchDelta {
PitchDelta(self.0 - rhs.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn volume_full_times_sample_is_identity_within_1_lsb() {
let s = Amp::from_q15(Q15::from_raw(12345));
let out = Volume::FULL.apply(s);
assert!((out.into_q15().raw() - 12345).abs() <= 1);
}
#[test]
fn volume_silent_kills_signal() {
let s = Amp::from_q15(Q15::from_raw(20000));
assert_eq!(Volume::SILENT.apply(s), Amp::SILENCE);
}
#[test]
fn volume_with_tremolo_clamps_above_one() {
let v = Volume::from_q15(Q15::from_raw(0x7000));
let modulated = v.with_tremolo(Q15::from_raw(0x4000));
assert_eq!(modulated, Volume::FULL);
}
#[test]
fn volume_with_tremolo_clamps_below_zero() {
let v = Volume::from_q15(Q15::from_raw(0x2000));
let modulated = v.with_tremolo(Q15::from_raw(-0x4000));
assert_eq!(modulated, Volume::SILENT);
}
#[test]
fn pitch_acc_round_trip() {
let p = Pitch::C4;
let acc = PitchAcc::from_pitch(p);
assert_eq!(acc.into_pitch(), p);
}
#[test]
fn pitch_acc_clamps_low() {
let acc = PitchAcc::from_pitch(Pitch::C0).add_delta(PitchDelta::from_semitones(-50));
assert_eq!(acc.into_pitch(), Pitch::C0);
}
#[test]
fn pitch_acc_clamps_high() {
let acc = PitchAcc::from_pitch(Pitch::B9).add_delta(PitchDelta::from_semitones(50));
assert_eq!(acc.into_pitch(), Pitch::B9);
}
#[test]
fn pitch_quantize_glissando() {
let p = Pitch::from_q8_8(Q8_8::from_raw((48 << 8) + 0x66));
assert_eq!(p.quantized(), Pitch::from_semitone(48));
let p = Pitch::from_q8_8(Q8_8::from_raw((48 << 8) + 0x9A));
assert_eq!(p.quantized(), Pitch::from_semitone(49));
}
#[test]
fn retrig_mul_basic_ratios() {
let v = Volume::FULL;
assert_eq!(RetrigMul::UNITY.applied_to(v), v);
let half = RetrigMul::from_ratio(1, 2);
let out = half.applied_to(Volume::FULL);
assert!((out.raw().raw() - Q15::HALF.raw()).abs() <= 4);
let two = RetrigMul::from_ratio(2, 1);
let half_volume = Volume::from_q15(Q15::HALF);
let out = two.applied_to(half_volume);
assert!(out.raw().raw() >= 0x7FF0);
}
#[test]
fn frequency_octave_shift() {
let f = Frequency::from_hz(440);
assert_eq!(f.shift_octave(1), Frequency::from_hz(880));
assert_eq!(f.shift_octave(-1), Frequency::from_hz(220));
}
#[test]
fn sample_step_zero_rate_yields_zero() {
let f = Frequency::from_hz(440);
let r = SampleRate(0);
assert_eq!(SampleStep::for_frequency(f, r), SampleStep::ZERO);
}
#[test]
fn sample_step_basic() {
let f = Frequency::from_hz(8372);
let r = SampleRate(48_000);
let s = SampleStep::for_frequency(f, r);
let raw = s.into_q7_25().raw();
assert!(
(raw as i64 - 5_852_452).abs() < 4,
"got raw {}, expected ~5_852_452",
raw
);
}
}