Skip to content

Conversation

@0----0
Copy link

@0----0 0----0 commented Nov 7, 2025

Fixes #811

SourcesQueueOutput currently violates the span interface by returning incorrect values for sample_rate and channels while the iterator is positioned on a source boundary (i.e. at the end of one source, before the next source has started.).

This PR adjusts its sample_rate and channels implementations so that they use current_span_len == 0 as an admissible heuristic to check if the current source is empty. This means that it can obey the span interface contract as long as the sources are returning Some current_span_len values. (If they aren't, there's probably no way we can possibly obey that contract.)

In order to actually uncomment the basic test for the queue, I also had to adjust the SamplesBuffer implementation to return a Some current_span_len value. This showed some unrelated rounding errors in SkipDuration, so I made unrelated adjustments to the SkipDuration math to reduce rounding errors, just to keep tests passing.

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.
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.
The queue will now change samples and sample rate immediately on source boundaries that correctly report their current_span_len, so this test passes
unrelated to the rest of the PR, but since I'm fixing current_span_len, might as well do this too

thanks @max-m
Copy link
Member

@roderickvd roderickvd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for tackling this one! Sorry for the late response. Here are a few points and questions for further improvement.

#[inline]
fn current_span_len(&self) -> Option<usize> {
None
Some(self.data.len() - self.pos)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contract is that current_span_len returns the length of the span / buffer regardless of its current position. That's for size_hint to do.

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.data.len(), Some(self.data.len()))
(self.data.len() - self.pos, Some(self.data.len() - self.pos))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SamplesBuffer could therefore also implement ExactSizeIterator. I know that's not directly related to this change, but see it also doesn't implement it currently.

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the cheekiness 😄

How much sense do you think it would make to add an is_empty helper method to Source?

@roderickvd roderickvd added the bug label Dec 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

First 512 samples of a source added to an empty Sink are always mono

2 participants