1 // madronalib: a C++ framework for DSP applications.
2 // Copyright (c) 2020 Madrona Labs LLC. http://www.madronalabs.com
3 // Distributed under the MIT license: http://madrona-labs.mit-license.org/
4
5 // a unit test made using the Catch framework in catch.hpp / tests.cpp.
6
7 #include <chrono>
8 using namespace std::chrono;
9
10 #include <thread>
11
12 #include "MLDSPBuffer.h"
13 #include "catch.hpp"
14 #include "mldsp.h"
15
16 using namespace ml;
17
18 namespace dspBufferTest
19 {
20 TEST_CASE("madronalib/core/dspbuffer", "[dspbuffer]")
21 {
22 std::cout << "\nDSPBUFFER\n";
23
24 // buffer should be next larger power-of-two size
25 DSPBuffer buf;
26 buf.resize(197);
27 REQUIRE(buf.getWriteAvailable() == 256);
28
29 // write to near end
30 std::vector<float> nines;
31 nines.resize(256);
32 std::fill(nines.begin(), nines.end(), 9.f);
33 buf.write(nines.data(), 250);
34 buf.read(nines.data(), 250);
35
36 // write indices with wrap
37 DSPVector v1(columnIndex());
38 buf.write(v1.getConstBuffer(), kFloatsPerDSPVector);
39 DSPVector v2{};
40 buf.read(v2.getBuffer(), kFloatsPerDSPVector);
41 std::cout << v2 << "\n";
42 REQUIRE(buf.getReadAvailable() == 0);
43 REQUIRE(v2 == v1);
44 }
45
46 const int kTestBufferSize = 256;
47 const int kTestWrites = 200;
48
49 float transmitSum = 0;
50 float receiveSum = 0;
51
52 // buffer shared between threads
53 DSPBuffer testBuf;
54
55 size_t samplesTransmitted = 0;
56 size_t maxSamplesInBuffer = 0;
57 size_t samplesReceived = 0;
58 float kEndFlag = 99;
59
60 RandomScalarSource rr;
61 const int kMaxReadWriteSize = 16;
62
63 auto randToLength = projections::linear({-1, 1}, {1, kMaxReadWriteSize});
64
transmitTest()65 void transmitTest()
66 {
67 testBuf.resize(kTestBufferSize);
68 float data[kMaxReadWriteSize];
69
70 for (int i = 0; i < kTestWrites; ++i)
71 {
72 size_t writeLen = randToLength(rr.getFloat());
73
74 for (int j = 0; j < writeLen; ++j)
75 {
76 float f = rr.getFloat();
77 data[j] = f;
78 transmitSum += f;
79 }
80
81 testBuf.write(data, writeLen);
82 std::cout << "+"; // show write
83 samplesTransmitted += writeLen;
84
85 std::this_thread::sleep_for(milliseconds(2));
86 }
87
88 data[0] = kEndFlag;
89 testBuf.write(data, 1);
90 }
91
receiveTest()92 void receiveTest()
93 {
94 bool done = false;
95 float data[kMaxReadWriteSize];
96
97 while (!done)
98 {
99 if (size_t k = testBuf.getReadAvailable())
100 {
101 if (k > maxSamplesInBuffer)
102 {
103 maxSamplesInBuffer = k;
104 }
105
106 size_t readLen = randToLength(rr.getFloat());
107 readLen = std::min(readLen, k);
108 testBuf.read(data, std::min(readLen, k));
109 std::cout << "-"; // show read
110
111 for (int i = 0; i < readLen; ++i)
112 {
113 float f = data[i];
114 if (f == kEndFlag)
115 {
116 done = true;
117 break;
118 }
119 else
120 {
121 samplesReceived++;
122 receiveSum += f;
123 }
124 }
125 }
126 else
127 {
128 std::cout << "."; // show wait
129 }
130 std::this_thread::sleep_for(milliseconds(1));
131 }
132 }
133
134 TEST_CASE("madronalib/core/dspbuffer/threads", "[dspbuffer][threads]")
135 {
136 // start threads
137 std::thread transmit(transmitTest);
138 std::thread receive(receiveTest);
139
140 // wait for threads to finish
141 transmit.join();
142 receive.join();
143
144 std::cout << "\ntransmit sum: " << transmitSum
145 << "\nreceive sum: " << receiveSum << "\n";
146 std::cout << "total samples transmitted: " << samplesTransmitted << "\n";
147 std::cout << "total samples received: " << samplesReceived << "\n";
148 std::cout << "buffer size: " << kTestBufferSize << "\n";
149 std::cout << "max samples in buffer: " << maxSamplesInBuffer << "\n";
150
151 REQUIRE(testBuf.getReadAvailable() == 0);
152 REQUIRE(transmitSum == receiveSum);
153 REQUIRE(samplesTransmitted == samplesReceived);
154 }
155
156 TEST_CASE("madronalib/core/dspbuffer/overlap", "[dspbuffer][overlap]")
157 {
158 DSPBuffer buf;
159 buf.resize(256);
160
161 DSPVector outputVec, outputVec2;
162 int overlap = kFloatsPerDSPVector / 2;
163
164 // write constant window buffer
165 DSPVector windowVec;
166 makeWindow(windowVec.getBuffer(), kFloatsPerDSPVector, windows::triangle);
167 // TODO ConstDSPVector windowVec(windows::triangle);
168 // - would require constexpr-capable reimplementation of Projections, not
169 // using std::function
170
171 // write overlapping triangle windows
172 for (int i = 0; i < 8; ++i)
173 {
174 buf.writeWithOverlapAdd(windowVec.getBuffer(), kFloatsPerDSPVector,
175 overlap);
176 }
177
178 // read past startup
179 buf.read(outputVec);
180
181 // after startup, sums of windows should be constant
182 buf.read(outputVec);
183 buf.read(outputVec2);
184
185 REQUIRE(outputVec == outputVec2);
186 }
187
188 TEST_CASE("madronalib/core/dspbuffer/vectors", "[dspbuffer][vectors]")
189 {
190 DSPBuffer buf;
191 buf.resize(256);
192
193 constexpr size_t kRows = 3;
194 DSPVectorArray<kRows> inputVec, outputVec;
195
196 // make a DSPVectorArray with a unique int at each sample
197 inputVec =
198 map([](DSPVector v,
__anon5754d3ba0102(DSPVector v, int row) 199 int row) { return v + DSPVector(kFloatsPerDSPVector * row); },
200 repeatRows<kRows>(columnIndex()));
201
202 // write long enough that we will wrap
203 for (int i = 0; i < 4; ++i)
204 {
205 buf.write(inputVec);
206 buf.read(outputVec);
207 }
208
209 REQUIRE(inputVec == outputVec);
210 }
211
212 TEST_CASE("madronalib/core/dspbuffer/peek", "[dspbuffer][peek]")
213 {
214 // buffer should be next larger power-of-two size
215 DSPBuffer buf;
216 buf.resize(256);
217
218 // write to near end
219 std::vector<float> nines;
220 nines.resize(256);
221 std::fill(nines.begin(), nines.end(), 9.f);
222 buf.write(nines.data(), 203);
223 buf.read(nines.data(), 203);
224
225 // write DSPVectors with wrap
226 DSPVector v1(columnIndex());
227 buf.write(v1);
228 v1 += DSPVector(kFloatsPerDSPVector);
229 buf.write(v1);
230
231 // write one more sample
232 float f{128};
233 buf.write(&f, 1);
234
235 // peek data regions to buffer
236 std::vector<float> floatVec;
237 floatVec.resize(200);
238 buf.peekMostRecent(floatVec.data(), 20);
239
240 REQUIRE(floatVec[0] == 109);
241 REQUIRE(floatVec[19] == 128);
242 }
243 } // namespace dspBufferTest
244