@@ -25,7 +25,10 @@ namespace Exiled.API.Features.Audio.PcmSources
2525 /// </summary>
2626 public sealed class MixerSource : IPcmSource
2727 {
28- private readonly List < IPcmSource > sources = new ( ) ;
28+ private readonly object sourcesLock = new ( ) ;
29+
30+ private volatile IPcmSource [ ] sources ;
31+
2932 private float [ ] tempBuffer ;
3033
3134 /// <summary>
@@ -35,15 +38,24 @@ public sealed class MixerSource : IPcmSource
3538 public MixerSource ( IEnumerable < IPcmSource > initialSources )
3639 {
3740 if ( initialSources != null )
38- sources . AddRange ( initialSources . Where ( s => s != null ) ) ;
41+ sources = initialSources . Where ( s => s != null ) . ToArray ( ) ;
42+ else
43+ sources = Array . Empty < IPcmSource > ( ) ;
3944
4045 TrackInfo = new TrackData { Path = "Audio Mixer" , Duration = 0 } ;
4146 }
4247
4348 /// <summary>
4449 /// Gets a value indicating whether this mixer contains any live audio sources.
4550 /// </summary>
46- public bool ContainsLiveSource => sources . Any ( source => source is ILiveSource || ( source is MixerSource mixer && mixer . ContainsLiveSource ) ) ;
51+ public bool ContainsLiveSource
52+ {
53+ get
54+ {
55+ IPcmSource [ ] currentSources = sources ;
56+ return currentSources . Any ( source => source is ILiveSource || ( source is MixerSource mixer && mixer . ContainsLiveSource ) ) ;
57+ }
58+ }
4759
4860 /// <summary>
4961 /// Gets or sets a value indicating whether the mixer should stay alive and output silence even when all internal sources have finished playing.
@@ -58,21 +70,39 @@ public MixerSource(IEnumerable<IPcmSource> initialSources)
5870 /// <summary>
5971 /// Gets the maximum total duration of all active sources in the mixer, in seconds.
6072 /// </summary>
61- public double TotalDuration => sources . Count > 0 ? sources . Max ( x => x . TotalDuration ) : 0.0 ;
73+ public double TotalDuration
74+ {
75+ get
76+ {
77+ IPcmSource [ ] currentSources = sources ;
78+ return currentSources . Length > 0 ? currentSources . Max ( x => x . TotalDuration ) : 0.0 ;
79+ }
80+ }
6281
6382 /// <summary>
6483 /// Gets or sets the current playback position in seconds across all active sources.
6584 /// </summary>
6685 public double CurrentTime
6786 {
68- get => sources . Count > 0 ? sources . Max ( x => x . CurrentTime ) : 0.0 ;
87+ get
88+ {
89+ IPcmSource [ ] currentSources = sources ;
90+ return currentSources . Length > 0 ? currentSources . Max ( x => x . CurrentTime ) : 0.0 ;
91+ }
6992 set => Seek ( value ) ;
7093 }
7194
7295 /// <summary>
7396 /// Gets a value indicating whether all internal sources have ended and <see cref="KeepAlive"/> is set to false.
7497 /// </summary>
75- public bool Ended => ! KeepAlive && ( sources . Count == 0 || sources . All ( x => x . Ended ) ) ;
98+ public bool Ended
99+ {
100+ get
101+ {
102+ IPcmSource [ ] currentSources = sources ;
103+ return ! KeepAlive && ( currentSources . Length == 0 || currentSources . All ( x => x . Ended ) ) ;
104+ }
105+ }
76106
77107 /// <summary>
78108 /// Reads a sequence of mixed PCM samples from all active sources into the specified buffer.
@@ -83,20 +113,43 @@ public double CurrentTime
83113 /// <returns>The number of samples written to the <paramref name="buffer"/>.</returns>
84114 public int Read ( float [ ] buffer , int offset , int count )
85115 {
116+ IPcmSource [ ] currentSources = sources ;
117+
118+ if ( currentSources . Length == 0 )
119+ {
120+ Array . Clear ( buffer , offset , count ) ;
121+ return KeepAlive ? count : 0 ;
122+ }
123+
124+ int maxRead = 0 ;
125+ bool needsCleanup = false ;
126+
127+ if ( currentSources . Length == 1 )
128+ {
129+ IPcmSource src = currentSources [ 0 ] ;
130+ if ( src . Ended )
131+ {
132+ Array . Clear ( buffer , offset , count ) ;
133+ CleanupEndedSources ( ) ;
134+ return KeepAlive ? count : 0 ;
135+ }
136+
137+ maxRead = src . Read ( buffer , offset , count ) ;
138+ return KeepAlive ? count : maxRead ;
139+ }
140+
86141 if ( tempBuffer == null || tempBuffer . Length < count )
87142 tempBuffer = new float [ count ] ;
88143
89144 Array . Clear ( buffer , offset , count ) ;
90- int maxRead = 0 ;
91145
92- for ( int i = sources . Count - 1 ; i >= 0 ; i -- )
146+ for ( int i = currentSources . Length - 1 ; i >= 0 ; i -- )
93147 {
94- IPcmSource src = sources [ i ] ;
148+ IPcmSource src = currentSources [ i ] ;
95149
96150 if ( src . Ended )
97151 {
98- src . Dispose ( ) ;
99- sources . RemoveAt ( i ) ;
152+ needsCleanup = true ;
100153 continue ;
101154 }
102155
@@ -111,6 +164,9 @@ public int Read(float[] buffer, int offset, int count)
111164 for ( int i = 0 ; i < maxRead ; i ++ )
112165 buffer [ offset + i ] = Mathf . Clamp ( buffer [ offset + i ] , - 1f , 1f ) ;
113166
167+ if ( needsCleanup )
168+ CleanupEndedSources ( ) ;
169+
114170 return KeepAlive ? count : maxRead ;
115171 }
116172
@@ -120,7 +176,8 @@ public int Read(float[] buffer, int offset, int count)
120176 /// <param name="seconds">The target position in seconds.</param>
121177 public void Seek ( double seconds )
122178 {
123- foreach ( IPcmSource pcmSource in sources )
179+ IPcmSource [ ] currentSources = sources ;
180+ foreach ( IPcmSource pcmSource in currentSources )
124181 pcmSource . Seek ( seconds ) ;
125182 }
126183
@@ -129,7 +186,8 @@ public void Seek(double seconds)
129186 /// </summary>
130187 public void Reset ( )
131188 {
132- foreach ( IPcmSource pcmSource in sources )
189+ IPcmSource [ ] currentSources = sources ;
190+ foreach ( IPcmSource pcmSource in currentSources )
133191 pcmSource . Reset ( ) ;
134192 }
135193
@@ -138,10 +196,13 @@ public void Reset()
138196 /// </summary>
139197 public void Dispose ( )
140198 {
141- foreach ( IPcmSource pcmSource in sources )
142- pcmSource ? . Dispose ( ) ;
199+ lock ( sourcesLock )
200+ {
201+ foreach ( IPcmSource pcmSource in sources )
202+ pcmSource ? . Dispose ( ) ;
143203
144- sources . Clear ( ) ;
204+ sources = Array . Empty < IPcmSource > ( ) ;
205+ }
145206 }
146207
147208 /// <summary>
@@ -150,8 +211,15 @@ public void Dispose()
150211 /// <param name="source">The audio source to add.</param>
151212 public void AddSource ( IPcmSource source )
152213 {
153- if ( source != null )
154- sources . Add ( source ) ;
214+ if ( source == null )
215+ return ;
216+
217+ lock ( sourcesLock )
218+ {
219+ List < IPcmSource > newList = sources . ToList ( ) ;
220+ newList . Add ( source ) ;
221+ sources = newList . ToArray ( ) ;
222+ }
155223 }
156224
157225 /// <summary>
@@ -167,7 +235,36 @@ public void RemoveSource(IPcmSource source, bool dispose = true)
167235 if ( dispose )
168236 source . Dispose ( ) ;
169237
170- sources . Remove ( source ) ;
238+ lock ( sourcesLock )
239+ {
240+ List < IPcmSource > newList = sources . ToList ( ) ;
241+ if ( newList . Remove ( source ) )
242+ {
243+ sources = newList . ToArray ( ) ;
244+ }
245+ }
246+ }
247+
248+ private void CleanupEndedSources ( )
249+ {
250+ lock ( sourcesLock )
251+ {
252+ List < IPcmSource > newList = sources . ToList ( ) ;
253+ bool changed = false ;
254+
255+ for ( int i = newList . Count - 1 ; i >= 0 ; i -- )
256+ {
257+ if ( newList [ i ] . Ended )
258+ {
259+ newList [ i ] . Dispose ( ) ;
260+ newList . RemoveAt ( i ) ;
261+ changed = true ;
262+ }
263+ }
264+
265+ if ( changed )
266+ sources = newList . ToArray ( ) ;
267+ }
171268 }
172269 }
173270}
0 commit comments