12#include "dsp/hart_dsp_all.hpp"
14#include "matchers/hart_matcher.hpp"
18#include "signals/hart_signals_all.hpp"
37template <
typename SampleType>
44 template <
typename DSPType>
46 typename std::enable_if<
47 std::is_lvalue_reference<DSPType&&>::value &&
48 std::is_base_of<
DSP<SampleType>,
typename std::decay<DSPType>::type>::value
50 : m_processor (dsp.copy())
57 template <
typename DSPType>
59 typename std::enable_if<
60 ! std::is_lvalue_reference<DSPType&&>::value &&
61 std::is_base_of<
DSP<SampleType>,
typename std::decay<DSPType>::type>::value
63 : m_processor (std::forward<DSPType> (dsp).move())
72 : m_processor (std::move (dsp))
81 if (sampleRateHz <= 0)
84 if (! m_processor->supportsSampleRate (sampleRateHz))
87 m_sampleRateHz = sampleRateHz;
95 if (blockSizeFrames == 0)
98 m_blockSizeFrames = blockSizeFrames;
109 paramValues.emplace_back (ParamValue { id, value });
117 if (durationSeconds < 0)
120 m_durationSeconds = durationSeconds;
129 m_inputSignal = std::move (signal.copy());
139 if (numInputChannels == 0)
142 if (numInputChannels > 128)
145 m_numInputChannels = numInputChannels;
155 if (numOutputChannels == 0)
158 if (numOutputChannels > 128)
161 m_numOutputChannels = numOutputChannels;
168 return this->withInputChannels (2);
174 return this->withOutputChannels (2);
180 return this->withInputChannels (1);
186 return this->withOutputChannels (1);
192 return this->withMonoInput().withMonoOutput();
198 return this->withStereoInput().withStereoOutput();
205 addCheck (matcher, SignalAssertionLevel::expect,
true);
211 template <
typename MatcherType>
214 using DecayedType =
typename std::decay<MatcherType>::type;
216 std::is_base_of<
Matcher<SampleType>, DecayedType>::value,
217 "MatcherType must be a hart::Matcher subclass"
220 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::expect,
true);
228 addCheck (matcher, SignalAssertionLevel::expect,
false);
234 template <
typename MatcherType>
237 using DecayedType =
typename std::decay<MatcherType>::type;
239 std::is_base_of<
Matcher<SampleType>, DecayedType>::value,
240 "MatcherType must be a hart::Matcher subclass"
242 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::expect,
false);
250 addCheck (matcher, SignalAssertionLevel::assert,
true);
256 template <
typename MatcherType>
259 using DecayedType =
typename std::decay<MatcherType>::type;
261 std::is_base_of<
Matcher<SampleType>, DecayedType>::value,
262 "MatcherType must be a hart::Matcher subclass"
264 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::assert,
true);
272 addCheck (matcher, SignalAssertionLevel::assert,
false);
278 template <
typename MatcherType>
281 using DecayedType =
typename std::decay<MatcherType>::type;
283 std::is_base_of<
Matcher<SampleType>, DecayedType>::value,
284 "MatcherType must be a hart::Matcher subclass"
286 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::assert,
false);
301 m_saveOutputMode = mode;
302 m_saveOutputWavFormat = wavFormat;
317 m_savePlotMode = mode;
327 m_testLabel = testLabel;
335 m_durationFrames = (size_t) std::round (m_sampleRateHz * m_durationSeconds);
337 if (m_durationFrames == 0)
340 for (
auto& check : perBlockChecks)
342 check.matcher->prepare (m_sampleRateHz, m_numOutputChannels, m_blockSizeFrames);
343 check.matcher->reset();
344 check.shouldSkip =
false;
347 for (
auto& check : fullSignalChecks)
349 check.matcher->prepare (m_sampleRateHz, m_numOutputChannels, m_blockSizeFrames);
350 check.matcher->reset();
351 check.shouldSkip =
false;
355 m_processor->reset();
356 m_processor->prepareWithEnvelopes (m_sampleRateHz, m_numInputChannels, m_numOutputChannels, m_blockSizeFrames);
358 for (
const ParamValue& paramValue : paramValues)
361 m_processor->setValue (paramValue.id, paramValue.value);
364 if (m_inputSignal ==
nullptr)
367 m_inputSignal->resetWithDSPChain();
368 m_inputSignal->prepareWithDSPChain (m_sampleRateHz, m_numInputChannels, m_blockSizeFrames);
371 AudioBuffer<SampleType> fullInputBuffer (m_numInputChannels);
372 AudioBuffer<SampleType> fullOutputBuffer (m_numOutputChannels);
373 bool atLeastOneCheckFailed =
false;
375 while (offsetFrames < m_durationFrames)
379 const size_t blockSizeFrames = std::min (m_blockSizeFrames, m_durationFrames - offsetFrames);
381 hart::
AudioBuffer<SampleType> inputBlock (m_numInputChannels, blockSizeFrames);
382 hart::
AudioBuffer<SampleType> outputBlock (m_numOutputChannels, blockSizeFrames);
383 m_inputSignal->renderNextBlockWithDSPChain (inputBlock);
384 m_processor->processWithEnvelopes (inputBlock, outputBlock);
386 const bool allChecksPassed = processChecks (perBlockChecks, outputBlock);
387 atLeastOneCheckFailed |= ! allChecksPassed;
388 fullInputBuffer.appendFrom (inputBlock);
389 fullOutputBuffer.appendFrom (outputBlock);
391 offsetFrames += blockSizeFrames;
394 const bool allChecksPassed = processChecks (fullSignalChecks, fullOutputBuffer);
395 atLeastOneCheckFailed |= ! allChecksPassed;
398 WavWriter<SampleType>::writeBuffer (fullOutputBuffer, m_saveOutputPath, m_sampleRateHz, m_saveOutputWavFormat);
401 plotData (fullInputBuffer, fullOutputBuffer, m_sampleRateHz, m_savePlotPath);
403 return std::move (m_processor);
413 enum class SignalAssertionLevel
421 std::unique_ptr<
Matcher<SampleType>> matcher;
422 SignalAssertionLevel signalAssertionLevel;
427 std::unique_ptr<
DSP<SampleType>> m_processor;
428 std::unique_ptr<
Signal<SampleType>> m_inputSignal;
429 double m_sampleRateHz = (
double) 44100;
430 size_t m_blockSizeFrames = 1024;
431 size_t m_numInputChannels = 1;
432 size_t m_numOutputChannels = 1;
433 std::vector<ParamValue> paramValues;
434 double m_durationSeconds = 0.1;
435 size_t m_durationFrames =
static_cast<size_t> (m_durationSeconds * m_sampleRateHz);
436 size_t offsetFrames = 0;
437 std::string m_testLabel = {};
439 std::vector<Check> perBlockChecks;
440 std::vector<Check> fullSignalChecks;
442 std::string m_saveOutputPath;
446 std::string m_savePlotPath;
449 void addCheck (
const Matcher<SampleType>& matcher, SignalAssertionLevel signalAssertionLevel,
bool shouldPass)
451 const bool forceFullSignal = ! shouldPass;
453 (matcher.canOperatePerBlock() && ! forceFullSignal)
458 signalAssertionLevel,
464 template <
typename MatcherType>
465 void addCheck (MatcherType&& matcher, SignalAssertionLevel signalAssertionLevel,
bool shouldPass)
467 using DecayedType =
typename std::decay<MatcherType>::type;
469 std::is_base_of<
Matcher<SampleType>, DecayedType>::value,
470 "MatcherType must be a hart::Matcher subclass"
472 const bool forceFullSignal = ! shouldPass;
474 (matcher.canOperatePerBlock() && ! forceFullSignal)
478 hart::make_unique<DecayedType> (std::forward<MatcherType> (matcher)),
479 signalAssertionLevel,
485 bool processChecks (std::vector<Check>& checksGroup,
AudioBuffer<SampleType>& outputBlock)
487 for (
auto& check : checksGroup)
489 if (check.shouldSkip)
492 auto& assertionLevel = check.signalAssertionLevel;
493 auto& matcher = check.matcher;
495 const bool matchPassed = matcher->match (outputBlock);
497 if (matchPassed != check.shouldPass)
499 check.shouldSkip =
true;
502 if (assertionLevel == SignalAssertionLevel::assert)
504 std::stringstream stream;
505 stream << (check.shouldPass ?
"assertTrue() failed" :
"assertFalse() failed");
507 if (! m_testLabel.empty())
508 stream <<
" at \"" << m_testLabel <<
"\"";
510 stream << std::endl <<
"Condition: " << *matcher;
512 if (check.shouldPass)
513 appendFailureDetails (stream, matcher->getFailureDetails(), outputBlock);
519 std::stringstream stream;
520 stream << (check.shouldPass ?
"expectTrue() failed" :
"expectFalse() failed");
522 if (!m_testLabel.empty())
523 stream <<
" at \"" << m_testLabel <<
"\"";
525 stream << std::endl <<
"Condition: " << * matcher;
527 if (check.shouldPass)
528 appendFailureDetails (stream, matcher->getFailureDetails(), outputBlock);
545 const double timestampSeconds =
static_cast<
double> (offsetFrames + details
.frame) / m_sampleRateHz;
546 const SampleType sampleValue = observedAudioBlock[details
.channel][details
.frame];
549 <<
"Channel: " << details
.channel << std::endl
550 <<
"Frame: " << details
.frame << std::endl
551 <<
secPrecision <<
"Timestamp: " << timestampSeconds <<
" seconds" << std::endl
553 <<
dbPrecision <<
" (" << ratioToDecibels (std::abs (sampleValue)) <<
" dB)" << std::endl
563template <
typename DSPType>
566 return AudioTestBuilder<
typename std::decay<DSPType>::type::SampleTypePublicAlias> (std::forward<DSPType>(dsp));
575template <
typename DSPType>
578 using SampleType =
typename DSPType::SampleTypePublicAlias;
584 using hart::processAudioWith;
588 using hart::processAudioWith;
A DSP host used for building and running tests inside a test case.
AudioTestBuilder & assertTrue(const Matcher< SampleType > &matcher)
Adds an "assert" check.
AudioTestBuilder & inMono()
Sets number of input and output channels to one.
AudioTestBuilder(DSPType &&dsp, typename std::enable_if< ! std::is_lvalue_reference< DSPType && >::value &&std::is_base_of< DSP< SampleType >, typename std::decay< DSPType >::type >::value >::type *=0)
Moves the DSP instance into the host.
std::unique_ptr< DSP< SampleType > > process()
Perfoems the test.
AudioTestBuilder & withLabel(const std::string &testLabel)
Adds a label to the test.
AudioTestBuilder & withDuration(double durationSeconds)
Sets the total duration of the input signal to be processed.
AudioTestBuilder & withStereoInput()
Sets number of input channels to two.
AudioTestBuilder & expectFalse(MatcherType &&matcher)
Adds a reversed "expect" check.
AudioTestBuilder & assertFalse(MatcherType &&matcher)
Adds a reversed "assert" check.
AudioTestBuilder & withInputChannels(size_t numInputChannels)
Sets arbitrary number of input channels.
AudioTestBuilder(std::unique_ptr< DSP< SampleType > > dsp)
Transfers the DSP smart pointer into the host.
AudioTestBuilder & expectTrue(MatcherType &&matcher)
Adds an "expect" check.
AudioTestBuilder & withSampleRate(double sampleRateHz)
Sets the sample rate for the test.
AudioTestBuilder & savePlotTo(const std::string &path, Save mode=Save::always)
Enables saving a plot to an SVG file.
AudioTestBuilder & withInputSignal(const Signal< SampleType > &signal)
Sets the input signal for the test.
AudioTestBuilder & withMonoOutput()
Sets number of output channels to one.
AudioTestBuilder & assertTrue(MatcherType &&matcher)
Adds an "assert" check.
AudioTestBuilder & withValue(int id, double value)
Sets the initial param value for the tested DSP.
AudioTestBuilder(DSPType &&dsp, typename std::enable_if< std::is_lvalue_reference< DSPType && >::value &&std::is_base_of< DSP< SampleType >, typename std::decay< DSPType >::type >::value >::type *=0)
Copies the DSP instance into the host.
AudioTestBuilder & saveOutputTo(const std::string &path, Save mode=Save::always, WavFormat wavFormat=WavFormat::pcm24)
Enables saving output audio to a wav file.
AudioTestBuilder & withMonoInput()
Sets number of input channels to one.
AudioTestBuilder & expectFalse(const Matcher< SampleType > &matcher)
Adds a reversed "expect" check.
AudioTestBuilder & withStereoOutput()
Sets number of output channels to two.
AudioTestBuilder & inStereo()
Sets number of input and output channels to two.
AudioTestBuilder & expectTrue(const Matcher< SampleType > &matcher)
Adds an "expect" check.
AudioTestBuilder & withOutputChannels(size_t numOutputChannels)
Sets arbitrary number of output channels.
AudioTestBuilder & withBlockSize(size_t blockSizeFrames)
Sets the block size for the test.
AudioTestBuilder & assertFalse(const Matcher< SampleType > &matcher)
Adds a reversed "assert" check.
static std::vector< std::string > & get()
std::ostream & linPrecision(std::ostream &stream)
Sets number of decimal places for linear (sample) values.
std::ostream & secPrecision(std::ostream &stream)
Sets number of decimal places for values in seconds.
std::ostream & dbPrecision(std::ostream &stream)
Sets number of decimal places for values in decibels.
Save
Determines when to save a file.
AudioTestBuilder< typename std::decay< DSPType >::type::SampleTypePublicAlias > processAudioWith(DSPType &&dsp)
Call this to start building your test.
AudioTestBuilder< typename DSPType::SampleTypePublicAlias > processAudioWith(std::unique_ptr< DSPType > &&dsp)
Call this to start building your test.
@ whenFails
File will be saved only when the test has failed.
@ never
File will not be saved.
@ always
File will be saved always, after the test is performed.
static std::string toAbsolutePath(const std::string &path)
Converts path to absolute, if it's relative @deials Relative paths are resolved based on a provided -...
#define HART_THROW_OR_RETURN(ExceptionType, message, returnValue)
Details about matcher failure.
size_t channel
Index of channel at which the failure was detected.
std::string description
Readable description of why the match has failed.
size_t frame
Index of frame at which the match has failed.