HART  0.1.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_process_audio.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // min()
4#include <cassert>
5#include <cmath>
6#include <iomanip>
7#include <memory>
8#include <sstream>
9#include <vector>
10
12#include "dsp/hart_dsp_all.hpp"
14#include "matchers/hart_matcher.hpp"
15#include "hart_plot.hpp"
18#include "signals/hart_signals_all.hpp"
19#include "hart_utils.hpp" // make_unique()
20
21namespace hart {
22
23/// @defgroup TestRunner Test Runner
24/// @brief Runs the tests
25
26/// @brief Determines when to save a file
27/// @ingroup TestRunner
28enum class Save
29{
30 always, ///< File will be saved always, after the test is performed
31 whenFails, ///< File will be saved only when the test has failed
32 never ///< File will not be saved
33};
34
35/// @brief A DSP host used for building and running tests inside a test case
36/// @ingroup TestRunner
37template <typename SampleType>
39{
40public:
41 /// @brief Copies the DSP instance into the host
42 /// @details DSP instance will be moved into this host, and then returned by @ref process(), so you can re-use it.
43 /// @param dsp Your DSP instance.
44 template <typename DSPType>
45 AudioTestBuilder (DSPType&& dsp,
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
49 >::type* = 0)
50 : m_processor (dsp.copy())
51 {
52 }
53
54 /// @brief Moves the DSP instance into the host
55 /// @details DSP instance will be moved into this host, and then returned by @ref process(), so you can re-use it.
56 /// @param dsp Your DSP instance
57 template <typename DSPType>
58 AudioTestBuilder (DSPType&& dsp,
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
62 >::type* = 0)
63 : m_processor (std::forward<DSPType> (dsp).move())
64 {
65 }
66
67 /// @brief Transfers the DSP smart pointer into the host
68 /// @details Use this if your DSP does not support copying or moving. It will be owned by this host,
69 /// and then returned by @ref process(), so you can re-use it.
70 /// @param dsp A smart pointer to your DSP instance
71 AudioTestBuilder (std::unique_ptr<DSP<SampleType>> dsp)
72 : m_processor (std::move (dsp))
73 {
74 }
75
76 /// @brief Sets the sample rate for the test
77 /// @details All the signals, effects and sub hosts are guaranteed to be initialized to this sample rate
78 /// @param sampleRateHz Sample rate in Hz. You can use frequency-related literails from @ref Units.
79 AudioTestBuilder& withSampleRate (double sampleRateHz)
80 {
81 if (sampleRateHz <= 0)
82 HART_THROW_OR_RETURN (hart::ValueError, "Sample rate should be a positive value in Hz", *this);
83
84 if (! m_processor->supportsSampleRate (sampleRateHz))
85 HART_THROW_OR_RETURN (hart::SampleRateError, "Sample rate is not supported by the tested DSP", *this);
86
87 m_sampleRateHz = sampleRateHz;
88 return *this;
89 }
90
91 /// @brief Sets the block size for the test
92 /// @param blockSizeFrames Block size in frames (samples)
93 AudioTestBuilder& withBlockSize (size_t blockSizeFrames)
94 {
95 if (blockSizeFrames == 0)
96 HART_THROW_OR_RETURN (hart::SizeError, "Illegal block size - should be a positive value in frames (samples)", *this);
97
98 m_blockSizeFrames = blockSizeFrames;
99 return *this;
100 }
101
102 /// @brief Sets the initial param value for the tested DSP
103 /// @details It will call @ref DSP::setValue() for DSP under test
104 /// @param id Parameter ID (see @ref DSP::setValue())
105 /// @param value Value that needs to be set
106 AudioTestBuilder& withValue (int id, double value)
107 {
108 // TODO: Handle cases when processor already has an envelope for this id
109 paramValues.emplace_back (ParamValue { id, value });
110 return *this;
111 }
112
113 /// @brief Sets the total duration of the input signal to be processed
114 /// @param Duration of the signal in seconds. You can use time-related literails from @ref Units.
115 AudioTestBuilder& withDuration (double durationSeconds)
116 {
117 if (durationSeconds < 0)
118 HART_THROW_OR_RETURN(hart::ValueError, "Signal duration should be a non-negative value in Hz", *this);
119
120 m_durationSeconds = durationSeconds;
121 return *this;
122 }
123
124 /// @brief Sets the input signal for the test
125 /// @param signal Input signal, see @ref Signals
126 AudioTestBuilder& withInputSignal (const Signal<SampleType>& signal)
127 {
128 // TODO: Implement moving/transfering a signal instance as well
129 m_inputSignal = std::move (signal.copy());
130 return *this;
131 }
132
133 /// @brief Sets arbitrary number of input channels
134 /// @details For common mono and stereo cases, you may use dedicated methods like @ref inStereo() or
135 /// @ref withMonoInput() instead of this one for better readability.
136 /// @param numInputChannels Number of input channels
137 AudioTestBuilder& withInputChannels (size_t numInputChannels)
138 {
139 if (numInputChannels == 0)
140 HART_THROW_OR_RETURN (SizeError, "There should be at least one (mono) audio channel", *this);
141
142 if (numInputChannels > 128)
143 HART_THROW_OR_RETURN (SizeError, "The number of channels is unexpectedly large... Do people really use so many channels?", *this);
144
145 m_numInputChannels = numInputChannels;
146 return *this;
147 }
148
149 /// @brief Sets arbitrary number of output channels
150 /// @details For common mono and stereo cases, you may use dedicated methods like @ref inMono() or
151 /// @ref withStereoOutput() instead of this one for better readability.
152 /// @param numOutputChannels Number of output channels
153 AudioTestBuilder& withOutputChannels (size_t numOutputChannels)
154 {
155 if (numOutputChannels == 0)
156 HART_THROW_OR_RETURN(SizeError, "There should be at least one (mono) audio channel", *this);
157
158 if (numOutputChannels > 128)
159 HART_THROW_OR_RETURN(SizeError, "The number of channels is unexpectedly large... Do people really use so many channels?", *this);
160
161 m_numOutputChannels = numOutputChannels;
162 return *this;
163 }
164
165 /// @brief Sets number of input channels to two
167 {
168 return this->withInputChannels (2);
169 }
170
171 /// @brief Sets number of output channels to two
173 {
174 return this->withOutputChannels (2);
175 }
176
177 /// @brief Sets number of input channels to one
179 {
180 return this->withInputChannels (1);
181 }
182
183 /// @brief Sets number of output channels to one
185 {
186 return this->withOutputChannels (1);
187 }
188
189 /// @brief Sets number of input and output channels to one
191 {
192 return this->withMonoInput().withMonoOutput();
193 }
194
195 /// @brief Sets number of input and output channels to two
197 {
198 return this->withStereoInput().withStereoOutput();
199 }
200
201 /// @brief Adds an "expect" check
202 /// @param matcher Matcher to perform the check, see @ref Matchers
203 AudioTestBuilder& expectTrue (const Matcher<SampleType>& matcher)
204 {
205 addCheck (matcher, SignalAssertionLevel::expect, true);
206 return *this;
207 }
208
209 /// @brief Adds an "expect" check
210 /// @param matcher Matcher to perform the check, see @ref Matchers
211 template <typename MatcherType>
212 AudioTestBuilder& expectTrue (MatcherType&& matcher)
213 {
214 using DecayedType = typename std::decay<MatcherType>::type;
215 static_assert (
216 std::is_base_of<Matcher<SampleType>, DecayedType>::value,
217 "MatcherType must be a hart::Matcher subclass"
218 );
219
220 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::expect, true);
221 return *this;
222 }
223
224 /// @brief Adds a reversed "expect" check
225 /// @param matcher Matcher to perform the check, see @ref Matchers
226 AudioTestBuilder& expectFalse (const Matcher<SampleType>& matcher)
227 {
228 addCheck (matcher, SignalAssertionLevel::expect, false);
229 return *this;
230 }
231
232 /// @brief Adds a reversed "expect" check
233 /// @param matcher Matcher to perform the check, see @ref Matchers
234 template <typename MatcherType>
235 AudioTestBuilder& expectFalse (MatcherType&& matcher)
236 {
237 using DecayedType = typename std::decay<MatcherType>::type;
238 static_assert (
239 std::is_base_of<Matcher<SampleType>, DecayedType>::value,
240 "MatcherType must be a hart::Matcher subclass"
241 );
242 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::expect, false);
243 return *this;
244 }
245
246 /// @brief Adds an "assert" check
247 /// @param matcher Matcher to perform the check, see @ref Matchers
248 AudioTestBuilder& assertTrue (const Matcher<SampleType>& matcher)
249 {
250 addCheck (matcher, SignalAssertionLevel::assert, true);
251 return *this;
252 }
253
254 /// @brief Adds an "assert" check
255 /// @param matcher Matcher to perform the check, see @ref Matchers
256 template <typename MatcherType>
257 AudioTestBuilder& assertTrue (MatcherType&& matcher)
258 {
259 using DecayedType = typename std::decay<MatcherType>::type;
260 static_assert (
261 std::is_base_of<Matcher<SampleType>, DecayedType>::value,
262 "MatcherType must be a hart::Matcher subclass"
263 );
264 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::assert, true);
265 return *this;
266 }
267
268 /// @brief Adds a reversed "assert" check
269 /// @param matcher Matcher to perform the check, see @ref Matchers
270 AudioTestBuilder& assertFalse (const Matcher<SampleType>& matcher)
271 {
272 addCheck (matcher, SignalAssertionLevel::assert, false);
273 return *this;
274 }
275
276 /// @brief Adds a reversed "assert" check
277 /// @param matcher Matcher to perform the check, see @ref Matchers
278 template <typename MatcherType>
279 AudioTestBuilder& assertFalse (MatcherType&& matcher)
280 {
281 using DecayedType = typename std::decay<MatcherType>::type;
282 static_assert (
283 std::is_base_of<Matcher<SampleType>, DecayedType>::value,
284 "MatcherType must be a hart::Matcher subclass"
285 );
286 addCheck (std::forward<MatcherType>(matcher), SignalAssertionLevel::assert, false);
287 return *this;
288 }
289
290 /// @brief Enables saving output audio to a wav file
291 /// @param path File path - relative or absolute. If relative path is set, it will be appended to the provided `--data-root-path` CLI argument.
292 /// @param mode When to save, see @ref hart::Save
293 /// @param wavFormat Format of the wav file, see hart::WavFormat for supported options
294 /// @see HART_REQUIRES_DATA_PATH_ARG
295 AudioTestBuilder& saveOutputTo (const std::string& path, Save mode = Save::always, WavFormat wavFormat = WavFormat::pcm24)
296 {
297 if (path.empty())
298 return *this;
299
300 m_saveOutputPath = toAbsolutePath (path);
301 m_saveOutputMode = mode;
302 m_saveOutputWavFormat = wavFormat;
303 return *this;
304 }
305
306 /// @brief Enables saving a plot to an SVG file
307 /// @details This will plot an input and output audio as a waveform
308 /// @param path File path - relative or absolute. If relative path is set, it will be appended to the provided `--data-root-path` CLI argument.
309 /// @param mode When to save, see @ref hart::Save
310 /// @see HART_REQUIRES_DATA_PATH_ARG
311 AudioTestBuilder& savePlotTo (const std::string& path, Save mode = Save::always)
312 {
313 if (path.empty())
314 return *this;
315
316 m_savePlotPath = toAbsolutePath (path);
317 m_savePlotMode = mode;
318 return *this;
319 }
320
321 /// @brief Adds a label to the test
322 /// @details Useful when you call @ref process() multiple times in one test case - the label
323 /// will be put into test failure report to indicate exactly which test has failed.
324 /// @param testLabel Any text, to be used as a label
325 AudioTestBuilder& withLabel (const std::string& testLabel)
326 {
327 m_testLabel = testLabel;
328 return *this;
329 }
330
331 /// @brief Perfoems the test
332 /// @details Call this after setting all the test parameters
333 std::unique_ptr<DSP<SampleType>> process()
334 {
335 m_durationFrames = (size_t) std::round (m_sampleRateHz * m_durationSeconds);
336
337 if (m_durationFrames == 0)
338 HART_THROW_OR_RETURN (hart::SizeError, "Nothing to process", std::move (m_processor));
339
340 for (auto& check : perBlockChecks)
341 {
342 check.matcher->prepare (m_sampleRateHz, m_numOutputChannels, m_blockSizeFrames);
343 check.matcher->reset();
344 check.shouldSkip = false;
345 }
346
347 for (auto& check : fullSignalChecks)
348 {
349 check.matcher->prepare (m_sampleRateHz, m_numOutputChannels, m_blockSizeFrames);
350 check.matcher->reset();
351 check.shouldSkip = false;
352 }
353
354 // TODO: Ckeck supportsChannelLayout() here
355 m_processor->reset();
356 m_processor->prepareWithEnvelopes (m_sampleRateHz, m_numInputChannels, m_numOutputChannels, m_blockSizeFrames);
357
358 for (const ParamValue& paramValue : paramValues)
359 {
360 // TODO: Add true/false return to indicate if setting the parameter was successful
361 m_processor->setValue (paramValue.id, paramValue.value);
362 }
363
364 if (m_inputSignal == nullptr)
365 HART_THROW_OR_RETURN (hart::StateError, "No input signal - call withInputSignal() first!", std::move (m_processor));
366
367 m_inputSignal->resetWithDSPChain();
368 m_inputSignal->prepareWithDSPChain (m_sampleRateHz, m_numInputChannels, m_blockSizeFrames);
369 offsetFrames = 0;
370
371 AudioBuffer<SampleType> fullInputBuffer (m_numInputChannels);
372 AudioBuffer<SampleType> fullOutputBuffer (m_numOutputChannels);
373 bool atLeastOneCheckFailed = false;
374
375 while (offsetFrames < m_durationFrames)
376 {
377 // TODO: Do not continue if there are no checks, or all checks should skip and there's no input and output file to write
378
379 const size_t blockSizeFrames = std::min (m_blockSizeFrames, m_durationFrames - offsetFrames);
380
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);
385
386 const bool allChecksPassed = processChecks (perBlockChecks, outputBlock);
387 atLeastOneCheckFailed |= ! allChecksPassed;
388 fullInputBuffer.appendFrom (inputBlock);
389 fullOutputBuffer.appendFrom (outputBlock);
390
391 offsetFrames += blockSizeFrames;
392 }
393
394 const bool allChecksPassed = processChecks (fullSignalChecks, fullOutputBuffer);
395 atLeastOneCheckFailed |= ! allChecksPassed;
396
397 if (m_saveOutputMode == Save::always || (m_saveOutputMode == Save::whenFails && atLeastOneCheckFailed))
398 WavWriter<SampleType>::writeBuffer (fullOutputBuffer, m_saveOutputPath, m_sampleRateHz, m_saveOutputWavFormat);
399
400 if (m_savePlotMode == Save::always || (m_savePlotMode == Save::whenFails && atLeastOneCheckFailed))
401 plotData (fullInputBuffer, fullOutputBuffer, m_sampleRateHz, m_savePlotPath);
402
403 return std::move (m_processor);
404 }
405
406private:
407 struct ParamValue
408 {
409 int id;
410 double value;
411 };
412
413 enum class SignalAssertionLevel
414 {
415 expect,
416 assert,
417 };
418
419 struct Check
420 {
421 std::unique_ptr<Matcher<SampleType>> matcher;
422 SignalAssertionLevel signalAssertionLevel;
423 bool shouldSkip;
424 bool shouldPass;
425 };
426
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 = {};
438
439 std::vector<Check> perBlockChecks;
440 std::vector<Check> fullSignalChecks;
441
442 std::string m_saveOutputPath;
443 Save m_saveOutputMode = Save::never;
444 WavFormat m_saveOutputWavFormat = WavFormat::pcm24;
445
446 std::string m_savePlotPath;
447 Save m_savePlotMode = Save::never;
448
449 void addCheck (const Matcher<SampleType>& matcher, SignalAssertionLevel signalAssertionLevel, bool shouldPass)
450 {
451 const bool forceFullSignal = ! shouldPass; // No per-block checks for inverted matchers
452 auto& checksGroup =
453 (matcher.canOperatePerBlock() && ! forceFullSignal)
454 ? perBlockChecks
455 : fullSignalChecks;
456 checksGroup.emplace_back (AudioTestBuilder::Check {
457 matcher.copy(),
458 signalAssertionLevel,
459 false, // shouldSkip
460 shouldPass
461 });
462 }
463
464 template <typename MatcherType>
465 void addCheck (MatcherType&& matcher, SignalAssertionLevel signalAssertionLevel, bool shouldPass)
466 {
467 using DecayedType = typename std::decay<MatcherType>::type;
468 static_assert (
469 std::is_base_of<Matcher<SampleType>, DecayedType>::value,
470 "MatcherType must be a hart::Matcher subclass"
471 );
472 const bool forceFullSignal = ! shouldPass; // No per-block checks for inverted matchers
473 auto& checksGroup =
474 (matcher.canOperatePerBlock() && ! forceFullSignal)
475 ? perBlockChecks
476 : fullSignalChecks;
477 checksGroup.emplace_back (AudioTestBuilder::Check {
478 hart::make_unique<DecayedType> (std::forward<MatcherType> (matcher)),
479 signalAssertionLevel,
480 false, // shouldSkip
481 shouldPass
482 });
483 }
484
485 bool processChecks (std::vector<Check>& checksGroup, AudioBuffer<SampleType>& outputBlock)
486 {
487 for (auto& check : checksGroup)
488 {
489 if (check.shouldSkip)
490 continue;
491
492 auto& assertionLevel = check.signalAssertionLevel;
493 auto& matcher = check.matcher;
494
495 const bool matchPassed = matcher->match (outputBlock);
496
497 if (matchPassed != check.shouldPass)
498 {
499 check.shouldSkip = true;
500 // TODO: Add optional label for each test
501
502 if (assertionLevel == SignalAssertionLevel::assert)
503 {
504 std::stringstream stream;
505 stream << (check.shouldPass ? "assertTrue() failed" : "assertFalse() failed");
506
507 if (! m_testLabel.empty())
508 stream << " at \"" << m_testLabel << "\"";
509
510 stream << std::endl << "Condition: " << *matcher;
511
512 if (check.shouldPass)
513 appendFailureDetails (stream, matcher->getFailureDetails(), outputBlock);
514
515 throw hart::TestAssertException (std::string (stream.str()));
516 }
517 else
518 {
519 std::stringstream stream;
520 stream << (check.shouldPass ? "expectTrue() failed" : "expectFalse() failed");
521
522 if (!m_testLabel.empty())
523 stream << " at \"" << m_testLabel << "\"";
524
525 stream << std::endl << "Condition: " << * matcher;
526
527 if (check.shouldPass)
528 appendFailureDetails (stream, matcher->getFailureDetails(), outputBlock);
529
530 hart::ExpectationFailureMessages::get().emplace_back (stream.str());
531 }
532
533 // TODO: FIXME: Do not throw indife of per-block loop if requested to write input or output to a wav file, throw after the loop instead
534 // TODO: Stop processing if expect has failed and outputting to a file wasn't requested
535 // TODO: Skip all checks if check failed, but asked to output a wav file
536 return false;
537 }
538 }
539
540 return true;
541 }
542
543 void appendFailureDetails (std::stringstream& stream, const MatcherFailureDetails& details, AudioBuffer<SampleType>& observedAudioBlock)
544 {
545 const double timestampSeconds = static_cast<double> (offsetFrames + details.frame) / m_sampleRateHz;
546 const SampleType sampleValue = observedAudioBlock[details.channel][details.frame];
547
548 stream << std::endl
549 << "Channel: " << details.channel << std::endl
550 << "Frame: " << details.frame << std::endl
551 << secPrecision << "Timestamp: " << timestampSeconds << " seconds" << std::endl
552 << linPrecision << "Sample value: " << sampleValue
553 << dbPrecision << " (" << ratioToDecibels (std::abs (sampleValue)) << " dB)" << std::endl
554 << details.description;
555 }
556};
557
558/// @brief Call this to start building your test
559/// @param dsp Instance of your DSP effect
560/// @return @ref AudioTestBuilder instance - you can chain a bunch of test parameters with it.
561/// @ingroup TestRunner
562/// @relates AudioTestBuilder
563template <typename DSPType>
564AudioTestBuilder<typename std::decay<DSPType>::type::SampleTypePublicAlias> processAudioWith (DSPType&& dsp)
565{
566 return AudioTestBuilder<typename std::decay<DSPType>::type::SampleTypePublicAlias> (std::forward<DSPType>(dsp));
567}
568
569/// @brief Call this to start building your test
570/// @details Call this for DSP objects that do not support moving or copying
571/// @param dsp Instance of your DSP effect wrapped in a smart pointer
572/// @return @ref AudioTestBuilder instance - you can chain a bunch of test parameters with it.
573/// @ingroup TestRunner
574/// @relates AudioTestBuilder
575template <typename DSPType>
576AudioTestBuilder<typename DSPType::SampleTypePublicAlias> processAudioWith (std::unique_ptr<DSPType>&& dsp)
577{
578 using SampleType = typename DSPType::SampleTypePublicAlias;
579 return AudioTestBuilder<SampleType> (std::unique_ptr<DSP<SampleType>> (dsp.release()));
580}
581
583{
584 using hart::processAudioWith;
585}
587{
588 using hart::processAudioWith;
589}
590
591} // namespace hart
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.
Base for DSP effects.
Definition hart_dsp.hpp:34
static std::vector< std::string > & get()
Base for audio matchers.
Base class for signals.
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)
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.