#[cfg(feature = "wide-backend")]
const TWENTY_OVER_LOG2_10: f32 = 6.020_6;
#[inline]
pub fn db_to_linear_block(out: &mut [f32], src: &[f32]) {
#[cfg(feature = "wide-backend")]
{
use wide::f32x8;
let n = out.len().min(src.len());
let n8 = n / 8 * 8;
let scale = f32x8::splat(core::f32::consts::LN_10 / 20.0);
let (head_out, tail_out) = out[..n].split_at_mut(n8);
for (out_chunk, src_chunk) in head_out.chunks_exact_mut(8).zip(src[..n8].chunks_exact(8)) {
let v = f32x8::from(<[f32; 8]>::try_from(src_chunk).unwrap_or_default());
out_chunk.copy_from_slice((v * scale).exp().as_array_ref());
}
db_to_linear_block_scalar(tail_out, &src[n8..n]);
}
#[cfg(not(feature = "wide-backend"))]
db_to_linear_block_scalar(out, src);
}
#[inline]
pub fn db_to_linear_block_scalar(out: &mut [f32], src: &[f32]) {
let n = out.len().min(src.len());
for i in 0..n {
out[i] = 10.0_f32.powf(src[i] / 20.0);
}
}
#[inline]
pub fn linear_to_db_block(out: &mut [f32], src: &[f32]) {
#[cfg(feature = "wide-backend")]
{
use wide::f32x8;
let n = out.len().min(src.len());
let n8 = n / 8 * 8;
let scale = f32x8::splat(TWENTY_OVER_LOG2_10);
let (head_out, tail_out) = out[..n].split_at_mut(n8);
for (out_chunk, src_chunk) in head_out.chunks_exact_mut(8).zip(src[..n8].chunks_exact(8)) {
let v = f32x8::from(<[f32; 8]>::try_from(src_chunk).unwrap_or_default());
out_chunk.copy_from_slice((v.log2() * scale).as_array_ref());
}
linear_to_db_block_scalar(tail_out, &src[n8..n]);
}
#[cfg(not(feature = "wide-backend"))]
linear_to_db_block_scalar(out, src);
}
#[inline]
pub fn linear_to_db_block_scalar(out: &mut [f32], src: &[f32]) {
let n = out.len().min(src.len());
for i in 0..n {
out[i] = 20.0 * src[i].log10();
}
}
#[inline]
pub fn exp2_block(out: &mut [f32], src: &[f32]) {
#[cfg(feature = "wide-backend")]
{
use wide::f32x8;
let n = out.len().min(src.len());
let n8 = n / 8 * 8;
let ln2 = f32x8::splat(core::f32::consts::LN_2);
let (head_out, tail_out) = out[..n].split_at_mut(n8);
for (out_chunk, src_chunk) in head_out.chunks_exact_mut(8).zip(src[..n8].chunks_exact(8)) {
let v = f32x8::from(<[f32; 8]>::try_from(src_chunk).unwrap_or_default());
out_chunk.copy_from_slice((v * ln2).exp().as_array_ref());
}
exp2_block_scalar(tail_out, &src[n8..n]);
}
#[cfg(not(feature = "wide-backend"))]
exp2_block_scalar(out, src);
}
#[inline]
pub fn exp2_block_scalar(out: &mut [f32], src: &[f32]) {
let n = out.len().min(src.len());
for i in 0..n {
out[i] = src[i].exp2();
}
}
#[inline]
pub fn log2_block(out: &mut [f32], src: &[f32]) {
#[cfg(feature = "wide-backend")]
{
use wide::f32x8;
let n = out.len().min(src.len());
let n8 = n / 8 * 8;
let (head_out, tail_out) = out[..n].split_at_mut(n8);
for (out_chunk, src_chunk) in head_out.chunks_exact_mut(8).zip(src[..n8].chunks_exact(8)) {
let v = f32x8::from(<[f32; 8]>::try_from(src_chunk).unwrap_or_default());
out_chunk.copy_from_slice(v.log2().as_array_ref());
}
log2_block_scalar(tail_out, &src[n8..n]);
}
#[cfg(not(feature = "wide-backend"))]
log2_block_scalar(out, src);
}
#[inline]
pub fn log2_block_scalar(out: &mut [f32], src: &[f32]) {
let n = out.len().min(src.len());
for i in 0..n {
out[i] = src[i].log2();
}
}
#[inline]
pub fn tanh_block(out: &mut [f32], src: &[f32]) {
#[cfg(feature = "wide-backend")]
{
use wide::f32x8;
let n = out.len().min(src.len());
let n8 = n / 8 * 8;
let bound = f32x8::splat(10.0);
let neg_bound = f32x8::splat(-10.0);
let two = f32x8::splat(2.0);
let one = f32x8::splat(1.0);
let (head_out, tail_out) = out[..n].split_at_mut(n8);
for (out_chunk, src_chunk) in head_out.chunks_exact_mut(8).zip(src[..n8].chunks_exact(8)) {
let x = f32x8::from(<[f32; 8]>::try_from(src_chunk).unwrap_or_default());
let x_clamped = x.fast_max(neg_bound).fast_min(bound);
let e2x = (x_clamped * two).exp();
let result = (e2x - one) / (e2x + one);
out_chunk.copy_from_slice(result.as_array_ref());
}
tanh_block_scalar(tail_out, &src[n8..n]);
}
#[cfg(not(feature = "wide-backend"))]
tanh_block_scalar(out, src);
}
#[inline]
pub fn tanh_block_scalar(out: &mut [f32], src: &[f32]) {
let n = out.len().min(src.len());
for i in 0..n {
out[i] = src[i].tanh();
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp, clippy::cast_precision_loss)]
use super::*;
fn max_abs_err(a: &[f32], b: &[f32]) -> f32 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs())
.fold(0.0_f32, f32::max)
}
fn max_rel_err(a: &[f32], b: &[f32]) -> f32 {
a.iter()
.zip(b.iter())
.filter(|(_, y)| y.abs() > 1e-6)
.map(|(x, y)| ((x - y) / y).abs())
.fold(0.0_f32, f32::max)
}
#[test]
fn db_to_linear_block_matches_libm() {
let src: Vec<f32> = (-120..=24).map(|i| i as f32).collect();
let mut out = vec![0.0; src.len()];
db_to_linear_block(&mut out, &src);
let expected: Vec<f32> = src.iter().map(|&x| 10.0_f32.powf(x / 20.0)).collect();
assert!(
max_rel_err(&out, &expected) < 1e-5,
"rel err = {}",
max_rel_err(&out, &expected)
);
}
#[test]
fn linear_to_db_round_trips() {
let db: Vec<f32> = (-100..=20).map(|i| i as f32).collect();
let mut lin = vec![0.0; db.len()];
let mut roundtrip = vec![0.0; db.len()];
db_to_linear_block(&mut lin, &db);
linear_to_db_block(&mut roundtrip, &lin);
let err = max_abs_err(&db, &roundtrip);
assert!(err < 1e-4, "round-trip err = {err} dB");
}
#[test]
fn exp2_block_matches_libm() {
let src: Vec<f32> = (-100..=100).map(|i| i as f32 * 0.1).collect();
let mut out = vec![0.0; src.len()];
exp2_block(&mut out, &src);
let expected: Vec<f32> = src.iter().map(|&x| x.exp2()).collect();
assert!(
max_rel_err(&out, &expected) < 1e-5,
"rel err = {}",
max_rel_err(&out, &expected)
);
}
#[test]
fn log2_block_matches_libm() {
let src: Vec<f32> = (1..=200).map(|i| i as f32).collect();
let mut out = vec![0.0; src.len()];
log2_block(&mut out, &src);
let expected: Vec<f32> = src.iter().map(|&x| x.log2()).collect();
assert!(
max_abs_err(&out, &expected) < 1e-5,
"abs err = {}",
max_abs_err(&out, &expected)
);
}
#[test]
fn tanh_block_matches_libm() {
let src: Vec<f32> = (-100..=100).map(|i| i as f32 * 0.1).collect();
let mut out = vec![0.0; src.len()];
tanh_block(&mut out, &src);
let expected: Vec<f32> = src.iter().map(|&x| x.tanh()).collect();
let err = max_abs_err(&out, &expected);
assert!(err < 5e-6, "abs err = {err}");
}
#[test]
fn tanh_block_saturates_for_large_inputs() {
let src = [-50.0, -20.0, 20.0, 50.0];
let mut out = [0.0; 4];
tanh_block(&mut out, &src);
for &y in &out {
assert!(
(y.abs() - 1.0).abs() < 1e-4,
"expected saturation near ±1, got {y}"
);
}
}
#[test]
fn lengths_min_clamped() {
let src = [1.0_f32, 2.0, 3.0];
let mut out = [0.0_f32; 5];
db_to_linear_block(&mut out, &src);
assert_eq!(out[3], 0.0);
assert_eq!(out[4], 0.0);
}
}