#[cfg(feature = "simd")]
use std::simd::StdFloat;
use bbx_core::random::XorShiftRng;
#[cfg(feature = "simd")]
use crate::polyblep::{apply_polyblamp_triangle, apply_polyblep_pulse, apply_polyblep_saw, apply_polyblep_square};
#[cfg(feature = "simd")]
use crate::sample::SIMD_LANES;
use crate::{
polyblep::{polyblamp_triangle, polyblep_pulse, polyblep_saw, polyblep_square},
sample::Sample,
};
#[derive(Debug, Clone, Copy)]
pub enum Waveform {
Sine,
Square,
Sawtooth,
Triangle,
Pulse,
Noise,
}
pub(crate) const DEFAULT_DUTY_CYCLE: f64 = 0.5;
#[cfg(feature = "simd")]
fn generate_naive_samples_simd<S: Sample>(
waveform: Waveform,
phases: S::Simd,
duty_cycle: S,
two_pi: S::Simd,
inv_two_pi: S::Simd,
) -> Option<[S; SIMD_LANES]> {
match waveform {
Waveform::Sine => Some(S::simd_to_array(phases.sin())),
Waveform::Square => {
let half = S::simd_splat(S::from_f64(0.5));
let one = S::simd_splat(S::ONE);
let neg_one = S::simd_splat(-S::ONE);
let normalized = (phases % two_pi) * inv_two_pi;
Some(S::simd_to_array(S::simd_select_lt(normalized, half, one, neg_one)))
}
Waveform::Sawtooth => {
let two = S::simd_splat(S::from_f64(2.0));
let one = S::simd_splat(S::ONE);
let normalized = (phases % two_pi) * inv_two_pi;
Some(S::simd_to_array(two * normalized - one))
}
Waveform::Triangle => {
let half = S::simd_splat(S::from_f64(0.5));
let four = S::simd_splat(S::from_f64(4.0));
let one = S::simd_splat(S::ONE);
let three = S::simd_splat(S::from_f64(3.0));
let normalized = (phases % two_pi) * inv_two_pi;
let rising = four * normalized - one;
let falling = three - four * normalized;
Some(S::simd_to_array(S::simd_select_lt(normalized, half, rising, falling)))
}
Waveform::Pulse => {
let duty = S::simd_splat(duty_cycle);
let one = S::simd_splat(S::ONE);
let neg_one = S::simd_splat(-S::ONE);
let normalized = (phases % two_pi) * inv_two_pi;
Some(S::simd_to_array(S::simd_select_lt(normalized, duty, one, neg_one)))
}
Waveform::Noise => None,
}
}
pub(crate) fn generate_waveform_sample(
waveform: Waveform,
phase: f64,
phase_increment: f64,
duty_cycle: f64,
rng: &mut XorShiftRng,
) -> f64 {
let normalized_phase = (phase % <f64 as Sample>::TAU) * <f64 as Sample>::INV_TAU;
let normalized_inc = phase_increment * <f64 as Sample>::INV_TAU;
match waveform {
Waveform::Sine => phase.sin(),
Waveform::Sawtooth => polyblep_saw(normalized_phase, normalized_inc),
Waveform::Square => polyblep_square(normalized_phase, normalized_inc),
Waveform::Pulse => polyblep_pulse(normalized_phase, normalized_inc, duty_cycle),
Waveform::Triangle => polyblamp_triangle(normalized_phase, normalized_inc),
Waveform::Noise => rng.next_noise_sample(),
}
}
pub(crate) fn process_waveform_scalar<S: Sample>(
output: &mut [S],
waveform: Waveform,
phase: &mut f64,
phase_increment: f64,
rng: &mut XorShiftRng,
scale: f64,
) {
for sample in output.iter_mut() {
let value = generate_waveform_sample(waveform, *phase, phase_increment, DEFAULT_DUTY_CYCLE, rng);
*sample = S::from_f64(value * scale);
*phase += phase_increment;
}
*phase = phase.rem_euclid(<f64 as Sample>::TAU);
}
#[cfg(feature = "simd")]
pub(crate) fn generate_waveform_samples_simd<S: Sample>(
waveform: Waveform,
phases: S::Simd,
phases_normalized: [S; SIMD_LANES],
phase_inc_normalized: S,
duty_cycle: S,
two_pi: S::Simd,
inv_two_pi: S::Simd,
) -> Option<[S; SIMD_LANES]> {
let mut samples = generate_naive_samples_simd(waveform, phases, duty_cycle, two_pi, inv_two_pi)?;
match waveform {
Waveform::Sine | Waveform::Noise => {}
Waveform::Sawtooth => {
apply_polyblep_saw(&mut samples, phases_normalized, phase_inc_normalized);
}
Waveform::Square => {
apply_polyblep_square(&mut samples, phases_normalized, phase_inc_normalized);
}
Waveform::Pulse => {
apply_polyblep_pulse(&mut samples, phases_normalized, phase_inc_normalized, duty_cycle);
}
Waveform::Triangle => {
apply_polyblamp_triangle(&mut samples, phases_normalized, phase_inc_normalized);
}
}
Some(samples)
}