16namespace tracktion {
inline namespace graph
20namespace test_utilities
30 for (
double time = 0.0;
time < durationSeconds;
31 time += r.
nextDouble() * noteLengthRange.getLength() + noteLengthRange.getStart())
40 noteNumber = r.
nextInt ({ 1, 127 });
45 sequence.updateMatchedPairs();
55 for (
auto itr : buffer)
57 const auto& result = itr.getMessage();
58 int samplePosition = itr.samplePosition;
60 const double time = samplePosition / sampleRate;
61 sequence.addEvent (result, time);
68 static inline float getPhaseIncrement (
float frequency,
double sampleRate)
75 void reset (
float frequency,
double sampleRate)
78 phaseIncrement = getPhaseIncrement (frequency, sampleRate);
88 float phase = 0, phaseIncrement = 0;
91 static inline void fillBufferWithSinData (choc::buffer::ChannelArrayView<float> buffer)
94 setAllFrames (buffer, [=] (
auto frame) {
return std::sin ((
float) (frame * phaseIncrement)); });
97 static inline auto createSineBuffer (
int numChannels,
int numFrames,
double phaseIncrement)
99 return choc::buffer::createChannelArrayBuffer (numChannels, numFrames,
100 [=] (
auto,
auto frame) {
return std::sin ((
float) (frame * phaseIncrement)); });
103 static inline auto createSquareBuffer (
int numChannels,
int numFrames,
double phaseIncrement)
105 return choc::buffer::createChannelArrayBuffer (numChannels, numFrames,
106 [=] (
auto,
auto frame)
108 const auto sinValue =
std::sin ((
float) (frame * phaseIncrement));
110 if (sinValue > 0.0f)
return 1.0f;
111 if (sinValue < 0.0f)
return -1.0f;
129 for (
auto itr : buffer)
131 const auto& result = itr.getMessage();
132 DBG(result.getDescription());
158 template<
typename AudioFormatType>
159 void writeToFile (
juce::File file, choc::buffer::ChannelArrayView<float> block,
double sampleRate,
int qualityOptionIndex)
161 AudioFormatType type;
165 block.getNumChannels(),
166 16, {}, qualityOptionIndex)))
168 writer->writeFromAudioSampleBuffer (
toAudioBuffer (block), 0, (
int) block.getNumFrames());
173 template<
typename AudioFormatType>
177 writeToFile<AudioFormatType> (f->getFile(), block, sampleRate, qualityOptionIndex);
186 auto nodesWithInternalNodes = createNodeMap (nodes);
187 return std::accumulate (nodesWithInternalNodes.begin(), nodesWithInternalNodes.end(), (
size_t) 0,
188 [] (
size_t total,
auto& nodeAndID)
190 return total + nodeAndID.node->getAllocatedBytes();
195 static inline size_t getMemoryUsage (
const NodeGraph& graph)
197 return std::accumulate (graph.sortedNodes.begin(), graph.sortedNodes.end(), (
size_t) 0,
198 [] (
size_t total,
auto& nodeAndID)
200 return total + nodeAndID.node->getAllocatedBytes();
205 static inline size_t getMemoryUsage (Node& node)
207 return getMemoryUsage (tracktion::graph::getNodes (node, tracktion::graph::VertexOrdering::postordering));
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";
229 return { ThreadPoolStrategy::lightweightSemHybrid,
230 ThreadPoolStrategy::lightweightSemaphore,
231 ThreadPoolStrategy::semaphore,
232 ThreadPoolStrategy::conditionVariable,
233 ThreadPoolStrategy::realTime,
234 ThreadPoolStrategy::hybrid };
242 static void logNode (
Node& n,
int depth)
247 static void visitInputs (
Node& n,
int depth)
252 visitInputs (*input, depth + 1);
256 Visitor::visitInputs (node, 0);
267 template<
typename AudioFormatType>
269 int numChannels = 1,
float frequency = 220.0f,
270 int qualityOptionIndex = -1)
272 auto buffer = createSineBuffer (numChannels, (
int) (sampleRate * durationInSeconds),
273 getPhaseIncrement (frequency, sampleRate));
275 AudioFormatType format;
277 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
281 template<
typename AudioFormatType>
283 int numChannels = 1,
float frequency = 220.0f,
284 int qualityOptionIndex = -1)
286 auto buffer = createSquareBuffer (numChannels, (
int) (sampleRate * durationInSeconds),
287 getPhaseIncrement (frequency, sampleRate));
289 AudioFormatType format;
291 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
295 template<
typename AudioFormatType>
297 TimeDuration stepDuration,
299 int qualityOptionIndex = -1)
301 const auto stepNumFrames = toSamples (stepDuration, sampleRate);
302 const auto numSteps = duration / stepDuration;
303 const auto valPerStep =
static_cast<float> (1.0 / numSteps);
305 auto buffer = choc::buffer::createChannelArrayBuffer (numChannels, toSamples (duration, sampleRate),
306 [=] (
auto,
auto frame)
308 const int stepNum =
static_cast<int> (frame / stepNumFrames) + 1;
309 const float f = stepNum * valPerStep;
316 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
321 template<
typename AudioFormatType>
323 TimePosition transientPos,
float transientVal,
324 int numChannels = 1,
int qualityOptionIndex = -1)
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; });
333 writeToFile<AudioFormatType> (f->getFile(), buffer, sampleRate, qualityOptionIndex);
345 bool sequencesTheSame =
true;
352 auto desc1 = event1->message.getDescription();
353 auto desc2 = event2->message.getDescription();
359 sequencesTheSame =
false;
366 auto desc2 = event2->message.getDescription();
374 auto desc2 = event2->message.getDescription();
379 ut.
expect (sequencesTheSame,
"MIDI sequence contents not equal");
381 if (! sequencesTheSame)
384 logMidiMessageSequence (ut, actual);
386 logMidiMessageSequence (ut, expected);
393 expectMidiMessageSequence (ut, createMidiMessageSequence (buffer, sampleRate), seq);
405 float mag1,
float rms1,
float mag2,
float rms2)
409 0, numSampleToSplitAt);
410 expectAudioBuffer (ut, trimmedBuffer, channel, mag1, rms1);
416 numSampleToSplitAt, buffer.
getNumSamples() - numSampleToSplitAt);
417 expectAudioBuffer (ut, trimmedBuffer, channel, mag2, rms2);
422 template<
typename IntType>
424 float mag,
float rms)
429 expectAudioBuffer (ut, trimmedBuffer, channel, mag, rms);
440 auto* bPtr = b.getReadPointer (channel);
460 auto* bPtr = b.getReadPointer (channel);
464 const auto aSamp = *(aPtr +
sample);
465 const auto bSamp = *(bPtr +
sample);
466 const auto absDiff = std::abs (aSamp - bSamp);
468 if (absDiff > absSampleTolerance)
476 inline bool buffersAreEqual (
const choc::buffer::ChannelArrayView<float>& a,
const choc::buffer::ChannelArrayView<float>& b,
float absSampleTolerance = 0.0f)
478 return buffersAreEqual (toAudioBuffer (a), toAudioBuffer (b), absSampleTolerance);
482 static inline void expectUniqueNodeIDs (
juce::UnitTest& ut, Node& node,
bool ignoreZeroIDs)
484 auto areUnique = node_player_utils::areNodeIDsUnique (node, ignoreZeroIDs);
485 ut.
expect (areUnique,
"nodeIDs are not unique");
494 auto size = buffer.getSize();
495 auto samples = buffer.getIterator (0);
497 for (
decltype (
size.numFrames) i = 0; i <
size.numFrames; ++i)
498 if (
auto s = *samples++; s > 0.0f)
508 double sampleRate = 44100.0;
510 bool randomiseBlockSizes =
false;
518 +
juce::String (ts.randomiseBlockSizes ?
", random" :
"");
526 #if JUCE_DEBUG || GRAPH_UNIT_TESTS_QUICK_VALIDATE
527 for (
double sampleRate : { 44100.0, 96000.0 })
528 for (
int blockSize : { 64, 512 })
530 for (
double sampleRate : { 44100.0, 48000.0, 96000.0 })
531 for (
int blockSize : { 64, 256, 512, 1024 })
533 for (
bool randomiseBlockSizes : {
false,
true })
544 int numProcessMisses = 0;
547 template<
typename NodePlayerType>
551 const int numChannelsToUse,
const double duration,
553 : testSetup (ts), numChannels (numChannelsToUse), durationInSeconds (duration)
557 buffer.resize ({ (choc::buffer::ChannelCount) numChannels, (choc::buffer::FrameCount) ts.blockSize });
560 if (writeToBuffer && numChannels > 0)
565 ts.sampleRate, (
uint32_t) numChannels, 16, {}, 0));
573 setPlayer (std::move (playerToUse));
579 return juce::String (
"{numChannels} channels, {durationInSeconds}s")
586 return performanceMeasurement;
591 return performanceMeasurement.getStatisticsAndReset();
594 Node& getNode()
const
596 return *player->getNode();
599 NodePlayerType& getNodePlayer()
const
607 player->setNode (std::move (newNode));
612 jassert (newPlayerToUse !=
nullptr);
613 player = std::move (newPlayerToUse);
614 player->prepareToPlay (testSetup.sampleRate, testSetup.blockSize);
619 playHead = newPlayHead;
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);
636 auto subSectionView = buffer.getStart ((choc::buffer::FrameCount) numThisTime);
637 subSectionView.clear();
642 playHead->setReferenceSampleRange (referenceSampleRange);
644 numProcessMisses += player->process ({ (choc::buffer::FrameCount) numThisTime, referenceSampleRange, { subSectionView, midi } });
648 auto audioBuffer = tracktion::graph::toAudioBuffer (subSectionView);
649 writer->writeFromAudioSampleBuffer (audioBuffer, 0, audioBuffer.getNumSamples());
653 for (
const auto& m : midi)
655 const int sampleNumber = (
int)
std::floor (m.getTimeStamp() * testSetup.sampleRate);
656 context->midi.addEvent (m, numSamplesDone + sampleNumber);
659 numSamplesToDo -= numThisTime;
660 numSamplesDone += numThisTime;
661 maxNumSamples -= numThisTime;
663 if (maxNumSamples <= 0)
667 return numSamplesToDo > 0;
672 process (numSamplesToDo);
673 return getTestResult();
688 reader->read (&tempBuffer, 0, tempBuffer.getNumSamples(), 0,
true,
true);
689 context->buffer = std::move (tempBuffer);
700 const int numChannels;
701 const double durationInSeconds;
708 choc::buffer::ChannelArrayBuffer<float> buffer;
710 int numSamplesToDo = 0;
711 int numSamplesDone = 0;
712 int numProcessMisses = 0;
714 PerformanceMeasurement performanceMeasurement {
"TestProcess" , -1 };
717 template<
typename NodePlayerType>
719 const int numChannels,
const double durationInSeconds)
721 return TestProcess<NodePlayerType> (std::move (player), ts, numChannels, durationInSeconds,
true).processAll();
725 const int numChannels,
const double durationInSeconds)
728 return createTestContext (std::move (player), ts, numChannels, durationInSeconds);
732 const TestSetup ts,
const int numChannels,
const double durationInSeconds)
735 return createTestContext (std::move (player), ts, numChannels, durationInSeconds);
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
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())
Main graph Node processor class.
virtual std::vector< Node * > getDirectInputNodes()
Should return all the inputs directly feeding in to this node.
Converts a monotonically increasing reference range in to a timeline range.
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.
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.
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.