Skip to content

Commit 548a63d

Browse files
committed
Fixes race conditions in multi channel signal
When the output count changes, we build new signals instead of tempering with the previous ones. Once those new signals are built, we can adjust the buffers in the audio thread without having to worry about them getting changed.
1 parent e746839 commit 548a63d

File tree

9 files changed

+429
-809
lines changed

9 files changed

+429
-809
lines changed

VL.Audio.UI/deployment/VL.Audio.UI.nuspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
33
<metadata>
44
<id>VL.Audio.UI</id>
5-
<version>1.9.10</version>
5+
<version>1.9.11</version>
66
<title>VL.Audio.UI</title>
77
<authors>vvvv</authors>
88
<owners>vvvv</owners>
@@ -11,7 +11,7 @@
1111
<description>Extension pack for VL.Audio that brings in UI's for driver configuration and more</description>
1212
<tags>VL</tags>
1313
<dependencies>
14-
<dependency id="VL.Audio" version="1.9.10" />
14+
<dependency id="VL.Audio" version="1.9.11" />
1515
<dependency id="VL.ImGui.Skia" version="2024.6.7" />
1616
</dependencies>
1717
<license type="expression">LGPL-3.0-only</license>

VL.Audio/VL.Audio.vl

Lines changed: 344 additions & 732 deletions
Large diffs are not rendered by default.

VL.Audio/deployment/VL.Audio.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
33
<metadata>
44
<id>VL.Audio</id>
5-
<version>1.9.10</version>
5+
<version>1.9.11</version>
66
<title>VL.Audio</title>
77
<authors>NAudio, vvvv</authors>
88
<owners>vvvv</owners>
Lines changed: 36 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#region usings
22
using System;
33
using System.Collections.Generic;
4+
using System.Collections.Immutable;
45
using System.Linq;
56
using System.Runtime.InteropServices;
67

@@ -23,6 +24,8 @@ public void SetBuffer(float[] buffer)
2324
}
2425

2526
float[] FBuffer;
27+
28+
internal float[] Buffer => FBuffer;
2629

