diff --git a/Cargo.lock b/Cargo.lock index 9842babbe0..990203967b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14716,6 +14716,7 @@ dependencies = [ "tauri-plugin-store2", "tauri-specta", "thiserror 2.0.17", + "tokio", "vergen-gix", ] diff --git a/crates/audio-utils/src/resampler/dynamic_new.rs b/crates/audio-utils/src/resampler/dynamic_new.rs index 67a3b5806b..653c0e2841 100644 --- a/crates/audio-utils/src/resampler/dynamic_new.rs +++ b/crates/audio-utils/src/resampler/dynamic_new.rs @@ -130,6 +130,7 @@ where backend: Backend, last_source_rate: u32, draining: bool, + pending_sample: Option<(f32, u32)>, } impl ResamplerDynamicNew @@ -161,6 +162,7 @@ where backend, last_source_rate: source_rate, draining: false, + pending_sample: None, }) } @@ -215,33 +217,35 @@ where let me = Pin::into_inner(self); loop { - if let Some(chunk) = me.try_yield_chunk(me.draining) { - return Poll::Ready(Some(Ok(chunk))); - } - - if me.draining { - return Poll::Ready(None); - } - - let current_rate = me.source.sample_rate(); - if current_rate != me.last_source_rate { + if let Some((sample, new_rate)) = me.pending_sample.take() { match me.drain_for_rate_change() { Ok(true) => { - if let Err(err) = me.rebuild_backend(current_rate) { + if let Err(err) = me.rebuild_backend(new_rate) { return Poll::Ready(Some(Err(err))); } + me.backend.push_sample(sample); continue; } Ok(false) => { if let Some(chunk) = me.try_yield_chunk(true) { + me.pending_sample = Some((sample, new_rate)); return Poll::Ready(Some(Ok(chunk))); } + me.pending_sample = Some((sample, new_rate)); continue; } Err(err) => return Poll::Ready(Some(Err(err))), } } + if let Some(chunk) = me.try_yield_chunk(me.draining) { + return Poll::Ready(Some(Ok(chunk))); + } + + if me.draining { + return Poll::Ready(None); + } + match me.backend.process_all_ready_blocks() { Ok(true) => continue, Ok(false) => {} @@ -254,18 +258,25 @@ where inner.poll_next(cx) }; - match sample_poll { - Poll::Ready(Some(sample)) => { - me.backend.push_sample(sample); - } + let sample = match sample_poll { + Poll::Ready(Some(sample)) => sample, Poll::Ready(None) => { if let Err(err) = me.drain_at_eos() { return Poll::Ready(Some(Err(err))); } me.draining = true; + continue; } Poll::Pending => return Poll::Pending, + }; + + let current_rate = me.source.sample_rate(); + if current_rate != me.last_source_rate { + me.pending_sample = Some((sample, current_rate)); + continue; } + + me.backend.push_sample(sample); } } } diff --git a/crates/audio-utils/src/resampler/mod.rs b/crates/audio-utils/src/resampler/mod.rs index 85442fec56..7b1c427d76 100644 --- a/crates/audio-utils/src/resampler/mod.rs +++ b/crates/audio-utils/src/resampler/mod.rs @@ -222,4 +222,25 @@ mod tests { chunks.iter().flatten().flatten().copied() ); } + + #[tokio::test] + async fn test_dynamic_new_rate_change_boundary() { + let segments = vec![ + (vec![1.0, 2.0, 3.0, 4.0], 8000), + (vec![5.0, 6.0, 7.0, 8.0], 16000), + ]; + let target_rate = 16000; + let chunk_size = 4; + + let source = DynamicRateSource::new(segments); + let resampler = ResamplerDynamicNew::new(source, target_rate, chunk_size).unwrap(); + + let chunks: Vec<_> = resampler.collect().await; + let actual: Vec = chunks.into_iter().flatten().flatten().collect(); + + let expected_second_segment = vec![5.0, 6.0, 7.0, 8.0]; + let actual_second_segment: Vec = actual.iter().rev().take(4).rev().copied().collect(); + + assert_eq!(expected_second_segment, actual_second_segment,); + } }