9#include "matchers/hart_matcher.hpp"
23template<
typename SampleType>
32 m_expectedFundamentalHz (expectedFundamentalHz),
33 m_toleranceCents (toleranceCents)
35 if (expectedFundamentalHz <= 0.0)
39 void prepare (
double sampleRateHz, size_t , size_t )
override
41 m_sampleRateHz = sampleRateHz;
46 const size_t numFrames = observedOutputAudio.getNumFrames();
51 if (!
this->m_channelsToMatch.anyTrue())
56 std::vector<
double> observedAudioMono (numFrames, 0.0);
57 const double numChannelsSelected =
static_cast<
double> (
this->m_channelsToMatch.numTrue());
59 for (size_t channel = 0; channel < observedOutputAudio.getNumChannels(); ++channel)
61 if (!
this->appliesToChannel (channel))
64 for (size_t frame = 0; frame < numFrames; ++frame)
65 observedAudioMono[frame] +=
static_cast<
double> (observedOutputAudio[channel][frame]) / numChannelsSelected;
71 while (fftSize < numFrames)
75 std::vector<std::complex<
double>> spectrum (fftSize, 0.0);
77 for (size_t i = 0; i < numFrames; ++i)
78 spectrum[i] = observedAudioMono[i];
80 calculateFFTInPlace (spectrum);
83 double maxPower = -1.0;
84 size_t strongestBin = 0;
86 for (size_t bin = 2; bin < fftSize / 2; ++bin)
88 const double power = std::norm (spectrum[bin]);
98 const double ym1 = std::abs (spectrum[strongestBin - 1]);
99 const double y0 = std::abs (spectrum[strongestBin]);
100 const double yp1 = std::abs (spectrum[strongestBin + 1]);
102 const double delta = 0.5 * (ym1 - yp1) / (ym1 - 2.0 * y0 + yp1 + 1e-30);
103 const double preciseBin =
static_cast<
double> (strongestBin) + delta;
105 m_observedHz = preciseBin * m_sampleRateHz /
static_cast<
double> (fftSize);
106 const double deviationCents = 1200.0 * std::log2 (m_observedHz / m_expectedFundamentalHz);
108 if (std::abs (deviationCents) > m_toleranceCents)
110 m_centsError = deviationCents;
126 std::stringstream stream;
127 stream <<
"Observed fundamental "
140 s <<
"FundamentalEquals ("
141 <<
hzPrecision << m_expectedFundamentalHz <<
"_Hz, "
146 const double m_expectedFundamentalHz;
147 const double m_toleranceCents;
148 double m_sampleRateHz = 44100.0;
149 double m_observedHz = 0.0;
150 double m_centsError = 0.0;
152 static void calculateFFTInPlace (std::vector<std::complex<
double>>& spectrum)
157 for (size_t temp = spectrum.size(); temp > 1; temp >>= 1)
160 for (size_t i = 0; i < spectrum.size(); ++i)
164 for (size_t j = 0; j < log2n; ++j)
165 if (i & (size_t (1) << j))
166 rev |= size_t (1) << (log2n - 1 - j);
169 std::swap (spectrum[i], spectrum[rev]);
173 for (size_t len = 2; len <= spectrum.size(); len <<= 1)
176 const std::complex<
double> wlen (std::cos (angleRadians), std::sin (angleRadians));
178 for (size_t i = 0; i < spectrum.size(); i += len)
180 std::complex<
double> w (1.0);
181 for (size_t j = 0; j < len / 2; ++j)
183 std::complex<
double> u = spectrum[i + j];
184 std::complex<
double> v = spectrum[i + j + len / 2] * w;
185 spectrum[i + j] = u + v;
186 spectrum[i + j + len / 2] = u - v;
Container for audio data.
Checks the fundamental frequency of the signal.
void represent(std::ostream &s) const override
Makes a text representation of this Matcher for test failure outputs.
bool match(const AudioBuffer< SampleType > &, const AudioBuffer< SampleType > &observedOutputAudio) override
Tells the host if the piece of audio satisfies Matcher's condition or not.
MatcherFailureDetails getFailureDetails() const override
Returns a description of why the match has failed.
FundamentalEquals(double expectedFundamentalHz, double toleranceCents=1.0)
Creates a matcher for a specific fundamental frequency.
void prepare(double sampleRateHz, size_t, size_t) override
Prepare for processing It is guaranteed that all subsequent process() calls will be in line with the ...
bool canOperatePerBlock() const override
Tells the host if it can operate on a block-by-block basis.
void reset() override
Resets the matcher to its initial state.
Thrown when an unexpected container size is encountered.
Thrown when an inappropriate value is encountered.
#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_THROW(ExceptionType, message)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message otherwise.
std::ostream & centsPrecision(std::ostream &stream)
Sets number of decimal places for values in cents (frequency deviation)
std::ostream & hzPrecision(std::ostream &stream)
Sets number of decimal places for values in hertz.
constexpr double twoPi
2 * pi
#define HART_MATCHER_DECLARE_ALIASES_FOR(ClassName)
Details about matcher failure.
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.