2025-10-30 08:54:47 +01:00
|
|
|
//! AudioChunk : Représentation générique de données audio stéréo
|
|
|
|
|
//!
|
|
|
|
|
//! Cette nouvelle architecture supporte différents types de samples :
|
2025-11-01 21:10:57 +01:00
|
|
|
//! - Entiers : i16, I24 (24-bit), i32
|
2025-10-30 08:54:47 +01:00
|
|
|
//! - Flottants : f32, f64
|
|
|
|
|
//!
|
|
|
|
|
//! L'utilisation de génériques permet de factoriser le code tout en gardant
|
|
|
|
|
//! des performances optimales grâce à la monomorphisation.
|
|
|
|
|
|
2025-10-11 00:33:13 +02:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
use crate::{dsp, BitDepth, Sample, I24};
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// AudioChunkData<T> : Structure générique pour un chunk audio typé
|
|
|
|
|
// ============================================================================
|
2025-10-28 18:41:34 +01:00
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Représente un chunk audio stéréo typé avec partage zero-copy via Arc
|
2025-10-11 00:33:13 +02:00
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Cette structure générique encapsule des données audio de n'importe quel type
|
2025-11-01 21:10:57 +01:00
|
|
|
/// de sample (i16, I24, i32, f32, f64). Les données sont partagées via `Arc`
|
2025-10-30 08:54:47 +01:00
|
|
|
/// pour permettre un partage efficace entre plusieurs consumers sans copier.
|
2025-10-11 00:33:13 +02:00
|
|
|
///
|
|
|
|
|
/// # Optimisation zero-copy
|
|
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// - Le clonage d'un `AudioChunkData` ne clone que le pointeur Arc (très rapide)
|
|
|
|
|
/// - Les données audio réelles ne sont jamais copiées tant qu'on ne modifie pas
|
2025-10-11 00:33:13 +02:00
|
|
|
/// - Plusieurs nodes peuvent partager le même chunk simultanément
|
|
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// # Gain
|
|
|
|
|
///
|
|
|
|
|
/// Le gain est stocké en décibels (dB) et n'est pas appliqué aux données tant
|
|
|
|
|
/// qu'on n'appelle pas explicitement `apply_gain()`. Cela permet de propager
|
|
|
|
|
/// des changements de gain sans recopier les données.
|
|
|
|
|
///
|
2025-10-11 00:33:13 +02:00
|
|
|
/// # Exemples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2025-10-30 08:54:47 +01:00
|
|
|
/// use pmoaudio::{AudioChunkData, I24};
|
2025-10-11 00:33:13 +02:00
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// // Créer un chunk I24
|
|
|
|
|
/// let stereo = vec![[I24::new(1_000_000).unwrap(), I24::new(500_000).unwrap()]; 1000];
|
|
|
|
|
/// let chunk = AudioChunkData::new(stereo, 48_000, 0.0);
|
2025-10-11 00:33:13 +02:00
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// assert_eq!(chunk.len(), 1000);
|
2025-10-28 18:41:34 +01:00
|
|
|
/// assert_eq!(chunk.sample_rate(), 48_000);
|
2025-10-11 00:33:13 +02:00
|
|
|
/// ```
|
|
|
|
|
#[derive(Debug, Clone)]
|
2025-10-30 08:54:47 +01:00
|
|
|
pub struct AudioChunkData<T: Sample> {
|
|
|
|
|
/// Frames stéréo [L, R], partagées et immuables via Arc
|
|
|
|
|
stereo: Arc<[[T; 2]]>,
|
2025-10-11 00:33:13 +02:00
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Taux d'échantillonnage en Hz (44100, 48000, 96000, 192000, etc.)
|
2025-10-28 18:41:34 +01:00
|
|
|
sample_rate: u32,
|
2025-10-11 00:33:13 +02:00
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Gain appliqué au flux audio, en décibels (dB)
|
2025-10-11 00:33:13 +02:00
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Conversion : `gain_linear = 10^(gain_db / 20)`
|
|
|
|
|
/// Valeur par défaut : `0.0 dB` (aucune modification)
|
|
|
|
|
/// Exemples : `-6 dB` ≈ moitié du volume ; `+6 dB` ≈ double
|
|
|
|
|
gain_db: f64,
|
2025-10-11 00:33:13 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
impl<T: Sample> AudioChunkData<T> {
|
2025-10-11 00:33:13 +02:00
|
|
|
/// Crée un nouveau chunk audio
|
|
|
|
|
///
|
|
|
|
|
/// Les vecteurs sont automatiquement wrappés dans `Arc`.
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// * `stereo` - Frames stéréo `[L, R]`
|
2025-10-11 00:33:13 +02:00
|
|
|
/// * `sample_rate` - Taux d'échantillonnage en Hz
|
2025-10-30 08:54:47 +01:00
|
|
|
/// * `gain_db` - Gain initial en décibels (0.0 = unity gain)
|
2025-10-11 00:33:13 +02:00
|
|
|
///
|
|
|
|
|
/// # Exemples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
2025-10-30 08:54:47 +01:00
|
|
|
/// use pmoaudio::AudioChunkData;
|
2025-10-11 00:33:13 +02:00
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// let chunk = AudioChunkData::new(
|
|
|
|
|
/// vec![[0.0f32, 0.0f32]; 1000],
|
2025-10-28 18:41:34 +01:00
|
|
|
/// 48_000,
|
2025-10-30 08:54:47 +01:00
|
|
|
/// 0.0,
|
2025-10-11 00:33:13 +02:00
|
|
|
/// );
|
|
|
|
|
/// ```
|
2025-10-30 08:54:47 +01:00
|
|
|
pub fn new(stereo: Vec<[T; 2]>, sample_rate: u32, gain_db: f64) -> Arc<Self> {
|
2025-10-28 18:41:34 +01:00
|
|
|
Arc::new(Self {
|
|
|
|
|
stereo: Arc::from(stereo),
|
2025-10-11 00:33:13 +02:00
|
|
|
sample_rate,
|
2025-10-30 08:54:47 +01:00
|
|
|
gain_db,
|
2025-10-28 18:41:34 +01:00
|
|
|
})
|
2025-10-11 15:36:06 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Retourne le nombre d'échantillons par canal (frames)
|
|
|
|
|
#[inline]
|
2025-10-11 00:33:13 +02:00
|
|
|
pub fn len(&self) -> usize {
|
2025-10-28 18:41:34 +01:00
|
|
|
self.stereo.len()
|
2025-10-11 00:33:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est vide
|
2025-10-30 08:54:47 +01:00
|
|
|
#[inline]
|
2025-10-11 00:33:13 +02:00
|
|
|
pub fn is_empty(&self) -> bool {
|
2025-10-28 18:41:34 +01:00
|
|
|
self.stereo.is_empty()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Taux d'échantillonnage (Hz)
|
|
|
|
|
#[inline]
|
2025-11-04 20:34:44 +01:00
|
|
|
pub fn get_sample_rate(&self) -> u32 {
|
2025-10-28 18:41:34 +01:00
|
|
|
self.sample_rate
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Gain courant en décibels
|
|
|
|
|
#[inline]
|
2025-11-04 20:34:44 +01:00
|
|
|
pub fn get_gain_db(&self) -> f64 {
|
2025-10-30 08:54:47 +01:00
|
|
|
self.gain_db
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Gain sous forme linéaire
|
|
|
|
|
#[inline]
|
2025-11-04 20:34:44 +01:00
|
|
|
pub fn get_gain_linear(&self) -> f64 {
|
2025-11-01 21:10:57 +01:00
|
|
|
gain_linear_from_db(self.gain_db)
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Retourne une vue immuable sur les frames `[L, R]`
|
|
|
|
|
#[inline]
|
2025-11-04 20:34:44 +01:00
|
|
|
pub fn get_frames(&self) -> &[[T; 2]] {
|
2025-10-28 18:41:34 +01:00
|
|
|
&self.stereo
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Clone les frames stéréo dans un `Vec`
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn clone_frames(&self) -> Vec<[T; 2]> {
|
2025-10-28 18:41:34 +01:00
|
|
|
self.stereo.to_vec()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Définit le gain (retourne un nouveau chunk avec le même Arc mais gain différent)
|
2025-10-11 15:36:06 +02:00
|
|
|
///
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Cette méthode est très peu coûteuse car elle ne clone que la structure, pas les données audio.
|
|
|
|
|
pub fn set_gain_db(&self, gain_db: f64) -> Arc<Self> {
|
2025-10-28 18:41:34 +01:00
|
|
|
Arc::new(Self {
|
|
|
|
|
stereo: self.stereo.clone(),
|
|
|
|
|
sample_rate: self.sample_rate,
|
2025-10-30 08:54:47 +01:00
|
|
|
gain_db,
|
2025-10-28 18:41:34 +01:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Définit le gain à l'aide d'un facteur linéaire (>0)
|
2025-10-28 18:41:34 +01:00
|
|
|
pub fn set_gain_linear(&self, gain_linear: f64) -> Arc<Self> {
|
2025-11-01 21:10:57 +01:00
|
|
|
self.set_gain_db(gain_db_from_linear(gain_linear))
|
2025-10-11 15:36:06 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Modifie le gain de ce chunk (ajoute un delta en dB)
|
2025-10-28 18:41:34 +01:00
|
|
|
pub fn with_modified_gain_db(&self, delta_gain_db: f64) -> Arc<Self> {
|
2025-10-30 08:54:47 +01:00
|
|
|
self.set_gain_db(self.gain_db + delta_gain_db)
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Modifie le gain via un facteur linéaire multiplié au gain courant
|
2025-10-28 18:41:34 +01:00
|
|
|
pub fn with_modified_gain_linear(&self, gain_linear: f64) -> Arc<Self> {
|
2025-11-01 21:10:57 +01:00
|
|
|
self.with_modified_gain_db(gain_db_from_linear(gain_linear))
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
2025-10-28 18:41:34 +01:00
|
|
|
|
2025-11-01 21:10:57 +01:00
|
|
|
// Méthodes spécifiques pour les types entiers (i16, I24, i32)
|
2025-10-30 08:54:47 +01:00
|
|
|
impl AudioChunkData<i32> {
|
|
|
|
|
/// Applique le gain et retourne un nouveau chunk avec les données modifiées
|
|
|
|
|
///
|
|
|
|
|
/// Cette méthode crée un nouveau chunk avec les samples multipliés par le gain.
|
|
|
|
|
/// Le gain du chunk résultant est remis à 0.0 dB.
|
|
|
|
|
pub fn apply_gain(self: Arc<Self>) -> Arc<Self> {
|
|
|
|
|
if self.gain_db.abs() < f64::EPSILON {
|
|
|
|
|
return self; // Pas de gain à appliquer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut stereo = self.clone_frames();
|
2025-11-01 21:10:57 +01:00
|
|
|
dsp::apply_gain_stereo_i32(&mut stereo, self.gain_db);
|
2025-10-30 08:54:47 +01:00
|
|
|
|
|
|
|
|
AudioChunkData::new(stereo, self.sample_rate, 0.0)
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
2025-10-30 08:54:47 +01:00
|
|
|
|
|
|
|
|
/// Construit un chunk depuis deux vecteurs `i32` séparés (L/R)
|
|
|
|
|
pub fn from_channels(left: Vec<i32>, right: Vec<i32>, sample_rate: u32) -> Arc<Self> {
|
2025-11-01 21:10:57 +01:00
|
|
|
assert_eq!(
|
|
|
|
|
left.len(),
|
|
|
|
|
right.len(),
|
|
|
|
|
"channels must have identical length"
|
|
|
|
|
);
|
2025-10-30 08:54:47 +01:00
|
|
|
let stereo = left
|
|
|
|
|
.into_iter()
|
|
|
|
|
.zip(right.into_iter())
|
|
|
|
|
.map(|(l, r)| [l, r])
|
|
|
|
|
.collect();
|
|
|
|
|
AudioChunkData::new(stereo, sample_rate, 0.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Change la profondeur de bits (bit depth conversion)
|
|
|
|
|
pub fn set_bit_depth(self: Arc<Self>, old_depth: BitDepth, new_depth: BitDepth) -> Arc<Self> {
|
|
|
|
|
if old_depth == new_depth {
|
2025-10-28 18:41:34 +01:00
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
let mut stereo = self.clone_frames();
|
|
|
|
|
dsp::bitdepth_change_stereo(&mut stereo, old_depth, new_depth);
|
2025-10-28 18:41:34 +01:00
|
|
|
|
|
|
|
|
Arc::new(Self {
|
|
|
|
|
stereo: Arc::from(stereo),
|
2025-10-11 15:36:06 +02:00
|
|
|
sample_rate: self.sample_rate,
|
2025-10-30 08:54:47 +01:00
|
|
|
gain_db: self.gain_db,
|
2025-10-28 18:41:34 +01:00
|
|
|
})
|
2025-10-11 15:36:06 +02:00
|
|
|
}
|
2025-10-11 00:33:13 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
// Méthodes spécifiques pour f32
|
|
|
|
|
impl AudioChunkData<f32> {
|
|
|
|
|
/// Applique le gain et retourne un nouveau chunk avec les données modifiées
|
|
|
|
|
pub fn apply_gain(self: Arc<Self>) -> Arc<Self> {
|
|
|
|
|
if self.gain_db.abs() < f64::EPSILON {
|
|
|
|
|
return self; // Pas de gain à appliquer
|
|
|
|
|
}
|
2025-10-11 00:33:13 +02:00
|
|
|
|
2025-11-01 21:10:57 +01:00
|
|
|
let gain_linear = gain_linear_from_db(self.gain_db) as f32;
|
2025-10-30 08:54:47 +01:00
|
|
|
let mut stereo = self.clone_frames();
|
|
|
|
|
for frame in &mut stereo {
|
|
|
|
|
frame[0] *= gain_linear;
|
|
|
|
|
frame[1] *= gain_linear;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AudioChunkData::new(stereo, self.sample_rate, 0.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Construit un chunk depuis deux vecteurs `f32` séparés (L/R)
|
|
|
|
|
pub fn from_channels(left: Vec<f32>, right: Vec<f32>, sample_rate: u32) -> Arc<Self> {
|
2025-11-01 21:10:57 +01:00
|
|
|
assert_eq!(
|
|
|
|
|
left.len(),
|
|
|
|
|
right.len(),
|
|
|
|
|
"channels must have identical length"
|
|
|
|
|
);
|
2025-10-30 08:54:47 +01:00
|
|
|
let stereo = left
|
|
|
|
|
.into_iter()
|
|
|
|
|
.zip(right.into_iter())
|
|
|
|
|
.map(|(l, r)| [l, r])
|
|
|
|
|
.collect();
|
|
|
|
|
AudioChunkData::new(stereo, sample_rate, 0.0)
|
2025-10-11 00:33:13 +02:00
|
|
|
}
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
2025-10-11 00:33:13 +02:00
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
// Méthodes spécifiques pour f64
|
|
|
|
|
impl AudioChunkData<f64> {
|
|
|
|
|
/// Applique le gain et retourne un nouveau chunk avec les données modifiées
|
|
|
|
|
pub fn apply_gain(self: Arc<Self>) -> Arc<Self> {
|
|
|
|
|
if self.gain_db.abs() < f64::EPSILON {
|
|
|
|
|
return self; // Pas de gain à appliquer
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 21:10:57 +01:00
|
|
|
let gain_linear = gain_linear_from_db(self.gain_db);
|
2025-10-30 08:54:47 +01:00
|
|
|
let mut stereo = self.clone_frames();
|
|
|
|
|
for frame in &mut stereo {
|
|
|
|
|
frame[0] *= gain_linear;
|
|
|
|
|
frame[1] *= gain_linear;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AudioChunkData::new(stereo, self.sample_rate, 0.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Construit un chunk depuis deux vecteurs `f64` séparés (L/R)
|
|
|
|
|
pub fn from_channels(left: Vec<f64>, right: Vec<f64>, sample_rate: u32) -> Arc<Self> {
|
2025-11-01 21:10:57 +01:00
|
|
|
assert_eq!(
|
|
|
|
|
left.len(),
|
|
|
|
|
right.len(),
|
|
|
|
|
"channels must have identical length"
|
|
|
|
|
);
|
2025-10-30 08:54:47 +01:00
|
|
|
let stereo = left
|
|
|
|
|
.into_iter()
|
|
|
|
|
.zip(right.into_iter())
|
|
|
|
|
.map(|(l, r)| [l, r])
|
|
|
|
|
.collect();
|
|
|
|
|
AudioChunkData::new(stereo, sample_rate, 0.0)
|
|
|
|
|
}
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
2025-10-11 00:33:13 +02:00
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
// ============================================================================
|
|
|
|
|
// AudioChunk : Enum pour tous les types de chunks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// Enum contenant tous les types de chunks audio possibles
|
|
|
|
|
///
|
|
|
|
|
/// Cette enum permet de manipuler des chunks de différents types dans un
|
|
|
|
|
/// pipeline unifié, tout en conservant l'information de type.
|
|
|
|
|
///
|
|
|
|
|
/// # Variantes
|
|
|
|
|
///
|
|
|
|
|
/// - `I16` : Échantillons 16-bit signés
|
|
|
|
|
/// - `I24` : Échantillons 24-bit signés (stockés sur i32)
|
|
|
|
|
/// - `I32` : Échantillons 32-bit signés
|
|
|
|
|
/// - `F32` : Échantillons flottants 32-bit normalisés [-1.0, 1.0]
|
|
|
|
|
/// - `F64` : Échantillons flottants 64-bit normalisés [-1.0, 1.0]
|
|
|
|
|
///
|
|
|
|
|
/// # Exemples
|
|
|
|
|
///
|
|
|
|
|
/// ```
|
|
|
|
|
/// use pmoaudio::{AudioChunk, AudioChunkData};
|
|
|
|
|
///
|
|
|
|
|
/// let chunk_f32 = AudioChunkData::new(vec![[0.5f32, 0.25f32]; 1000], 48_000, 0.0);
|
|
|
|
|
/// let chunk = AudioChunk::F32(chunk_f32);
|
|
|
|
|
///
|
|
|
|
|
/// match &chunk {
|
|
|
|
|
/// AudioChunk::F32(data) => println!("F32 chunk with {} frames", data.len()),
|
|
|
|
|
/// _ => println!("Other type"),
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum AudioChunk {
|
|
|
|
|
I16(Arc<AudioChunkData<i16>>),
|
|
|
|
|
I24(Arc<AudioChunkData<I24>>),
|
|
|
|
|
I32(Arc<AudioChunkData<i32>>),
|
|
|
|
|
F32(Arc<AudioChunkData<f32>>),
|
|
|
|
|
F64(Arc<AudioChunkData<f64>>),
|
2025-10-28 18:41:34 +01:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
impl AudioChunk {
|
|
|
|
|
/// Retourne le nombre de frames du chunk
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
|
match self {
|
|
|
|
|
AudioChunk::I16(d) => d.len(),
|
|
|
|
|
AudioChunk::I24(d) => d.len(),
|
|
|
|
|
AudioChunk::I32(d) => d.len(),
|
|
|
|
|
AudioChunk::F32(d) => d.len(),
|
|
|
|
|
AudioChunk::F64(d) => d.len(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est vide
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
self.len() == 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Taux d'échantillonnage (Hz)
|
|
|
|
|
pub fn sample_rate(&self) -> u32 {
|
|
|
|
|
match self {
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioChunk::I16(d) => d.get_sample_rate(),
|
|
|
|
|
AudioChunk::I24(d) => d.get_sample_rate(),
|
|
|
|
|
AudioChunk::I32(d) => d.get_sample_rate(),
|
|
|
|
|
AudioChunk::F32(d) => d.get_sample_rate(),
|
|
|
|
|
AudioChunk::F64(d) => d.get_sample_rate(),
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gain courant en décibels
|
|
|
|
|
pub fn gain_db(&self) -> f64 {
|
|
|
|
|
match self {
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioChunk::I16(d) => d.get_gain_db(),
|
|
|
|
|
AudioChunk::I24(d) => d.get_gain_db(),
|
|
|
|
|
AudioChunk::I32(d) => d.get_gain_db(),
|
|
|
|
|
AudioChunk::F32(d) => d.get_gain_db(),
|
|
|
|
|
AudioChunk::F64(d) => d.get_gain_db(),
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gain sous forme linéaire
|
|
|
|
|
pub fn gain_linear(&self) -> f64 {
|
2025-11-01 21:10:57 +01:00
|
|
|
gain_linear_from_db(self.gain_db())
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Définit le gain en dB
|
|
|
|
|
pub fn set_gain_db(&self, gain_db: f64) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
AudioChunk::I16(d) => AudioChunk::I16(d.set_gain_db(gain_db)),
|
|
|
|
|
AudioChunk::I24(d) => AudioChunk::I24(d.set_gain_db(gain_db)),
|
|
|
|
|
AudioChunk::I32(d) => AudioChunk::I32(d.set_gain_db(gain_db)),
|
|
|
|
|
AudioChunk::F32(d) => AudioChunk::F32(d.set_gain_db(gain_db)),
|
|
|
|
|
AudioChunk::F64(d) => AudioChunk::F64(d.set_gain_db(gain_db)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Définit le gain via un facteur linéaire
|
|
|
|
|
pub fn set_gain_linear(&self, gain_linear: f64) -> Self {
|
2025-11-01 21:10:57 +01:00
|
|
|
self.set_gain_db(gain_db_from_linear(gain_linear))
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Modifie le gain (ajoute un delta en dB)
|
|
|
|
|
pub fn with_modified_gain_db(&self, delta_gain_db: f64) -> Self {
|
|
|
|
|
self.set_gain_db(self.gain_db() + delta_gain_db)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applique le gain et retourne un nouveau chunk avec les données modifiées
|
|
|
|
|
///
|
|
|
|
|
/// Le gain du chunk résultant est remis à 0.0 dB.
|
|
|
|
|
pub fn apply_gain(self) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
AudioChunk::I16(d) => {
|
2025-11-04 20:34:44 +01:00
|
|
|
let gain_db = d.get_gain_db();
|
2025-10-30 08:54:47 +01:00
|
|
|
if gain_db.abs() < f64::EPSILON {
|
|
|
|
|
return AudioChunk::I16(d);
|
|
|
|
|
}
|
2025-11-01 21:10:57 +01:00
|
|
|
let gain_linear = gain_linear_from_db(gain_db) as f32;
|
2025-10-30 08:54:47 +01:00
|
|
|
let mut stereo = d.clone_frames();
|
|
|
|
|
for frame in &mut stereo {
|
2025-11-01 21:10:57 +01:00
|
|
|
frame[0] = (frame[0] as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-32768.0, 32767.0) as i16;
|
|
|
|
|
frame[1] = (frame[1] as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-32768.0, 32767.0) as i16;
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioChunk::I16(AudioChunkData::new(stereo, d.get_sample_rate(), 0.0))
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
AudioChunk::I24(d) => {
|
2025-11-04 20:34:44 +01:00
|
|
|
let gain_db = d.get_gain_db();
|
2025-10-30 08:54:47 +01:00
|
|
|
if gain_db.abs() < f64::EPSILON {
|
|
|
|
|
return AudioChunk::I24(d);
|
|
|
|
|
}
|
2025-11-01 21:10:57 +01:00
|
|
|
let gain_linear = gain_linear_from_db(gain_db) as f32;
|
2025-10-30 08:54:47 +01:00
|
|
|
let mut stereo = d.clone_frames();
|
|
|
|
|
for frame in &mut stereo {
|
2025-11-01 21:10:57 +01:00
|
|
|
let l = (frame[0].as_i32() as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-8_388_608.0, 8_388_607.0) as i32;
|
|
|
|
|
let r = (frame[1].as_i32() as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-8_388_608.0, 8_388_607.0) as i32;
|
2025-10-30 08:54:47 +01:00
|
|
|
frame[0] = I24::new_clamped(l);
|
|
|
|
|
frame[1] = I24::new_clamped(r);
|
|
|
|
|
}
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioChunk::I24(AudioChunkData::new(stereo, d.get_sample_rate(), 0.0))
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
AudioChunk::I32(d) => AudioChunk::I32(d.apply_gain()),
|
|
|
|
|
AudioChunk::F32(d) => AudioChunk::F32(d.apply_gain()),
|
|
|
|
|
AudioChunk::F64(d) => AudioChunk::F64(d.apply_gain()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retourne le nom du type de sample
|
|
|
|
|
pub fn type_name(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
AudioChunk::I16(_) => "i16",
|
|
|
|
|
AudioChunk::I24(_) => "I24",
|
|
|
|
|
AudioChunk::I32(_) => "i32",
|
|
|
|
|
AudioChunk::F32(_) => "f32",
|
|
|
|
|
AudioChunk::F64(_) => "f64",
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-01 21:10:57 +01:00
|
|
|
|
|
|
|
|
/// Tente de convertir vers AudioIntegerChunk (retourne None si float)
|
|
|
|
|
pub fn try_as_integer(&self) -> Option<AudioIntegerChunk> {
|
|
|
|
|
match self {
|
|
|
|
|
AudioChunk::I16(d) => Some(AudioIntegerChunk::I16(d.clone())),
|
|
|
|
|
AudioChunk::I24(d) => Some(AudioIntegerChunk::I24(d.clone())),
|
|
|
|
|
AudioChunk::I32(d) => Some(AudioIntegerChunk::I32(d.clone())),
|
|
|
|
|
AudioChunk::F32(_) | AudioChunk::F64(_) => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tente de convertir vers AudioFloatChunk (retourne None si integer)
|
|
|
|
|
pub fn try_as_float(&self) -> Option<AudioFloatChunk> {
|
|
|
|
|
match self {
|
|
|
|
|
AudioChunk::F32(d) => Some(AudioFloatChunk::F32(d.clone())),
|
|
|
|
|
AudioChunk::F64(d) => Some(AudioFloatChunk::F64(d.clone())),
|
|
|
|
|
AudioChunk::I16(_) | AudioChunk::I24(_) | AudioChunk::I32(_) => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est de type entier
|
|
|
|
|
pub fn is_integer(&self) -> bool {
|
|
|
|
|
matches!(
|
|
|
|
|
self,
|
|
|
|
|
AudioChunk::I16(_) | AudioChunk::I24(_) | AudioChunk::I32(_)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est de type flottant
|
|
|
|
|
pub fn is_float(&self) -> bool {
|
|
|
|
|
matches!(self, AudioChunk::F32(_) | AudioChunk::F64(_))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum AudioIntegerChunk {
|
|
|
|
|
I16(Arc<AudioChunkData<i16>>),
|
|
|
|
|
I24(Arc<AudioChunkData<I24>>),
|
|
|
|
|
I32(Arc<AudioChunkData<i32>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AudioIntegerChunk {
|
|
|
|
|
/// Retourne le nombre de frames du chunk
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(d) => d.len(),
|
|
|
|
|
AudioIntegerChunk::I24(d) => d.len(),
|
|
|
|
|
AudioIntegerChunk::I32(d) => d.len(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est vide
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
self.len() == 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Taux d'échantillonnage (Hz)
|
|
|
|
|
pub fn sample_rate(&self) -> u32 {
|
|
|
|
|
match self {
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioIntegerChunk::I16(d) => d.get_sample_rate(),
|
|
|
|
|
AudioIntegerChunk::I24(d) => d.get_sample_rate(),
|
|
|
|
|
AudioIntegerChunk::I32(d) => d.get_sample_rate(),
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gain courant en décibels
|
|
|
|
|
pub fn gain_db(&self) -> f64 {
|
|
|
|
|
match self {
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioIntegerChunk::I16(d) => d.get_gain_db(),
|
|
|
|
|
AudioIntegerChunk::I24(d) => d.get_gain_db(),
|
|
|
|
|
AudioIntegerChunk::I32(d) => d.get_gain_db(),
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gain sous forme linéaire
|
|
|
|
|
pub fn gain_linear(&self) -> f64 {
|
|
|
|
|
gain_linear_from_db(self.gain_db())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Définit le gain en dB
|
|
|
|
|
pub fn set_gain_db(&self, gain_db: f64) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(d) => AudioIntegerChunk::I16(d.set_gain_db(gain_db)),
|
|
|
|
|
AudioIntegerChunk::I24(d) => AudioIntegerChunk::I24(d.set_gain_db(gain_db)),
|
|
|
|
|
AudioIntegerChunk::I32(d) => AudioIntegerChunk::I32(d.set_gain_db(gain_db)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Définit le gain via un facteur linéaire
|
|
|
|
|
pub fn set_gain_linear(&self, gain_linear: f64) -> Self {
|
|
|
|
|
self.set_gain_db(gain_db_from_linear(gain_linear))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Modifie le gain (ajoute un delta en dB)
|
|
|
|
|
pub fn with_modified_gain_db(&self, delta_gain_db: f64) -> Self {
|
|
|
|
|
self.set_gain_db(self.gain_db() + delta_gain_db)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applique le gain et retourne un nouveau chunk avec les données modifiées
|
|
|
|
|
///
|
|
|
|
|
/// Le gain du chunk résultant est remis à 0.0 dB.
|
|
|
|
|
pub fn apply_gain(self) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(d) => {
|
2025-11-04 20:34:44 +01:00
|
|
|
let gain_db = d.get_gain_db();
|
2025-11-01 21:10:57 +01:00
|
|
|
if gain_db.abs() < f64::EPSILON {
|
|
|
|
|
return AudioIntegerChunk::I16(d);
|
|
|
|
|
}
|
|
|
|
|
let gain_linear = gain_linear_from_db(gain_db) as f32;
|
|
|
|
|
let mut stereo = d.clone_frames();
|
|
|
|
|
for frame in &mut stereo {
|
|
|
|
|
frame[0] = (frame[0] as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-32768.0, 32767.0) as i16;
|
|
|
|
|
frame[1] = (frame[1] as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-32768.0, 32767.0) as i16;
|
|
|
|
|
}
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioIntegerChunk::I16(AudioChunkData::new(stereo, d.get_sample_rate(), 0.0))
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
AudioIntegerChunk::I24(d) => {
|
2025-11-04 20:34:44 +01:00
|
|
|
let gain_db = d.get_gain_db();
|
2025-11-01 21:10:57 +01:00
|
|
|
if gain_db.abs() < f64::EPSILON {
|
|
|
|
|
return AudioIntegerChunk::I24(d);
|
|
|
|
|
}
|
|
|
|
|
let gain_linear = gain_linear_from_db(gain_db) as f32;
|
|
|
|
|
let mut stereo = d.clone_frames();
|
|
|
|
|
for frame in &mut stereo {
|
|
|
|
|
let l = (frame[0].as_i32() as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-8_388_608.0, 8_388_607.0) as i32;
|
|
|
|
|
let r = (frame[1].as_i32() as f32 * gain_linear)
|
|
|
|
|
.round()
|
|
|
|
|
.clamp(-8_388_608.0, 8_388_607.0) as i32;
|
|
|
|
|
frame[0] = I24::new_clamped(l);
|
|
|
|
|
frame[1] = I24::new_clamped(r);
|
|
|
|
|
}
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioIntegerChunk::I24(AudioChunkData::new(stereo, d.get_sample_rate(), 0.0))
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
AudioIntegerChunk::I32(d) => AudioIntegerChunk::I32(d.apply_gain()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retourne le nom du type de sample
|
|
|
|
|
pub fn type_name(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(_) => "i16",
|
|
|
|
|
AudioIntegerChunk::I24(_) => "I24",
|
|
|
|
|
AudioIntegerChunk::I32(_) => "i32",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est de type I16
|
|
|
|
|
pub fn is_i16(&self) -> bool {
|
|
|
|
|
matches!(self, AudioIntegerChunk::I16(_))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est de type I24
|
|
|
|
|
pub fn is_i24(&self) -> bool {
|
|
|
|
|
matches!(self, AudioIntegerChunk::I24(_))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est de type I32
|
|
|
|
|
pub fn is_i32(&self) -> bool {
|
|
|
|
|
matches!(self, AudioIntegerChunk::I32(_))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retourne la profondeur de bit du chunk
|
|
|
|
|
pub fn bit_depth(&self) -> u8 {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(_) => 16,
|
|
|
|
|
AudioIntegerChunk::I24(_) => 24,
|
|
|
|
|
AudioIntegerChunk::I32(_) => 32,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convertit vers AudioChunk
|
|
|
|
|
pub fn as_audio_chunk(&self) -> AudioChunk {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(d) => AudioChunk::I16(d.clone()),
|
|
|
|
|
AudioIntegerChunk::I24(d) => AudioChunk::I24(d.clone()),
|
|
|
|
|
AudioIntegerChunk::I32(d) => AudioChunk::I32(d.clone()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convertit vers I16 (avec conversion si nécessaire)
|
|
|
|
|
pub fn to_i16(&self) -> AudioIntegerChunk {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(_) => self.clone(),
|
|
|
|
|
AudioIntegerChunk::I24(d) => {
|
|
|
|
|
// I24 -> I32 -> I16
|
|
|
|
|
let i32_chunk = crate::conversions::convert_i24_to_i32(d);
|
|
|
|
|
let converted = crate::conversions::convert_i32_to_i16(&i32_chunk);
|
|
|
|
|
AudioIntegerChunk::I16(converted)
|
|
|
|
|
}
|
|
|
|
|
AudioIntegerChunk::I32(d) => {
|
|
|
|
|
let converted = crate::conversions::convert_i32_to_i16(d);
|
|
|
|
|
AudioIntegerChunk::I16(converted)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convertit vers I24 (avec conversion si nécessaire)
|
|
|
|
|
pub fn to_i24(&self) -> AudioIntegerChunk {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(d) => {
|
|
|
|
|
// I16 -> I32 -> I24
|
|
|
|
|
let i32_chunk = crate::conversions::convert_i16_to_i32(d);
|
|
|
|
|
let converted = crate::conversions::convert_i32_to_i24(&i32_chunk);
|
|
|
|
|
AudioIntegerChunk::I24(converted)
|
|
|
|
|
}
|
|
|
|
|
AudioIntegerChunk::I24(_) => self.clone(),
|
|
|
|
|
AudioIntegerChunk::I32(d) => {
|
|
|
|
|
let converted = crate::conversions::convert_i32_to_i24(d);
|
|
|
|
|
AudioIntegerChunk::I24(converted)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convertit vers I32 (avec conversion si nécessaire)
|
|
|
|
|
pub fn to_i32(&self) -> AudioIntegerChunk {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(d) => {
|
|
|
|
|
let converted = crate::conversions::convert_i16_to_i32(d);
|
|
|
|
|
AudioIntegerChunk::I32(converted)
|
|
|
|
|
}
|
|
|
|
|
AudioIntegerChunk::I24(d) => {
|
|
|
|
|
let converted = crate::conversions::convert_i24_to_i32(d);
|
|
|
|
|
AudioIntegerChunk::I32(converted)
|
|
|
|
|
}
|
|
|
|
|
AudioIntegerChunk::I32(_) => self.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retourne un itérateur sur les frames
|
|
|
|
|
pub fn frames(&self) -> Box<dyn Iterator<Item = [i32; 2]> + '_> {
|
|
|
|
|
match self {
|
|
|
|
|
AudioIntegerChunk::I16(d) => {
|
2025-11-04 20:34:44 +01:00
|
|
|
Box::new(d.get_frames().iter().map(|f| [f[0] as i32, f[1] as i32]))
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
2025-11-14 10:43:53 +01:00
|
|
|
AudioIntegerChunk::I24(d) => Box::new(
|
|
|
|
|
d.get_frames()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|f| [f[0].as_i32(), f[1].as_i32()]),
|
|
|
|
|
),
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioIntegerChunk::I32(d) => Box::new(d.get_frames().iter().map(|f| [f[0], f[1]])),
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<AudioChunk> for AudioIntegerChunk {
|
|
|
|
|
/// Convertit depuis AudioChunk (panic si le chunk est float)
|
|
|
|
|
fn from(chunk: AudioChunk) -> Self {
|
|
|
|
|
match chunk {
|
|
|
|
|
AudioChunk::I16(d) => AudioIntegerChunk::I16(d),
|
|
|
|
|
AudioChunk::I24(d) => AudioIntegerChunk::I24(d),
|
|
|
|
|
AudioChunk::I32(d) => AudioIntegerChunk::I32(d),
|
|
|
|
|
AudioChunk::F32(_) | AudioChunk::F64(_) => {
|
|
|
|
|
panic!("Cannot convert float AudioChunk to AudioIntegerChunk")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum AudioFloatChunk {
|
|
|
|
|
F32(Arc<AudioChunkData<f32>>),
|
|
|
|
|
F64(Arc<AudioChunkData<f64>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AudioFloatChunk {
|
|
|
|
|
/// Retourne le nombre de frames du chunk
|
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(d) => d.len(),
|
|
|
|
|
AudioFloatChunk::F64(d) => d.len(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est vide
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
self.len() == 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Taux d'échantillonnage (Hz)
|
|
|
|
|
pub fn sample_rate(&self) -> u32 {
|
|
|
|
|
match self {
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioFloatChunk::F32(d) => d.get_sample_rate(),
|
|
|
|
|
AudioFloatChunk::F64(d) => d.get_sample_rate(),
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gain courant en décibels
|
|
|
|
|
pub fn gain_db(&self) -> f64 {
|
|
|
|
|
match self {
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioFloatChunk::F32(d) => d.get_gain_db(),
|
|
|
|
|
AudioFloatChunk::F64(d) => d.get_gain_db(),
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gain sous forme linéaire
|
|
|
|
|
pub fn gain_linear(&self) -> f64 {
|
|
|
|
|
gain_linear_from_db(self.gain_db())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Définit le gain en dB
|
|
|
|
|
pub fn set_gain_db(&self, gain_db: f64) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(d) => AudioFloatChunk::F32(d.set_gain_db(gain_db)),
|
|
|
|
|
AudioFloatChunk::F64(d) => AudioFloatChunk::F64(d.set_gain_db(gain_db)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Définit le gain via un facteur linéaire
|
|
|
|
|
pub fn set_gain_linear(&self, gain_linear: f64) -> Self {
|
|
|
|
|
self.set_gain_db(gain_db_from_linear(gain_linear))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Modifie le gain (ajoute un delta en dB)
|
|
|
|
|
pub fn with_modified_gain_db(&self, delta_gain_db: f64) -> Self {
|
|
|
|
|
self.set_gain_db(self.gain_db() + delta_gain_db)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applique le gain et retourne un nouveau chunk avec les données modifiées
|
|
|
|
|
///
|
|
|
|
|
/// Le gain du chunk résultant est remis à 0.0 dB.
|
|
|
|
|
pub fn apply_gain(self) -> Self {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(d) => AudioFloatChunk::F32(d.apply_gain()),
|
|
|
|
|
AudioFloatChunk::F64(d) => AudioFloatChunk::F64(d.apply_gain()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retourne le nom du type de sample
|
|
|
|
|
pub fn type_name(&self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(_) => "f32",
|
|
|
|
|
AudioFloatChunk::F64(_) => "f64",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est de type F32
|
|
|
|
|
pub fn is_f32(&self) -> bool {
|
|
|
|
|
matches!(self, AudioFloatChunk::F32(_))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Vérifie si le chunk est de type F64
|
|
|
|
|
pub fn is_f64(&self) -> bool {
|
|
|
|
|
matches!(self, AudioFloatChunk::F64(_))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retourne la profondeur de bit du chunk (32 ou 64)
|
|
|
|
|
pub fn bit_depth(&self) -> u8 {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(_) => 32,
|
|
|
|
|
AudioFloatChunk::F64(_) => 64,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convertit vers AudioChunk
|
|
|
|
|
pub fn as_audio_chunk(&self) -> AudioChunk {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(d) => AudioChunk::F32(d.clone()),
|
|
|
|
|
AudioFloatChunk::F64(d) => AudioChunk::F64(d.clone()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convertit vers F32 (avec conversion si nécessaire)
|
|
|
|
|
pub fn to_f32(&self) -> AudioFloatChunk {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(_) => self.clone(),
|
|
|
|
|
AudioFloatChunk::F64(d) => {
|
|
|
|
|
let converted = crate::conversions::convert_f64_to_f32(d);
|
|
|
|
|
AudioFloatChunk::F32(converted)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convertit vers F64 (avec conversion si nécessaire)
|
|
|
|
|
pub fn to_f64(&self) -> AudioFloatChunk {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(d) => {
|
|
|
|
|
let converted = crate::conversions::convert_f32_to_f64(d);
|
|
|
|
|
AudioFloatChunk::F64(converted)
|
|
|
|
|
}
|
|
|
|
|
AudioFloatChunk::F64(_) => self.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retourne un itérateur sur les frames
|
|
|
|
|
pub fn frames(&self) -> Box<dyn Iterator<Item = [f64; 2]> + '_> {
|
|
|
|
|
match self {
|
|
|
|
|
AudioFloatChunk::F32(d) => {
|
2025-11-04 20:34:44 +01:00
|
|
|
Box::new(d.get_frames().iter().map(|f| [f[0] as f64, f[1] as f64]))
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
2025-11-04 20:34:44 +01:00
|
|
|
AudioFloatChunk::F64(d) => Box::new(d.get_frames().iter().map(|f| [f[0], f[1]])),
|
2025-11-01 21:10:57 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<AudioChunk> for AudioFloatChunk {
|
|
|
|
|
/// Convertit depuis AudioChunk (panic si le chunk est entier)
|
|
|
|
|
fn from(chunk: AudioChunk) -> Self {
|
|
|
|
|
match chunk {
|
|
|
|
|
AudioChunk::F32(d) => AudioFloatChunk::F32(d),
|
|
|
|
|
AudioChunk::F64(d) => AudioFloatChunk::F64(d),
|
|
|
|
|
AudioChunk::I16(_) | AudioChunk::I24(_) | AudioChunk::I32(_) => {
|
|
|
|
|
panic!("Cannot convert integer AudioChunk to AudioFloatChunk")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Fonctions utilitaires de conversion gain
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
2025-10-28 18:41:34 +01:00
|
|
|
const MIN_GAIN_DB: f64 = -120.0;
|
2025-10-11 00:33:13 +02:00
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Convertit un gain linéaire (>0) en décibels
|
|
|
|
|
#[inline]
|
2025-11-01 21:10:57 +01:00
|
|
|
pub fn gain_db_from_linear(gain_linear: f64) -> f64 {
|
2025-10-28 18:41:34 +01:00
|
|
|
if gain_linear <= 0.0 {
|
|
|
|
|
MIN_GAIN_DB
|
|
|
|
|
} else {
|
|
|
|
|
(20.0 * gain_linear.log10()).max(MIN_GAIN_DB)
|
2025-10-11 00:33:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-28 18:41:34 +01:00
|
|
|
|
2025-10-30 08:54:47 +01:00
|
|
|
/// Convertit un gain en décibels vers un gain linéaire
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn gain_linear_from_db(gain_db: f64) -> f64 {
|
2025-11-01 21:10:57 +01:00
|
|
|
10f64.powf(gain_db / 20.0)
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Tests
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_audio_chunk_data_f32() {
|
|
|
|
|
let stereo: Vec<[f32; 2]> = vec![[0.5, 0.25], [0.75, 0.125]];
|
|
|
|
|
let chunk = AudioChunkData::new(stereo, 48000, 0.0);
|
|
|
|
|
|
|
|
|
|
assert_eq!(chunk.len(), 2);
|
2025-11-04 20:34:44 +01:00
|
|
|
assert_eq!(chunk.get_sample_rate(), 48000);
|
2025-10-30 08:54:47 +01:00
|
|
|
assert!(!chunk.is_empty());
|
2025-11-04 20:34:44 +01:00
|
|
|
assert_eq!(chunk.get_gain_db(), 0.0);
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_audio_chunk_data_i32() {
|
|
|
|
|
let stereo: Vec<[i32; 2]> = vec![[1000, 2000], [3000, 4000]];
|
|
|
|
|
let chunk = AudioChunkData::new(stereo, 48000, -6.0);
|
|
|
|
|
|
|
|
|
|
assert_eq!(chunk.len(), 2);
|
2025-11-04 20:34:44 +01:00
|
|
|
assert_eq!(chunk.get_gain_db(), -6.0);
|
2025-10-30 08:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_audio_chunk_enum() {
|
|
|
|
|
let data_f32 = AudioChunkData::new(vec![[0.5f32, 0.25f32]; 1000], 48000, 0.0);
|
|
|
|
|
let chunk = AudioChunk::F32(data_f32);
|
|
|
|
|
|
|
|
|
|
assert_eq!(chunk.len(), 1000);
|
|
|
|
|
assert_eq!(chunk.sample_rate(), 48000);
|
|
|
|
|
assert_eq!(chunk.type_name(), "f32");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_gain_conversion() {
|
|
|
|
|
let linear = 2.0;
|
2025-11-01 21:10:57 +01:00
|
|
|
let db = gain_db_from_linear(linear);
|
2025-10-30 08:54:47 +01:00
|
|
|
assert!((db - 6.0206).abs() < 0.01); // 2x ≈ +6dB
|
|
|
|
|
|
2025-11-01 21:10:57 +01:00
|
|
|
let back = gain_linear_from_db(db);
|
2025-10-30 08:54:47 +01:00
|
|
|
assert!((back - linear).abs() < 0.001);
|
|
|
|
|
}
|
|
|
|
|
}
|