torque-tracker-engine 0.1.0

Audio Backend for Torque Tracker, an old school music tracker
Documentation
use core::slice;
use std::{array, ops::IndexMut};

use dasp::sample::ToSample;

pub(crate) mod instrument;
pub mod playback;
pub(crate) mod sample;

#[repr(transparent)]
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub struct Frame([f32; 2]);

impl std::ops::AddAssign for Frame {
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}

impl std::ops::Add for Frame {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self([self.0[0] + rhs.0[0], self.0[1] + rhs.0[1]])
    }
}

impl std::ops::Sub for Frame {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        Self([self.0[0] - rhs.0[0], self.0[1] - rhs.0[1]])
    }
}

impl std::ops::SubAssign for Frame {
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl std::ops::MulAssign<f32> for Frame {
    fn mul_assign(&mut self, rhs: f32) {
        *self.0.index_mut(0) *= rhs;
        *self.0.index_mut(1) *= rhs;
    }
}

impl std::ops::Mul<f32> for Frame {
    type Output = Self;

    fn mul(self, rhs: f32) -> Self::Output {
        Self([self.0[0] * rhs, self.0[1] * rhs])
    }
}

impl std::iter::Sum for Frame {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.reduce(|acc, x| acc + x).unwrap_or_default()
    }
}

impl From<[f32; 2]> for Frame {
    fn from(value: [f32; 2]) -> Self {
        Self(value)
    }
}

impl From<f32> for Frame {
    fn from(value: f32) -> Self {
        Self([value, value])
    }
}

impl Frame {
    // split into left and right.
    pub fn split_array<const N: usize>(value: [Frame; N]) -> ([f32; N], [f32; N]) {
        (
            array::from_fn(|i| value[i].0[0]),
            array::from_fn(|i| value[i].0[1]),
        )
    }

    pub fn sum_to_mono(self) -> f32 {
        self.0[0] + self.0[1]
    }

    pub fn from_mut<'a>(value: &'a mut [f32; 2]) -> &'a mut Self {
        // SAFETY: lifetime is specified, both mut, Self is repr(transparent).
        unsafe { std::mem::transmute::<&'a mut [f32; 2], &'a mut Self>(value) }
    }

    pub fn from_interleaved(value: &[f32]) -> &[Frame] {
        debug_assert!(value.len().rem_euclid(2) == 0);
        let len = value.len() / 2;
        let ptr = value.as_ptr().cast();
        // SAFETY: keeps the same lifetime and mutability.
        // [f32; 2] is a valid frame
        unsafe { slice::from_raw_parts(ptr, len) }
    }

    pub fn from_ref<'a>(value: &'a [f32; 2]) -> &'a Self {
        // SAFETY: lifetime is specified, both not mut, Self is repr(transparent).
        unsafe { std::mem::transmute::<&'a [f32; 2], &'a Self>(value) }
    }

    pub fn to_sample<S: dasp::sample::FromSample<f32>>(self) -> [S; 2] {
        [self.0[0].to_sample_(), self.0[1].to_sample_()]
    }

    pub fn to_raw<'a>(into: &mut [Self]) -> &'a mut [[f32; 2]] {
        unsafe { std::mem::transmute(into) }
    }

    // pan laws taken from: https://www.cs.cmu.edu/~music/icm-online/readings/panlaws/index.html

    // /// angle in radians between 0 and 90°
    // pub fn pan_linear(&mut self, angle: f32) {
    //     self.0[0] *= (std::f32::consts::FRAC_PI_2 - angle) * std::f32::consts::FRAC_2_PI;
    //     self.0[1] *= angle * std::f32::consts::FRAC_2_PI;
    // }

    /// angle in radians between 0 and 90°
    pub fn pan_constant_power(&mut self, angle: f32) {
        self.0[0] *= angle.cos();
        self.0[1] *= angle.sin();
    }

    // /// angle in radians between 0 and 90°
    // pub fn pan_compromise(&mut self, angle: f32) {
    //     self.0[0] *= f32::sqrt(
    //         (std::f32::consts::FRAC_PI_2 - angle) * std::f32::consts::FRAC_2_PI * angle.cos(),
    //     );
    //     self.0[1] *= f32::sqrt(angle * std::f32::consts::FRAC_2_PI * angle.sin());
    // }
}