pre-passage f32
This commit is contained in:
195
src-tauri/src/utils/audio_utils.rs
Normal file
195
src-tauri/src/utils/audio_utils.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use rubato::{Resampler, SincFixedIn, SincInterpolationType, SincInterpolationParameters, WindowFunction};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Resampler audio optimisé avec rubato pour temps réel
|
||||
#[derive(Clone)]
|
||||
pub struct AudioResampler {
|
||||
// État du resampler (recréé quand les paramètres changent)
|
||||
state: Arc<Mutex<ResamplerState>>,
|
||||
// Buffer de conversion réutilisable (évite allocations)
|
||||
conversion_buffers: Arc<Mutex<ConversionBuffers>>,
|
||||
}
|
||||
|
||||
struct ResamplerState {
|
||||
resampler: Option<SincFixedIn<f32>>,
|
||||
current_from_rate: u32,
|
||||
current_to_rate: u32,
|
||||
current_channels: usize,
|
||||
}
|
||||
|
||||
struct ConversionBuffers {
|
||||
f32_buffer: Vec<f32>,
|
||||
planar_input: Vec<Vec<f32>>,
|
||||
output_i16: Vec<i16>,
|
||||
}
|
||||
|
||||
impl AudioResampler {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(ResamplerState {
|
||||
resampler: None,
|
||||
current_from_rate: 0,
|
||||
current_to_rate: 0,
|
||||
current_channels: 0,
|
||||
})),
|
||||
conversion_buffers: Arc::new(Mutex::new(ConversionBuffers {
|
||||
f32_buffer: Vec::with_capacity(8192),
|
||||
planar_input: Vec::new(),
|
||||
output_i16: Vec::with_capacity(8192),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resample audio en gardant la continuité entre les chunks
|
||||
pub fn resample(
|
||||
&self,
|
||||
input: &[i16],
|
||||
from_sample_rate: u32,
|
||||
to_sample_rate: u32,
|
||||
channels: usize,
|
||||
) -> Vec<i16> {
|
||||
// ✅ Pas de conversion si même sample rate
|
||||
if from_sample_rate == to_sample_rate || input.is_empty() {
|
||||
return input.to_vec();
|
||||
}
|
||||
|
||||
let mut state = self.state.lock();
|
||||
let mut buffers = self.conversion_buffers.lock();
|
||||
|
||||
// 🔄 Recrée le resampler si configuration changée
|
||||
let need_new_resampler = state.resampler.is_none()
|
||||
|| state.current_from_rate != from_sample_rate
|
||||
|| state.current_to_rate != to_sample_rate
|
||||
|| state.current_channels != channels;
|
||||
|
||||
if state.resampler.is_none()
|
||||
|| state.current_from_rate != from_sample_rate
|
||||
|| state.current_to_rate != to_sample_rate
|
||||
|| state.current_channels != channels {
|
||||
match Self::create_resampler(from_sample_rate, to_sample_rate, channels) {
|
||||
Ok(new_resampler) => {
|
||||
state.resampler = Some(new_resampler);
|
||||
state.current_from_rate = from_sample_rate;
|
||||
state.current_to_rate = to_sample_rate;
|
||||
state.current_channels = channels;
|
||||
println!("🔧 Resampler reconfiguré: {}Hz → {}Hz, {} canaux",
|
||||
from_sample_rate, to_sample_rate, channels);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Erreur création resampler: {}", e);
|
||||
return input.to_vec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🚀 Processing avec le resampler
|
||||
if let Some(ref mut resampler) = state.resampler {
|
||||
match Self::process_with_resampler(resampler, input, channels, &mut buffers) {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Erreur resampling: {}", e);
|
||||
input.to_vec()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
input.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crée un resampler optimisé pour votre cas d'usage
|
||||
fn create_resampler(
|
||||
from_rate: u32,
|
||||
to_rate: u32,
|
||||
channels: usize,
|
||||
) -> Result<SincFixedIn<f32>, Box<dyn std::error::Error>> {
|
||||
let ratio = to_rate as f64 / from_rate as f64;
|
||||
|
||||
// 🎯 Paramètres optimisés pour audio temps réel de qualité
|
||||
let params = SincInterpolationParameters {
|
||||
sinc_len: 256, // Bon compromis qualité/performance
|
||||
f_cutoff: 0.95, // Anti-aliasing fort
|
||||
interpolation: SincInterpolationType::Linear, // Plus rapide que Cubic
|
||||
oversampling_factor: 256,
|
||||
window: WindowFunction::BlackmanHarris2,
|
||||
};
|
||||
|
||||
// Chunk size optimisé pour vos frames audio
|
||||
let chunk_size = 1024; // Compatible avec vos frames de 960-1024 samples
|
||||
|
||||
Ok(SincFixedIn::<f32>::new(
|
||||
ratio,
|
||||
2.0, // Max ratio change pour stabilité
|
||||
params,
|
||||
chunk_size,
|
||||
channels,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Process audio avec buffers réutilisables
|
||||
fn process_with_resampler(
|
||||
resampler: &mut SincFixedIn<f32>,
|
||||
input: &[i16],
|
||||
channels: usize,
|
||||
buffers: &mut ConversionBuffers,
|
||||
) -> Result<Vec<i16>, Box<dyn std::error::Error>> {
|
||||
let frames = input.len() / channels;
|
||||
|
||||
// 🔄 1. Conversion i16 → f32 (réutilise buffer)
|
||||
buffers.f32_buffer.clear();
|
||||
buffers.f32_buffer.extend(input.iter().map(|&s| s as f32 / 32768.0));
|
||||
|
||||
// 🔄 2. Conversion interleaved → planar (réutilise buffers)
|
||||
buffers.planar_input.clear();
|
||||
buffers.planar_input.resize(channels, Vec::with_capacity(frames));
|
||||
|
||||
for ch in 0..channels {
|
||||
buffers.planar_input[ch].clear();
|
||||
}
|
||||
|
||||
for (frame_idx, frame) in buffers.f32_buffer.chunks_exact(channels).enumerate() {
|
||||
for (ch, &sample) in frame.iter().enumerate() {
|
||||
buffers.planar_input[ch].push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
// 🎯 3. Resampling magique !
|
||||
let output_planar = resampler.process(&buffers.planar_input, None)?;
|
||||
|
||||
// 🔄 4. Conversion planar → interleaved i16 (réutilise buffer)
|
||||
let output_frames = output_planar[0].len();
|
||||
buffers.output_i16.clear();
|
||||
buffers.output_i16.reserve(output_frames * channels);
|
||||
|
||||
for frame_idx in 0..output_frames {
|
||||
for ch in 0..channels {
|
||||
let sample = (output_planar[ch][frame_idx] * 32767.0)
|
||||
.round()
|
||||
.clamp(-32768.0, 32767.0) as i16;
|
||||
buffers.output_i16.push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buffers.output_i16.clone())
|
||||
}
|
||||
|
||||
/// Réinitialise l'état interne (pour éviter glitches lors de changements)
|
||||
pub fn reset(&self) {
|
||||
let mut state = self.state.lock();
|
||||
if let Some(ref mut resampler) = state.resampler {
|
||||
let _ = resampler.reset();
|
||||
}
|
||||
println!("🔄 Resampler reset");
|
||||
}
|
||||
|
||||
/// Version oneshot sans état (pour tests)
|
||||
pub fn resample_oneshot(
|
||||
input: &[i16],
|
||||
from_rate: u32,
|
||||
to_rate: u32,
|
||||
channels: usize,
|
||||
) -> Result<Vec<i16>, Box<dyn std::error::Error>> {
|
||||
let resampler = AudioResampler::new();
|
||||
Ok(resampler.resample(input, from_rate, to_rate, channels))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user