#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_lossless)]
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Q15(i16);
impl Q15 {
pub const ZERO: Self = Self(0);
pub const ONE: Self = Self(0x7FFF);
pub const NEG_ONE: Self = Self(-0x8000);
pub const HALF: Self = Self(0x4000);
pub const NEG_HALF: Self = Self(-0x4000);
pub const QUARTER: Self = Self(0x2000);
pub const NEG_QUARTER: Self = Self(-0x2000);
#[inline]
pub const fn from_ratio(num: i32, den: i32) -> Self {
if den == 0 {
return if num >= 0 { Self::ONE } else { Self::NEG_ONE };
}
let scaled = (num as i64) << 15;
let q = scaled / den as i64;
if q > 0x7FFF {
Self::ONE
} else if q < -0x8000 {
Self::NEG_ONE
} else {
Self(q as i16)
}
}
#[inline]
pub const fn from_raw(raw: i16) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw(self) -> i16 {
self.0
}
#[inline(always)]
pub const fn mul(self, other: Self) -> Self {
let prod = (self.0 as i32) * (other.0 as i32);
let prod = prod + (1 << 14);
let shifted = prod >> 15;
if shifted > 0x7FFF {
Self::ONE
} else if shifted < -0x8000 {
Self::NEG_ONE
} else {
Self(shifted as i16)
}
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
let s = (self.0 as i32) + (other.0 as i32);
if s > 0x7FFF {
Self::ONE
} else if s < -0x8000 {
Self::NEG_ONE
} else {
Self(s as i16)
}
}
#[inline]
pub const fn saturating_sub(self, other: Self) -> Self {
let s = (self.0 as i32) - (other.0 as i32);
if s > 0x7FFF {
Self::ONE
} else if s < -0x8000 {
Self::NEG_ONE
} else {
Self(s as i16)
}
}
#[inline]
pub const fn clamp(self, lo: Self, hi: Self) -> Self {
if self.0 < lo.0 {
lo
} else if self.0 > hi.0 {
hi
} else {
self
}
}
#[inline]
pub const fn abs(self) -> Self {
if self.0 == i16::MIN {
Self::ONE
} else if self.0 < 0 {
Self(-self.0)
} else {
self
}
}
#[inline]
pub const fn neg(self) -> Self {
if self.0 == i16::MIN {
Self::ONE
} else {
Self(-self.0)
}
}
#[inline]
pub const fn halved(self) -> Self {
Self(self.0 >> 1)
}
#[inline]
pub const fn as_i32(self) -> i32 {
self.0 as i32
}
#[inline(always)]
pub const fn widen_q15_16(self) -> i32 {
(self.0 as i32) << 1
}
#[inline(always)]
pub const fn from_i32_sat(v: i32) -> Self {
if v > i16::MAX as i32 {
Self(i16::MAX)
} else if v < i16::MIN as i32 {
Self(i16::MIN)
} else {
Self(v as i16)
}
}
#[inline]
pub const fn from_i32_unsigned_sat(v: i32) -> Self {
if v > 0x7FFF {
Self(0x7FFF)
} else if v < 0 {
Self(0)
} else {
Self(v as i16)
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Q8_8(i16);
impl Q8_8 {
pub const ZERO: Self = Self(0);
#[inline]
pub const fn from_int(n: i16) -> Self {
if n >= 128 {
Self(0x7F00)
} else if n <= -128 {
Self(-0x8000)
} else {
Self(n << 8)
}
}
#[inline]
pub const fn from_raw(raw: i16) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw(self) -> i16 {
self.0
}
#[inline]
pub const fn trunc(self) -> i16 {
self.0 >> 8
}
#[inline]
pub const fn round(self) -> i16 {
let raw = self.0 as i32;
let r = if raw >= 0 {
(raw + 0x80) >> 8
} else {
-((-raw + 0x80) >> 8)
};
if r > i16::MAX as i32 {
i16::MAX
} else if r < i16::MIN as i32 {
i16::MIN
} else {
r as i16
}
}
#[inline]
pub const fn ceil_int(self) -> i16 {
let raw = self.0 as i32;
let c = (raw + 0xFF) >> 8;
if c > i16::MAX as i32 {
i16::MAX
} else if c < i16::MIN as i32 {
i16::MIN
} else {
c as i16
}
}
#[inline]
pub const fn frac_byte(self) -> u8 {
self.0 as u8
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
let s = (self.0 as i32) + (other.0 as i32);
if s > 0x7FFF {
Self(0x7FFF)
} else if s < -0x8000 {
Self(-0x8000)
} else {
Self(s as i16)
}
}
#[inline]
pub const fn saturating_sub(self, other: Self) -> Self {
let s = (self.0 as i32) - (other.0 as i32);
if s > 0x7FFF {
Self(0x7FFF)
} else if s < -0x8000 {
Self(-0x8000)
} else {
Self(s as i16)
}
}
#[inline]
pub const fn from_ratio(num: i16, den: i16) -> Self {
if den == 0 {
return if num >= 0 {
Self(0x7FFF)
} else {
Self(-0x8000)
};
}
let scaled = (num as i32) << 8;
let q = scaled / den as i32;
if q > 0x7FFF {
Self(0x7FFF)
} else if q < -0x8000 {
Self(-0x8000)
} else {
Self(q as i16)
}
}
#[inline]
pub const fn to_ratio_byte(self, den: u32) -> u8 {
let raw = self.0 as i64;
if raw <= 0 {
return 0;
}
let scaled = raw * den as i64;
let rounded = (scaled + 128) >> 8;
if rounded > 255 {
255
} else {
rounded as u8
}
}
#[inline]
pub const fn as_i32(self) -> i32 {
self.0 as i32
}
#[inline(always)]
pub const fn from_i32_sat(v: i32) -> Self {
if v > i16::MAX as i32 {
Self(i16::MAX)
} else if v < i16::MIN as i32 {
Self(i16::MIN)
} else {
Self(v as i16)
}
}
#[inline]
pub const fn from_semitones_int(n: i16) -> Self {
Self::from_int(n)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Q24_8(i32);
impl Q24_8 {
pub const ZERO: Self = Self(0);
#[inline]
pub const fn from_q8_8(p: Q8_8) -> Self {
Self(p.raw() as i32)
}
#[inline]
pub const fn to_q8_8_saturating(self) -> Q8_8 {
if self.0 > 0x7FFF {
Q8_8::from_raw(0x7FFF)
} else if self.0 < -0x8000 {
Q8_8::from_raw(-0x8000)
} else {
Q8_8::from_raw(self.0 as i16)
}
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
#[inline]
pub const fn saturating_sub(self, other: Self) -> Self {
Self(self.0.saturating_sub(other.0))
}
#[inline]
pub const fn add_finetune_q15(self, ft: Q15) -> Self {
Self(self.0 + ((ft.raw() as i32) >> 7))
}
#[inline(always)]
pub const fn raw(self) -> i32 {
self.0
}
#[inline]
pub const fn from_raw(raw: i32) -> Self {
Self(raw)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Q16_16(u32);
impl Q16_16 {
pub const ZERO: Self = Self(0);
#[inline]
pub const fn from_int(hz: u16) -> Self {
Self((hz as u32) << 16)
}
#[inline]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw(self) -> u32 {
self.0
}
#[inline]
pub const fn shift_octave(self, octaves: i8) -> Self {
if octaves >= 0 {
let shift = octaves as u32;
if shift >= 32 {
Self(u32::MAX)
} else {
let shifted = (self.0 as u64) << shift;
if shifted > u32::MAX as u64 {
Self(u32::MAX)
} else {
Self(shifted as u32)
}
}
} else {
let shift = (-octaves) as u32;
if shift >= 32 {
Self(0)
} else {
Self(self.0 >> shift)
}
}
}
#[inline(always)]
pub const fn mul_q16_16(self, other: Self) -> Self {
let prod = (self.0 as u64) * (other.0 as u64);
let prod = prod + (1u64 << 15);
let r = prod >> 16;
if r > u32::MAX as u64 {
Self(u32::MAX)
} else {
Self(r as u32)
}
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Debug, Default)]
#[repr(transparent)]
#[serde(transparent)]
pub struct Q7_25(u32);
impl Q7_25 {
pub const FRAC_BITS: u32 = 25;
pub const FRAC_MASK: u32 = (1 << Self::FRAC_BITS) - 1;
pub const ZERO: Self = Self(0);
#[inline]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}
#[inline(always)]
pub const fn raw(self) -> u32 {
self.0
}
}
impl core::ops::Neg for Q15 {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Q15::neg(self)
}
}
impl core::ops::Add for Q15 {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
self.saturating_add(rhs)
}
}
impl core::ops::Sub for Q15 {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
self.saturating_sub(rhs)
}
}
impl core::ops::Mul for Q15 {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
Q15::mul(self, rhs)
}
}
impl core::ops::Neg for Q8_8 {
type Output = Self;
#[inline]
fn neg(self) -> Self {
if self.0 == i16::MIN {
Self(i16::MAX)
} else {
Self(-self.0)
}
}
}
impl core::ops::Add for Q8_8 {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
self.saturating_add(rhs)
}
}
impl core::ops::Sub for Q8_8 {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
self.saturating_sub(rhs)
}
}
#[macro_export]
macro_rules! q15 {
($x:expr) => {
$crate::fixed::fixed::Q15::from_raw(($x * 32768.0) as i16)
};
}
#[macro_export]
macro_rules! q8_8 {
($x:expr) => {
$crate::fixed::fixed::Q8_8::from_raw(($x * 256.0) as i16)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn q15_one_times_one_does_not_overflow() {
let p = Q15::ONE.mul(Q15::ONE);
assert!(p.raw() >= 0x7FFE);
}
#[test]
fn q15_neg_one_squared_saturates_to_one() {
let p = Q15::NEG_ONE.mul(Q15::NEG_ONE);
assert_eq!(p, Q15::ONE);
}
#[test]
fn q15_half_times_half_is_quarter() {
let p = Q15::HALF.mul(Q15::HALF);
assert!((p.raw() - 0x2000).abs() <= 1);
}
#[test]
fn q15_from_ratio_basic() {
assert_eq!(Q15::from_ratio(1, 2), Q15::HALF);
assert_eq!(Q15::from_ratio(0, 1), Q15::ZERO);
assert_eq!(Q15::from_ratio(64, 64), Q15::ONE);
assert_eq!(Q15::from_ratio(-1, 1), Q15::NEG_ONE);
}
#[test]
fn q15_round_trip_no_drift() {
let mut x = Q15::HALF;
for _ in 0..100 {
x = x.mul(Q15::ONE);
}
assert!((x.raw() - Q15::HALF.raw()).abs() < 4);
}
#[test]
fn q15_saturating_add_and_sub() {
assert_eq!(Q15::ONE.saturating_add(Q15::ONE), Q15::ONE);
assert_eq!(Q15::NEG_ONE.saturating_sub(Q15::ONE), Q15::NEG_ONE);
}
#[test]
fn q8_8_int_round_trip() {
for n in -120..120 {
assert_eq!(Q8_8::from_int(n).trunc(), n);
}
}
#[test]
fn q8_8_round_to_nearest() {
let p = Q8_8::from_raw(0x0180); assert_eq!(p.round(), 2);
let p = Q8_8::from_raw(-0x0180);
assert_eq!(p.round(), -2);
let p = Q8_8::from_raw(0x0166);
assert_eq!(p.round(), 1);
}
#[test]
fn q24_8_widen_narrow() {
let p = Q8_8::from_int(48);
let acc = Q24_8::from_q8_8(p);
assert_eq!(acc.to_q8_8_saturating(), p);
}
#[test]
fn q24_8_finetune_addition() {
let acc = Q24_8::from_q8_8(Q8_8::from_int(48));
let acc2 = acc.add_finetune_q15(Q15::ONE);
assert!((acc2.to_q8_8_saturating().round() - 49).abs() <= 1);
}
#[test]
fn q16_16_octave_shift() {
let f = Q16_16::from_int(440);
assert_eq!(f.shift_octave(1), Q16_16::from_int(880));
assert_eq!(f.shift_octave(-1), Q16_16::from_int(220));
}
#[test]
fn q16_16_octave_saturates() {
let f = Q16_16::from_int(40000);
assert_eq!(f.shift_octave(2), Q16_16::from_raw(u32::MAX));
}
}