HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_matcher.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm> // max()
4#include <memory>
5#include <string>
6
12#include "hart_utils.hpp" // make_unique(), HART_DEPRECATED()
13
14/// @defgroup Matchers Matchers
15/// @brief Check audio
16
17namespace hart
18{
19
20/// @brief Polymorphic base for all matchers
21/// @warning This class exists only for type erasure and polymorphism.
22/// Do NOT inherit custom matchers from this class directly.
23/// Inherit from @ref hart::Matcher instead.
24/// @ingroup Matchers
25template<typename SampleType>
27{
28public:
29 virtual ~MatcherBase() = default;
30
31 /// @brief Prepare for processing
32 /// It is guaranteed that all subsequent process() calls will be in line with the arguments received in this callback.
33 /// This callback is guaranteed to be called after canOperatePerBlock()
34 /// If any of the values supplied by this callback are not supported by the matcher, it is expected to act as if
35 /// the match has failed when match() gets called.
36 /// @param sampleRateHz sample rate at which the audio should be interpreted
37 /// @param numInputChannels Number of channels in the input (reference) audio
38 /// @param numOutputChannels Number of channels in the output (observed) audio
39 /// @param maxBlockSizeFrames Maximum block size in frames (samples)
40 virtual void prepare (double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t maxBlockSizeFrames) = 0;
41
42 /// @brief Tells the host if the piece of audio satisfies Matcher's condition or not
43 /// @details It is guaranteed to be called only after `prepare()`, or not be called at all.
44 /// It is guaranteed to be handed a pair of `AudioBuffer`s in line with values set by the last `prepare()` call.
45 /// If `canOperatePerBlock()` has returned `false`, this callback is guaranteed to be handed a full piece of
46 /// audio to check. Otherwise, it may still get a full piece of audio, or get data on a block-by-block basis.
47 /// @param inputAudio A piece of input audio. Some matchers may be based on a relationship between input and output,
48 /// rather than checking just the output audio. And in a lot of cases Matcher can ingore the input completely.
49 /// @param observedOutputAudio A piece of observed output audio to check
50 /// @returns `true` if the audio satisfies the Matcher's condition, `false` otherwise
51 /// @deprecated This method is deprecated. Switch to the one that uses AnalysisContext instead.
52 HART_DEPRECATED ("This method is deprecated. Switch to the one that uses AnalysisContext instead.")
53 virtual bool match (const AudioBuffer<SampleType>& inputAudio, const AudioBuffer<SampleType>& observedOutputAudio)
54 {
55 const AnalysisContext<SampleType> analysisContext (inputAudio, observedOutputAudio);
56 return match (analysisContext);
57 }
58
59 /// @brief Tells the host if the piece of audio satisfies Matcher's condition or not
60 /// @details It is guaranteed to be called only after `prepare()`, or not be called at all.
61 /// It is guaranteed to be handed a pair of `AudioBuffer`s in line with values set by the last `prepare()` call.
62 /// If `canOperatePerBlock()` has returned `false`, this callback is guaranteed to be handed a full piece of
63 /// audio to check. Otherwise, it may still get a full piece of audio, or get data on a block-by-block basis.
64 /// @param analysisContext Carries everything matcher should know about the input and output audio.
65 /// @returns `true` if the audio satisfies the Matcher's condition, `false` otherwise
66 virtual bool match (AnalysisContext<SampleType> analysisContext)
67 {
68 return match (analysisContext.inputAudio(), analysisContext.outputAudio());
69 }
70
71 /// @brief Tells the host if it can operate on a block-by-block basis
72 /// @details Some types of conditions absolutely require having a full piece of audio
73 /// to produce an appropriate response. For example, @ref hart::PeaksAt matcher.
74 /// Those types of matchers will return false on this callback.
75 /// Matcher is guaranteed to receive a full piece of audio if this callback has
76 /// returned \c false. Otherwise, it may receive audio either block-by-block
77 /// basis, or still get a full piece of audio, if the host decides to do so.
78 virtual bool canOperatePerBlock() const = 0;
79
80 /// @brief Resets the matcher to its initial state
81 virtual void reset() {}
82
83 /// @brief Returns a description of why the match has failed
84 /// @details It is guaranteed to be called strictly after calling match(), or not called at all
85 /// @note This method is a callback for the test host, so you probably don't need to call it
86 /// yourself ever. If you're making a custom matcher, use it to communicate the data with test host.
87 /// @retval MatcherFailureDetails::frame Index of frame at which the match has failed
88 /// @retval MatcherFailureDetails::channel Index of channel at which the failure was detected
89 /// @retval MatcherFailureDetails::description Readable description of why the match has failed.
90 /// Do not include the value of observed frame value or its timing in the description, as well as
91 /// any of values printed by represent(), as all of this will be added to the output anyway.
92 /// Also, query @ref hart::CLIConfig for number of displayed decimal places, whenever applicable.
93 /// @see MatcherFailureDetails
95
96 /// @brief Makes a text representation of this Matcher for test failure outputs.
97 /// @details It is strongly encouraged to follow python's
98 /// <a href="https://docs.python.org/3/reference/datamodel.html#object.__repr__" target="_blank">repr()</a>
99 /// conventions for returned text - basically, put something like "MyClass(value1, value2)" (with no quotes)
100 /// into the stream whenever possible, or "<Readable info in angled brackets>" otherwise.
101 /// Also, use built-in stream manipulators like @ref hart::dbPrecision wherever applicable.
102 /// Use @ref HART_DEFINE_GENERIC_REPRESENT() to get a basic implementation for this method.
103 /// @param[out] stream Output stream to write to
104 virtual void represent (std::ostream& stream) const = 0;
105
106 /// @brief Tells the host whether this Matcher is capable of operating on audio with a specific number of channels
107 /// @details It is guaranteed that the matcher will not receive unsupported number of channels in @ref match().
108 /// This method is guaranteed to be called at least once before @ref prepare()
109 /// @param numInputChannels Number of channels in input (reference) buffer that will need to be processed
110 /// @param numOutputChannels Number of channels in output (observed) buffer that will need to be processed
111 /// @return true if signal is capable of processing audio with requested number of channels, false otherwise
112 virtual bool supportsChannelLayout (size_t /* numInputChannels */, size_t /* numOutputChannels */) const { return true; }
113
114 /// @brief Tells whether this Matcher supports given sample rate
115 /// @details It is guaranteed to be called before @ref prepare()
116 /// @param sampleRateHz sample rate at which the audio will be presented
117 /// @return true if matcher is capable of processing audio with a given sample rate, false otherwise
118 virtual bool supportsSampleRate (double /* sampleRateHz */) const { return true; }
119
120 /// @brief Returns a smart pointer with a copy of this object
121 /// @return Copy of this object wrapped in a smart pointer
122 virtual std::unique_ptr<MatcherBase<SampleType>> copy() const = 0;
123
124 /// @brief Returns a smart pointer with a moved instance of this object
125 /// @return This object, moved and wrapped in a smart pointer
126 virtual std::unique_ptr<MatcherBase<SampleType>> move() = 0;
127
128protected:
130
131 /// @brief Indicates whether this matcher should check a specific channel
132 /// @details You might want to use it in the @ref match() callback.
133 /// See @ref atChannels(), @ref atChannel(), @ref atAllChannels(), @ref atAllChannelsExcept().
134 bool appliesToChannel (size_t channel)
135 {
136 return this->m_channelsToMatch[channel];
137 }
138
139 /// @brief Returns flags for the channels that the matcher should apply to
140 /// @details Handy to use with @ref Metrics. Guaranteed to have number of
141 /// channels equal to `max (numInputChannels, numOutputChannels)`.
143 {
144 return this->m_channelsToMatch;
145 }
146
147public:
148 /// @brief Prepare for processing, meant to be called by a host
149 /// @details For internal use by hosts
150 void prepareWithActiveChannels (double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t maxBlockSizeFrames)
151 {
152 m_channelsToMatch.resize (std::max (numInputChannels, numOutputChannels));
153 prepare (sampleRateHz, numInputChannels, numOutputChannels, maxBlockSizeFrames);
154 }
155
156 /// @brief Makes a text representation of this Matcher with optional "atChannels" appendix
157 /// @details For internal use by hosts
158 void representWithActiveChannels (std::ostream& stream) const
159 {
160 this->represent (stream);
161
163 return;
164
165 stream << ".atChannels (";
167 stream << ')';
168 }
169};
170
171/// @brief Base for audio matchers
172/// @details Those matchers get a piece of audio generated by some DSP effect and asked if if satisfies their condition or not
173/// You can inherit from this class to create a new matcher
174/// (note the <a href="https://en.cppreference.com/w/cpp/language/crtp.html">CRTP</a>
175/// in the template args here, it's important!):
176/// @code{.cpp}
177/// template<typename SampleType>
178/// class MyCustomMatcher: public Matcher<SampleType, MyCustomMatcher<SampleType>>
179/// {
180/// // ...
181/// };
182/// @endcode
183/// @tparam SampleType Type of data in the buffers to be checked, typically `float` or `double`.
184/// @tparam Derived Subclass for CRTP
185/// @ingroup Matchers
186template<typename SampleType, typename Derived>
188 public MatcherBase<SampleType>
189{
190
191public:
192 /// @brief Returns a smart pointer with a copy of this object
193 /// @return Copy of this object wrapped in a smart pointer
194 virtual std::unique_ptr<MatcherBase<SampleType>> copy() const override
195 {
196 return hart::make_unique<Derived> (static_cast<const Derived&> (*this));
197 }
198
199 /// @brief Returns a smart pointer with a moved instance of this object
200 /// @return This object, moved and wrapped in a smart pointer
201 virtual std::unique_ptr<MatcherBase<SampleType>> move() override
202 {
203 return hart::make_unique<Derived> (std::move (static_cast<Derived&> (*this)));
204 }
205
206 /// @brief Makes this matcher check only specific channels, and ignore the rest
207 /// @details If not set, the matcher applies to all channels by default.
208 /// If you call this method multiple times, only the last one will be applied.
209 /// To select only one channel, consider using @ref Matcher::atChannel() instead.
210 /// @param channelsToMatch List of channels this matcher should apply to,
211 /// e.g. `{0, 1}` or `{Channel::left, Channel::right}` for left and right channels only.
212 /// @see `hart::Channel`
213 Derived& atChannels (std::initializer_list<size_t> channelsToMatch) &
214 {
215 _atChannels (channelsToMatch);
216 return static_cast<Derived&> (*this);
217 }
218
219 /// @brief Makes this matcher check only specific channels, and ignore the rest
220 /// @details If not set, the matcher applies to all channels by default.
221 /// If you call this method multiple times, only the last one will be applied.
222 /// To select only one channel, consider using @ref Matcher::atChannel() instead.
223 /// @param channelsToMatch List of channels this matcher should apply to,
224 /// e.g. `{0, 1}` or `{Channel::left, Channel::right}` for left and right channels only.
225 /// @see `hart::Channel`
226 Derived&& atChannels (std::initializer_list<size_t> channelsToMatch) &&
227 {
228 _atChannels (channelsToMatch);
229 return static_cast<Derived&&> (*this);
230 }
231
232 /// @brief Makes this matcher check only one specific channel, and ignore the rest
233 /// @details If not set, the matcher applies to all channels by default.
234 /// If you call this method multiple times, only the last one will be applied.
235 /// To select multiple channels, use @ref Matcher::atChannels() instead.
236 /// @param channelToMatch Channel this matcher should apply to (zero-based),
237 /// e.g. `0` or `Channel::left` for left channel.
238 /// @note If not set, the matcher applies to all channels by default
239 /// @see `hart::Channel`
240 Derived& atChannel (size_t channelToMatch) &
241 {
242 _atChannel (channelToMatch);
243 return static_cast<Derived&> (*this);
244 }
245
246 /// @brief Makes this matcher check only one specific channel, and ignore the rest
247 /// @details If not set, the matcher applies to all channels by default.
248 /// If you call this method multiple times, only the last one will be applied.
249 /// To select multiple channels, use @ref Matcher::atChannels() instead.
250 /// @param channelToMatch Channel this matcher should apply to (zero-based),
251 /// e.g. `0` or `Channel::left` for left channel.
252 /// @note If not set, the matcher applies to all channels by default
253 /// @see `hart::Channel`
254 Derived&& atChannel (size_t channelToMatch) &&
255 {
256 _atChannel (channelToMatch);
257 return static_cast<Derived&&> (*this);
258 }
259
260 /// @brief Makes this matcher check all channels
261 /// @details This is the default setting anyway, so this method is only
262 /// for cases when you need to override previous `atChannel()`,
263 /// `atChannels()` or `atAllChannelsExcept()` calls.
264 Derived& atAllChannels() &
265 {
266 _atAllChannels();
267 return static_cast<Derived&> (*this);
268 }
269
270 /// @brief Makes this matcher check all channels
271 /// @details This is the default setting anyway, so this method is only
272 /// for cases when you need to override previous `atChannel()`,
273 /// `atChannels()` or `atAllChannelsExcept()` calls.
274 Derived&& atAllChannels() &&
275 {
276 _atAllChannels();
277 return static_cast<Derived&&> (*this);
278 }
279
280 /// @brief Makes this matcher check only specific channels, and ignore the rest
281 /// @details If not set, matcher checks all channels by default.
282 /// If you call this method multiple times, only the last one will be applied.
283 /// @param channelsToSkip List of channels this matcher should NOT check,
284 /// e.g. `{0, 1}` or `{Channel::left, Channel::right}` to skip left and right
285 /// channels, and match the rest
286 /// @see `hart::Channel`
287 Derived& atAllChannelsExcept (std::initializer_list<size_t> channelsToSkip) &
288 {
289 _atAllChannelsExcept (channelsToSkip);
290 return static_cast<Derived&> (*this);
291 }
292
293 /// @brief Makes this matcher check only specific channels, and ignore the rest
294 /// @details If not set, matcher checks all channels by default.
295 /// If you call this method multiple times, only the last one will be applied.
296 /// @param channelsToSkip List of channels this matcher should NOT check,
297 /// e.g. `{0, 1}` or `{Channel::left, Channel::right}` to skip left and right
298 /// channels, and match the rest
299 /// @see `hart::Channel`
300 Derived&& atAllChannelsExcept (std::initializer_list<size_t> channelsToSkip) &&
301 {
302 _atAllChannelsExcept (channelsToSkip);
303 return static_cast<Derived&&> (*this);
304 }
305
306private:
307 void _atChannels (std::initializer_list<size_t> channelsToMatch)
308 {
309 this->m_channelsToMatch.setAllTo (false);
310
311 for (size_t channel : channelsToMatch)
312 {
313 if (channel >= this->m_channelsToMatch.size())
314 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Channel exceeds max number of channels");
315
316 this->m_channelsToMatch[channel] = true;
317 }
318 }
319
320 void _atChannel (size_t channelToMatch)
321 {
322 if (channelToMatch >= this->m_channelsToMatch.size())
323 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Channel exceeds max number of channels");
324
325 this->m_channelsToMatch.setAllTo (false);
326 this->m_channelsToMatch[channelToMatch] = true;
327 }
328
329 void _atAllChannels()
330 {
331 this->m_channelsToMatch.setAllTo (true);
332 }
333
334 void _atAllChannelsExcept (std::initializer_list<size_t> channelsToSkip)
335 {
336 this->m_channelsToMatch.setAllTo (true);
337
338 for (size_t channel : channelsToSkip)
339 {
340 if (channel >= this->m_channelsToMatch.size())
341 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Channel exceeds max number of channels");
342
343 this->m_channelsToMatch[channel] = false;
344 }
345 }
346};
347
348/// @brief Prints readable text representation of the Matcher object into the I/O stream
349/// @relates Matcher
350/// @ingroup Matchers
351template <typename SampleType>
352inline std::ostream& operator<< (std::ostream& stream, const MatcherBase<SampleType>& matcher)
353{
354 matcher.representWithActiveChannels (stream);
355 return stream;
356}
357
358} // namespace hart
359
360/// @private
361#define HART_MATCHER_DECLARE_ALIASES_FOR(ClassName)
362 namespace aliases_float{
363 using ClassName = hart::ClassName<float>;
364 }
365 namespace aliases_double{
366 using ClassName = hart::ClassName<double>;
367 }
Contains audio-related artefacts useful for analysis by matchers.
Container for audio data.
A set of boolean flags mapped to each audio channel.
bool allTrue() const noexcept
Checks if all flags are set to true
ChannelFlags(bool defaultValues=true, size_t numChannels=m_maxChannels)
Creates a new channel flags object.
void resize(size_t newNumChannels)
Resizes the container.
void representAsInitializerList(std::ostream &stream) const
Makes text representation of itself as a initializer list of active channels.
Polymorphic base for all matchers.
bool appliesToChannel(size_t channel)
Indicates whether this matcher should check a specific channel.
virtual std::unique_ptr< MatcherBase< SampleType > > copy() const =0
Returns a smart pointer with a copy of this object.
void representWithActiveChannels(std::ostream &stream) const
Makes a text representation of this Matcher with optional "atChannels" appendix.
virtual void represent(std::ostream &stream) const =0
Makes a text representation of this Matcher for test failure outputs.
virtual bool canOperatePerBlock() const =0
Tells the host if it can operate on a block-by-block basis.
virtual bool supportsChannelLayout(size_t, size_t) const
Tells the host whether this Matcher is capable of operating on audio with a specific number of channe...
virtual void reset()
Resets the matcher to its initial state.
virtual MatcherFailureDetails getFailureDetails() const =0
Returns a description of why the match has failed.
void prepareWithActiveChannels(double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t maxBlockSizeFrames)
Prepare for processing, meant to be called by a host.
virtual bool supportsSampleRate(double) const
Tells whether this Matcher supports given sample rate.
const AudioBuffer< SampleType > & observedOutputAudio
virtual std::unique_ptr< MatcherBase< SampleType > > move()=0
Returns a smart pointer with a moved instance of this object.
ChannelFlags getChannelFlags() const
Returns flags for the channels that the matcher should apply to.
virtual void prepare(double sampleRateHz, size_t numInputChannels, size_t numOutputChannels, size_t maxBlockSizeFrames)=0
Prepare for processing It is guaranteed that all subsequent process() calls will be in line with the ...
virtual ~MatcherBase()=default
virtual bool match(AnalysisContext< SampleType > analysisContext)
Tells the host if the piece of audio satisfies Matcher's condition or not.
ChannelFlags m_channelsToMatch
Base for audio matchers.
Derived & atChannels(std::initializer_list< size_t > channelsToMatch) &
Makes this matcher check only specific channels, and ignore the rest.
Derived && atChannels(std::initializer_list< size_t > channelsToMatch) &&
Makes this matcher check only specific channels, and ignore the rest.
virtual std::unique_ptr< MatcherBase< SampleType > > copy() const override
Returns a smart pointer with a copy of this object.
Derived && atChannel(size_t channelToMatch) &&
Makes this matcher check only one specific channel, and ignore the rest.
Derived & atChannel(size_t channelToMatch) &
Makes this matcher check only one specific channel, and ignore the rest.
Derived & atAllChannelsExcept(std::initializer_list< size_t > channelsToSkip) &
Makes this matcher check only specific channels, and ignore the rest.
Derived & atAllChannels() &
Makes this matcher check all channels.
virtual std::unique_ptr< MatcherBase< SampleType > > move() override
Returns a smart pointer with a moved instance of this object.
Derived && atAllChannels() &&
Makes this matcher check all channels.
Derived && atAllChannelsExcept(std::initializer_list< size_t > channelsToSkip) &&
Makes this matcher check only specific channels, and ignore the rest.
Thrown when an inappropriate value is encountered.
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns otherwise.
#define HART_DEPRECATED(msg)