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