tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_TestUtilities.h
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11#pragma once
12
13#include <numeric>
15
16namespace tracktion { inline namespace graph
17{
18
19//==============================================================================
20namespace test_utilities
21{
22 //==============================================================================
24 static inline juce::MidiMessageSequence createRandomMidiMessageSequence (double durationSeconds, juce::Random r,
25 juce::Range<double> noteLengthRange = { 0.1, 0.6 })
26 {
28 int noteNumber = -1;
29
30 for (double time = 0.0; time < durationSeconds;
31 time += r.nextDouble() * noteLengthRange.getLength() + noteLengthRange.getStart())
32 {
33 if (noteNumber != -1)
34 {
35 sequence.addEvent (juce::MidiMessage::noteOff (1, noteNumber), time);
36 noteNumber = -1;
37 }
38 else
39 {
40 noteNumber = r.nextInt ({ 1, 127 });
41 sequence.addEvent (juce::MidiMessage::noteOn (1, noteNumber, 1.0f), time);
42 }
43 }
44
45 sequence.updateMatchedPairs();
46
47 return sequence;
48 }
49
51 static inline juce::MidiMessageSequence createMidiMessageSequence (const juce::MidiBuffer& buffer, double sampleRate)
52 {
54
55 for (auto itr : buffer)
56 {
57 const auto& result = itr.getMessage();
58 int samplePosition = itr.samplePosition;
59
60 const double time = samplePosition / sampleRate;
61 sequence.addEvent (result, time);
62 }
63
64 return sequence;
65 }
66
67 //==============================================================================
68 static inline float getPhaseIncrement (float frequency, double sampleRate)
69 {
70 return (float) ((frequency * juce::MathConstants<double>::pi * 2.0) / sampleRate);
71 }
72
74 {
75 void reset (float frequency, double sampleRate)
76 {
77 phase = 0;
78 phaseIncrement = getPhaseIncrement (frequency, sampleRate);
79 }
80
81 float getNext()
82 {
83 auto v = std::sin (phase);
84 phase = std::fmod (phase + phaseIncrement, juce::MathConstants<float>::pi * 2.0f);
85 return v;
86 }
87
88 float phase = 0, phaseIncrement = 0;
89 };
90
91 static inline void fillBufferWithSinData (choc::buffer::ChannelArrayView<float> buffer)
92 {
93 auto phaseIncrement = juce::MathConstants<float>::twoPi / buffer.getNumFrames();
94 setAllFrames (buffer, [=] (auto frame) { return std::sin ((float) (frame * phaseIncrement)); });
95 }
96
97 static inline auto createSineBuffer (int numChannels, int numFrames, double phaseIncrement)
98 {
99 return choc::buffer::createChannelArrayBuffer (numChannels, numFrames,
100 [=] (auto, auto frame) { return std::sin ((float) (frame * phaseIncrement)); });
101 }
102
103 static inline auto createSquareBuffer (int numChannels, int numFrames, double phaseIncrement)
104 {
105 return choc::buffer::createChannelArrayBuffer (numChannels, numFrames,
106 [=] (auto, auto frame)
107 {
108 const auto sinValue = std::sin ((float) (frame * phaseIncrement));
109
110 if (sinValue > 0.0f) return 1.0f;
111 if (sinValue < 0.0f) return -1.0f;
112
113 return 0.0f;
114 });
115 }
116
118 static inline void logMidiMessageSequence (juce::UnitTest& ut, const juce::MidiMessageSequence& seq)
119 {
120 for (int i = 0; i < seq.getNumEvents(); ++i)
121 ut.logMessage (seq.getEventPointer (i)->message.getDescription() + " - " + juce::String (seq.getEventPointer (i)->message.getTimeStamp()));
122 }
123
125 static inline void dgbMidiBuffer (const juce::MidiBuffer& buffer)
126 {
127 ignoreUnused (buffer);
128 #if JUCE_DEBUG
129 for (auto itr : buffer)
130 {
131 const auto& result = itr.getMessage();
132 DBG(result.getDescription());
133 }
134 #endif
135 }
136
138 static inline juce::MidiMessageSequence stripNonNoteOnOffMessages (juce::MidiMessageSequence seq)
139 {
140 for (int i = seq.getNumEvents(); --i >= 0;)
141 if (! seq.getEventPointer (i)->message.isNoteOnOrOff())
142 seq.deleteEvent (i, false);
143
144 return seq;
145 }
146
148 static inline juce::MidiMessageSequence stripMetaEvents (juce::MidiMessageSequence seq)
149 {
150 for (int i = seq.getNumEvents(); --i >= 0;)
151 if (! seq.getEventPointer (i)->message.isMetaEvent())
152 seq.deleteEvent (i, false);
153
154 return seq;
155 }
156
158 template<typename AudioFormatType>
159 void writeToFile (juce::File file, choc::buffer::ChannelArrayView<float> block, double sampleRate, int qualityOptionIndex)
160 {
161 AudioFormatType type;
162
163 if (auto writer = std::unique_ptr<juce::AudioFormatWriter> (type.createWriterFor (file.createOutputStream().release(),
164 sampleRate,
165 block.getNumChannels(),
166 16, {}, qualityOptionIndex)))
167 {
168 writer->writeFromAudioSampleBuffer (toAudioBuffer (block), 0, (int) block.getNumFrames());
169 }
170 }
171
173 template<typename AudioFormatType>
174 std::unique_ptr<juce::TemporaryFile> writeToTemporaryFile (choc::buffer::ChannelArrayView<float> block, double sampleRate, int qualityOptionIndex)
175 {
176 auto f = std::make_unique<juce::TemporaryFile> (AudioFormatType().getFileExtensions()[0]);
177 writeToFile<AudioFormatType> (f->getFile(), block, sampleRate, qualityOptionIndex);
178
179 return f;
180 }
181
182 //==============================================================================
184 static inline size_t getMemoryUsage (const std::vector<Node*>& nodes)
185 {
186 auto nodesWithInternalNodes = createNodeMap (nodes);
187 return std::accumulate (nodesWithInternalNodes.begin(), nodesWithInternalNodes.end(), (size_t) 0,
188 [] (size_t total, auto& nodeAndID)
189 {
190 return total + nodeAndID.node->getAllocatedBytes();
191 });
192 }
193
195 static inline size_t getMemoryUsage (const NodeGraph& graph)
196 {
197 return std::accumulate (graph.sortedNodes.begin(), graph.sortedNodes.end(), (size_t) 0,
198 [] (size_t total, auto& nodeAndID)
199 {
200 return total + nodeAndID.node->getAllocatedBytes();
201 });
202 }
203
205 static inline size_t getMemoryUsage (Node& node)
206 {
207 return getMemoryUsage (tracktion::graph::getNodes (node, tracktion::graph::VertexOrdering::postordering));
208 }
209
212 {
213 switch (type)
214 {
215 case ThreadPoolStrategy::conditionVariable: return "conditionVariable";
216 case ThreadPoolStrategy::realTime: return "realTime";
217 case ThreadPoolStrategy::hybrid: return "hybrid";
218 case ThreadPoolStrategy::semaphore: return "semaphore";
219 case ThreadPoolStrategy::lightweightSemaphore: return "lightweightSemaphore";
220 case ThreadPoolStrategy::lightweightSemHybrid: return "lightweightSemaphoreHybrid";
221 }
222
224 return {};
225 }
226
227 inline std::vector<ThreadPoolStrategy> getThreadPoolStrategies()
228 {
229 return { ThreadPoolStrategy::lightweightSemHybrid,
230 ThreadPoolStrategy::lightweightSemaphore,
231 ThreadPoolStrategy::semaphore,
232 ThreadPoolStrategy::conditionVariable,
233 ThreadPoolStrategy::realTime,
234 ThreadPoolStrategy::hybrid };
235 }
236
238 inline void logGraph (Node& node)
239 {
240 struct Visitor
241 {
242 static void logNode (Node& n, int depth)
243 {
244 juce::Logger::writeToLog (juce::String::repeatedString (" ", depth * 2) + typeid (n).name());
245 }
246
247 static void visitInputs (Node& n, int depth)
248 {
249 logNode (n, depth);
250
251 for (auto input : n.getDirectInputNodes())
252 visitInputs (*input, depth + 1);
253 }
254 };
255
256 Visitor::visitInputs (node, 0);
257 }
258
263 std::string createGraphDescription (Node&);
264
265
266 //==============================================================================
267 template<typename AudioFormatType>
268 std::unique_ptr<juce::TemporaryFile> getSinFile (double sampleRate, double durationInSeconds,
269 int numChannels = 1, float frequency = 220.0f,
270 int qualityOptionIndex = -1)
271 {
272 auto buffer = createSineBuffer (numChannels, (int) (sampleRate * durationInSeconds),
273 getPhaseIncrement (frequency, sampleRate));
274
275 AudioFormatType format;
276 auto f = std::make_unique<juce::TemporaryFile> (format.getFileExtensions()[0]);
277 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
278 return f;
279 }
280
281 template<typename AudioFormatType>
282 std::unique_ptr<juce::TemporaryFile> getSquareFile (double sampleRate, double durationInSeconds,
283 int numChannels = 1, float frequency = 220.0f,
284 int qualityOptionIndex = -1)
285 {
286 auto buffer = createSquareBuffer (numChannels, (int) (sampleRate * durationInSeconds),
287 getPhaseIncrement (frequency, sampleRate));
288
289 AudioFormatType format;
290 auto f = std::make_unique<juce::TemporaryFile> (format.getFileExtensions()[0]);
291 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
292 return f;
293 }
294
295 template<typename AudioFormatType>
296 std::unique_ptr<juce::TemporaryFile> getTimeEncodedFile (double sampleRate, TimeDuration duration,
297 TimeDuration stepDuration,
298 int numChannels = 1,
299 int qualityOptionIndex = -1)
300 {
301 const auto stepNumFrames = toSamples (stepDuration, sampleRate);
302 const auto numSteps = duration / stepDuration;
303 const auto valPerStep = static_cast<float> (1.0 / numSteps);
304
305 auto buffer = choc::buffer::createChannelArrayBuffer (numChannels, toSamples (duration, sampleRate),
306 [=] (auto, auto frame)
307 {
308 const int stepNum = static_cast<int> (frame / stepNumFrames) + 1;
309 const float f = stepNum * valPerStep;
310
311 return f;
312 });
313
314 AudioFormatType format;
315 auto f = std::make_unique<juce::TemporaryFile> (format.getFileExtensions()[0]);
316 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
317
318 return f;
319 }
320
321 template<typename AudioFormatType>
322 std::unique_ptr<juce::TemporaryFile> getTransientFile (double sampleRate, TimeDuration duration,
323 TimePosition transientPos, float transientVal,
324 int numChannels = 1, int qualityOptionIndex = -1)
325 {
326 using namespace choc::buffer;
327 auto transientSample = toSamples (transientPos, sampleRate);
328 auto buffer = createChannelArrayBuffer (numChannels, toSamples (duration, sampleRate),
329 [=] (auto, auto frame) { return frame == transientSample ? transientVal : 0.0f; });
330
331 AudioFormatType format;
332 auto f = std::make_unique<juce::TemporaryFile> (format.getFileExtensions()[0]);
333 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
334
335 return f;
336 }
337
338
339 //==============================================================================
341 static inline void expectMidiMessageSequence (juce::UnitTest& ut, const juce::MidiMessageSequence& actual, const juce::MidiMessageSequence& expected)
342 {
343 ut.expectEquals (actual.getNumEvents(), expected.getNumEvents(), "Num MIDI events not equal");
344
345 bool sequencesTheSame = true;
346
347 for (int i = 0; i < std::min (actual.getNumEvents(), expected.getNumEvents()); ++i)
348 {
349 auto event1 = actual.getEventPointer (i);
350 auto event2 = expected.getEventPointer (i);
351
352 auto desc1 = event1->message.getDescription();
353 auto desc2 = event2->message.getDescription();
354
355 if (desc1 != desc2)
356 {
357 ut.logMessage (juce::String ("Event at index 123 is:\n\tactual: XXX\n\texpected is: YYY")
358 .replace ("123", juce::String (i)).replace ("XXX", desc1).replace ("YYY", desc2));
359 sequencesTheSame = false;
360 }
361 }
362
363 for (int i = actual.getNumEvents(); i < expected.getNumEvents(); ++i)
364 {
365 auto event2 = expected.getEventPointer (i);
366 auto desc2 = event2->message.getDescription();
367 ut.logMessage (juce::String ("Missing event at index 123 is:\n\texpected is: YYY, TTT")
368 .replace ("123", juce::String (i)).replace ("YYY", desc2).replace ("TTT", juce::String (event2->message.getTimeStamp())));
369 }
370
371 for (int i = expected.getNumEvents(); i < actual.getNumEvents(); ++i)
372 {
373 auto event2 = actual.getEventPointer (i);
374 auto desc2 = event2->message.getDescription();
375 ut.logMessage (juce::String ("Extra event at index 123 is:\n\tactual is: YYY, TTT")
376 .replace ("123", juce::String (i)).replace ("YYY", desc2).replace ("TTT", juce::String (event2->message.getTimeStamp())));
377 }
378
379 ut.expect (sequencesTheSame, "MIDI sequence contents not equal");
380
381 if (! sequencesTheSame)
382 {
383 ut.logMessage ("Actual:");
384 logMidiMessageSequence (ut, actual);
385 ut.logMessage ("Expected:");
386 logMidiMessageSequence (ut, expected);
387 }
388 }
389
391 static inline void expectMidiBuffer (juce::UnitTest& ut, const juce::MidiBuffer& buffer, double sampleRate, const juce::MidiMessageSequence& seq)
392 {
393 expectMidiMessageSequence (ut, createMidiMessageSequence (buffer, sampleRate), seq);
394 }
395
397 static inline void expectAudioBuffer (juce::UnitTest& ut, const juce::AudioBuffer<float>& buffer, int channel, float mag, float rms)
398 {
399 ut.expectWithinAbsoluteError (buffer.getMagnitude (channel, 0, buffer.getNumSamples()), mag, 0.01f);
400 ut.expectWithinAbsoluteError (buffer.getRMSLevel (channel, 0, buffer.getNumSamples()), rms, 0.01f);
401 }
402
404 static inline void expectAudioBuffer (juce::UnitTest& ut, juce::AudioBuffer<float>& buffer, int channel, int numSampleToSplitAt,
405 float mag1, float rms1, float mag2, float rms2)
406 {
407 {
408 juce::AudioBuffer<float> trimmedBuffer (buffer.getArrayOfWritePointers(), buffer.getNumChannels(),
409 0, numSampleToSplitAt);
410 expectAudioBuffer (ut, trimmedBuffer, channel, mag1, rms1);
411 }
412
413 {
415 buffer.getNumChannels(),
416 numSampleToSplitAt, buffer.getNumSamples() - numSampleToSplitAt);
417 expectAudioBuffer (ut, trimmedBuffer, channel, mag2, rms2);
418 }
419 }
420
422 template<typename IntType>
423 static inline void expectAudioBuffer (juce::UnitTest& ut, juce::AudioBuffer<float>& buffer, int channel, juce::Range<IntType> sampleRange,
424 float mag, float rms)
425 {
427 buffer.getNumChannels(),
428 (int) sampleRange.getStart(), (int) sampleRange.getLength());
429 expectAudioBuffer (ut, trimmedBuffer, channel, mag, rms);
430 }
431
432 static inline void expectAudioBuffer (juce::UnitTest& ut, const juce::AudioBuffer<float>& a, const juce::AudioBuffer<float>& b)
433 {
434 ut.expect (a.getNumChannels() == b.getNumChannels());
435 ut.expect (a.getNumSamples() == b.getNumSamples());
436
437 for (int channel = 0; channel < a.getNumChannels(); ++channel)
438 {
439 auto* aPtr = a.getReadPointer (channel);
440 auto* bPtr = b.getReadPointer (channel);
441
442 ut.expect (std::vector<float> (aPtr, aPtr + a.getNumSamples())
443 == std::vector<float> (bPtr, bPtr + b.getNumSamples()));
444 }
445 }
446
447 inline bool buffersAreEqual (const juce::AudioBuffer<float>& a, const juce::AudioBuffer<float>& b, float absSampleTolerance = 0.0f)
448 {
449 if (a.getNumChannels() != b.getNumChannels())
450 return false;
451
452 if (a.getNumSamples() != b.getNumSamples())
453 return false;
454
455 const int numSamples = a.getNumSamples();
456
457 for (int channel = 0; channel < a.getNumChannels(); ++channel)
458 {
459 auto* aPtr = a.getReadPointer (channel);
460 auto* bPtr = b.getReadPointer (channel);
461
462 for (int sample = 0; sample < numSamples; ++sample)
463 {
464 const auto aSamp = *(aPtr + sample);
465 const auto bSamp = *(bPtr + sample);
466 const auto absDiff = std::abs (aSamp - bSamp);
467
468 if (absDiff > absSampleTolerance)
469 return false;
470 }
471 }
472
473 return true;
474 }
475
476 inline bool buffersAreEqual (const choc::buffer::ChannelArrayView<float>& a, const choc::buffer::ChannelArrayView<float>& b, float absSampleTolerance = 0.0f)
477 {
478 return buffersAreEqual (toAudioBuffer (a), toAudioBuffer (b), absSampleTolerance);
479 }
480
482 static inline void expectUniqueNodeIDs (juce::UnitTest& ut, Node& node, bool ignoreZeroIDs)
483 {
484 auto areUnique = node_player_utils::areNodeIDsUnique (node, ignoreZeroIDs);
485 ut.expect (areUnique, "nodeIDs are not unique");
486
487 if (! areUnique)
488 visitNodes (node, [&] (Node& n) { ut.logMessage (juce::String (typeid (n).name()) + " - " + juce::String (n.getNodeProperties().nodeID)); }, false);
489 }
490
491 //==============================================================================
492 inline std::optional<std::pair<choc::buffer::FrameCount, float>> findFirstNonZeroSample (choc::buffer::MonoView<float> buffer)
493 {
494 auto size = buffer.getSize();
495 auto samples = buffer.getIterator (0);
496
497 for (decltype (size.numFrames) i = 0; i < size.numFrames; ++i)
498 if (auto s = *samples++; s > 0.0f)
499 return std::make_pair (i, s);
500
501 return {};
502 }
503
504 //==============================================================================
505 //==============================================================================
507 {
508 double sampleRate = 44100.0;
509 int blockSize = 512;
510 bool randomiseBlockSizes = false;
511 juce::Random random;
512 };
513
515 static inline juce::String getDescription (const TestSetup& ts)
516 {
517 return juce::String (ts.sampleRate, 0) + ", " + juce::String (ts.blockSize)
518 + juce::String (ts.randomiseBlockSizes ? ", random" : "");
519 }
520
522 static inline std::vector<TestSetup> getTestSetups (juce::UnitTest& ut)
523 {
525
526 #if JUCE_DEBUG || GRAPH_UNIT_TESTS_QUICK_VALIDATE
527 for (double sampleRate : { 44100.0, 96000.0 })
528 for (int blockSize : { 64, 512 })
529 #else
530 for (double sampleRate : { 44100.0, 48000.0, 96000.0 })
531 for (int blockSize : { 64, 256, 512, 1024 })
532 #endif
533 for (bool randomiseBlockSizes : { false, true })
534 setups.push_back ({ sampleRate, blockSize, randomiseBlockSizes, ut.getRandom() });
535
536 return setups;
537 }
538
539 //==============================================================================
541 {
543 juce::MidiBuffer midi;
544 int numProcessMisses = 0;
545 };
546
547 template<typename NodePlayerType>
549 {
551 const int numChannelsToUse, const double duration,
552 bool writeToBuffer)
553 : testSetup (ts), numChannels (numChannelsToUse), durationInSeconds (duration)
554 {
556
557 buffer.resize ({ (choc::buffer::ChannelCount) numChannels, (choc::buffer::FrameCount) ts.blockSize });
558 numSamplesToDo = juce::roundToInt (durationInSeconds * ts.sampleRate);
559
560 if (writeToBuffer && numChannels > 0)
561 {
562 if (auto os = std::make_unique<juce::MemoryOutputStream> (audioOutputBlock, false))
563 {
564 writer = std::unique_ptr<juce::AudioFormatWriter> (juce::WavAudioFormat().createWriterFor (os.release(),
565 ts.sampleRate, (uint32_t) numChannels, 16, {}, 0));
566 }
567 else
568 {
570 }
571 }
572
573 setPlayer (std::move (playerToUse));
574 }
575
578 {
579 return juce::String ("{numChannels} channels, {durationInSeconds}s")
580 .replace ("{numChannels}", juce::String (numChannels))
581 .replace ("{durationInSeconds}", juce::String (durationInSeconds)).toStdString();
582 }
583
584 const PerformanceMeasurement& getPerformanceMeasurement()
585 {
586 return performanceMeasurement;
587 }
588
589 PerformanceMeasurement::Statistics getStatisticsAndReset()
590 {
591 return performanceMeasurement.getStatisticsAndReset();
592 }
593
594 Node& getNode() const
595 {
596 return *player->getNode();
597 }
598
599 NodePlayerType& getNodePlayer() const
600 {
601 return *player;
602 }
603
604 void setNode (std::unique_ptr<Node> newNode)
605 {
606 jassert (newNode != nullptr);
607 player->setNode (std::move (newNode));
608 }
609
610 void setPlayer (std::unique_ptr<NodePlayerType> newPlayerToUse)
611 {
612 jassert (newPlayerToUse != nullptr);
613 player = std::move (newPlayerToUse);
614 player->prepareToPlay (testSetup.sampleRate, testSetup.blockSize);
615 }
616
617 void setPlayHead (tracktion::graph::PlayHead* newPlayHead)
618 {
619 playHead = newPlayHead;
620 }
621
625 bool process (int maxNumSamples)
626 {
627 for (;;)
628 {
629 const ScopedPerformanceMeasurement spm (performanceMeasurement);
630
631 auto maxNumThisTime = testSetup.randomiseBlockSizes ? std::min (testSetup.random.nextInt ({ 1, testSetup.blockSize }), numSamplesToDo)
632 : std::min (testSetup.blockSize, numSamplesToDo);
633 auto numThisTime = std::min (maxNumSamples, maxNumThisTime);
634 midi.clear();
635
636 auto subSectionView = buffer.getStart ((choc::buffer::FrameCount) numThisTime);
637 subSectionView.clear();
638
639 const auto referenceSampleRange = juce::Range<int64_t>::withStartAndLength ((int64_t) numSamplesDone, (int64_t) numThisTime);
640
641 if (playHead)
642 playHead->setReferenceSampleRange (referenceSampleRange);
643
644 numProcessMisses += player->process ({ (choc::buffer::FrameCount) numThisTime, referenceSampleRange, { subSectionView, midi } });
645
646 if (writer)
647 {
648 auto audioBuffer = tracktion::graph::toAudioBuffer (subSectionView);
649 writer->writeFromAudioSampleBuffer (audioBuffer, 0, audioBuffer.getNumSamples());
650 }
651
652 // Copy MIDI to buffer
653 for (const auto& m : midi)
654 {
655 const int sampleNumber = (int) std::floor (m.getTimeStamp() * testSetup.sampleRate);
656 context->midi.addEvent (m, numSamplesDone + sampleNumber);
657 }
658
659 numSamplesToDo -= numThisTime;
660 numSamplesDone += numThisTime;
661 maxNumSamples -= numThisTime;
662
663 if (maxNumSamples <= 0)
664 break;
665 }
666
667 return numSamplesToDo > 0;
668 }
669
671 {
672 process (numSamplesToDo);
673 return getTestResult();
674 }
675
676 std::shared_ptr<TestContext> getTestResult()
677 {
678 if (writer)
679 {
680 writer->flush();
681
682 // Then read it back in to the buffer
683 if (auto is = std::make_unique<juce::MemoryInputStream> (audioOutputBlock, false))
684 {
685 if (auto reader = std::unique_ptr<juce::AudioFormatReader> (juce::WavAudioFormat().createReaderFor (is.release(), true)))
686 {
687 juce::AudioBuffer<float> tempBuffer (numChannels, (int) reader->lengthInSamples);
688 reader->read (&tempBuffer, 0, tempBuffer.getNumSamples(), 0, true, true);
689 context->buffer = std::move (tempBuffer);
690 }
691 }
692 }
693
694 return context;
695 }
696
697 private:
699 TestSetup testSetup;
700 const int numChannels;
701 const double durationInSeconds;
702 tracktion::graph::PlayHead* playHead = nullptr;
703
705 juce::MemoryBlock audioOutputBlock;
707
708 choc::buffer::ChannelArrayBuffer<float> buffer;
710 int numSamplesToDo = 0;
711 int numSamplesDone = 0;
712 int numProcessMisses = 0;
713
714 PerformanceMeasurement performanceMeasurement { "TestProcess" , -1 };
715 };
716
717 template<typename NodePlayerType>
718 static inline std::shared_ptr<TestContext> createTestContext (std::unique_ptr<NodePlayerType> player, TestSetup ts,
719 const int numChannels, const double durationInSeconds)
720 {
721 return TestProcess<NodePlayerType> (std::move (player), ts, numChannels, durationInSeconds, true).processAll();
722 }
723
724 static inline std::shared_ptr<TestContext> createBasicTestContext (std::unique_ptr<Node> node, const TestSetup ts,
725 const int numChannels, const double durationInSeconds)
726 {
727 auto player = std::make_unique<NodePlayer> (std::move (node));
728 return createTestContext (std::move (player), ts, numChannels, durationInSeconds);
729 }
730
731 static inline std::shared_ptr<TestContext> createBasicTestContext (std::unique_ptr<Node> node, PlayHeadState& playHeadStateToUse,
732 const TestSetup ts, const int numChannels, const double durationInSeconds)
733 {
734 auto player = std::make_unique<NodePlayer> (std::move (node), &playHeadStateToUse);
735 return createTestContext (std::move (player), ts, numChannels, durationInSeconds);
736 }
737}
738
739}}
T accumulate(T... args)
Type getMagnitude(int channel, int startSample, int numSamples) const noexcept
Type getRMSLevel(int channel, int startSample, int numSamples) const noexcept
int getNumChannels() const noexcept
int getNumSamples() const noexcept
const Type * getReadPointer(int channelNumber) const noexcept
Type *const * getArrayOfWritePointers() noexcept
static void JUCE_CALLTYPE writeToLog(const String &message)
void deleteEvent(int index, bool deleteMatchingNoteUp)
MidiEventHolder * getEventPointer(int index) const noexcept
int getNumEvents() const noexcept
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
int nextInt() noexcept
double nextDouble() noexcept
static Range withStartAndLength(const ValueType startValue, const ValueType length) noexcept
constexpr ValueType getStart() const noexcept
constexpr ValueType getLength() const noexcept
static String repeatedString(StringRef stringToRepeat, int numberOfTimesToRepeat)
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
void logMessage(const String &message)
void expectEquals(ValueType actual, ValueType expected, String failureMessage=String())
void expect(bool testResult, const String &failureMessage=String())
void expectWithinAbsoluteError(ValueType actual, ValueType expected, ValueType maxAbsoluteError, String failureMessage=String())
Random getRandom() const
Main graph Node processor class.
virtual std::vector< Node * > getDirectInputNodes()
Should return all the inputs directly feeding in to this node.
A timer for measuring performance of code.
Converts a monotonically increasing reference range in to a timeline range.
RAII wrapper to start and stop a performance measurement.
T floor(T... args)
T fmod(T... args)
T format(T... args)
T is_pointer_v
#define jassert(expression)
#define DBG(textToWrite)
#define jassertfalse
typedef int
T make_pair(T... args)
T min(T... args)
void ignoreUnused(Types &&...) noexcept
int roundToInt(const FloatType value) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
juce::AudioBuffer< float > toAudioBuffer(choc::buffer::ChannelArrayView< float > view)
Creates a juce::AudioBuffer from a choc::buffer::BufferView.
ThreadPoolStrategy
Available strategies for thread pools.
constexpr int64_t toSamples(TimePosition, double sampleRate)
Converts a TimePosition to a number of samples.
void visitNodes(Node &, Visitor &&, bool preordering)
Should call the visitor for any direct inputs to the node exactly once.
T push_back(T... args)
T sample(T... args)
T sin(T... args)
T size(T... args)
typedef uint32_t
Holds a graph in an order ready for processing and a sorted map for quick lookups.
bool process(int maxNumSamples)
Processes a number of samples.
std::string getDescription() const
Returns a description of the number of channels and length of rendering.
time
std::unique_ptr< juce::TemporaryFile > writeToTemporaryFile(choc::buffer::ChannelArrayView< float > block, double sampleRate, int qualityOptionIndex)
Writes an audio buffer to a file.
void writeToFile(juce::File file, choc::buffer::ChannelArrayView< float > block, double sampleRate, int qualityOptionIndex)
Writes an audio buffer to a file.
void logGraph(Node &node)
Logs the graph structure to the console.