11using SoundFlow . Enums ;
22using SoundFlow . Interfaces ;
3+ using SoundFlow . Providers ;
34
45namespace SoundFlow . Abstracts ;
56
@@ -87,8 +88,7 @@ protected SoundPlayerBase(ISoundDataProvider dataProvider)
8788 var resampleBufferFrames = Math . Max ( 256 , initialSampleRate / 10 ) ;
8889 _resampleBuffer = new float [ resampleBufferFrames * initialChannels ] ;
8990 _timeStretcher = new WsolaTimeStretcher ( initialChannels , _playbackSpeed ) ;
90- _timeStretcherInputBuffer =
91- new float [ Math . Max ( _timeStretcher . MinInputSamplesToProcess * 2 , 8192 * initialChannels ) ] ;
91+ _timeStretcherInputBuffer = new float [ Math . Max ( _timeStretcher . MinInputSamplesToProcess * 2 , 8192 * initialChannels ) ] ;
9292 }
9393
9494 /// <inheritdoc />
@@ -101,6 +101,20 @@ protected override void GenerateAudio(Span<float> output)
101101 return ;
102102 }
103103
104+ // Directly read from provider when playback speed is 1.0
105+ if ( Math . Abs ( _playbackSpeed - 1.0f ) < 0.001f )
106+ {
107+ int samplesRead = _dataProvider . ReadBytes ( output ) ;
108+ _rawSamplePosition += samplesRead ;
109+
110+ if ( samplesRead < output . Length )
111+ {
112+ HandleEndOfStream ( output [ samplesRead ..] ) ;
113+ }
114+ return ;
115+ }
116+
117+
104118 var channels = AudioEngine . Channels ;
105119 // Ensure time stretcher has correct channel count.
106120 if ( _timeStretcher . GetTargetSpeed ( ) == 0f && _playbackSpeed != 0f && channels > 0 )
@@ -212,6 +226,14 @@ private int FillResampleBuffer(int minSamplesRequiredInOutputBuffer)
212226 Array . Resize ( ref _resampleBuffer ,
213227 Math . Max ( minSamplesRequiredInOutputBuffer , _resampleBuffer . Length * 2 ) ) ;
214228 }
229+
230+ // When playback speed is close to 1.0, use simpler interpolation
231+ if ( Math . Abs ( _playbackSpeed - 1.0f ) < 0.1f )
232+ {
233+ var directRead = _dataProvider . ReadBytes ( _resampleBuffer . AsSpan ( _resampleBufferValidSamples ) ) ;
234+ _resampleBufferValidSamples += directRead ;
235+ return directRead ;
236+ }
215237
216238 var totalSourceSamplesRepresented = 0 ;
217239
@@ -223,30 +245,41 @@ private int FillResampleBuffer(int minSamplesRequiredInOutputBuffer)
223245
224246 var availableInStretcherInput =
225247 _timeStretcherInputBufferValidSamples - _timeStretcherInputBufferReadOffset ;
226- var providerHasMoreData = _dataProvider . Position < _dataProvider . Length ;
248+ var providerHasMoreData = _dataProvider . Position < _dataProvider . Length || _dataProvider . Length == - 1 ; // -1 = unknown length or infinite stream
227249
228250 // If time stretcher input buffer needs more data and provider has it.
229251 if ( availableInStretcherInput < _timeStretcher . MinInputSamplesToProcess && providerHasMoreData )
230252 {
231- // Shift existing valid data to the beginning of the input buffer .
232- if ( _timeStretcherInputBufferReadOffset > 0 && availableInStretcherInput > 0 )
253+ // Compact the buffer by moving the remaining valid samples to the start if we have a read offset .
254+ if ( _timeStretcherInputBufferReadOffset > 0 )
233255 {
234- Buffer . BlockCopy ( _timeStretcherInputBuffer , _timeStretcherInputBufferReadOffset * sizeof ( float ) ,
235- _timeStretcherInputBuffer , 0 , availableInStretcherInput * sizeof ( float ) ) ;
256+ // Calculate remaining samples. It should not be negative, but we defend against it.
257+ var remaining = _timeStretcherInputBufferValidSamples - _timeStretcherInputBufferReadOffset ;
258+ if ( remaining > 0 )
259+ {
260+ // Shift the remaining valid data to the beginning of the input buffer.
261+ Buffer . BlockCopy ( _timeStretcherInputBuffer , _timeStretcherInputBufferReadOffset * sizeof ( float ) ,
262+ _timeStretcherInputBuffer , 0 , remaining * sizeof ( float ) ) ;
263+ _timeStretcherInputBufferValidSamples = remaining ;
264+ }
265+ else
266+ {
267+ // If no samples remain, the buffer is effectively empty.
268+ _timeStretcherInputBufferValidSamples = 0 ;
269+ }
270+ // After compacting, the next read position is the start of the buffer.
271+ _timeStretcherInputBufferReadOffset = 0 ;
236272 }
237273
238- _timeStretcherInputBufferValidSamples = availableInStretcherInput ;
239- _timeStretcherInputBufferReadOffset = 0 ;
240-
241274 // Read more data from the data provider into the time stretcher input buffer.
242275 var spaceToReadIntoInput = _timeStretcherInputBuffer . Length - _timeStretcherInputBufferValidSamples ;
243276 if ( spaceToReadIntoInput > 0 )
244277 {
245- var readFromProvider = _dataProvider . ReadBytes (
246- _timeStretcherInputBuffer . AsSpan ( _timeStretcherInputBufferValidSamples ,
247- spaceToReadIntoInput ) ) ;
278+ var readFromProvider = _dataProvider . ReadBytes ( _timeStretcherInputBuffer . AsSpan ( _timeStretcherInputBufferValidSamples , spaceToReadIntoInput ) ) ;
248279 _timeStretcherInputBufferValidSamples += readFromProvider ;
249- availableInStretcherInput = _timeStretcherInputBufferValidSamples ;
280+
281+ // After reading, the available samples have increased. We must recalculate it for the current loop iteration.
282+ availableInStretcherInput = _timeStretcherInputBufferValidSamples - _timeStretcherInputBufferReadOffset ;
250283 providerHasMoreData = _dataProvider . Position < _dataProvider . Length ;
251284 }
252285 }
@@ -317,8 +350,51 @@ private int FillResampleBuffer(int minSamplesRequiredInOutputBuffer)
317350 /// </summary>
318351 protected virtual void HandleEndOfStream ( Span < float > remainingOutputBuffer )
319352 {
320- if ( IsLooping )
353+ // For live streams with unknown length, don't treat buffer underflow as end-of-stream
354+ if ( ! IsLooping && _dataProvider . Length > 0 )
355+ {
356+ // Original end-of-stream handling
357+ if ( ! remainingOutputBuffer . IsEmpty )
358+ {
359+ var spaceToFill = remainingOutputBuffer . Length ;
360+ var currentlyValidInResample = _resampleBufferValidSamples ;
361+
362+ if ( currentlyValidInResample < spaceToFill )
363+ {
364+ var sourceSamplesFromFinalFill = FillResampleBuffer ( Math . Max ( currentlyValidInResample , spaceToFill ) ) ;
365+ _rawSamplePosition += sourceSamplesFromFinalFill ;
366+ _rawSamplePosition = Math . Min ( _rawSamplePosition , _dataProvider . Length ) ;
367+ }
368+
369+ var toCopy = Math . Min ( spaceToFill , _resampleBufferValidSamples ) ;
370+ if ( toCopy > 0 )
371+ {
372+ SafeCopyTo ( _resampleBuffer . AsSpan ( 0 , toCopy ) , remainingOutputBuffer . Slice ( 0 , toCopy ) ) ;
373+ var remainingInResampleAfterCopy = _resampleBufferValidSamples - toCopy ;
374+ if ( remainingInResampleAfterCopy > 0 )
375+ {
376+ Buffer . BlockCopy ( _resampleBuffer , toCopy * sizeof ( float ) , _resampleBuffer , 0 ,
377+ remainingInResampleAfterCopy * sizeof ( float ) ) ;
378+ }
379+
380+ _resampleBufferValidSamples = remainingInResampleAfterCopy ;
381+ if ( toCopy < spaceToFill )
382+ {
383+ remainingOutputBuffer . Slice ( toCopy ) . Clear ( ) ;
384+ }
385+ }
386+ else
387+ {
388+ remainingOutputBuffer . Clear ( ) ;
389+ }
390+ }
391+
392+ State = PlaybackState . Stopped ;
393+ OnPlaybackEnded ( ) ;
394+ }
395+ else if ( IsLooping )
321396 {
397+ // Original looping handling
322398 var targetLoopStart = Math . Max ( 0 , _loopStartSamples ) ;
323399 var actualLoopEnd = ( _loopEndSamples == - 1 )
324400 ? _dataProvider . Length
@@ -334,48 +410,11 @@ protected virtual void HandleEndOfStream(Span<float> remainingOutputBuffer)
334410 return ;
335411 }
336412 }
337-
338- // If not looping or loop points are invalid, fill remaining buffer with what's left and stop.
339- if ( ! remainingOutputBuffer . IsEmpty )
413+ // For live streams (Length <= 0), just clear the buffer and continue
414+ else
340415 {
341- var spaceToFill = remainingOutputBuffer . Length ;
342- var currentlyValidInResample = _resampleBufferValidSamples ;
343-
344- // Attempt one last fill of the resample buffer.
345- if ( currentlyValidInResample < spaceToFill )
346- {
347- var sourceSamplesFromFinalFill = FillResampleBuffer ( Math . Max ( currentlyValidInResample , spaceToFill ) ) ;
348- _rawSamplePosition += sourceSamplesFromFinalFill ;
349- _rawSamplePosition = Math . Min ( _rawSamplePosition , _dataProvider . Length ) ;
350- }
351-
352- // Copy remaining valid samples to output and clear the rest.
353- var toCopy = Math . Min ( spaceToFill , _resampleBufferValidSamples ) ;
354- if ( toCopy > 0 )
355- {
356- _resampleBuffer . AsSpan ( 0 , toCopy ) . CopyTo ( remainingOutputBuffer . Slice ( 0 , toCopy ) ) ;
357- var remainingInResampleAfterCopy = _resampleBufferValidSamples - toCopy ;
358- if ( remainingInResampleAfterCopy > 0 )
359- {
360- // Shift remaining samples in resample buffer.
361- Buffer . BlockCopy ( _resampleBuffer , toCopy * sizeof ( float ) , _resampleBuffer , 0 ,
362- remainingInResampleAfterCopy * sizeof ( float ) ) ;
363- }
364-
365- _resampleBufferValidSamples = remainingInResampleAfterCopy ;
366- if ( toCopy < spaceToFill )
367- {
368- remainingOutputBuffer . Slice ( toCopy ) . Clear ( ) ; // Clear any unfilled part.
369- }
370- }
371- else
372- {
373- remainingOutputBuffer . Clear ( ) ; // No valid samples, clear entire buffer.
374- }
416+ remainingOutputBuffer . Clear ( ) ;
375417 }
376-
377- State = PlaybackState . Stopped ;
378- OnPlaybackEnded ( ) ;
379418 }
380419
381420 /// <summary>
@@ -483,6 +522,14 @@ public bool Seek(int sampleOffset)
483522 }
484523
485524 #endregion
525+
526+ private static void SafeCopyTo ( Span < float > source , Span < float > destination )
527+ {
528+ for ( var i = 0 ; i < Math . Min ( source . Length , destination . Length ) ; i ++ )
529+ {
530+ destination [ i ] = Math . Clamp ( source [ i ] , - 1f , 1f ) ;
531+ }
532+ }
486533
487534 #region Loop Point Configuration Methods
488535
0 commit comments