HART  0.1.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
8#include "hart_utils.hpp" // make_unique()
9
10/// @defgroup Matchers Matchers
11/// @brief Check audio
12
13namespace hart
14{
15
16/// @brief Base for audio matchers
17/// @details Those matchers get a piece of audio generated by some DSP effect and asked if if satisfies their condition or not
18/// @tparam SampleType Type of data in the buffers to be checked, typically ```float``` or ```double```.
19/// @ingroup Matchers
20template<typename SampleType>
22{
23public:
24 /// @brief Prepare for processing
25 /// It is guaranteed that all subsequent process() calls will be in line with the arguments received in this callback.
26 /// This callback is guaranteed to be called after canOperatePerBlock()
27 /// If any of the values supplied by this callback are not supported by the matcher, it is expected to act as if
28 /// the match has failed when match() gets called.
29 /// @param sampleRateHz sample rate at which the audio should be interpreted
30 /// @param numChannels Number of audio channels
31 /// @param maxBlockSizeFrames Maximum block size in frames (samples)
32 virtual void prepare (double sampleRateHz, size_t numChannels, size_t maxBlockSizeFrames) = 0;
33
34 /// @brief Tells the host if the piece of audio satisfies Matcher's condition or not
35 /// @details It is guaranteed to be called only after prepare(), or not be called at all.
36 /// It is guaranteed to be handed an AudioBuffer in line with values set by the last prepare() call.
37 /// If canOperatePerBlock() has returned \c false, this callback is guaranteed to be handed a full piece of
38 /// audio to check. Otherwise, it may still get a full piece of audio, or get data on a block-by-block basis.
39 /// @param observedAudio A piece of audio to check
40 /// @returns true if the audio satisfies the Matcher's condition, false otherwise
41 virtual bool match (const AudioBuffer<SampleType>& observedAudio) = 0;
42
43 /// @brief Tells the host is if can operate on a block-by-block basis
44 /// @details Some types of conditions absolutely require having a full piece of audio
45 /// to produce an appropriate responce. For example, @ref PeaksAt matcher.
46 /// Those types of matchers will return false on this callback.
47 /// Matcher is guaranteed to receive a full piece of audio if this callback has
48 /// returned \c false. Otherwise, it may receive audio either block-by-block
49 /// basis, or still get a full piece of audio, if the host decides to do so.
50 virtual bool canOperatePerBlock() = 0;
51
52 /// Resets the matcher to its initial state
53 virtual void reset() = 0;
54
55 /// @brief Returns a smart pointer with a copy of this object
56 /// @details Just put one of those two macros into your class body, and your @ref copy() and @ref move() are sorted:
57 /// - @ref HART_MATCHER_DEFINE_COPY_AND_MOVE() for movable and copyable classes
58 /// - @ref HART_MATCHER_FORBID_COPY_AND_MOVE for non-movable and non-copyable classes
59 ///
60 /// Read their description, and choose one that fits your class.
61 /// You can, of course, make your own implementation, but you're not supposed to, unless you're doing something obscure.
62 virtual std::unique_ptr<Matcher<SampleType>> copy() const = 0;
63
64 /// @brief Returns a smart pointer with a moved instance of this object
65 /// @details Just pick a macro to define it - see description for @ref copy() for details
66 virtual std::unique_ptr<Matcher<SampleType>> move() = 0;
67
68 /// @brief Returns a description of why the match has failed
69 /// @details It is guaranteed to be called strictly after calling match(), or not called at all
70 /// @note This method is a callback for the test host, so you probably don't need to call it
71 /// yourself ever. If you're making a custom matcher, use it to communicate the data with test host.
72 /// @retval MatcherFailureDetails::frame Index of frame at which the match has failed
73 /// @retval MatcherFailureDetails::channel Index of channel at which the failure was detected
74 /// @retval MatcherFailureDetails::description Readable description of why the match has failed.
75 /// Do not include the value of observed frame value or its timing in the description, as well as
76 /// any of values printed by represent(), as all of this will be added to the output anyway.
77 /// Also, query @ref CLIConfig for number of displayed decimal places, whenever applicable.
78 /// @see MatcherFailureDetails
80
81 /// @brief Makes a text representation of this Macther for test failure outputs.
82 /// @details It is strongly encouraged to follow python's
83 /// <a href="https://docs.python.org/3/reference/datamodel.html#object.__repr__" target="_blank">repr()</a>
84 /// conventions for returned text - basically, put something like "MyClass(value1, value2)" (with no quotes)
85 /// into the stream whenever possible, or "<Readable info in angled brackets>" otherwise.
86 /// Also, use built-in stream manipulators like @ref dbPrecision wherever applicable.
87 /// Use @ref HART_DEFINE_GENERIC_REPRESENT() to get a basic implementation for this method.
88 /// @param[out] stream Output stream to write to
89 virtual void represent (std::ostream& stream) const = 0;
90
91 /// @brief Destructor
92 virtual ~Matcher() = default;
93};
94
95/// @brief Prints readable text representation of the Matcher object into the I/O stream
96/// @relates Matcher
97/// @ingroup Matchers
98template <typename SampleType>
99inline std::ostream& operator<< (std::ostream& stream, const Matcher<SampleType>& dsp)
100{
101 dsp.represent (stream);
102 return stream;
103}
104
105/// @brief Defines @ref hart::Matcher::copy() and @ref hart::Matcher::move() methods
106/// @details Put this into your class body's ```public``` section if either is true:
107/// - Your class is trivially copyable and movable
108/// - You have your Rule Of Five methods explicitly defined in this class
109/// (see <a href="https://en.cppreference.com/w/cpp/language/rule_of_three.html" target="_blank">Rule Of Three/Five/Zero</a>)
110///
111/// If neither of those is true, or you're unsure, use @ref HART_MATCHER_FORBID_COPY_AND_MOVE instead
112///
113/// Despite returning a smart pointer to an abstract Matcher class, those two methods must construct
114/// an object of a specific class, hence the mandatory boilerplate methods - sorry!
115/// @param ClassName Name of your class
116/// @ingroup Matchers
117#define HART_MATCHER_DEFINE_COPY_AND_MOVE(ClassName)
118 std::unique_ptr<Matcher<SampleType>> copy() const override {
119 return hart::make_unique<ClassName> (*this);
120 }
121 std::unique_ptr<Matcher<SampleType>> move() override {
122 return hart::make_unique<ClassName> (std::move (*this));
123 }
124
125/// @brief Forbids @ref hart::Matcher::copy() and @ref hart::Matcher::move() methods
126/// @details Put this into your class body's ```public``` section if either is true:
127/// - Your class is not trivially copyable and movable
128/// - You don't want to trouble yourself with implementing move and copy semantics for your class
129///
130/// Otherwise, use @ref HART_MATCHER_DEFINE_COPY_AND_MOVE() instead.
131/// Obviously, you won't be able to pass your class to the host
132/// by reference, copy or explicit move, but you still can pass
133/// it wrapped into a smart pointer like so:
134/// ```cpp
135/// processAudioWith (hart::make_unique<MyDspType>()).withThis().withThat().process();
136/// ```
137/// But it's still better to get your move and copy semantics figured out - this is a
138/// perfect chance to stress-test your effect's resource management, among other things!
139/// @ingroup Matchers
140#define HART_MATCHER_FORBID_COPY_AND_MOVE
141 std::unique_ptr<Matcher<SampleType>> copy() const override {
142 static_assert (false, "This Matcher cannot be copied");
143 return nullptr;
144 }
145 std::unique_ptr<Matcher<SampleType>> move() override {
146 static_assert (false, "This Matcher cannot be moved");
147 return nullptr;
148 }
149
150
151} // namespace hart
152
153/// @private
154#define HART_MATCHER_DECLARE_ALIASES_FOR(ClassName)
155 namespace aliases_float{
156 using ClassName = hart::ClassName<float>;
157 }
158 namespace aliases_double{
159 using ClassName = hart::ClassName<double>;
160 }
Base for audio matchers.
virtual bool canOperatePerBlock()=0
Tells the host is if can operate on a block-by-block basis.
virtual ~Matcher()=default
Destructor.
virtual void reset()=0
Resets the matcher to its initial state.
virtual std::unique_ptr< Matcher< SampleType > > move()=0
Returns a smart pointer with a moved instance of this object.
virtual void represent(std::ostream &stream) const =0
Makes a text representation of this Macther 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 std::unique_ptr< Matcher< SampleType > > copy() const =0
Returns a smart pointer with a copy of this object.