use super::*;
use crate::{IScalarFxP, PhaseFxP};
use core::mem::transmute;
use core::option::Option;
use rand::{rngs::SmallRng, RngCore, SeedableRng};
const RANDOM_SEED: u64 = 0xce607a9d25ec3d88u64;
pub(crate) mod detail {
use super::*;
pub trait LfoOps: crate::DspFormatBase {
fn phase_per_smp(context: &Self::Context, frequency: Self::LfoFreq) -> Self::Phase;
fn calc_lfo(
phase: Self::Phase,
wave: lfo::LfoWave,
rands: &[Self::Sample; 2],
) -> Self::Sample;
}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct LfoOptions {
bits: u16,
}
impl LfoOptions {
const BIPOLAR: u16 = 1 << 8;
const RETRIGGER: u16 = 1 << 9;
pub fn wave(&self) -> Option<LfoWave> {
let value = (self.bits & 0xFF) as u8;
LfoWave::try_from(value).ok()
}
pub fn bipolar(&self) -> bool {
self.bits & Self::BIPOLAR != 0
}
pub fn retrigger(&self) -> bool {
self.bits & Self::RETRIGGER != 0
}
pub fn new(wave: LfoWave, bipolar: bool, retrigger: bool) -> Self {
LfoOptions {
bits: (wave as u16)
| if bipolar { Self::BIPOLAR } else { 0 }
| if retrigger { Self::RETRIGGER } else { 0 },
}
}
}
impl Default for LfoOptions {
fn default() -> Self {
Self::new(LfoWave::Sine, true, true)
}
}
#[derive(Default, Clone, Copy)]
#[repr(u8)]
pub enum LfoWave {
#[default]
Sine,
Square,
Triangle,
Saw,
SampleHold,
SampleGlide,
}
impl LfoWave {
const ELEM: [LfoWave; 6] = [
Self::Sine,
Self::Square,
Self::Triangle,
Self::Saw,
Self::SampleHold,
Self::SampleGlide,
];
pub const fn waves() -> &'static [LfoWave] {
&Self::ELEM
}
pub const fn to_str(&self) -> &'static str {
[
"Sine",
"Square",
"Triangle",
"Saw",
"Sample & Hold",
"Sample & Glide",
][*self as usize]
}
pub const fn to_str_short(&self) -> &'static str {
[
crate::util::SIN_CHARSTR,
crate::util::SQ_CHARSTR,
crate::util::TRI_CHARSTR,
crate::util::SAW_CHARSTR,
"S+H",
"S+G",
][*self as usize]
}
}
impl From<LfoWave> for &'static str {
fn from(value: LfoWave) -> Self {
value.to_str()
}
}
impl TryFrom<u8> for LfoWave {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, &'static str> {
if value >= LfoWave::Sine as u8 && value <= LfoWave::SampleGlide as u8 {
unsafe { Ok(transmute::<u8, LfoWave>(value)) }
} else {
Err("Conversion of u8 to LfoWave Overflowed")
}
}
}
#[derive(Default, Clone)]
pub struct LfoParams<T: DspFormatBase> {
pub freq: T::LfoFreq,
pub depth: T::Scalar,
pub opts: LfoOptions,
}
impl<T: DspFloat> From<&LfoParams<i16>> for LfoParams<T> {
fn from(value: &LfoParams<i16>) -> Self {
LfoParams::<T> {
freq: value.freq.to_num(),
depth: value.depth.to_num(),
opts: value.opts,
}
}
}
#[derive(Clone)]
pub struct Lfo<T: DspFormatBase + detail::LfoOps> {
rng: SmallRng,
phase: T::Phase,
rand_smps: [T::Sample; 2],
last_gate: bool,
}
impl<T: DspFormatBase + detail::LfoOps> Lfo<T> {
pub fn new(seed: u64) -> Self {
let mut retval = Self {
rng: SmallRng::seed_from_u64(seed),
phase: T::Phase::zero(),
rand_smps: [T::Sample::zero(); 2],
last_gate: false,
};
retval.update_rands();
retval.update_rands();
retval
}
fn update_rands(&mut self) {
self.rand_smps[1] = self.rand_smps[0];
let rand_num = self.rng.next_u32() & (u16::MAX as u32);
let rand_scalar = ScalarFxP::from_bits(rand_num as u16);
self.rand_smps[0] = T::sample_from_fixed(IScalarFxP::from_num(rand_scalar));
}
}
impl<T: DspFormat> Device<T> for Lfo<T> {
type Input = bool;
type Params = LfoParams<T>;
type Output = T::Sample;
fn next(&mut self, context: &T::Context, gate: bool, params: LfoParams<T>) -> T::Sample {
if params.opts.retrigger() && gate && !self.last_gate {
self.phase = T::Phase::zero();
}
self.last_gate = gate;
let mut value = T::calc_lfo(
self.phase,
params.opts.wave().unwrap_or_default(),
&self.rand_smps,
);
if !params.opts.bipolar() {
value = (value + T::Sample::one()).divide_by_two();
}
value = value.scale(params.depth);
self.phase = self.phase + T::phase_per_smp(context, params.freq);
if self.phase >= T::Phase::PI {
self.phase = self.phase - T::Phase::TAU;
self.update_rands();
}
value
}
}
impl<T: DspFormatBase + detail::LfoOps> Default for Lfo<T> {
fn default() -> Self {
Self::new(RANDOM_SEED)
}
}
impl detail::LfoOps for i16 {
fn calc_lfo(phase: PhaseFxP, wave: lfo::LfoWave, rands: &[SampleFxP; 2]) -> SampleFxP {
use crate::fixed_traits::Fixed16;
use crate::fixedmath::{cos_fixed, sin_fixed};
const TWO: SampleFxP = SampleFxP::lit("2");
let frac_2phase_pi = SampleFxP::from_num(phase).scale_fixed(ScalarFxP::FRAC_2_PI);
match wave {
LfoWave::Saw => frac_2phase_pi.unwrapped_shr(1),
LfoWave::Square => {
if phase < 0 {
SampleFxP::NEG_ONE
} else {
SampleFxP::ONE
}
}
LfoWave::Triangle => {
if phase < PhaseFxP::FRAC_PI_2.unwrapped_neg() {
frac_2phase_pi.unwrapped_neg() - TWO
} else if phase > PhaseFxP::FRAC_PI_2 {
TWO - frac_2phase_pi
} else {
frac_2phase_pi
}
}
LfoWave::Sine => {
if phase < PhaseFxP::FRAC_PI_2.unwrapped_neg() {
cos_fixed(SampleFxP::from_num(phase + PhaseFxP::FRAC_PI_2)).unwrapped_neg()
} else if phase < PhaseFxP::FRAC_PI_2 {
cos_fixed(SampleFxP::from_num(phase - PhaseFxP::FRAC_PI_2))
} else {
sin_fixed(SampleFxP::from_num(phase))
}
}
LfoWave::SampleHold => rands[0],
LfoWave::SampleGlide => {
rands[0] + SampleFxP::multiply(frac_2phase_pi, rands[1] - rands[0])
}
}
}
fn phase_per_smp(context: &ContextFxP, frequency: Self::LfoFreq) -> Self::Phase {
PhaseFxP::from_num(
frequency.wide_mul(context.sample_rate.frac_2pi4096_sr()).unwrapped_shr(12),
)
}
}
impl<T: DspFloat> detail::LfoOps for T {
fn calc_lfo(phase: T, wave: lfo::LfoWave, rands: &[T; 2]) -> T {
let frac_2phase_pi = (phase + phase) / T::PI;
let pi_2 = T::FRAC_PI_2;
match wave {
LfoWave::Saw => frac_2phase_pi / T::TWO,
LfoWave::Square => {
if phase < T::ZERO {
T::ONE.neg()
} else {
T::ONE
}
}
LfoWave::Triangle => {
if phase < pi_2.neg() {
frac_2phase_pi.neg() - T::TWO
} else if phase > pi_2 {
T::TWO - frac_2phase_pi
} else {
frac_2phase_pi
}
}
LfoWave::Sine => {
if phase < pi_2.neg() {
T::fcos(phase + pi_2).neg()
} else if phase < pi_2 {
T::fcos(phase - pi_2)
} else {
T::fsin(phase)
}
}
LfoWave::SampleHold => rands[0],
LfoWave::SampleGlide => rands[0] + (frac_2phase_pi * (rands[1] - rands[0])),
}
}
fn phase_per_smp(context: &Context<T>, frequency: T) -> T {
(frequency * T::TAU) / context.sample_rate
}
}