HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_equalsto.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath> // abs()
4#include <iomanip>
5#include <sstream>
6
8#include "matchers/hart_matcher.hpp"
10#include "signals/hart_signal.hpp"
11#include "hart_utils.hpp" // floatsEqual(), ratioToDecibels()
12
13namespace hart
14{
15
16/// @brief Checks whether the audio is identical to some signal
17/// @details Holds an internal Signal instance, and gets it to generate reference audio as
18/// the matcher receives new audio blocks through match(). Reports mismatch if even
19/// one of the frames in the audio does not match the reference signal within tolerance
20/// specified in @c epsilon during its instantiation.
21/// @ingroup Matchers
22template<typename SampleType>
24 public Matcher<SampleType, EqualsTo<SampleType>>
25{
26public:
27 /// @brief Creates a matcher for a specific signal by transfering smart pointer
28 /// details The reference signal can be something simple like a @ref SineWave, or more
29 /// complex signal with DSP effects chain and automation envelopes.
30 /// @note Tip: To compare audio to a pre-recorded wav file, you can use @ref WavFile.
31 /// @param referenceSignal Signal to compare the incoming audio against
32 /// @param toleranceLinear Absolute tolerance for comparing frames, in linear domain (not decibels)
33 EqualsTo (std::unique_ptr<SignalBase<SampleType>>&& referenceSignal, double toleranceLinear = (SampleType) 1e-5):
34 m_referenceSignal (std::move (referenceSignal)),
35 m_toleranceLinear ((SampleType) toleranceLinear)
36 {
37 }
38
39 /// @brief Creates a matcher for a specific signal by copying it
40 /// details The reference signal can be something simple like a @ref SineWave, or more
41 /// complex signal with DSP effects chain and automation envelopes.
42 /// @note Tip: To compare audio to a pre-recorded wav file, you can use @ref WavFile.
43 /// @param referenceSignal Signal to compare the incoming audio against
44 /// @param toleranceLinear Absolute tolerance for comparing frames, in linear domain (not decibels)
45 EqualsTo (const SignalBase<SampleType>& referenceSignal, double toleranceLinear = (SampleType) 1e-5):
46 EqualsTo (referenceSignal.copy(), toleranceLinear)
47 {
48 }
49
50 /// @brief Creates a matcher for a specific signal by moving it
51 /// details The reference signal can be something simple like a @ref SineWave, or more
52 /// complex signal with DSP effects chain and automation envelopes.
53 /// @note Tip: To compare audio to a pre-recorded wav file, you can use @ref WavFile.
54 /// @param referenceSignal Signal to compare the incoming audio against
55 /// @param toleranceLinear Absolute tolerance for comparing frames, in linear domain (not decibels)
56 EqualsTo (SignalBase<SampleType>&& referenceSignal, double toleranceLinear = (SampleType) 1e-5):
57 EqualsTo (referenceSignal.move(), toleranceLinear)
58 {
59 }
60
61 EqualsTo (EqualsTo&& other) noexcept:
62 Matcher<SampleType, EqualsTo<SampleType>> (std::move (other)),
63 m_referenceSignal (std::move (other.m_referenceSignal)),
64 m_toleranceLinear (other.m_toleranceLinear)
65 {
66 }
67
68 EqualsTo (const EqualsTo& other):
69 Matcher<SampleType, EqualsTo<SampleType>> (other),
70 m_referenceSignal (other.m_referenceSignal != nullptr ? other.m_referenceSignal->copy() : nullptr),
71 m_toleranceLinear (other.m_toleranceLinear)
72 {
73 }
74
75 EqualsTo& operator= (const EqualsTo& other)
76 {
77 if (this == &other)
78 return *this;
79
80 Matcher<SampleType, EqualsTo<SampleType>>::operator= (other);
81
82 m_referenceSignal = other.m_referenceSignal != nullptr ? other.m_referenceSignal->copy() : nullptr;
83 m_toleranceLinear = other.m_toleranceLinear;
84
85 return *this;
86 }
87
88 EqualsTo& operator= (EqualsTo&& other) noexcept
89 {
90 if (this == &other)
91 return *this;
92
93 // Move base first
94 Matcher<SampleType, EqualsTo<SampleType>>::operator=(std::move(other));
95
96 // Then move our members
97 m_referenceSignal = std::move(other.m_referenceSignal);
98 m_toleranceLinear = other.m_toleranceLinear;
99
100 return *this;
101 }
102
103 ~EqualsTo() override = default;
104
105 void prepare (double sampleRateHz, size_t /* numInputChannels */, size_t numOutputChannels, size_t maxBlockSizeFrames) override
106 {
107 m_maxBlockSizeFrames = maxBlockSizeFrames;
108 m_referenceOutputAudio = AudioBuffer<SampleType> (numOutputChannels, maxBlockSizeFrames, sampleRateHz);
109 m_referenceSignal->prepareWithDSPChain (sampleRateHz, numOutputChannels, maxBlockSizeFrames);
110 }
111
112 bool match (AnalysisContext<SampleType> context) override
113 {
114 const AudioBuffer<SampleType>& observedOutputAudio = context.outputAudio();
115
116 if (observedOutputAudio.getNumFrames() <= m_maxBlockSizeFrames)
117 {
118 // Usual block-wise rendering
119
120 if (m_referenceOutputAudio.getNumFrames() != observedOutputAudio.getNumFrames())
121 m_referenceOutputAudio.setNumFrames (observedOutputAudio.getNumFrames()); // To partial block and back
122
123 m_referenceSignal->renderNextBlockWithDSPChain (m_referenceOutputAudio);
124 }
125 else
126 {
127 // Matcher was given a full piece of audio, instead of block-by-block rendering.
128 // HART guarantees to the Signal instances that the renderNextBlock() will be called
129 // with audio buffer sizes that are in line with max block size set in prepare().
130 // Since this matcher is a "host" for the reference signal, it has to obey those
131 // rules as well, by slicing the full audio buffer into properly sized blocks.
132
133 AudioBuffer<SampleType> fullOutputBuffer (observedOutputAudio.getNumChannels(), 0, observedOutputAudio.getSampleRateHz());
134 const size_t totalDurationFrames = observedOutputAudio.getNumFrames();
135 size_t offsetFrames = 0;
136
137 while (offsetFrames < totalDurationFrames)
138 {
139 const size_t blockSizeFrames = std::min (m_maxBlockSizeFrames, totalDurationFrames - offsetFrames);
140
141 if (m_referenceOutputAudio.getNumFrames() != blockSizeFrames)
142 m_referenceOutputAudio.setNumFrames (blockSizeFrames); // To partial block and back
143
144 m_referenceSignal->renderNextBlockWithDSPChain (m_referenceOutputAudio);
145 fullOutputBuffer.appendFrom (m_referenceOutputAudio);
146
147 offsetFrames += blockSizeFrames;
148 }
149
150 m_referenceOutputAudio = std::move (fullOutputBuffer);
151 }
152
153 // Sanity checks
154 hassert (m_referenceOutputAudio.getNumFrames() == observedOutputAudio.getNumFrames());
155 hassert (m_referenceOutputAudio.getNumChannels() == observedOutputAudio.getNumChannels());
156 hassert (floatsEqual (m_referenceOutputAudio.getSampleRateHz(), observedOutputAudio.getSampleRateHz()));
157
158 for (size_t channel = 0; channel < m_referenceOutputAudio.getNumChannels(); ++channel)
159 {
160 if (! this->appliesToChannel (channel))
161 continue;
162
163 for (size_t frame = 0; frame < m_referenceOutputAudio.getNumFrames(); ++frame)
164 {
165 if (notEqual (observedOutputAudio[channel][frame], m_referenceOutputAudio[channel][frame]))
166 {
167 m_failedFrame = frame;
168 m_failedChannel = (int) channel;
169 m_failedObservedValue = observedOutputAudio[channel][frame];
170 m_failedExpectedValue = m_referenceOutputAudio[channel][frame];
171 return false;
172 }
173 }
174 }
175
176 return true;
177 }
178
179 bool canOperatePerBlock() const override
180 {
181 return true;
182 }
183
184 bool supportsChannelLayout (size_t /* numInputChannels */, size_t numOutputChannels) const override
185 {
186 return m_referenceSignal->supportsNumChannelsWithDSPChain (numOutputChannels);
187 }
188
189 bool supportsSampleRate (double sampleRateHz) const override
190 {
191 return m_referenceSignal->supportsSampleRateWithDSPChain (sampleRateHz);
192 }
193
194 void reset() override
195 {
196 m_referenceSignal->resetWithDSPChain();
197 }
198
200 {
201 const SampleType m_differenceLinear = std::abs (m_failedExpectedValue - m_failedObservedValue);
202 std::stringstream stream;
203 stream << linPrecision << "Expected sample value: " << m_failedExpectedValue
204 << dbPrecision << " (" << ratioToDecibels (m_failedExpectedValue) << " dB)"
205 << linPrecision << ", difference: " << m_differenceLinear
206 << dbPrecision << " (" << ratioToDecibels (m_differenceLinear) << " dB)";
207
208 MatcherFailureDetails details;
209 details.frame = m_failedFrame;
210 details.channel = m_failedChannel;
211 details.description = stream.str();
212 return details;
213 }
214
215 void represent (std::ostream& stream) const override
216 {
217 stream << "EqualsTo (" << *m_referenceSignal
218 << linPrecision << ", " << m_toleranceLinear << ')';
219 }
220
221private:
222 std::unique_ptr<SignalBase<SampleType>> m_referenceSignal;
223 const SampleType m_toleranceLinear;
224
225 size_t m_maxBlockSizeFrames = 0;
226 AudioBuffer<SampleType> m_referenceOutputAudio;
227
228 size_t m_failedFrame = 0;
229 size_t m_failedChannel = 0;
230 SampleType m_failedObservedValue = (SampleType) 0;
231 SampleType m_failedExpectedValue = (SampleType) 0;
232
233 inline bool notEqual (SampleType x, SampleType y)
234 {
235 return std::abs (x - y) > m_toleranceLinear;
236 }
237};
238
240
241} // namespace hart
Contains audio-related artefacts useful for analysis by matchers.
Container for audio data.
Checks whether the audio is identical to some signal.
bool supportsSampleRate(double sampleRateHz) const override
Tells whether this Matcher supports given sample rate.
EqualsTo(std::unique_ptr< SignalBase< SampleType > > &&referenceSignal, double toleranceLinear=(SampleType) 1e-5)
Creates a matcher for a specific signal by transfering smart pointer details The reference signal can...
EqualsTo(SignalBase< SampleType > &&referenceSignal, double toleranceLinear=(SampleType) 1e-5)
Creates a matcher for a specific signal by moving it details The reference signal can be something si...
~EqualsTo() override=default
bool supportsChannelLayout(size_t, size_t numOutputChannels) const override
Tells the host whether this Matcher is capable of operating on audio with a specific number of channe...
void represent(std::ostream &stream) const override
Makes a text representation of this Matcher for test failure outputs.
EqualsTo & operator=(const EqualsTo &other)
bool match(AnalysisContext< SampleType > context) override
Tells the host if the piece of audio satisfies Matcher's condition or not.
EqualsTo(const SignalBase< SampleType > &referenceSignal, double toleranceLinear=(SampleType) 1e-5)
Creates a matcher for a specific signal by copying it details The reference signal can be something s...
EqualsTo(EqualsTo &&other) noexcept
void prepare(double sampleRateHz, size_t, size_t numOutputChannels, size_t maxBlockSizeFrames) override
Prepare for processing It is guaranteed that all subsequent process() calls will be in line with the ...
bool canOperatePerBlock() const override
Tells the host if it can operate on a block-by-block basis.
virtual MatcherFailureDetails getFailureDetails() const override
Returns a description of why the match has failed.
EqualsTo(const EqualsTo &other)
void reset() override
Resets the matcher to its initial state.
EqualsTo & operator=(EqualsTo &&other) noexcept
Base for audio matchers.
#define hassert(condition)
Triggers a HartAssertException if the condition is false
std::ostream & linPrecision(std::ostream &stream)
Sets number of decimal places for linear (sample) values.
std::ostream & dbPrecision(std::ostream &stream)
Sets number of decimal places for values in decibels.
#define HART_MATCHER_DECLARE_ALIASES_FOR(ClassName)
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.