use std::f32::consts::PI;
const SUBBANDS: usize = 32;
const FIFO_SIZE: usize = 1024;
pub struct SynthesisFilter {
fifo: [[f32; FIFO_SIZE]; 2],
offset: usize,
window: [f32; 512],
cos_table: [[f32; 64]; 32],
}
impl Default for SynthesisFilter {
fn default() -> Self {
Self::new()
}
}
impl SynthesisFilter {
#[must_use]
pub fn new() -> Self {
let mut filter = Self {
fifo: [[0.0; FIFO_SIZE]; 2],
offset: 0,
window: [0.0; 512],
cos_table: [[0.0; 64]; 32],
};
filter.init_tables();
filter
}
fn init_tables(&mut self) {
for i in 0..512 {
#[allow(clippy::cast_precision_loss)]
let n = i as f32;
let angle = PI / 512.0 * (n + 0.5);
self.window[i] = angle.sin();
}
for i in 0..32 {
for k in 0..64 {
#[allow(clippy::cast_precision_loss)]
let angle = PI / 64.0 * f32::from((2 * k + 1) as u8) * f32::from((16 + i) as u8);
self.cos_table[i][k] = angle.cos();
}
}
}
pub fn synthesize(&mut self, samples: &[f32], channel: usize, output: &mut [f32]) {
debug_assert!(samples.len() >= SUBBANDS);
debug_assert!(output.len() >= SUBBANDS);
let mut v = [0.0f32; 64];
for i in 0..32 {
let mut sum = 0.0f32;
for k in 0..32 {
sum += samples[k] * self.cos_table[k][i * 2];
}
v[i] = sum;
v[i + 32] = sum;
}
let offset = self.offset;
for (i, &val) in v.iter().enumerate() {
self.fifo[channel][(offset + i) % FIFO_SIZE] = val;
}
let mut u = [0.0f32; 512];
for i in 0..8 {
for j in 0..32 {
let idx = (offset + i * 64 + j) % FIFO_SIZE;
u[i * 64 + j] = self.fifo[channel][idx] * self.window[i * 64 + j];
}
for j in 0..32 {
let idx = (offset + i * 64 + j + 32) % FIFO_SIZE;
u[i * 64 + j + 32] = self.fifo[channel][idx] * self.window[i * 64 + j + 32];
}
}
for i in 0..32 {
let mut sum = 0.0f32;
for j in 0..16 {
sum += u[i + j * 32];
}
output[i] = sum;
}
self.offset = (self.offset + 64) % FIFO_SIZE;
}
pub fn reset(&mut self) {
self.fifo = [[0.0; FIFO_SIZE]; 2];
self.offset = 0;
}
#[must_use]
pub const fn offset(&self) -> usize {
self.offset
}
}
pub struct SimpleSynthesis {
overlap: [[f32; 16]; 2],
}
impl Default for SimpleSynthesis {
fn default() -> Self {
Self::new()
}
}
impl SimpleSynthesis {
#[must_use]
pub const fn new() -> Self {
Self {
overlap: [[0.0; 16]; 2],
}
}
pub fn synthesize(&mut self, input: &[f32], channel: usize, output: &mut [f32]) {
debug_assert!(input.len() >= 32);
debug_assert!(output.len() >= 32);
for i in 0..16 {
output[i] = input[i] + self.overlap[channel][i];
self.overlap[channel][i] = input[i + 16];
}
for i in 16..32 {
output[i] = input[i];
}
}
pub fn reset(&mut self) {
self.overlap = [[0.0; 16]; 2];
}
}
#[allow(clippy::cast_precision_loss)]
pub fn dct_synthesis(input: &[f32], output: &mut [f32]) {
debug_assert!(input.len() >= 32);
debug_assert!(output.len() >= 32);
for k in 0..32 {
let mut sum = 0.0f32;
for n in 0..32 {
let angle = PI / 32.0 * f32::from((k + 1) as u8) * (f32::from(n as u8) + 0.5);
sum += input[n] * angle.cos();
}
output[k] = sum * (2.0 / 32.0f32).sqrt();
}
}
pub fn apply_deemphasis(samples: &mut [f32], emphasis: bool, state: &mut f32) {
if !emphasis {
return;
}
const COEF: f32 = 0.95;
for sample in samples.iter_mut() {
*sample += *state * COEF;
*state = *sample;
}
}
pub fn normalize_samples(samples: &mut [f32]) {
let mut peak = 0.0f32;
for &sample in samples.iter() {
peak = peak.max(sample.abs());
}
if peak > 1.0 {
let scale = 1.0 / peak;
for sample in samples.iter_mut() {
*sample *= scale;
}
}
}
pub fn interleave_stereo(left: &[f32], right: &[f32], output: &mut [f32]) {
debug_assert!(output.len() >= left.len() + right.len());
debug_assert!(left.len() == right.len());
for (i, (&l, &r)) in left.iter().zip(right.iter()).enumerate() {
output[i * 2] = l;
output[i * 2 + 1] = r;
}
}
pub fn convert_to_i16(input: &[f32], output: &mut [i16]) {
debug_assert!(input.len() == output.len());
for (inp, out) in input.iter().zip(output.iter_mut()) {
let clamped = inp.clamp(-1.0, 1.0);
#[allow(clippy::cast_possible_truncation)]
let scaled = (clamped * 32767.0) as i16;
*out = scaled;
}
}
pub fn convert_to_f32(input: &[f32], output: &mut [f32]) {
debug_assert!(input.len() == output.len());
for (inp, out) in input.iter().zip(output.iter_mut()) {
*out = inp.clamp(-1.0, 1.0);
}
}