2730
protected override void FillBuffer(float[] buffer, int offset, int count)
2831
{
@@ -45,55 +48,62 @@ public override void Dispose()
4548
/// </summary>
4649
public class MultiChannelSignal : AudioSignal
4750
{
48-
protected int FOutputCount;
51+
private ImmutableArray<SingleSignal> FOutputs = ImmutableArray<SingleSignal>.Empty;
52+
4953
public MultiChannelSignal()
5054
{
51-
Outputs = new List<AudioSignal>();
5255
SetOutputCount(2);
5356
}
5457

5558
public void SetOutputCount(int newCount)
5659
{
57-
//recreate output signals?
58-
if(FOutputCount != newCount)
60+
if (newCount != FOutputs.Length)
5961
{
60-
FOutputCount = newCount;
61-
62-
Outputs.ResizeAndDispose(newCount, () => new SingleSignal(Read));
62+
foreach (var s in FOutputs)
63+
s.Dispose();
6364

64-
FReadBuffers = new float[FOutputCount][];
65-
}
66-
67-
//make sure new buffers get assigned by the manage buffers method
68-
if(FOutputCount > 0)
69-
{
70-
FReadBuffers = new float[FOutputCount][];
65+
var outputs = ImmutableArray.CreateBuilder<SingleSignal>(newCount);
66+
for (int i = 0; i < newCount; i++)
67+
outputs.Add(new SingleSignal(Read));
68+
FOutputs = outputs.MoveToImmutable();
69+
FNeedsRead = true;
7170
}
7271
}
73-
74-
public List<AudioSignal> Outputs
75-
{
76-
get;
77-
protected set;
78-
}
79-
72+
73+
public IReadOnlyList<AudioSignal> Outputs => FOutputs;
74+
public int OutputCount => FOutputs.Length;
75+
8076
protected float[][] FReadBuffers;
8177
protected void ManageBuffers(int count)
8278
{
83-
if(FReadBuffers[0] is null || FReadBuffers[0].Length < count)
79+
var outputs = FOutputs;
80+
if (!BuffersAreLargeEnough(outputs, count))
8481
{
85-
FReadBuffers = new float[FOutputCount][];
86-
for (int i = 0; i < FOutputCount; i++)
82+
FReadBuffers = new float[outputs.Length][];
83+
for (int i = 0; i < FReadBuffers.Length; i++)
8784
{
8885
FReadBuffers[i] = GC.AllocateArray<float>(count, pinned: true);
89-
(Outputs[i] as SingleSignal).SetBuffer(FReadBuffers[i]);
86+
outputs[i].SetBuffer(FReadBuffers[i]);
9087
}
9188
}
89+
90+
static bool BuffersAreLargeEnough(ImmutableArray<SingleSignal> outputs, int count)
91+
{
92+
foreach (var o in outputs)
93+
if (!BufferIsLargeEnough(o.Buffer, count))
94+
return false;
95+
return true;
96+
}
97+
98+
static bool BufferIsLargeEnough(float[] buffer, int count)
99+
{
100+
return buffer != null && buffer.Length >= count;
101+
}
92102
}
93103

94104
protected void Read(int offset, int count)
95105
{
96-
if(FNeedsRead && FOutputCount > 0)
106+
if (FNeedsRead && FOutputs.Length > 0)
97107
{
98108
ManageBuffers(count);
99109
FillBuffers(FReadBuffers, offset, count);
@@ -122,36 +132,6 @@ public override void Dispose()
122132
base.Dispose();
123133
}
124134
}
125-
126-
public static class ListExtra
127-
{
128-
public static void ResizeAndDispose<T>(this List<T> list, int newSize, Func<T> create)
129-
{
130-
int count = list.Count;
131-
if(newSize < count)
132-
{
133-
var itemCount = count - newSize;
134-
var toRemove = list.GetRange(newSize, itemCount);
135-
toRemove.Reverse();
136-
foreach(var item in toRemove)
137-
{
138-
list.Remove(item);
139-
var disposable = item as IDisposable;
140-
if(item != null)
141-
disposable.Dispose();
142-
}
143-
}
144-
else if(newSize > count)
145-
{
146-
var itemCount = newSize - count;
147-
148-
for(int i=0; i < itemCount; i++)
149-
{
150-
list.Add(create());
151-
}
152-
}
153-
}
154-
}
155135
}
156136

157137

VL.Audio/src/Nodes/AudioSignalToAudioStream.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ public AudioSignalToAudioStream()
2525

2626
public IReadOnlyList<AudioSignal> Update(IReadOnlyList<AudioSignal> input, string metadata, out AudioStream audioStream)
2727
{
28-
if (input.Count != FOutputCount)
29-
{
30-
SetOutputCount(input.Count);
31-
FNeedsRead = true;
32-
}
28+
SetOutputCount(input.Count);
3329

3430
Input = input;
3531
_metadata = metadata;

VL.Audio/src/Nodes/AudioSourceToAudioSignal.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected override void FillBuffers(float[][] buffers, int offset, int sampleCou
4242
}
4343
else
4444
{
45-
if (audioFrame.ChannelCount != FOutputCount)
45+
if (audioFrame.ChannelCount != OutputCount)
4646
{
4747
SetOutputCount(audioFrame.ChannelCount);
4848
ManageBuffers(sampleCount);

VL.Audio/src/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"VL.Audio": {
44
"commandName": "Executable",
5-
"executablePath": "C:\\Program Files\\vvvv\\vvvv_gamma_5.3-0192-g8dd4ea9c96\\vvvv.exe",
5+
"executablePath": "C:\\Program Files\\vvvv\\vvvv_gamma_7.0-0305-g98ea128603-win-x64\\vvvv.exe",
66
"commandLineArgs": "--package-repositories $(ParentDirectoryOfThisRepo) --editable-packages VL.Audio"
77
}
88
}

VL.Audio/src/Signals/Routing/MatrixMixerSignal.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public int OutputChannelCount
1515
{
1616
get
1717
{
18-
return this.FOutputCount;
18+
return this.OutputCount;
1919
}
2020
set
2121
{
@@ -31,12 +31,12 @@ protected override void FillBuffers(float[][] buffer, int offset, int count)
3131
if (FInput != null && FInput.Count != 0)
3232
{
3333
FTempBuffer = BufferHelpers.Ensure(FTempBuffer, count);
34-
for (int outSlice = 0; outSlice < FOutputCount; outSlice++)
34+
for (int outSlice = 0; outSlice < buffer.Length; outSlice++)
3535
{
3636
var outbuf = buffer[outSlice];
3737
for (int inSlice = 0; inSlice < FInput.Count; inSlice++)
3838
{
39-
var gain = GainMatrix[outSlice + inSlice * FOutputCount];
39+
var gain = GainMatrix[outSlice + inSlice * buffer.Length];
4040
var inSig = FInput[inSlice];
4141
if (inSig != null)
4242
{

VL.Audio/src/Signals/Sources/FileStreamSignal.cs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ namespace VL.Audio
1111
{
1212
public class FileStreamSignal : MultiChannelSignal
1313
{
14+
private readonly BlockingCollection<AudioFileReaderVVVV> FFileQueue = new(boundedCapacity: 1);
15+
private readonly BlockingCollection<float> FSeekQueue = new(boundedCapacity: 1);
16+
private readonly ManualResetEvent FInTheWorks = new ManualResetEvent(true);
17+
private AudioFileReaderVVVV FAudioFile;
18+
1419
public bool FLoop;
1520

1621
public bool FPlay = false;
@@ -21,19 +26,31 @@ public class FileStreamSignal : MultiChannelSignal
2126

2227
public TimeSpan FSeekTime;
2328

24-
public AudioFileReaderVVVV FAudioFile;
29+
public double Speed { get; set; }
30+
31+
public float Volume { get; set; } = 1f;
32+
33+
public float Position => FAudioFile != null ? (float)FAudioFile.CurrentTime.TotalSeconds : 0f;
34+
35+
public float Duration { get; private set; }
36+
37+
public bool CanSeek { get; private set; }
38+
39+
public int Channels { get; private set; }
40+
41+
public int OriginalSampleRate { get; private set; }
2542

26-
private BlockingCollection<AudioFileReaderVVVV> FFileQueue = new(boundedCapacity: 1);
27-
private ManualResetEvent FInTheWorks = new ManualResetEvent(true);
43+
public string OriginalFormat { get; private set; }
2844

29-
public double Speed
45+
public void Seek(float seekTime)
3046
{
31-
get;
32-
set;
47+
if (!FSeekQueue.TryAddSafe(seekTime))
48+
{
49+
FSeekQueue.TryTake(out _);
50+
FSeekQueue.TryAddSafe(seekTime);
51+
}
3352
}
3453

35-
public float Volume { get; set; } = 1f;
36-
3754
public void OpenFile(string filename)
3855
{
3956
if (FFileQueue.TryTake(out var oldfile))
@@ -45,11 +62,23 @@ public void OpenFile(string filename)
4562
//NOTE: switching between files of different output count may cause troubles here:
4663
SetOutputCount(audiofile.WaveFormat.Channels);
4764
FFileQueue.TryAddSafe(audiofile);
65+
66+
Duration = (float)audiofile.TotalTime.TotalSeconds;
67+
CanSeek = audiofile.CanSeek;
68+
Channels = audiofile.WaveFormat.Channels;
69+
OriginalSampleRate = audiofile.WaveFormat.SampleRate;
70+
OriginalFormat = audiofile.OriginalFileFormat.ToString();
4871
}
4972
else
5073
{
5174
SetOutputCount(0);
5275
FFileQueue.TryAddSafe(null);
76+
77+
Duration = default;
78+
CanSeek = default;
79+
Channels = default;
80+
OriginalSampleRate = default;
81+
OriginalFormat = string.Empty;
5382
}
5483
}
5584

@@ -74,6 +103,9 @@ protected override void FillBuffers(float[][] buffer, int offset, int sampleCoun
74103
FInTheWorks.Reset();
75104
try
76105
{
106+
if (FSeekQueue.TryTake(out var seekTime))
107+
FAudioFile.CurrentTime = TimeSpan.FromSeconds(seekTime);
108+
77109
FAudioFile.Volume = Volume;
78110

79111
var channels = FAudioFile.WaveFormat.Channels;

0 commit comments

Comments
 (0)