@@ -21,6 +21,7 @@ You should have received a copy of the GNU General Public License
2121
2222using Amplitude . Models ;
2323using AmplitudeSoundboard ;
24+ using DynamicData ;
2425using ManagedBass ;
2526using ManagedBass . Mix ;
2627using System ;
@@ -38,15 +39,16 @@ class MSoundEngine : ISoundEngine
3839 public static ISoundEngine Instance => _instance ??= new MSoundEngine ( ) ;
3940
4041 private readonly object currentlyPlayingLock = new ( ) ;
41-
4242 private ObservableCollection < PlayingClip > _currentlyPlaying = [ ] ;
4343 public ObservableCollection < PlayingClip > CurrentlyPlaying => _currentlyPlaying ;
4444
4545 private readonly object queueLock = new ( ) ;
46-
4746 private ObservableCollection < SoundClip > _queued = [ ] ;
4847 public ObservableCollection < SoundClip > Queued => _queued ;
4948
49+ private readonly object streamsToFreeLock = new ( ) ;
50+ private Collection < StreamToFree > _streamsToFree = [ ] ;
51+
5052
5153 private const long TIMER_MS = 200 ;
5254 private Timer timer = new ( TIMER_MS )
@@ -56,30 +58,24 @@ class MSoundEngine : ISoundEngine
5658
5759 private void RefreshPlaybackProgressAndCheckQueue ( object ? sender , ElapsedEventArgs e )
5860 {
59- lock ( currentlyPlayingLock )
61+ foreach ( var track in CurrentlyPlaying . ToArray ( ) )
6062 {
61- List < PlayingClip > toRemove = [ ] ;
62- foreach ( var track in CurrentlyPlaying )
63- {
64- track . CurrentPos += TIMER_MS * 0.001d ;
65- if ( track . ProgressPct == 1 )
66- {
67- toRemove . Add ( track ) ;
68- }
69- }
63+ track . CurrentPos += TIMER_MS * 0.001d ;
7064
71- foreach ( var track in toRemove )
65+ if ( track . LoopClip )
7266 {
73- if ( track . LoopClip )
67+ if ( track . ProgressPct == 1 )
7468 {
7569 track . CurrentPos = 0 ;
7670 Bass . ChannelPlay ( track . BassStreamId , true ) ;
7771 }
78- else
79- {
80- Bass . StreamFree ( track . BassStreamId ) ;
81- CurrentlyPlaying . Remove ( track ) ;
82- }
72+ }
73+ // might be off by 100ms, but oh well
74+ else if ( track . CurrentPos + 0.1d >= track . Length - ( track . FadeOutMilis * 0.001d ) )
75+ {
76+ var timeRemainingMilis = 1000 * ( track . Length - track . CurrentPos - 0.1d ) ;
77+ var fadeOutDuration = timeRemainingMilis < track . FadeOutMilis ? timeRemainingMilis : track . FadeOutMilis ;
78+ StopPlaying ( track . BassStreamId , track . RemainingMilis , track . FadeOutMilis ) ;
8379 }
8480 }
8581 lock ( queueLock )
@@ -91,11 +87,21 @@ private void RefreshPlaybackProgressAndCheckQueue(object? sender, ElapsedEventAr
9187 Queued . RemoveAt ( 0 ) ;
9288 }
9389 }
90+ lock ( streamsToFreeLock )
91+ {
92+ var timeNow = DateTimeOffset . Now . ToUnixTimeMilliseconds ( ) ;
93+ var streamsToFreeAndRemove = _streamsToFree . Where ( it => timeNow >= it . freeAtUnixTime ) . ToArray ( ) ;
94+ foreach ( StreamToFree stream in streamsToFreeAndRemove )
95+ {
96+ StopPlaying ( stream . bassStreamId , 0 , 0 ) ;
97+ }
98+ _streamsToFree . RemoveMany ( streamsToFreeAndRemove ) ;
99+ }
94100 }
95101
96102 public const int SAMPLE_RATE = 44100 ;
97103
98- private readonly object bass_lock = new object ( ) ;
104+ private readonly object bass_lock = new ( ) ;
99105
100106
101107 public List < string > OutputDeviceListWithoutGlobal
@@ -166,20 +172,14 @@ private void StopAndRemoveFromQueue(string id)
166172 {
167173 lock ( queueLock )
168174 {
169- var toRemove = Queued . Where ( clip => clip . Id == id ) . ToList ( ) ;
170- foreach ( var clip in toRemove )
175+ foreach ( var clip in Queued . Where ( clip => clip . Id == id ) . ToArray ( ) )
171176 {
172177 Queued . Remove ( clip ) ;
173178 }
174179 }
175- lock ( currentlyPlayingLock )
180+ foreach ( var clip in CurrentlyPlaying . Where ( clip => clip . SoundClipId == id ) . ToArray ( ) )
176181 {
177- var toRemove = CurrentlyPlaying . Where ( clip => clip . SoundClipId == id ) . ToList ( ) ;
178- foreach ( var clip in toRemove )
179- {
180- Bass . StreamFree ( clip . BassStreamId ) ;
181- CurrentlyPlaying . Remove ( clip ) ;
182- }
182+ StopPlaying ( clip . BassStreamId , clip . RemainingMilis , clip . FadeOutMilis ) ;
183183 }
184184 }
185185
@@ -218,11 +218,11 @@ public void Play(SoundClip source, bool fromQueue = false)
218218
219219 foreach ( OutputSettings settings in source . OutputSettingsFromProfile )
220220 {
221- Play ( source . AudioFilePath , settings . Volume , source . Volume , settings . DeviceName , source . LoopClip , tempId , source . Name ) ;
221+ Play ( source . AudioFilePath , settings . Volume , source . Volume , settings . DeviceName , source . LoopClip , tempId , settings . FadeOutMilis , source . Name ) ;
222222 }
223223 }
224224
225- private void Play ( string fileName , int volume , int volumeMultiplier , string playerDeviceName , bool loopClip , string soundClipId , string ? name = null )
225+ private void Play ( string fileName , int volume , int volumeMultiplier , string playerDeviceName , bool loopClip , string soundClipId , int fadeOutMilis , string ? name = null )
226226 {
227227 double vol = ( volume / 100.0 ) * ( volumeMultiplier / 100.0 ) ;
228228
@@ -257,7 +257,15 @@ private void Play(string fileName, int volume, int volumeMultiplier, string play
257257 {
258258 var len = Bass . ChannelGetLength ( stream , PositionFlags . Bytes ) ;
259259 double length = Bass . ChannelBytes2Seconds ( stream , len ) ;
260- PlayingClip track = new ( string . IsNullOrEmpty ( name ) ? Path . GetFileNameWithoutExtension ( fileName ) ?? "" : name , soundClipId , playerDeviceName , stream , length , loopClip ) ;
260+ PlayingClip track = new (
261+ string . IsNullOrEmpty ( name ) ? Path . GetFileNameWithoutExtension ( fileName ) ?? "" : name ,
262+ soundClipId ,
263+ playerDeviceName ,
264+ stream ,
265+ length ,
266+ loopClip ,
267+ fadeOutMilis
268+ ) ;
261269
262270 lock ( currentlyPlayingLock )
263271 {
@@ -310,27 +318,33 @@ public void Reset()
310318 {
311319 Queued . Clear ( ) ;
312320 }
313- lock ( currentlyPlayingLock )
321+ foreach ( var stream in CurrentlyPlaying . ToArray ( ) )
314322 {
315- foreach ( var stream in CurrentlyPlaying )
316- {
317- Bass . StreamFree ( stream . BassStreamId ) ;
318- }
319-
320- CurrentlyPlaying . Clear ( ) ;
323+ var timeRemainingMilis = stream . RemainingMilis ;
324+ var fadeOutDuration = timeRemainingMilis < stream . FadeOutMilis ? timeRemainingMilis : stream . FadeOutMilis ;
325+ StopPlaying ( stream . BassStreamId , timeRemainingMilis , ( int ) fadeOutDuration ) ;
321326 }
322327 }
323328
324- public void StopPlaying ( int bassId )
329+ public void StopPlaying ( int handle , double remainingMilis , int fadeOutMilis )
325330 {
326- lock ( currentlyPlayingLock )
331+ if ( fadeOutMilis == 0 )
327332 {
328- PlayingClip ? track = CurrentlyPlaying . FirstOrDefault ( c => c . BassStreamId == bassId ) ;
329- if ( track != null )
333+ Bass . StreamFree ( handle ) ;
334+ lock ( currentlyPlayingLock )
330335 {
331- CurrentlyPlaying . Remove ( track ) ;
336+ PlayingClip ? track = CurrentlyPlaying . FirstOrDefault ( c => c . BassStreamId == handle ) ;
337+ if ( track != null )
338+ {
339+ CurrentlyPlaying . Remove ( track ) ;
340+ }
332341 }
333- Bass . StreamFree ( bassId ) ;
342+ }
343+ else
344+ {
345+ int remainingFadeOut = ( int ) ( remainingMilis < fadeOutMilis ? remainingMilis : fadeOutMilis ) ;
346+ Bass . ChannelSlideAttribute ( handle , ChannelAttribute . Volume , 0 , remainingFadeOut ) ;
347+ _streamsToFree . Add ( new StreamToFree ( handle , DateTimeOffset . Now . ToUnixTimeMilliseconds ( ) + remainingFadeOut ) ) ;
334348 }
335349 }
336350
@@ -349,5 +363,17 @@ public void Dispose()
349363 Reset ( ) ;
350364 Bass . Free ( ) ;
351365 }
352- }
366+
367+ private class StreamToFree
368+ {
369+ public int bassStreamId ;
370+ public long freeAtUnixTime ;
371+
372+ public StreamToFree ( int bassStreamId , long freeAtUnixTime )
373+ {
374+ this . bassStreamId = bassStreamId ;
375+ this . freeAtUnixTime = freeAtUnixTime ;
376+ }
377+ }
378+ }
353379}
0 commit comments