HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_matcher_function.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <functional>
4#include <ostream>
5
8#include "matchers/hart_matcher.hpp"
9
10namespace hart
11{
12
13/// @brief Matcher defined by a user-provided function
14/// @details This matcher allows defining custom matching logic using a callable object
15/// (e.g. a lambda, function, or functor), instead of creating a dedicated matcher class.
16/// The function can have one of the following signatures:
17/// @code
18/// Condition (const AudioBuffer<SampleType>& output);
19/// Condition (const AudioBuffer<SampleType>& input, const AudioBuffer<SampleType>& output);
20/// @endcode
21///
22/// Example:
23/// @code
24/// [] (const AudioBuffer<SampleType>& output) { return HART_LESS_THAN (crestFactorDb (output), 3_dB); }
25/// @endcode
26///
27/// Return value should be a hart::Condition object, which you can create using one of the built-in macros,
28/// like HART_FLOAT_EQUAL(), HART_LESS_THAN() etc. For the full list of available macros, see @ref Conditions.
29///
30/// The function may also return a regular boolean value, like so:
31/// @code
32/// [] (const AudioBuffer<SampleType>& output) { return crestFactorDb (output) < 3_dB; }
33/// @endcode
34///
35/// In this case result will still be implicitly converted to Condition. However, it is encouraged to use proper
36/// Condition macros, since returning a plain `bool` won't give you detailed traceback info if the test fails.
37///
38/// The matcher operates on the full rendered buffer and does not support
39/// per-block evaluation.
40/// @ingroup Matchers
41template<typename SampleType>
43 public Matcher<SampleType, MatcherFunction<SampleType>>
44{
45public:
46 /// @brief Constructs a matcher from a function that has access to the entire analysis context
47 /// @details This form is recommended for conditions based on `Spectrum` or `ImpulseResponse`,
48 /// as `AnalysisContext` can cache those computationally heavy containers.
49 /// @param matcherFunction A callable with signature:
50 /// @code
51 /// Condition (AnalysisContext<SampleType> analysisContext)
52 /// @endcode
53 /// It should return `true` if the output satisfies the expected condition, `false` otherwise
54 /// @param label Optional human-readable label used in failure reports
55 MatcherFunction (std::function <Condition (AnalysisContext<SampleType> analysisContext)> matcherFunction, const std::string& label = {}) :
56 m_matcherFunctionForAnalysisContext (std::move (matcherFunction)),
57 m_label (label)
58 {
59 }
60
61 /// @brief Creates a matcher from a function that compares input and output
62 /// @param matcherFunction A callable with signature:
63 /// @code
64 /// Condition (const AudioBuffer<SampleType>& input,
65 /// const AudioBuffer<SampleType>& output)
66 /// @endcode
67 /// It should return `true` if the output satisfies the expected condition, `false` otherwise
68 /// @param label Optional human-readable label used in failure reports
69 MatcherFunction (std::function <Condition (const AudioBuffer<SampleType>&, const AudioBuffer<SampleType>&)> matcherFunction, const std::string& label = {}) :
70 m_matcherFunctionForInputAndOutput (std::move (matcherFunction)),
71 m_label (label)
72 {
73 }
74
75 /// @brief Constructs a matcher from a function that inspects only the output
76 /// @param matcherFunction A callable with signature:
77 /// @code
78 /// Condition (const AudioBuffer<SampleType>& output)
79 /// @endcode
80 /// It should return `true` if the output satisfies the expected condition, `false` otherwise
81 /// @param label Optional human-readable label used in failure reports
82 MatcherFunction (std::function <Condition (const AudioBuffer<SampleType>&)> matcherFunction, const std::string& label = {}) :
83 m_matcherFunctionForOutputOnly (std::move (matcherFunction)),
84 m_label (label)
85 {
86 }
87
88 bool match (AnalysisContext<SampleType> context) override
89 {
90 if (m_matcherFunctionForOutputOnly != nullptr)
91 {
92 const AudioBuffer<SampleType>& observedOutputAudio = context.outputAudio();
93
94 m_condition = std::move (m_matcherFunctionForOutputOnly (observedOutputAudio));
95 return m_condition.getResult();
96 }
97
98 if (m_matcherFunctionForInputAndOutput != nullptr)
99 {
100 const AudioBuffer<SampleType>& inputAudio = context.inputAudio();
101 const AudioBuffer<SampleType>& observedOutputAudio = context.outputAudio();
102
103 m_condition = std::move (m_matcherFunctionForInputAndOutput (inputAudio, observedOutputAudio));
104 return m_condition.getResult();
105 }
106
107 if (m_matcherFunctionForAnalysisContext != nullptr)
108 {
109 m_condition = std::move (m_matcherFunctionForAnalysisContext (context));
110 return m_condition.getResult();
111 }
112
113 HART_THROW_OR_RETURN (hart::NullPointerError, "Matcher function is a nullptr!", false);
114 }
115
117 {
118 MatcherFailureDetails details;
119 details.frame = 0;
120 details.channel = 0;
121 std::ostringstream descriptionStream;
122
123 if (m_condition.hasDetailedMetadata())
124 {
125 hassert (m_condition.getLine() >= 0);
126 hassert (m_condition.getFile() != nullptr);
127 hassert (m_condition.getFile()[0] != '\0');
128
129 descriptionStream
130 << "Location: " << m_condition.getFile()
131 << ':' << m_condition.getLine()
132 << "\nCondition: ";
133 m_condition.representWithTokens (descriptionStream);
134 descriptionStream << "\nEvaluated: ";
135 m_condition.representWithStringRepresentations (descriptionStream);
136 }
137 else
138 {
139 descriptionStream << "Matcher function (" << (m_label.empty() ? "no label" : m_label) << ") has evaluated to false";
140 }
141
142 details.description = descriptionStream.str();
143 return details;
144 }
145
146 void represent (std::ostream& stream) const override
147 {
148 stream << "MatcherFunction (<function>, \"" << m_label << "\")";
149 }
150
151 bool canOperatePerBlock() const override { return false;}
152 void prepare (double /* sampleRateHz */, size_t /* numInputChannels */, size_t /* numOutputChannels */, size_t /* maxBlockSizeFrames */) override {}
153
154private:
155 const std::function <Condition (AnalysisContext<SampleType>&)> m_matcherFunctionForAnalysisContext = nullptr;
156 const std::function <Condition (const AudioBuffer<SampleType>&, const AudioBuffer<SampleType>&)> m_matcherFunctionForInputAndOutput = nullptr;
157 const std::function <Condition (const AudioBuffer<SampleType>&)> m_matcherFunctionForOutputOnly = nullptr;
158 const std::string m_label;
159 Condition m_condition;
160};
161
163
164} // namespace hart
Contains audio-related artefacts useful for analysis by matchers.
A class representing some condition.
Matcher defined by a user-provided function.
MatcherFunction(std::function< Condition(AnalysisContext< SampleType > analysisContext)> matcherFunction, const std::string &label={})
Constructs a matcher from a function that has access to the entire analysis context.
MatcherFunction(std::function< Condition(const AudioBuffer< SampleType > &, const AudioBuffer< SampleType > &)> matcherFunction, const std::string &label={})
Creates a matcher from a function that compares input and output.
void represent(std::ostream &stream) const override
Makes a text representation of this Matcher for test failure outputs.
void prepare(double, size_t, size_t, size_t) override
Prepare for processing It is guaranteed that all subsequent process() calls will be in line with the ...
bool match(AnalysisContext< SampleType > context) override
Tells the host if the piece of audio satisfies Matcher's condition or not.
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.
MatcherFunction(std::function< Condition(const AudioBuffer< SampleType > &)> matcherFunction, const std::string &label={})
Constructs a matcher from a function that inspects only the output.
Base for audio matchers.
Thrown when a nullptr could be handled gracefully.
#define hassert(condition)
Triggers a HartAssertException if the condition is false
#define HART_THROW_OR_RETURN(ExceptionType, message, returnValue)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns a specified ...
#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.