From f2b7e6e68970f88b7a0a3dc61ad158b553305de4 Mon Sep 17 00:00:00 2001 From: Nell Date: Tue, 22 Jul 2025 02:51:37 +0200 Subject: [PATCH] f32 instead of i16 --- src-tauri/src/core/mixer.rs | 111 +++++++++---------- src-tauri/src/core/playback.rs | 2 +- src-tauri/src/utils/audio_utils.rs | 164 +++++++++++++++++++++-------- 3 files changed, 180 insertions(+), 97 deletions(-) diff --git a/src-tauri/src/core/mixer.rs b/src-tauri/src/core/mixer.rs index 31e344f..2d9da25 100644 --- a/src-tauri/src/core/mixer.rs +++ b/src-tauri/src/core/mixer.rs @@ -4,48 +4,82 @@ use std::sync::Arc; use arc_swap::ArcSwap; use cpal::SampleRate; use crate::domain::audio_client::AudioClientManager; +use crate::utils::audio_utils::{AudioResampler, AudioTools}; use crate::utils::ringbuf::{RingBufReader, RingBufWriter, RingBuffer}; #[derive(Clone)] pub struct AudioMixer { audio_client_manager: AudioClientManager, - buffer_writer: Arc>, - buffer_reader: Arc>, - sample_rate: SampleRate, - channels: usize, + buffer_writer: Arc>, + buffer_reader: Arc>, + + internal_sample_rate: usize, + output_channels: usize, + output_sample_rate: usize, + resampler: AudioResampler, } impl AudioMixer { - pub fn new(sample_rate: usize, channels: usize, audio_client_manager: AudioClientManager) -> Self { + pub fn new(output_sample_rate: usize, output_channels: usize, audio_client_manager: AudioClientManager) -> Self { let (buffer_writer, buffer_reader) = RingBuffer::new(2048).split(); Self { audio_client_manager, buffer_writer: Arc::new(buffer_writer), - buffer_reader: Arc::new(buffer_reader) + buffer_reader: Arc::new(buffer_reader), + internal_sample_rate: 48000, + output_sample_rate, + output_channels, + resampler: AudioResampler::new(), } } - pub fn mix_next_frame(&self, size: usize) { - let mut frames = Vec::>::new(); - let users_audio = self.audio_client_manager.take_audio_collection(size/2).into_iter() - .map(|audio| AudioMixer::mono_to_stereo(audio)) - .collect::>>(); + pub fn mix_next_frame(&self, output_size: usize) { + // 1. Calcule combien de samples 48kHz on doit récupérer + let ratio = self.internal_sample_rate as f32 / self.output_sample_rate as f32; + let internal_frames_needed = ((output_size / self.output_channels) as f32 * ratio).ceil() as usize; + // 2. Récupère les données utilisateurs (48kHz mono) + let users_audio = self.audio_client_manager.take_audio_collection(internal_frames_needed); - frames.extend_from_slice(&users_audio); - - // Récupérer tous les sons des notifications (pas encore dev) - - let mixed_frame = if frames.is_empty() { - vec![0i16; size] - }else{ - Self::mix_frames(&frames, size) + // 3. Mix en 48kHz mono + let mixed_internal = if users_audio.is_empty() { + vec![0f32; internal_frames_needed] + } else { + AudioTools::mix_frames(&users_audio, internal_frames_needed) }; - self.buffer_writer.push_slice_overwrite(&mixed_frame); + // 3. Mix en 48kHz mono (résultat en f32) + let mixed_internal = if users_audio.is_empty() { + vec![0.0f32; internal_frames_needed] + } else { + AudioTools::mix_frames(&users_audio, internal_frames_needed) + }; + + // 4. Resample 48kHz -> output_rate si nécessaire + let resampled = if self.internal_sample_rate != self.output_sample_rate { + self.resampler.resample( + &mixed_internal, + self.internal_sample_rate, + self.output_sample_rate, + 1 // mono + ) + } else { + mixed_internal + }; + + // 5. Convert mono -> output_channels + let final_frame = AudioTools::change_channel_count( + &resampled, + 1, + self.output_channels + ); + + // 6. Écrit dans le ringbuffer (pas besoin de resize exacte) + self.buffer_writer.push_slice_overwrite(&final_frame); + } - pub fn read(&self, size: usize) -> Vec { - let mut data = vec![0i16; size]; + pub fn read(&self, size: usize) -> Vec { + let mut data = vec![0f32; size]; // Essaie de pop autant d'échantillons que possible let read = self.buffer_reader.pop_slice(&mut data); // Si on n'a pas tout eu, les éléments restants sont déjà à 0 @@ -53,37 +87,4 @@ impl AudioMixer { } -} - -impl AudioMixer { - fn mono_to_stereo(mono_samples: Vec) -> Vec { - let mut stereo_data = Vec::with_capacity(mono_samples.len() * 2); - - // Chaque échantillon mono devient deux échantillons stéréo identiques - for sample in mono_samples { - stereo_data.push(sample); // Canal gauche - stereo_data.push(sample); // Canal droit - } - - stereo_data - } - - fn mix_frames(frames: &[Vec], size: usize) -> Vec { - let mut mixed = vec![0i32; size]; - - for frame in frames { - for (i, &sample) in frame.iter().enumerate() { - if i < size { - mixed[i] += sample as i32; - } - } - } - - let count = frames.len().max(1) as i32; // éviter la division par zéro - mixed - .into_iter() - .map(|sample| (sample / count).clamp(i16::MIN as i32, i16::MAX as i32) as i16) - .collect() - - } } \ No newline at end of file diff --git a/src-tauri/src/core/playback.rs b/src-tauri/src/core/playback.rs index 3a47f1a..2b31c36 100644 --- a/src-tauri/src/core/playback.rs +++ b/src-tauri/src/core/playback.rs @@ -127,7 +127,7 @@ impl Speaker { pub fn build_stream(&self, callback: F) -> Stream where - F: FnMut(&mut [i16], &cpal::OutputCallbackInfo) + Send + 'static, + F: FnMut(&mut [f32], &cpal::OutputCallbackInfo) + Send + 'static, { let config = self.get_stream_config(); diff --git a/src-tauri/src/utils/audio_utils.rs b/src-tauri/src/utils/audio_utils.rs index 0d29aef..eadc55f 100644 --- a/src-tauri/src/utils/audio_utils.rs +++ b/src-tauri/src/utils/audio_utils.rs @@ -77,15 +77,15 @@ pub struct AudioResampler { struct ResamplerState { resampler: Option>, - current_from_rate: u32, - current_to_rate: u32, + current_from_rate: usize, + current_to_rate: usize, current_channels: usize, } struct ConversionBuffers { f32_buffer: Vec, planar_input: Vec>, - output_i16: Vec, + planar_output_buffer: Vec>, // Ajouté pour éviter des allocs } impl AudioResampler { @@ -100,19 +100,19 @@ impl AudioResampler { conversion_buffers: Arc::new(Mutex::new(ConversionBuffers { f32_buffer: Vec::with_capacity(8192), planar_input: Vec::new(), - output_i16: Vec::with_capacity(8192), + planar_output_buffer: Vec::new(), })), } } - /// Resample audio en gardant la continuité entre les chunks - pub fn resample( + /// Resample audio générique - fonctionne avec i16 et f32 + pub fn resample( &self, - input: &[i16], - from_sample_rate: u32, - to_sample_rate: u32, + input: &[T], + from_sample_rate: usize, + to_sample_rate: usize, channels: usize, - ) -> Vec { + ) -> Vec { // ✅ Pas de conversion si même sample rate if from_sample_rate == to_sample_rate || input.is_empty() { return input.to_vec(); @@ -122,11 +122,6 @@ impl AudioResampler { 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 @@ -149,7 +144,7 @@ impl AudioResampler { // 🚀 Processing avec le resampler if let Some(ref mut resampler) = state.resampler { - match Self::process_with_resampler(resampler, input, channels, &mut buffers) { + match Self::process_with_resampler_generic(resampler, input, channels, &mut buffers) { Ok(output) => output, Err(e) => { eprintln!("❌ Erreur resampling: {}", e); @@ -163,8 +158,8 @@ impl AudioResampler { /// Crée un resampler optimisé pour votre cas d'usage fn create_resampler( - from_rate: u32, - to_rate: u32, + from_rate: usize, + to_rate: usize, channels: usize, ) -> Result, Box> { let ratio = to_rate as f64 / from_rate as f64; @@ -190,20 +185,20 @@ impl AudioResampler { )?) } - /// Process audio avec buffers réutilisables - fn process_with_resampler( + /// Process audio générique avec buffers réutilisables + fn process_with_resampler_generic( resampler: &mut SincFixedIn, - input: &[i16], + input: &[T], channels: usize, buffers: &mut ConversionBuffers, - ) -> Result, Box> { + ) -> Result, Box> { let frames = input.len() / channels; - // 🔄 1. Conversion i16 → f32 (réutilise buffer) + // 🔄 1. Conversion vers f32 (utilise AudioSample trait) buffers.f32_buffer.clear(); - buffers.f32_buffer.extend(input.iter().map(|&s| s as f32 / 32768.0)); + buffers.f32_buffer.extend(input.iter().map(|sample| sample.to_f32())); - // 🔄 2. Conversion interleaved → planar (réutilise buffers) + // 🔄 2. Conversion interleaved → planar buffers.planar_input.clear(); buffers.planar_input.resize(channels, Vec::with_capacity(frames)); @@ -211,30 +206,117 @@ impl AudioResampler { buffers.planar_input[ch].clear(); } - for (frame_idx, frame) in buffers.f32_buffer.chunks_exact(channels).enumerate() { + for frame in buffers.f32_buffer.chunks_exact(channels) { for (ch, &sample) in frame.iter().enumerate() { buffers.planar_input[ch].push(sample); } } - // 🎯 3. Resampling magique ! + // 🎯 3. Resampling magique ! (Rubato travaille directement en f32) let output_planar = resampler.process(&buffers.planar_input, None)?; - // 🔄 4. Conversion planar → interleaved i16 (réutilise buffer) + // 🔄 4. Conversion planar → interleaved avec type générique let output_frames = output_planar[0].len(); - buffers.output_i16.clear(); - buffers.output_i16.reserve(output_frames * channels); + let mut output = Vec::with_capacity(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); + let f32_sample = output_planar[ch][frame_idx]; + // Utilise AudioSample pour reconvertir au format d'origine + let converted_sample = T::from_f32(f32_sample).clamp_audio(); + output.push(converted_sample); } } - Ok(buffers.output_i16.clone()) + Ok(output) + } + + /// Version spécialisée pour f32 (plus efficace, évite conversions inutiles) + pub fn resample_f32( + &self, + input: &[f32], + from_sample_rate: usize, + to_sample_rate: usize, + channels: usize, + ) -> Vec { + // ✅ 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 + 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; + } + Err(e) => { + eprintln!("❌ Erreur création resampler: {}", e); + return input.to_vec(); + } + } + } + + // 🚀 Processing optimisé pour f32 (pas de conversion) + if let Some(ref mut resampler) = state.resampler { + match Self::process_f32_direct(resampler, input, channels, &mut buffers) { + Ok(output) => output, + Err(e) => { + eprintln!("❌ Erreur resampling f32: {}", e); + input.to_vec() + } + } + } else { + input.to_vec() + } + } + + /// Processing optimisé pour f32 natif (pas de conversions) + fn process_f32_direct( + resampler: &mut SincFixedIn, + input: &[f32], + channels: usize, + buffers: &mut ConversionBuffers, + ) -> Result, Box> { + let frames = input.len() / channels; + + // 🔄 1. Conversion interleaved → planar (pas de conversion de format!) + 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 in input.chunks_exact(channels) { + for (ch, &sample) in frame.iter().enumerate() { + buffers.planar_input[ch].push(sample); + } + } + + // 🎯 2. Resampling direct ! + let output_planar = resampler.process(&buffers.planar_input, None)?; + + // 🔄 3. Conversion planar → interleaved (pas de conversion de format!) + let output_frames = output_planar[0].len(); + let mut output = Vec::with_capacity(output_frames * channels); + + for frame_idx in 0..output_frames { + for ch in 0..channels { + output.push(output_planar[ch][frame_idx]); + } + } + + Ok(output) } /// Réinitialise l'état interne (pour éviter glitches lors de changements) @@ -246,13 +328,13 @@ impl AudioResampler { println!("🔄 Resampler reset"); } - /// Version oneshot sans état (pour tests) - pub fn resample_oneshot( - input: &[i16], - from_rate: u32, - to_rate: u32, + /// Version oneshot générique sans état (pour tests) + pub fn resample_oneshot( + input: &[T], + from_rate: usize, + to_rate: usize, channels: usize, - ) -> Result, Box> { + ) -> Result, Box> { let resampler = AudioResampler::new(); Ok(resampler.resample(input, from_rate, to_rate, channels)) }