@@ -5,7 +5,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
55use std:: sync:: { Arc , Mutex } ;
66use std:: time:: Duration ;
77
8- use crate :: source:: { Empty , SeekError , Source , Zero } ;
8+ use dasp_sample:: Sample as _;
9+
10+ use crate :: source:: { Empty , SeekError , Source } ;
911use crate :: Sample ;
1012
1113use crate :: common:: { ChannelCount , SampleRate } ;
@@ -36,7 +38,7 @@ pub fn queue(keep_alive_if_empty: bool) -> (Arc<SourcesQueueInput>, SourcesQueue
3638 signal_after_end : None ,
3739 input : input. clone ( ) ,
3840 samples_consumed_in_span : 0 ,
39- padding_samples_remaining : 0 ,
41+ silence_samples_remaining : 0 ,
4042 } ;
4143
4244 ( input, output)
@@ -116,72 +118,55 @@ pub struct SourcesQueueOutput {
116118 // Track samples consumed in the current span to detect mid-span endings.
117119 samples_consumed_in_span : usize ,
118120
119- // When a source ends mid-frame, this counts how many silence samples to inject
120- // to complete the frame before transitioning to the next source.
121- padding_samples_remaining : usize ,
122- }
123-
124- /// Returns a threshold span length that ensures frame alignment.
125- ///
126- /// Spans must end on frame boundaries (multiples of channel count) to prevent
127- /// channel misalignment. Returns ~512 samples rounded to the nearest frame.
128- #[ inline]
129- fn threshold ( channels : ChannelCount ) -> usize {
130- const BASE_SAMPLES : usize = 512 ;
131- let ch = channels. get ( ) as usize ;
132- BASE_SAMPLES . div_ceil ( ch) * ch
121+ // This counts how many silence samples to inject when a source ends.
122+ silence_samples_remaining : usize ,
133123}
134124
135125impl Source for SourcesQueueOutput {
136126 #[ inline]
137127 fn current_span_len ( & self ) -> Option < usize > {
138128 if !self . current . is_exhausted ( ) {
139- return self . current . current_span_len ( ) ;
140- } else if self . input . keep_alive_if_empty . load ( Ordering :: Acquire )
141- && self . input . next_sounds . lock ( ) . unwrap ( ) . is_empty ( )
142- {
143- // Return what that Zero's current_span_len() will be: Some(threshold(channels)).
144- return Some ( threshold ( self . current . channels ( ) ) ) ;
129+ self . current . current_span_len ( ) . or_else ( || {
130+ // If the current source doesn't report a span length, provide one that ensures
131+ // frame alignment. Returning None here could cause downstream sources to
132+ // incorrectly assume all later queued sources have the same sample rate and
133+ // channel count.
134+ Some ( self . current . channels ( ) . get ( ) as usize )
135+ } )
136+ } else if self . input . keep_alive_if_empty . load ( Ordering :: Acquire ) {
137+ // We will be playing frames of silence until the next sound is loaded.
138+ Some ( self . current . channels ( ) . get ( ) as usize )
139+ } else {
140+ // Queue is empty, no sources queued
141+ None
145142 }
146-
147- None
148143 }
149144
150145 #[ inline]
151146 fn channels ( & self ) -> ChannelCount {
152- if !self . current . is_exhausted ( ) {
153- // Current source is active (producing samples)
154- // - Initially: never (Empty is exhausted immediately)
155- // - After append: the appended source while playing
156- // - With keep_alive: Zero (silence) while playing
157- self . current . channels ( )
158- } else if let Some ( ( next, _) ) = self . input . next_sounds . lock ( ) . unwrap ( ) . front ( ) {
159- // Current source exhausted, peek at next queued source
160- // This is critical: UniformSourceIterator queries metadata during append,
161- // before any samples are pulled. We must report the next source's metadata.
162- next. channels ( )
163- } else {
164- // Queue is empty, no sources queued
165- // - Initially: Empty
166- // - With keep_alive: exhausted Zero between silence chunks (matches Empty)
167- // - Without keep_alive: Empty (will end on next())
168- self . current . channels ( )
147+ if self . current . is_exhausted ( ) {
148+ if let Some ( ( next, _) ) = self . input . next_sounds . lock ( ) . unwrap ( ) . front ( ) {
149+ // Current source exhausted, peek at next queued source
150+ // This is critical: UniformSourceIterator queries metadata during append,
151+ // before any samples are pulled. We must report the next source's metadata.
152+ return next. channels ( ) ;
153+ }
169154 }
155+
156+ self . current . channels ( )
170157 }
171158
172159 #[ inline]
173160 fn sample_rate ( & self ) -> SampleRate {
174- if !self . current . is_exhausted ( ) {
175- // Current source is active (producing samples)
176- self . current . sample_rate ( )
177- } else if let Some ( ( next, _) ) = self . input . next_sounds . lock ( ) . unwrap ( ) . front ( ) {
178- // Current source exhausted, peek at next queued source
179- // This prevents wrong resampling setup in UniformSourceIterator
180- next. sample_rate ( )
181- } else {
182- // Queue is empty, no sources queued
183- self . current . sample_rate ( )
161+ if self . current . is_exhausted ( ) {
162+ if let Some ( ( next, _) ) = self . input . next_sounds . lock ( ) . unwrap ( ) . front ( ) {
163+ // Current source exhausted, peek at next queued source
164+ // This prevents wrong resampling setup in UniformSourceIterator
165+ return next. sample_rate ( ) ;
166+ }
184167 }
168+
169+ self . current . sample_rate ( )
185170 }
186171
187172 #[ inline]
@@ -211,34 +196,45 @@ impl Iterator for SourcesQueueOutput {
211196 fn next ( & mut self ) -> Option < Self :: Item > {
212197 loop {
213198 // If we're padding to complete a frame, return silence.
214- if self . padding_samples_remaining > 0 {
215- self . padding_samples_remaining -= 1 ;
216- return Some ( 0.0 ) ;
199+ if self . silence_samples_remaining > 0 {
200+ self . silence_samples_remaining -= 1 ;
201+ return Some ( Sample :: EQUILIBRIUM ) ;
217202 }
218203
219204 // Basic situation that will happen most of the time.
220205 if let Some ( sample) = self . current . next ( ) {
221- self . samples_consumed_in_span += 1 ;
206+ self . samples_consumed_in_span = self
207+ . samples_consumed_in_span
208+ . checked_add ( 1 )
209+ . unwrap_or_else ( || {
210+ self . samples_consumed_in_span % self . channels ( ) . get ( ) as usize + 1
211+ } ) ;
222212 return Some ( sample) ;
223213 }
224214
225- // Source ended - check if we ended mid-frame and need padding.
226- let channels = self . current . channels ( ) . get ( ) as usize ;
227- let incomplete_frame_samples = self . samples_consumed_in_span % channels;
228- if incomplete_frame_samples > 0 {
229- // We're mid-frame - need to pad with silence to complete it.
230- self . padding_samples_remaining = channels - incomplete_frame_samples;
231- // Reset counter now since we're transitioning to a new span.
232- self . samples_consumed_in_span = 0 ;
233- // Continue loop - next iteration will inject silence.
234- continue ;
215+ // Current source is exhausted - check if we ended mid-frame and need padding.
216+ if self . samples_consumed_in_span > 0 {
217+ let channels = self . current . channels ( ) . get ( ) as usize ;
218+ let incomplete_frame_samples = self . samples_consumed_in_span % channels;
219+ if incomplete_frame_samples > 0 {
220+ // We're mid-frame - need to pad with silence to complete it.
221+ self . silence_samples_remaining = channels - incomplete_frame_samples;
222+ // Reset counter now since we're transitioning to a new span.
223+ self . samples_consumed_in_span = 0 ;
224+ // Continue loop - next iterations will inject silence.
225+ continue ;
226+ }
235227 }
236228
237- // Reset counter and move to next sound.
238- // In order to avoid inlining this expensive operation, the code is in another function.
239- self . samples_consumed_in_span = 0 ;
229+ // Move to next sound, play silence, or end.
230+ // In order to avoid inlining that expensive operation, the code is in another function.
240231 if self . go_next ( ) . is_err ( ) {
241- return None ;
232+ if self . input . keep_alive_if_empty . load ( Ordering :: Acquire ) {
233+ self . silence_samples_remaining = self . current . channels ( ) . get ( ) as usize ;
234+ continue ;
235+ } else {
236+ return None ;
237+ }
242238 }
243239 }
244240 }
@@ -251,7 +247,7 @@ impl Iterator for SourcesQueueOutput {
251247
252248impl SourcesQueueOutput {
253249 // Called when `current` is empty, and we must jump to the next element.
254- // Returns `Ok` if the sound should continue playing, or an error if it should stop .
250+ // Returns `Ok` if there is another sound should continue playing, or `Err` when there is not .
255251 //
256252 // This method is separate so that it is not inlined.
257253 fn go_next ( & mut self ) -> Result < ( ) , ( ) > {
@@ -261,23 +257,7 @@ impl SourcesQueueOutput {
261257
262258 let ( next, signal_after_end) = {
263259 let mut next = self . input . next_sounds . lock ( ) . unwrap ( ) ;
264-
265- if let Some ( next) = next. pop_front ( ) {
266- next
267- } else {
268- let channels = self . current . channels ( ) ;
269- let silence = Box :: new ( Zero :: new_samples (
270- channels,
271- self . current . sample_rate ( ) ,
272- threshold ( channels) ,
273- ) ) as Box < _ > ;
274- if self . input . keep_alive_if_empty . load ( Ordering :: Acquire ) {
275- // Play a short silence in order to avoid spinlocking.
276- ( silence, None )
277- } else {
278- return Err ( ( ) ) ;
279- }
280- }
260+ next. pop_front ( ) . ok_or ( ( ) ) ?
281261 } ;
282262
283263 self . current = next;
@@ -350,7 +330,6 @@ mod tests {
350330 }
351331
352332 #[ test]
353- #[ ignore] // TODO: not yet implemented
354333 fn no_delay_when_added ( ) {
355334 let ( tx, mut rx) = queue:: queue ( true ) ;
356335
0 commit comments