Skip to content

Commit ec63ef8

Browse files
Merge pull request #18 from ktsu-dev/claude/vst-plugin-upstream-djlud9
2 parents 9547917 + 689a709 commit ec63ef8

4 files changed

Lines changed: 739 additions & 0 deletions

File tree

Containers.Test/DelayLineTests.cs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright (c) ktsu.dev
2+
// All rights reserved.
3+
// Licensed under the MIT license.
4+
5+
namespace ktsu.Containers.Tests;
6+
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
9+
[TestClass]
10+
public class DelayLineTests
11+
{
12+
[TestMethod]
13+
public void Constructor_NonPositiveCapacity_Throws()
14+
{
15+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => new DelayLine(0));
16+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => new DelayLine(-4));
17+
}
18+
19+
[TestMethod]
20+
public void Capacity_MatchesRequestedMaxDelay()
21+
{
22+
DelayLine line = new(100);
23+
Assert.AreEqual(100, line.Capacity);
24+
}
25+
26+
[TestMethod]
27+
public void NewDelayLine_ReadsZero()
28+
{
29+
DelayLine line = new(8);
30+
for (int delay = 0; delay <= line.Capacity; delay++)
31+
{
32+
Assert.AreEqual(0f, line.Read(delay));
33+
}
34+
}
35+
36+
[TestMethod]
37+
public void Read_ReturnsSampleNSamplesInThePast()
38+
{
39+
DelayLine line = new(8);
40+
line.Write(1f);
41+
line.Write(2f);
42+
line.Write(3f);
43+
44+
// Delay 0 == most recent, delay 2 == oldest of the three writes.
45+
Assert.AreEqual(3f, line.Read(0));
46+
Assert.AreEqual(2f, line.Read(1));
47+
Assert.AreEqual(1f, line.Read(2));
48+
}
49+
50+
[TestMethod]
51+
public void Process_OutputsInputFromExactlyDelaySamplesEarlier()
52+
{
53+
DelayLine line = new(4);
54+
// Process(x, 4) yields x[n - 4]: zero until the line has been primed with 4 samples.
55+
Assert.AreEqual(0f, line.Process(10f, 4));
56+
Assert.AreEqual(0f, line.Process(20f, 4));
57+
Assert.AreEqual(0f, line.Process(30f, 4));
58+
Assert.AreEqual(0f, line.Process(40f, 4));
59+
Assert.AreEqual(10f, line.Process(50f, 4));
60+
Assert.AreEqual(20f, line.Process(60f, 4));
61+
}
62+
63+
[TestMethod]
64+
public void Process_ZeroDelay_ReturnsInput()
65+
{
66+
DelayLine line = new(4);
67+
Assert.AreEqual(7f, line.Process(7f, 0));
68+
Assert.AreEqual(9f, line.Process(9f, 0));
69+
}
70+
71+
[TestMethod]
72+
public void Read_OutOfRange_Throws()
73+
{
74+
DelayLine line = new(8);
75+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => line.Read(-1));
76+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => line.Read(9));
77+
}
78+
79+
[TestMethod]
80+
public void ReadInterpolated_HalfwayBetweenSamples_AveragesNeighbours()
81+
{
82+
DelayLine line = new(8);
83+
line.Write(0f);
84+
line.Write(10f);
85+
86+
// delay 0 -> 10 (newest), delay 1 -> 0 (older). 0.5 interpolates halfway.
87+
Assert.AreEqual(5f, line.ReadInterpolated(0.5f), 1e-6f);
88+
}
89+
90+
[TestMethod]
91+
public void ReadInterpolated_IntegerDelay_MatchesRead()
92+
{
93+
DelayLine line = new(8);
94+
line.Write(3f);
95+
line.Write(7f);
96+
line.Write(11f);
97+
98+
Assert.AreEqual(line.Read(1), line.ReadInterpolated(1f), 1e-6f);
99+
}
100+
101+
[TestMethod]
102+
public void ReadInterpolated_OutOfRange_Throws()
103+
{
104+
DelayLine line = new(8);
105+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => line.ReadInterpolated(-0.5f));
106+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => line.ReadInterpolated(8.5f));
107+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => line.ReadInterpolated(float.NaN));
108+
}
109+
110+
[TestMethod]
111+
public void Write_FlushesDenormalsToZero()
112+
{
113+
DelayLine line = new(4);
114+
float denormal = float.Epsilon; // smallest subnormal float, well below the threshold
115+
line.Write(denormal);
116+
Assert.AreEqual(0f, line.Read(0), "Denormal input must be flushed to zero.");
117+
}
118+
119+
[TestMethod]
120+
public void Write_KeepsNormalSmallValues()
121+
{
122+
DelayLine line = new(4);
123+
const float audible = 1e-6f; // ~ -120 dBFS, a normal float that must be preserved
124+
line.Write(audible);
125+
Assert.AreEqual(audible, line.Read(0));
126+
}
127+
128+
[TestMethod]
129+
public void Clear_ZeroesAllSamples()
130+
{
131+
DelayLine line = new(4);
132+
line.Write(1f);
133+
line.Write(2f);
134+
line.Clear();
135+
136+
for (int delay = 0; delay <= line.Capacity; delay++)
137+
{
138+
Assert.AreEqual(0f, line.Read(delay));
139+
}
140+
}
141+
142+
[TestMethod]
143+
public void WrapAround_LongStreamReadsCorrectDelay()
144+
{
145+
DelayLine line = new(3);
146+
float previous = 0f;
147+
for (int i = 1; i <= 1000; i++)
148+
{
149+
// Process(i, 1) returns the value written on the previous call (i - 1), 0 on the first.
150+
float output = line.Process(i, 1);
151+
Assert.AreEqual(i - 1, output);
152+
previous = output;
153+
}
154+
155+
Assert.AreEqual(999f, previous);
156+
}
157+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (c) ktsu.dev
2+
// All rights reserved.
3+
// Licensed under the MIT license.
4+
5+
namespace ktsu.Containers.Tests;
6+
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
11+
[TestClass]
12+
public class SpscRingBufferTests
13+
{
14+
[TestMethod]
15+
public void Constructor_NonPositiveCapacity_Throws()
16+
{
17+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => new SpscRingBuffer<int>(0));
18+
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => new SpscRingBuffer<int>(-1));
19+
}
20+
21+
[TestMethod]
22+
public void Capacity_IsAtLeastRequested()
23+
{
24+
SpscRingBuffer<int> buffer = new(5);
25+
Assert.IsTrue(buffer.Capacity >= 5, "Usable capacity must be at least the requested amount.");
26+
}
27+
28+
[TestMethod]
29+
public void NewBuffer_IsEmpty()
30+
{
31+
SpscRingBuffer<int> buffer = new(4);
32+
Assert.IsTrue(buffer.IsEmpty);
33+
Assert.AreEqual(0, buffer.Count);
34+
Assert.IsFalse(buffer.TryDequeue(out _));
35+
}
36+
37+
[TestMethod]
38+
public void EnqueueDequeue_PreservesFifoOrder()
39+
{
40+
SpscRingBuffer<int> buffer = new(8);
41+
for (int i = 0; i < 5; i++)
42+
{
43+
Assert.IsTrue(buffer.TryEnqueue(i));
44+
}
45+
46+
for (int i = 0; i < 5; i++)
47+
{
48+
Assert.IsTrue(buffer.TryDequeue(out int value));
49+
Assert.AreEqual(i, value);
50+
}
51+
52+
Assert.IsTrue(buffer.IsEmpty);
53+
}
54+
55+
[TestMethod]
56+
public void TryEnqueue_WhenFull_ReturnsFalse()
57+
{
58+
SpscRingBuffer<int> buffer = new(4);
59+
int enqueued = 0;
60+
while (buffer.TryEnqueue(enqueued))
61+
{
62+
enqueued++;
63+
}
64+
65+
Assert.IsTrue(enqueued >= 4, "Should accept at least the requested capacity before reporting full.");
66+
Assert.IsFalse(buffer.TryEnqueue(999));
67+
}
68+
69+
[TestMethod]
70+
public void TryPeek_DoesNotRemoveElement()
71+
{
72+
SpscRingBuffer<int> buffer = new(4);
73+
buffer.TryEnqueue(42);
74+
75+
Assert.IsTrue(buffer.TryPeek(out int peeked));
76+
Assert.AreEqual(42, peeked);
77+
Assert.AreEqual(1, buffer.Count);
78+
79+
Assert.IsTrue(buffer.TryDequeue(out int dequeued));
80+
Assert.AreEqual(42, dequeued);
81+
}
82+
83+
[TestMethod]
84+
public void WrapAround_ReusesSlotsCorrectly()
85+
{
86+
SpscRingBuffer<int> buffer = new(4);
87+
88+
// Cycle through many more items than capacity to force repeated wraparound.
89+
for (int i = 0; i < 1000; i++)
90+
{
91+
Assert.IsTrue(buffer.TryEnqueue(i));
92+
Assert.IsTrue(buffer.TryDequeue(out int value));
93+
Assert.AreEqual(i, value);
94+
}
95+
96+
Assert.IsTrue(buffer.IsEmpty);
97+
}
98+
99+
[TestMethod]
100+
public async Task ConcurrentProducerConsumer_TransfersAllItemsInOrder()
101+
{
102+
const int itemCount = 1_000_000;
103+
SpscRingBuffer<int> buffer = new(1024);
104+
105+
Task producer = Task.Run(() =>
106+
{
107+
int produced = 0;
108+
while (produced < itemCount)
109+
{
110+
if (buffer.TryEnqueue(produced))
111+
{
112+
produced++;
113+
}
114+
else
115+
{
116+
Thread.SpinWait(1);
117+
}
118+
}
119+
});
120+
121+
Task<bool> consumer = Task.Run(() =>
122+
{
123+
int expected = 0;
124+
while (expected < itemCount)
125+
{
126+
if (buffer.TryDequeue(out int value))
127+
{
128+
if (value != expected)
129+
{
130+
return false;
131+
}
132+
133+
expected++;
134+
}
135+
else
136+
{
137+
Thread.SpinWait(1);
138+
}
139+
}
140+
141+
return true;
142+
});
143+
144+
await Task.WhenAll(producer, consumer).ConfigureAwait(false);
145+
Assert.IsTrue(await consumer.ConfigureAwait(false), "All items must be received exactly once and in order.");
146+
Assert.IsTrue(buffer.IsEmpty);
147+
}
148+
149+
[TestMethod]
150+
public void Dequeue_ReferenceType_ReleasesReference()
151+
{
152+
SpscRingBuffer<string> buffer = new(4);
153+
buffer.TryEnqueue("hello");
154+
Assert.IsTrue(buffer.TryDequeue(out string? value));
155+
Assert.AreEqual("hello", value);
156+
Assert.IsTrue(buffer.IsEmpty);
157+
}
158+
}

0 commit comments

Comments
 (0)