Skip to content

Commit 739a12a

Browse files
committed
d
1 parent d1ec21b commit 739a12a

5 files changed

Lines changed: 222 additions & 83 deletions

File tree

EXILED/Exiled.API/Features/Audio/PcmSources/MixerSource.cs

Lines changed: 116 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

EXILED/Exiled.API/Features/Audio/PcmSources/PreloadedPcmSource.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public sealed class PreloadedPcmSource : IPcmSource, IAsyncPcmSource
2828

2929
private volatile bool isReady = false;
3030
private volatile bool isFailed = false;
31+
private volatile bool isDisposed = false;
3132

3233
/// <summary>
3334
/// Initializes a new instance of the <see cref="PreloadedPcmSource"/> class.
@@ -82,7 +83,7 @@ public PreloadedPcmSource(float[] pcmData)
8283
/// <summary>
8384
/// Gets a value indicating whether the end of the PCM data buffer has been reached.
8485
/// </summary>
85-
public bool Ended => isFailed || (isReady && pos >= data.Length);
86+
public bool Ended => isFailed || isDisposed || (isReady && pos >= data.Length);
8687

8788
/// <summary>
8889
/// Gets the total duration of the audio in seconds.
@@ -162,6 +163,7 @@ public void Dispose()
162163

163164
data = null;
164165
isReady = false;
166+
isDisposed = true;
165167
}
166168
}
167169
}

0 commit comments

Comments
 (0)