From 1022b63abb9403f3ff9033d21e03238082384107 Mon Sep 17 00:00:00 2001 From: 0----0 Date: Fri, 7 Nov 2025 13:10:52 -0500 Subject: [PATCH 1/5] SourcesQueueOutput can peek metadata from next source channels and sample_rate currently return the old metadata on source boundaries, which is problematic for UniformSourceIterator, which checks metadata on span boundaries. This commit takes advantage of current_span_len's behavior to use it as a cheap "peek" to hopefully find out if we're done with the current source or not. --- src/queue.rs | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/queue.rs b/src/queue.rs index 495606b4..790ca443 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -113,6 +113,8 @@ pub struct SourcesQueueOutput { } const THRESHOLD: usize = 512; +const SILENCE_SAMPLE_RATE: SampleRate = nz!(44100); +const SILENCE_CHANNELS: ChannelCount = nz!(1); impl Source for SourcesQueueOutput { #[inline] @@ -154,12 +156,28 @@ impl Source for SourcesQueueOutput { #[inline] fn channels(&self) -> ChannelCount { - self.current.channels() + // current_span_len() should never return 0 unless the source is empty, so this is a cheeky hint + if self.current.current_span_len() != Some(0) { + self.current.channels() + } else if let Some((next, _)) = self.input.next_sounds.lock().unwrap().first() { + next.channels() + } else { + // If keep_alive_if_empty is true, then it'll be mono 44.1khz silence -- otherwise it doesn't matter what it is + SILENCE_CHANNELS + } } #[inline] fn sample_rate(&self) -> SampleRate { - self.current.sample_rate() + // current_span_len() should never return 0 unless the source is empty, so this is a cheeky hint + if self.current.current_span_len() != Some(0) { + self.current.sample_rate() + } else if let Some((next, _)) = self.input.next_sounds.lock().unwrap().first() { + next.sample_rate() + } else { + // If keep_alive_if_empty is true, then it'll be mono 44.1khz silence -- otherwise it doesn't matter what it is + SILENCE_SAMPLE_RATE + } } #[inline] @@ -221,7 +239,11 @@ impl SourcesQueueOutput { let mut next = self.input.next_sounds.lock().unwrap(); if next.is_empty() { - let silence = Box::new(Zero::new_samples(nz!(1), nz!(44100), THRESHOLD)) as Box<_>; + let silence = Box::new(Zero::new_samples( + SILENCE_CHANNELS, + SILENCE_SAMPLE_RATE, + THRESHOLD, + )) as Box<_>; if self.input.keep_alive_if_empty.load(Ordering::Acquire) { // Play a short silence in order to avoid spinlocking. (silence, None) From 1b510f09f20c8eded06331b500c7ad6e4c669f3c Mon Sep 17 00:00:00 2001 From: 0----0 Date: Fri, 7 Nov 2025 13:13:14 -0500 Subject: [PATCH 2/5] SkipDuration multiplies before dividing do_skip_duration's math was prone to rounding errors -- this commit shuffles the math around a bit so that the sample skip and the duration subtraction are each holding onto as much precision as possible. --- src/source/skip.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/source/skip.rs b/src/source/skip.rs index 36f8d210..d3c43857 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -39,18 +39,22 @@ where return; } - let ns_per_sample: u128 = - NS_PER_SECOND / input.sample_rate().get() as u128 / input.channels().get() as u128; + let sample_rate = input.sample_rate().get() as u128; + let channels = input.channels().get() as u128; + + let samples_per_channel = duration.as_nanos() * sample_rate / NS_PER_SECOND; + let samples_to_skip: u128 = samples_per_channel * channels; // Check if we need to skip only part of the current span. - if span_len as u128 * ns_per_sample > duration.as_nanos() { - skip_samples(input, (duration.as_nanos() / ns_per_sample) as usize); + if span_len as u128 > samples_to_skip { + skip_samples(input, samples_to_skip as usize); return; } + duration -= Duration::from_nanos( + (NS_PER_SECOND * span_len as u128 / channels / sample_rate) as u64, + ); skip_samples(input, span_len); - - duration -= Duration::from_nanos((span_len * ns_per_sample as usize) as u64); } } From 06752a78b3c7e3b22329a05b28f308f316a0effd Mon Sep 17 00:00:00 2001 From: 0----0 Date: Fri, 7 Nov 2025 13:13:57 -0500 Subject: [PATCH 3/5] SamplesBuffer reports its span length --- src/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buffer.rs b/src/buffer.rs index 305f6b1f..e2e601eb 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -73,7 +73,7 @@ impl SamplesBuffer { impl Source for SamplesBuffer { #[inline] fn current_span_len(&self) -> Option { - None + Some(self.data.len() - self.pos) } #[inline] From 5257f7bc357fea3ea93fd31d2b85c25cd0d547f1 Mon Sep 17 00:00:00 2001 From: 0----0 Date: Fri, 7 Nov 2025 13:14:37 -0500 Subject: [PATCH 4/5] Basic queue test is no longer ignored The queue will now change samples and sample rate immediately on source boundaries that correctly report their current_span_len, so this test passes --- src/queue.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/queue.rs b/src/queue.rs index 790ca443..923fcd5e 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -269,7 +269,6 @@ mod tests { use crate::source::Source; #[test] - #[ignore] // FIXME: samples rate and channel not updated immediately after transition fn basic() { let (tx, mut rx) = queue::queue(false); From 4d73ca915755387accfe8fa6de0d07264e6449e4 Mon Sep 17 00:00:00 2001 From: 0----0 Date: Fri, 7 Nov 2025 16:11:47 -0500 Subject: [PATCH 5/5] SamplesBuffer size_hint incorporates self.pos unrelated to the rest of the PR, but since I'm fixing current_span_len, might as well do this too thanks @max-m --- src/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buffer.rs b/src/buffer.rs index e2e601eb..ef84d9c2 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -126,7 +126,7 @@ impl Iterator for SamplesBuffer { #[inline] fn size_hint(&self) -> (usize, Option) { - (self.data.len(), Some(self.data.len())) + (self.data.len() - self.pos, Some(self.data.len() - self.pos)) } }