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()
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 virtual bool match (const AudioBuffer<SampleType>& observedAudio) = 0;
47
48 /// @brief Tells the host if it can operate on a block-by-block basis
49 /// @details Some types of conditions absolutely require having a full piece of audio
50 /// to produce an appropriate response. For example, @ref hart::PeaksAt matcher.
51 /// Those types of matchers will return false on this callback.
52 /// Matcher is guaranteed to receive a full piece of audio if this callback has
53 /// returned \c false. Otherwise, it may receive audio either block-by-block
54 /// basis, or still get a full piece of audio, if the host decides to do so.
55 virtual bool canOperatePerBlock() = 0;
56
57 /// @brief Resets the matcher to its initial state
58 virtual void reset() = 0;
59
60 /// @brief Returns a description of why the match has failed
61 /// @details It is guaranteed to be called strictly after calling match(), or not called at all
62 /// @note This method is a callback for the test host, so you probably don't need to call it
63 /// yourself ever. If you're making a custom matcher, use it to communicate the data with test host.
64 /// @retval MatcherFailureDetails::frame Index of frame at which the match has failed
65 /// @retval MatcherFailureDetails::channel Index of channel at which the failure was detected
66 /// @retval MatcherFailureDetails::description Readable description of why the match has failed.
67 /// Do not include the value of observed frame value or its timing in the description, as well as
68 /// any of values printed by represent(), as all of this will be added to the output anyway.
69 /// Also, query @ref hart::CLIConfig for number of displayed decimal places, whenever applicable.
70 /// @see MatcherFailureDetails
72
73 /// @brief Makes a text representation of this Matcher for test failure outputs.
74 /// @details It is strongly encouraged to follow python's
75 /// <a href="https://docs.python.org/3/reference/datamodel.html#object.__repr__" target="_blank">repr()</a>
76 /// conventions for returned text - basically, put something like "MyClass(value1, value2)" (with no quotes)
77 /// into the stream whenever possible, or "<Readable info in angled brackets>" otherwise.
78 /// Also, use built-in stream manipulators like @ref hart::dbPrecision wherever applicable.
79 /// Use @ref HART_DEFINE_GENERIC_REPRESENT() to get a basic implementation for this method.
80 /// @param[out] stream Output stream to write to
81 virtual void represent (std::ostream& stream) const = 0;
82
83 /// @brief Returns a smart pointer with a copy of this object
84 /// @return Copy of this object wrapped in a smart pointer
85 virtual std::unique_ptr<MatcherBase<SampleType>> copy() const = 0;
86
87 /// @brief Returns a smart pointer with a moved instance of this object
88 /// @return This object, moved and wrapped in a smart pointer
89 virtual std::unique_ptr<MatcherBase<SampleType>> move() = 0;
90
91 /// @brief Makes a text representation of this Matcher with optional "atChannels" appendix
92 /// @details For internal use by hosts, don't override it in custom matchers.
93 virtual void representWithActiveChannels (std::ostream& stream) const = 0;
94};
95
96/// @brief Base for audio matchers
97/// @details Those matchers get a piece of audio generated by some DSP effect and asked if if satisfies their condition or not
98/// You can inherit from this class to create a new matcher
99/// (note the <a href="https://en.cppreference.com/w/cpp/language/crtp.html">CRTP</a>
100/// in the template args here, it's important!):
101/// @code{.cpp}
102/// template<typename SampleType>
103/// class MyCustomMatcher: public Matcher<SampleType, MyCustomMatcher<SampleType>>
104/// {
105/// // ...
106/// };
107/// @endcode
108/// @tparam SampleType Type of data in the buffers to be checked, typically `float` or `double`.
109/// @tparam Derived Subclass for CRTP
110/// @ingroup Matchers
111template<typename SampleType, typename Derived>
113 public MatcherBase<SampleType>
114{
115
116public:
117 /// @brief Returns a smart pointer with a copy of this object
118 /// @return Copy of this object wrapped in a smart pointer
119 virtual std::unique_ptr<MatcherBase<SampleType>> copy() const override
120 {
121 return hart::make_unique<Derived> (static_cast<const Derived&> (*this));
122 }
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() override
127 {
128 return hart::make_unique<Derived> (std::move (static_cast<Derived&> (*this)));
129 }
130
131 /// @brief Makes this matcher check only specific channels, and ignore the rest
132 /// @details If not set, the matcher applies to all channels by default.
133 /// If you call this method multiple times, only the last one will be applied.
134 /// To select only one channel, consider using @ref Matcher::atChannel() instead.
135 /// @param channelsToMatch List of channels this matcher should apply to,
136 /// e.g. `{0, 1}` or `{Channel::left, Channel::right}` for left and right channels only.
137 /// @see `hart::Channel`
138 Derived& atChannels (std::initializer_list<size_t> channelsToMatch)
139 {
141
142 for (size_t channel : channelsToMatch)
143 {
144 if (channel >= m_channelsToMatch.size())
145 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Channel exceeds max number of channels");
146
147 m_channelsToMatch[channel] = true;
148 }
149
150 return static_cast<Derived&> (*this);
151 }
152
153 /// @brief Makes this matcher check only one specific channel, 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 multiple channels, use @ref Matcher::atChannels() instead.
157 /// @param channelToMatch Channel this matcher should apply to (zero-based),
158 /// e.g. `0` or `Channel::left` for left channel.
159 /// @note If not set, the matcher applies to all channels by default
160 /// @see `hart::Channel`
161 Derived& atChannel (size_t channelToMatch)
162 {
163 if (channelToMatch >= m_channelsToMatch.size())
164 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Channel exceeds max number of channels");
165
167 m_channelsToMatch[channelToMatch] = true;
168
169 return static_cast<Derived&> (*this);
170 }
171
172 /// @brief Makes this matcher check all channels
173 /// @details This is the default setting anyway, so this method is only
174 /// for cases when you need to override previous @ref atChannel(),
175 /// @ref `atChannels()` or @ref atAllChannelsExcept() calls.
176 Derived& atAllChannels()
177 {
179 return static_cast<Derived&> (*this);
180 }
181
182 /// @brief Makes this matcher check only specific channels, and ignore the rest
183 /// @details If not set, matcher checks all channels by default.
184 /// If you call this method multiple times, only the last one will be applied.
185 /// @param channelsToSkip List of channels this matcher should NOT check,
186 /// e.g. `{0, 1}` or `{Channel::left, Channel::right}` to skip left and right
187 /// channels, and match the rest
188 /// @see `hart::Channel`
189 Derived& atAllChannelsExcept (std::initializer_list<size_t> channelsToSkip)
190 {
192
193 for (size_t channel : channelsToSkip)
194 {
195 if (channel >= m_channelsToMatch.size())
196 HART_THROW_OR_RETURN_VOID (hart::ValueError, "Channel exceeds max number of channels");
197
198 m_channelsToMatch[channel] = false;
199 }
200
201 return static_cast<Derived&> (*this);
202 }
203
204 // TODO: Move to MatcherBase together with m_channelsToMatch to make it non-virtual?
205 void representWithActiveChannels (std::ostream& stream) const override
206 {
207 this->represent (stream);
208
210 return;
211
212 stream << ".atChannels (";
214 stream << ')';
215 }
216
217protected:
218 // TODO: Resize m_channelsToMatch() in host's version of prepare() method
220
221 /// @brief Indicates whether this matcher should check a specific channel
222 /// @details You might want to use it in the @ref match() callback.
223 /// See @ref atChannels(), @ref atChannel(), @ref atAllChannels(), @ref atAllChannelsExcept().
224 bool appliesToChannel (size_t channel)
225 {
226 return m_channelsToMatch[channel];
227 }
228};
229
230/// @brief Prints readable text representation of the Matcher object into the I/O stream
231/// @relates Matcher
232/// @ingroup Matchers
233template <typename SampleType>
234inline std::ostream& operator<< (std::ostream& stream, const MatcherBase<SampleType>& matcher)
235{
236 matcher.representWithActiveChannels (stream);
237 return stream;
238}
239
240} // namespace hart
241
242/// @private
243#define HART_MATCHER_DECLARE_ALIASES_FOR(ClassName)
244 namespace aliases_float{
245 using ClassName = hart::ClassName<float>;
246 }
247 namespace aliases_double{
248 using ClassName = hart::ClassName<double>;
249 }
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 bool canOperatePerBlock()=0
Tells the host if it can operate on a block-by-block basis.
virtual std::unique_ptr< MatcherBase< SampleType > > copy() const =0
Returns a smart pointer with a copy of this object.
virtual void reset()=0
Resets the matcher to its initial state.
virtual void represent(std::ostream &stream) const =0
Makes a text representation of this Matcher for test failure outputs.
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 MatcherFailureDetails getFailureDetails() const =0
Returns a description of why the match has failed.
virtual bool match(const AudioBuffer< SampleType > &observedAudio)=0
Tells the host if the piece of audio satisfies Matcher's condition or not.
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
Base for audio matchers.
Derived & atChannel(size_t channelToMatch)
Makes this matcher check only one specific channel, and ignore the rest.
bool appliesToChannel(size_t channel)
Indicates whether this matcher should check a specific channel.
virtual std::unique_ptr< MatcherBase< SampleType > > copy() const override
Returns a smart pointer with a copy of this object.
Derived & atChannels(std::initializer_list< size_t > channelsToMatch)
Makes this matcher check only specific channels, and ignore the rest.
Derived & atAllChannelsExcept(std::initializer_list< size_t > channelsToSkip)
Makes this matcher check only specific channels, and ignore the rest.
void representWithActiveChannels(std::ostream &stream) const override
Makes a text representation of this Matcher with optional "atChannels" appendix.
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.
ChannelFlags m_channelsToMatch
#define HART_THROW_OR_RETURN_VOID(ExceptionType, message)