Retour sur pmoaudio
This commit is contained in:
94
pmoaudio/src/dsp/gain_16bits.rs
Normal file
94
pmoaudio/src/dsp/gain_16bits.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
99
pmoaudio/src/dsp/gain_24bits.rs
Normal file
99
pmoaudio/src/dsp/gain_24bits.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user