Retour sur pmoaudio

This commit is contained in:
2025-11-01 21:10:57 +01:00
parent ec0a0e675e
commit 6bce26c4fc
26 changed files with 4481 additions and 1048 deletions

View File

@@ -0,0 +1,94 @@
/// Applique un gain (en dB) sur des échantillons stéréo interleavés `[L,R]`
/// codés sur 16 bits signés.
pub fn apply_gain_stereo_i16(samples: &mut [[i16; 2]], gain_db: f64) {
let gain = 10f64.powf(gain_db / 20.0);
let g_q15 = (gain * (1u32 << 15) as f64).round() as i16;
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
unsafe {
apply_gain_stereo_i16_neon(samples, g_q15);
return;
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
unsafe {
apply_gain_stereo_i16_avx2(samples, g_q15);
return;
}
// Fallback scalaire
#[cfg(not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(target_arch = "x86_64", target_feature = "avx2")
)))]
{
apply_gain_stereo_i16_scalar(samples, g_q15);
}
}
#[cfg(not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(target_arch = "x86_64", target_feature = "avx2")
)))]
#[inline(always)]
fn apply_gain_stereo_i16_scalar(samples: &mut [[i16; 2]], g_q15: i16) {
for frame in samples.iter_mut() {
// L
let prod_l = (frame[0] as i32 * g_q15 as i32 + (1 << 14)) >> 15;
frame[0] = prod_l.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
// R
let prod_r = (frame[1] as i32 * g_q15 as i32 + (1 << 14)) >> 15;
frame[1] = prod_r.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
#[inline(always)]
unsafe fn apply_gain_stereo_i16_neon(samples: &mut [[i16; 2]], g_q15: i16) {
use core::arch::aarch64::*;
let gvec = vdupq_n_s16(g_q15);
let mut i = 0;
let n = samples.len() * 2;
let ptr = samples.as_mut_ptr() as *mut i16;
while i + 8 <= n {
let v = vld1q_s16(ptr.add(i));
let res = vqdmulhq_s16(v, gvec); // Q15 multiply high
vst1q_s16(ptr.add(i), res);
i += 8;
}
// reste scalaire
let slice = std::slice::from_raw_parts_mut(ptr.add(i), n - i);
apply_gain_i16_scalar(slice, g_q15);
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
#[inline(always)]
unsafe fn apply_gain_stereo_i16_avx2(samples: &mut [[i16; 2]], g_q15: i16) {
use core::arch::x86_64::*;
let g = _mm256_set1_epi16(g_q15 as i16);
let mut i = 0;
let n = samples.len() * 2;
let ptr = samples.as_mut_ptr() as *mut i16;
while i + 16 <= n {
let x = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
let hi = _mm256_mulhi_epi16(x, g);
_mm256_storeu_si256(ptr.add(i) as *mut __m256i, hi);
i += 16;
}
// reste scalaire
let slice = std::slice::from_raw_parts_mut(ptr.add(i), n - i);
apply_gain_i16_scalar(slice, g_q15);
}
/// version mono utilisée pour le reste scalaire
#[inline(always)]
fn apply_gain_i16_scalar(samples: &mut [i16], g_q15: i16) {
for s in samples.iter_mut() {
let prod = (*s as i32 * g_q15 as i32 + (1 << 14)) >> 15;
*s = prod.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
}
}

View File

@@ -0,0 +1,99 @@
use crate::I24;
/// Applique un gain (en dB) sur des échantillons stéréo interleavés `[L,R]`
/// codés sur 24 bits signés (`I24`).
pub fn apply_gain_stereo_i24(samples: &mut [[I24; 2]], gain_db: f64) {
let gain = 10f64.powf(gain_db / 20.0);
// Q23 scaling
let g_q23 = (gain * (1u64 << 23) as f64).round() as i32;
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
unsafe {
apply_gain_stereo_i24_neon(samples, g_q23);
return;
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
unsafe {
apply_gain_stereo_i24_avx2(samples, g_q23);
return;
}
// Fallback scalaire
#[cfg(not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(target_arch = "x86_64", target_feature = "avx2")
)))]
{
apply_gain_stereo_i24_scalar(samples, g_q23);
}
}
#[cfg(not(any(
all(target_arch = "aarch64", target_feature = "neon"),
all(target_arch = "x86_64", target_feature = "avx2")
)))]
#[inline(always)]
fn apply_gain_stereo_i24_scalar(samples: &mut [[I24; 2]], g_q23: i32) {
for frame in samples.iter_mut() {
// L
let prod_l = (frame[0].as_i32() as i64 * g_q23 as i64 + (1 << 22)) >> 23;
let clamped_l = prod_l.clamp(I24::MIN_VALUE as i64, I24::MAX_VALUE as i64) as i32;
frame[0] = I24::new_clamped(clamped_l);
// R
let prod_r = (frame[1].as_i32() as i64 * g_q23 as i64 + (1 << 22)) >> 23;
let clamped_r = prod_r.clamp(I24::MIN_VALUE as i64, I24::MAX_VALUE as i64) as i32;
frame[1] = I24::new_clamped(clamped_r);
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
#[inline(always)]
unsafe fn apply_gain_stereo_i24_neon(samples: &mut [[I24; 2]], g_q23: i32) {
use core::arch::aarch64::*;
let gvec = vdupq_n_s32(g_q23);
let mut i = 0;
let n = samples.len() * 2;
let ptr = samples.as_mut_ptr() as *mut i32;
while i + 4 <= n {
let v = vld1q_s32(ptr.add(i));
let res = vqdmulhq_s32(v, gvec); // Q23 multiply high
vst1q_s32(ptr.add(i), res);
i += 4;
}
// reste scalaire
let slice = std::slice::from_raw_parts_mut(ptr.add(i), n - i);
apply_gain_i24_scalar(slice, g_q23);
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
#[inline(always)]
unsafe fn apply_gain_stereo_i24_avx2(samples: &mut [[I24; 2]], g_q23: i32) {
use core::arch::x86_64::*;
let g = _mm256_set1_epi32(g_q23);
let mut i = 0;
let n = samples.len() * 2;
let ptr = samples.as_mut_ptr() as *mut i32;
while i + 8 <= n {
let x = _mm256_loadu_si256(ptr.add(i) as *const __m256i);
let hi = _mm256_mulhi_epi32(x, g);
_mm256_storeu_si256(ptr.add(i) as *mut __m256i, hi);
i += 8;
}
// reste scalaire
let slice = std::slice::from_raw_parts_mut(ptr.add(i), n - i);
apply_gain_i24_scalar(slice, g_q23);
}
/// Version mono utilisée pour le reste scalaire.
#[inline(always)]
fn apply_gain_i24_scalar(samples: &mut [i32], g_q23: i32) {
for s in samples.iter_mut() {
let prod = (*s as i64 * g_q23 as i64 + (1 << 22)) >> 23;
*s = prod.clamp(I24::MIN_VALUE as i64, I24::MAX_VALUE as i64) as i32;
}
}

View File

@@ -1,16 +1,16 @@
/// Applique un gain (en dB) sur des échantillons stéréo interleavés `[L,R]`.
pub fn apply_gain_stereo(samples: &mut [[i32; 2]], gain_db: f64) {
pub fn apply_gain_stereo_i32(samples: &mut [[i32; 2]], gain_db: f64) {
let gain = 10f64.powf(gain_db / 20.0);
let g_q31 = (gain * (1u64 << 31) as f64).round() as i32;
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
unsafe {
apply_gain_stereo_neon(samples, g_q31);
apply_gain_stereo_i32_neon(samples, g_q31);
return;
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
unsafe {
apply_gain_stereo_avx2(samples, g_q31);
apply_gain_stereo_i32_avx2(samples, g_q31);
return;
}
@@ -20,7 +20,7 @@ pub fn apply_gain_stereo(samples: &mut [[i32; 2]], gain_db: f64) {
all(target_arch = "x86_64", target_feature = "avx2")
)))]
{
apply_gain_stereo_scalar(samples, g_q31);
apply_gain_stereo_i32_scalar(samples, g_q31);
}
}
@@ -29,7 +29,7 @@ pub fn apply_gain_stereo(samples: &mut [[i32; 2]], gain_db: f64) {
all(target_arch = "x86_64", target_feature = "avx2")
)))]
#[inline(always)]
fn apply_gain_stereo_scalar(samples: &mut [[i32; 2]], g_q31: i32) {
fn apply_gain_stereo_i32_scalar(samples: &mut [[i32; 2]], g_q31: i32) {
for frame in samples.iter_mut() {
// L
let prod_l = (frame[0] as i64 * g_q31 as i64 + (1 << 30)) >> 31;
@@ -43,7 +43,7 @@ fn apply_gain_stereo_scalar(samples: &mut [[i32; 2]], g_q31: i32) {
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
#[inline(always)]
unsafe fn apply_gain_stereo_neon(samples: &mut [[i32; 2]], g_q31: i32) {
unsafe fn apply_gain_stereo_i32_neon(samples: &mut [[i32; 2]], g_q31: i32) {
use core::arch::aarch64::*;
let gvec = vdupq_n_s32(g_q31);
let mut i = 0;
@@ -59,12 +59,12 @@ unsafe fn apply_gain_stereo_neon(samples: &mut [[i32; 2]], g_q31: i32) {
// reste scalaire
let slice = std::slice::from_raw_parts_mut(ptr.add(i), n - i);
apply_gain_scalar(slice, g_q31);
apply_gain_i32_scalar(slice, g_q31);
}
#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
#[inline(always)]
unsafe fn apply_gain_stereo_avx2(samples: &mut [[i32; 2]], g_q31: i32) {
unsafe fn apply_gain_stereo_i32_avx2(samples: &mut [[i32; 2]], g_q31: i32) {
use core::arch::x86_64::*;
let g = _mm256_set1_epi32(g_q31);
let mut i = 0;
@@ -80,12 +80,12 @@ unsafe fn apply_gain_stereo_avx2(samples: &mut [[i32; 2]], g_q31: i32) {
// reste scalaire
let slice = std::slice::from_raw_parts_mut(ptr.add(i), n - i);
apply_gain_scalar(slice, g_q31);
apply_gain_i32_scalar(slice, g_q31);
}
/// version mono utilisée pour le reste scalaire
#[inline(always)]
fn apply_gain_scalar(samples: &mut [i32], g_q31: i32) {
fn apply_gain_i32_scalar(samples: &mut [i32], g_q31: i32) {
for s in samples.iter_mut() {
let prod = (*s as i64 * g_q31 as i64 + (1 << 30)) >> 31;
*s = prod.clamp(i32::MIN as i64, i32::MAX as i64) as i32;

View File

@@ -1,5 +1,5 @@
use bytemuck::{cast_slice, cast_slice_mut};
use crate::BitDepth;
use bytemuck::{cast_slice, cast_slice_mut};
#[cfg(feature = "simd")]
use std::simd::num::{SimdFloat, SimdInt};
@@ -183,3 +183,307 @@ pub fn interleaved_f32_to_i32_stereo(
let input_pairs: &[[f32; 2]] = cast_slice(input_interleaved);
pairs_f32_to_i32_stereo(input_pairs, left, right, bit_depth);
}
/* ====================== CONVERSIONS I16 ↔ F32 SIMD ====================== */
/// Convertit deux canaux i16 (L/R) en pairs f32 normalisées [-1.0, 1.0]
#[cfg(feature = "simd")]
fn i16_stereo_to_pairs_f32_inner(
left: &[i16],
right: &[i16],
out_pairs: &mut [[f32; 2]],
max_value: f32,
) {
debug_assert_eq!(left.len(), right.len());
debug_assert_eq!(out_pairs.len(), left.len());
const LANES: usize = 8;
type Vf32 = Simd<f32, LANES>;
type Vi32 = Simd<i32, LANES>;
let scale = Vf32::splat(1.0 / max_value);
let (l_chunks, l_tail) = left.as_chunks::<LANES>();
let (r_chunks, r_tail) = right.as_chunks::<LANES>();
let (o_chunks, o_tail) = out_pairs.as_chunks_mut::<LANES>();
for (k, o) in o_chunks.iter_mut().enumerate() {
// Charger i16, caster en i32 puis en f32
let l_arr: [i32; LANES] = std::array::from_fn(|i| l_chunks[k][i] as i32);
let r_arr: [i32; LANES] = std::array::from_fn(|i| r_chunks[k][i] as i32);
let l = Vi32::from_array(l_arr).cast::<f32>() * scale;
let r = Vi32::from_array(r_arr).cast::<f32>() * scale;
for j in 0..LANES {
unsafe {
*o.get_unchecked_mut(j) = [l[j], r[j]];
}
}
}
let scale_scalar = 1.0 / max_value;
for (dst, (&l, &r)) in o_tail.iter_mut().zip(l_tail.iter().zip(r_tail.iter())) {
dst[0] = l as f32 * scale_scalar;
dst[1] = r as f32 * scale_scalar;
}
}
#[cfg(not(feature = "simd"))]
fn i16_stereo_to_pairs_f32_inner(
left: &[i16],
right: &[i16],
out_pairs: &mut [[f32; 2]],
max_value: f32,
) {
debug_assert_eq!(left.len(), right.len());
debug_assert_eq!(out_pairs.len(), left.len());
let scale = 1.0 / max_value;
for ((out, &l), &r) in out_pairs.iter_mut().zip(left).zip(right) {
out[0] = l as f32 * scale;
out[1] = r as f32 * scale;
}
}
/// Convertit deux canaux i16 (L/R) en pairs f32 normalisées [-1.0, 1.0]
pub fn i16_stereo_to_pairs_f32(
left: &[i16],
right: &[i16],
out_pairs: &mut [[f32; 2]],
) {
i16_stereo_to_pairs_f32_inner(left, right, out_pairs, 32768.0);
}
/// Convertit pairs f32 normalisées [-1.0, 1.0] en deux canaux i16 (L/R)
#[cfg(feature = "simd")]
fn pairs_f32_to_i16_stereo_inner(
input_pairs: &[[f32; 2]],
left: &mut [i16],
right: &mut [i16],
max_value: f32,
) {
debug_assert_eq!(input_pairs.len(), left.len());
debug_assert_eq!(input_pairs.len(), right.len());
const LANES: usize = 8;
type Vf32 = Simd<f32, LANES>;
let vmin = -max_value;
let vmax_clamp = max_value - 1.0;
let vscale = Vf32::splat(max_value);
let vminv = Vf32::splat(vmin);
let vmaxv = Vf32::splat(vmax_clamp);
let (in_chunks, in_tail) = input_pairs.as_chunks::<LANES>();
let (l_chunks, l_tail) = left.as_chunks_mut::<LANES>();
let (r_chunks, r_tail) = right.as_chunks_mut::<LANES>();
for (k, blk) in in_chunks.iter().enumerate() {
let mut l_arr = [0.0f32; LANES];
let mut r_arr = [0.0f32; LANES];
for j in 0..LANES {
let p = blk[j];
l_arr[j] = p[0];
r_arr[j] = p[1];
}
let lq = (Vf32::from_array(l_arr) * vscale)
.simd_clamp(vminv, vmaxv)
.round()
.cast::<i32>();
let rq = (Vf32::from_array(r_arr) * vscale)
.simd_clamp(vminv, vmaxv)
.round()
.cast::<i32>();
for j in 0..LANES {
l_chunks[k][j] = lq[j] as i16;
r_chunks[k][j] = rq[j] as i16;
}
}
for (j, (l, r)) in in_tail.iter().zip(l_tail.iter_mut().zip(r_tail.iter_mut())) {
let lx = (j[0] * max_value).clamp(vmin, vmax_clamp).round();
let rx = (j[1] * max_value).clamp(vmin, vmax_clamp).round();
*l = lx as i16;
*r = rx as i16;
}
}
#[cfg(not(feature = "simd"))]
fn pairs_f32_to_i16_stereo_inner(
input_pairs: &[[f32; 2]],
left: &mut [i16],
right: &mut [i16],
max_value: f32,
) {
debug_assert_eq!(input_pairs.len(), left.len());
debug_assert_eq!(input_pairs.len(), right.len());
let vmin = -max_value;
let vmax_clamp = max_value - 1.0;
for (i, pair) in input_pairs.iter().enumerate() {
let lx = (pair[0] * max_value).clamp(vmin, vmax_clamp).round();
let rx = (pair[1] * max_value).clamp(vmin, vmax_clamp).round();
left[i] = lx as i16;
right[i] = rx as i16;
}
}
/// Convertit pairs f32 normalisées [-1.0, 1.0] en deux canaux i16 (L/R)
pub fn pairs_f32_to_i16_stereo(
input_pairs: &[[f32; 2]],
left: &mut [i16],
right: &mut [i16],
) {
pairs_f32_to_i16_stereo_inner(input_pairs, left, right, 32768.0);
}
/* ====================== CONVERSIONS I24 ↔ F32 SIMD ====================== */
/// Convertit deux canaux i32 (contenant des valeurs I24) en pairs f32 normalisées
#[cfg(feature = "simd")]
fn i24_as_i32_stereo_to_pairs_f32_inner(
left: &[i32],
right: &[i32],
out_pairs: &mut [[f32; 2]],
max_value: f32,
) {
debug_assert_eq!(left.len(), right.len());
debug_assert_eq!(out_pairs.len(), left.len());
const LANES: usize = 8;
type Vf32 = Simd<f32, LANES>;
type Vi32 = Simd<i32, LANES>;
let scale = Vf32::splat(1.0 / max_value);
let (l_chunks, l_tail) = left.as_chunks::<LANES>();
let (r_chunks, r_tail) = right.as_chunks::<LANES>();
let (o_chunks, o_tail) = out_pairs.as_chunks_mut::<LANES>();
for (k, o) in o_chunks.iter_mut().enumerate() {
let l = Vi32::from_slice(&l_chunks[k]).cast::<f32>() * scale;
let r = Vi32::from_slice(&r_chunks[k]).cast::<f32>() * scale;
for j in 0..LANES {
unsafe {
*o.get_unchecked_mut(j) = [l[j], r[j]];
}
}
}
let scale_scalar = 1.0 / max_value;
for (dst, (&l, &r)) in o_tail.iter_mut().zip(l_tail.iter().zip(r_tail.iter())) {
dst[0] = l as f32 * scale_scalar;
dst[1] = r as f32 * scale_scalar;
}
}
#[cfg(not(feature = "simd"))]
fn i24_as_i32_stereo_to_pairs_f32_inner(
left: &[i32],
right: &[i32],
out_pairs: &mut [[f32; 2]],
max_value: f32,
) {
debug_assert_eq!(left.len(), right.len());
debug_assert_eq!(out_pairs.len(), left.len());
let scale = 1.0 / max_value;
for ((out, &l), &r) in out_pairs.iter_mut().zip(left).zip(right) {
out[0] = l as f32 * scale;
out[1] = r as f32 * scale;
}
}
/// Convertit deux canaux i32 (contenant des valeurs I24) en pairs f32 normalisées
pub fn i24_as_i32_stereo_to_pairs_f32(
left: &[i32],
right: &[i32],
out_pairs: &mut [[f32; 2]],
) {
i24_as_i32_stereo_to_pairs_f32_inner(left, right, out_pairs, 8388608.0);
}
/// Convertit pairs f32 normalisées en deux canaux i32 (valeurs I24 range)
#[cfg(feature = "simd")]
fn pairs_f32_to_i24_as_i32_stereo_inner(
input_pairs: &[[f32; 2]],
left: &mut [i32],
right: &mut [i32],
max_value: f32,
) {
debug_assert_eq!(input_pairs.len(), left.len());
debug_assert_eq!(input_pairs.len(), right.len());
const LANES: usize = 8;
type Vf32 = Simd<f32, LANES>;
let vmin = -max_value;
let vmax_clamp = max_value - 1.0;
let vscale = Vf32::splat(max_value);
let vminv = Vf32::splat(vmin);
let vmaxv = Vf32::splat(vmax_clamp);
let (in_chunks, in_tail) = input_pairs.as_chunks::<LANES>();
let (l_chunks, l_tail) = left.as_chunks_mut::<LANES>();
let (r_chunks, r_tail) = right.as_chunks_mut::<LANES>();
for (k, blk) in in_chunks.iter().enumerate() {
let mut l_arr = [0.0f32; LANES];
let mut r_arr = [0.0f32; LANES];
for j in 0..LANES {
let p = blk[j];
l_arr[j] = p[0];
r_arr[j] = p[1];
}
let lq = (Vf32::from_array(l_arr) * vscale)
.simd_clamp(vminv, vmaxv)
.round();
let rq = (Vf32::from_array(r_arr) * vscale)
.simd_clamp(vminv, vmaxv)
.round();
lq.cast::<i32>().copy_to_slice(&mut l_chunks[k]);
rq.cast::<i32>().copy_to_slice(&mut r_chunks[k]);
}
for (j, (l, r)) in in_tail.iter().zip(l_tail.iter_mut().zip(r_tail.iter_mut())) {
let lx = (j[0] * max_value).clamp(vmin, vmax_clamp).round();
let rx = (j[1] * max_value).clamp(vmin, vmax_clamp).round();
*l = lx as i32;
*r = rx as i32;
}
}
#[cfg(not(feature = "simd"))]
fn pairs_f32_to_i24_as_i32_stereo_inner(
input_pairs: &[[f32; 2]],
left: &mut [i32],
right: &mut [i32],
max_value: f32,
) {
debug_assert_eq!(input_pairs.len(), left.len());
debug_assert_eq!(input_pairs.len(), right.len());
let vmin = -max_value;
let vmax_clamp = max_value - 1.0;
for (i, pair) in input_pairs.iter().enumerate() {
let lx = (pair[0] * max_value).clamp(vmin, vmax_clamp).round();
let rx = (pair[1] * max_value).clamp(vmin, vmax_clamp).round();
left[i] = lx as i32;
right[i] = rx as i32;
}
}
/// Convertit pairs f32 normalisées en deux canaux i32 (valeurs I24 range)
pub fn pairs_f32_to_i24_as_i32_stereo(
input_pairs: &[[f32; 2]],
left: &mut [i32],
right: &mut [i32],
) {
pairs_f32_to_i24_as_i32_stereo_inner(input_pairs, left, right, 8388608.0);
}

View File

@@ -1,15 +1,21 @@
//! Module DSP pour les conversions et traitements audio optimisés (SIMD)
pub mod depth;
pub mod gain;
pub mod gain_16bits;
pub mod gain_24bits;
pub mod gain_32bits;
pub mod int_float;
pub mod resampling;
pub use depth::bitdepth_change_stereo;
pub use gain::apply_gain_stereo;
pub use gain_16bits::apply_gain_stereo_i16;
pub use gain_24bits::apply_gain_stereo_i24;
pub use gain_32bits::apply_gain_stereo_i32;
pub use int_float::{
i32_stereo_to_interleaved_f32, i32_stereo_to_pairs_f32, interleaved_f32_to_i32_stereo,
pairs_f32_to_i32_stereo,
i16_stereo_to_pairs_f32, i24_as_i32_stereo_to_pairs_f32, i32_stereo_to_interleaved_f32,
i32_stereo_to_pairs_f32, interleaved_f32_to_i32_stereo, pairs_f32_to_i16_stereo,
pairs_f32_to_i24_as_i32_stereo, pairs_f32_to_i32_stereo,
};
pub use resampling::resampling;