Restructuration de pmoaudio avec ajout des messages de synchro

This commit is contained in:
2025-10-30 08:54:47 +01:00
parent 579109374d
commit 8fe110eeb7
27 changed files with 3183 additions and 400 deletions

View File

@@ -1,48 +1,20 @@
use bytemuck::{cast_slice, cast_slice_mut};
use crate::BitDepth;
#[cfg(feature = "simd")]
use std::simd::num::{SimdFloat, SimdInt};
#[cfg(feature = "simd")]
use std::simd::{Simd, StdFloat};
/// Génère une implémentation de `BitDepth` pour une profondeur donnée.
/// Exemple :
/// ```ignore
/// use pmoaudio::dsp::int_float::{BitDepth, BitMax};
///
/// BitMax!(8);
/// assert_eq!(<Bit8 as BitDepth>::MAX_VALUE, 127.0);
/// ```
macro_rules! BitMax {
($bits:literal) => {
paste::paste! {
pub struct [<Bit $bits>];
impl BitDepth for [<Bit $bits>] {
const MAX_VALUE: f32 = ((1u32 << ($bits - 1)) as f32) - 1.0;
}
}
};
}
pub trait BitDepth {
const MAX_VALUE: f32; // Valeur max pour normaliser vers [-1.0, +1.0]
}
// Définir automatiquement les bit-depths
BitMax!(8);
BitMax!(16);
BitMax!(24);
BitMax!(32);
/* ====================== CŒURS CANONIQUES EN AoS ====================== */
// i32 L/R -> [[f32;2]]
// i32 L/R -> [[f32;2]] - version interne avec constante compile-time
#[cfg(feature = "simd")]
pub fn i32_stereo_to_pairs_f32<B: BitDepth>(
fn 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());
@@ -51,7 +23,7 @@ pub fn i32_stereo_to_pairs_f32<B: BitDepth>(
type Vf32 = Simd<f32, LANES>;
type Vi32 = Simd<i32, LANES>;
let scale = Vf32::splat(1.0 / B::MAX_VALUE);
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>();
@@ -69,47 +41,59 @@ pub fn i32_stereo_to_pairs_f32<B: BitDepth>(
}
}
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 * (1.0 / B::MAX_VALUE);
dst[1] = r as f32 * (1.0 / B::MAX_VALUE);
dst[0] = l as f32 * scale_scalar;
dst[1] = r as f32 * scale_scalar;
}
}
#[cfg(not(feature = "simd"))]
pub fn i32_stereo_to_pairs_f32<B: BitDepth>(
fn 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 / B::MAX_VALUE;
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;
}
}
// [[f32;2]] -> i32 L/R
/// Convertit deux canaux i32 (L/R) en pairs f32 normalisées [-1.0, 1.0]
pub fn i32_stereo_to_pairs_f32(
left: &[i32],
right: &[i32],
out_pairs: &mut [[f32; 2]],
bit_depth: BitDepth,
) {
i32_stereo_to_pairs_f32_inner(left, right, out_pairs, bit_depth.max_value());
}
// [[f32;2]] -> i32 L/R - version interne
#[cfg(feature = "simd")]
pub fn pairs_f32_to_i32_stereo<B: BitDepth>(
fn pairs_f32_to_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>;
type Vi32 = Simd<i32, LANES>;
let vmax = B::MAX_VALUE;
let vmin = -B::MAX_VALUE;
let vscale = Vf32::splat(vmax);
let vmin = -max_value;
let vmax_clamp = max_value - 1.0; // évite l'overflow après round→cast
let vscale = Vf32::splat(max_value);
let vminv = Vf32::splat(vmin);
let vmaxv = Vf32::splat(vmax - 1.0); // évite loverflow après round→cast
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>();
@@ -137,52 +121,65 @@ pub fn pairs_f32_to_i32_stereo<B: BitDepth>(
}
for (j, (l, r)) in in_tail.iter().zip(l_tail.iter_mut().zip(r_tail.iter_mut())) {
let lx = (j[0] * vmax).clamp(vmin, vmax - 1.0).round();
let rx = (j[1] * vmax).clamp(vmin, vmax - 1.0).round();
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"))]
pub fn pairs_f32_to_i32_stereo<B: BitDepth>(
fn pairs_f32_to_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 vmax = B::MAX_VALUE;
let vmin = -B::MAX_VALUE;
let vmin = -max_value;
let vmax_clamp = max_value - 1.0;
for (i, pair) in input_pairs.iter().enumerate() {
let lx = (pair[0] * vmax).clamp(vmin, vmax - 1.0).round();
let rx = (pair[1] * vmax).clamp(vmin, vmax - 1.0).round();
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 [-1.0, 1.0] en deux canaux i32 (L/R)
pub fn pairs_f32_to_i32_stereo(
input_pairs: &[[f32; 2]],
left: &mut [i32],
right: &mut [i32],
bit_depth: BitDepth,
) {
pairs_f32_to_i32_stereo_inner(input_pairs, left, right, bit_depth.max_value());
}
/* ====================== WRAPPERS INTERLEAVÉS ====================== */
// i32 L/R -> interleaved [f32]
pub fn i32_stereo_to_interleaved_f32<B: BitDepth>(
/// Convertit deux canaux i32 (L/R) en buffer f32 interleaved normalisé [-1.0, 1.0]
pub fn i32_stereo_to_interleaved_f32(
left: &[i32],
right: &[i32],
out_interleaved: &mut [f32],
bit_depth: BitDepth,
) {
debug_assert_eq!(out_interleaved.len(), left.len() * 2);
let out_pairs: &mut [[f32; 2]] = cast_slice_mut(out_interleaved);
i32_stereo_to_pairs_f32::<B>(left, right, out_pairs);
i32_stereo_to_pairs_f32(left, right, out_pairs, bit_depth);
}
// interleaved [f32] -> i32 L/R
pub fn interleaved_f32_to_i32_stereo<B: BitDepth>(
/// Convertit buffer f32 interleaved normalisé [-1.0, 1.0] en deux canaux i32 (L/R)
pub fn interleaved_f32_to_i32_stereo(
input_interleaved: &[f32],
left: &mut [i32],
right: &mut [i32],
bit_depth: BitDepth,
) {
debug_assert_eq!(input_interleaved.len(), left.len() * 2);
let input_pairs: &[[f32; 2]] = cast_slice(input_interleaved);
pairs_f32_to_i32_stereo::<B>(input_pairs, left, right);
pairs_f32_to_i32_stereo(input_pairs, left, right, bit_depth);
}

View File

@@ -1,3 +1,5 @@
//! Module DSP pour les conversions et traitements audio optimisés (SIMD)
pub mod depth;
pub mod gain;
pub mod int_float;

View File

@@ -2,41 +2,51 @@ use soxr::format::Stereo;
use soxr::params::{QualityRecipe, QualitySpec, RuntimeSpec};
use soxr::Soxr;
use crate::dsp::int_float::{Bit16, Bit24, Bit32, Bit8};
use crate::dsp::{i32_stereo_to_pairs_f32, pairs_f32_to_i32_stereo};
use crate::AudioError;
use crate::BitDepth;
// Type d'erreur simple pour resampling
#[derive(Debug)]
pub struct ResamplingError(pub String);
impl std::fmt::Display for ResamplingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Resampling error: {}", self.0)
}
}
impl std::error::Error for ResamplingError {}
pub struct Resampler {
source_hz: f64,
dest_hz: f64,
bit_depth: u32,
bit_depth: BitDepth,
soxr: Soxr<Stereo<f32>>,
}
pub fn build_resampler(
source_hz: u32,
dest_hz: u32,
bit_depth: u32,
) -> Result<Resampler, AudioError> {
bit_depth: BitDepth,
) -> Result<Resampler, ResamplingError> {
let qrecipe = match bit_depth {
8 => QualityRecipe::Medium,
16 => QualityRecipe::high(), // High plutôt que Bits16 pour 16-bit
24 => QualityRecipe::very_high(), // VeryHigh pour 24-bit
32 => QualityRecipe::very_high(), // VeryHigh pour 32-bit
_ => unreachable!(), // Déjà vérifié plus haut
BitDepth::B8 => QualityRecipe::Medium,
BitDepth::B16 => QualityRecipe::high(), // High pour 16-bit
BitDepth::B24 => QualityRecipe::very_high(), // VeryHigh pour 24-bit
BitDepth::B32 => QualityRecipe::very_high(), // VeryHigh pour 32-bit
};
let quality = QualitySpec::new(qrecipe); // Phase response linear, no steep filter
let rt = RuntimeSpec::default();
let soxr = Soxr::<Stereo<f32>>::new_with_params(source_hz as f64, dest_hz as f64, quality, rt)
.map_err(|e| AudioError::ProcessingError(e.to_string()))?;
.map_err(|e| ResamplingError(e.to_string()))?;
Ok(Resampler {
source_hz: source_hz as f64,
dest_hz: dest_hz as f64,
bit_depth: bit_depth,
soxr: soxr,
bit_depth,
soxr,
})
}
@@ -44,31 +54,22 @@ pub fn resampling(left: &[i32], right: &[i32], resampler: &mut Resampler) -> (Ve
if left.len() != right.len() {
panic!("Left and right channels must have the same length");
}
let mut input = vec![[0.0f32; 2]; left.len()];
match resampler.bit_depth {
8 => i32_stereo_to_pairs_f32::<Bit8>(left, right, &mut input),
16 => i32_stereo_to_pairs_f32::<Bit16>(left, right, &mut input),
24 => i32_stereo_to_pairs_f32::<Bit24>(left, right, &mut input),
32 => i32_stereo_to_pairs_f32::<Bit32>(left, right, &mut input),
_ => panic!("Unsupported bit depth: {}", resampler.bit_depth),
}
// Convertir i32 → f32 normalisé
let mut input = vec![[0.0f32; 2]; left.len()];
i32_stereo_to_pairs_f32(left, right, &mut input, resampler.bit_depth);
// Resampling
let output_len =
((input.len() as f64) * resampler.dest_hz / resampler.source_hz).ceil() as usize;
let mut output = vec![[0.0f32; 2]; output_len];
resampler.soxr.process(&input, &mut output).unwrap();
// Convertir f32 normalisé → i32
let mut oleft = vec![0i32; output.len()];
let mut oright = vec![0i32; output.len()];
match resampler.bit_depth {
8 => pairs_f32_to_i32_stereo::<Bit8>(&output, &mut oleft, &mut oright),
16 => pairs_f32_to_i32_stereo::<Bit16>(&output, &mut oleft, &mut oright),
24 => pairs_f32_to_i32_stereo::<Bit24>(&output, &mut oleft, &mut oright),
32 => pairs_f32_to_i32_stereo::<Bit32>(&output, &mut oleft, &mut oright),
_ => unreachable!(), // Déjà vérifié plus haut
};
pairs_f32_to_i32_stereo(&output, &mut oleft, &mut oright, resampler.bit_depth);
(oleft, oright)
}