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