use crate::error::check_count;
use crate::numeric::fixed::{Fixed, Q14};
use crate::Error;
const HISTORY_FRAMES: usize = 192_000;
const FADE_FRAMES: usize = 200;
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(rename_all = "camelCase")
)]
pub struct AlienConfig {
pub enabled: bool,
pub loop_frames: u32,
}
impl Default for AlienConfig {
fn default() -> Self {
Self {
enabled: false,
loop_frames: 95_999,
}
}
}
impl AlienConfig {
pub fn validate(self) -> Result<(), Error> {
check_count("alien.loop_frames", i64::from(self.loop_frames), 1, 96_000)
}
}
#[derive(Debug)]
pub(crate) struct Alien {
config: AlienConfig,
buffer: Vec<[Q14; 2]>,
read: usize,
phase: usize,
loop_len: usize,
}
impl Alien {
pub(crate) fn new(config: AlienConfig) -> Self {
Self {
config,
buffer: vec![[Q14::default(); 2]; HISTORY_FRAMES],
read: 0,
phase: 0,
loop_len: config.loop_frames as usize,
}
}
pub(crate) fn set_config(&mut self, config: AlienConfig) {
self.config = config;
self.loop_len = config.loop_frames as usize;
self.phase = self.phase.min(self.loop_len - 1);
}
pub(crate) fn loop_state(&self) -> (usize, usize) {
(self.loop_len, self.phase)
}
pub(crate) fn reset(&mut self) {
self.buffer.fill([Q14::default(); 2]);
self.read = 0;
self.phase = 0;
}
pub(crate) fn process_sample(&mut self, input: [Q14; 2]) -> [Q14; 2] {
self.buffer[self.read] = input;
let result = if self.config.enabled {
let read = self.read + HISTORY_FRAMES - self.phase * 2;
let read = if read >= HISTORY_FRAMES {
read - HISTORY_FRAMES
} else {
read
};
let distance = self.phase.min(self.loop_len - self.phase);
let mut result = self.buffer[read];
if distance < FADE_FRAMES {
let gain: Q14 = Fixed::<0>::from_bits(distance as i32)
.div(Fixed::<0>::from_bits(FADE_FRAMES as i32));
result = result.map(|sample| sample.mul::<14, 14>(gain));
}
result
} else {
input
};
self.read += 1;
if self.read == HISTORY_FRAMES {
self.read = 0;
}
self.phase += 1;
if self.phase == self.loop_len {
self.phase = 0;
}
result
}
}
pub(crate) fn tail_frames(config: AlienConfig) -> usize {
if config.enabled {
2 * config.loop_frames as usize + FADE_FRAMES
} else {
0
}
}