This module contains two things
- A collection of C++ free functions for tests that take an
dsp::dsp::AudioBlock
or ajuce::AudioBuffer
. - Catch2 matchers using the free functions that make it convenient to do things
like
REQUIRE_THAT (result, isValidAudio())
The Catch2 matchers give detail on AudioBlocks when a test fails. This includes displaying summary stats and sparkline for any AudioBlock:
REQUIRE_THAT(myAudioBlock, isEqualTo (someOtherBlock))
with expansion:
Block is 1 channel, 480 samples, min -0.766242, max 0.289615, 100% filled
[0—⎻—x—⎼⎽_⎽⎼—]
is equal to
Block is 1 channel, 480 samples, min -1, max 1, 100% filled
[0—⎻⎺‾⎺⎻—x—⎼⎽_⎽⎼—]
REQUIRE_THAT (myAudioBlock, isEqualTo (someOtherBlock));
REQUIRE_THAT (myStdVector, isEqualTo<float> (myStdVector));
This compares each channel of the two blocks and confirms each sample is within a tolerance
of std::numeric_limits<float>::epsilon() * 100
. Don't @ me, but assuming you are doing things like multiplying a
number of floats together to arrive at the end result, you probably don't want this tolerance any tighter.
However, you can loosen or tighten to your desire by passing an absolute tolerance:
REQUIRE_THAT (myAudioBlock, isEqualTo (someOtherBlock), 0.0005f));
Note that you can also use this vector matcher with std::vector<SampleType>
. I needed this because Catch's built in
vector matcher Catch::Matchers::Approx<float>
doesn't handle small numbers around 0 very
well: catchorg/Catch2#2659
REQUIRE_THAT (myAudioBlock, isFilled);
REQUIRE_THAT (myAudioBuffer, isFilled);
Passes when the block is completely filled. No more than 1 consecutive zero is allowed.
REQUIRE_THAT (myAudioBlock, isFilledUntil(256));
REQUIRE_THAT (myAudioBuffer, isFilledUntil(256));
Passes when the block is filled (doesn't contain consecutive zeros) up to to and including sample 256.
REQUIRE_THAT (myAudioBlock, isFilledAfter(256));
REQUIRE_THAT (myAudioBuffer, isFilledAfter(256));
Passes if, starting with this sample, the block is filled and doesn't contain consecutive zeros.
If the sample specified is zero (eg, start of a sine wave) but there are no consecutive zeros, it'll pass.
Fails if the block ends at the sample specified.
REQUIRE_THAT (myAudioBlock, isFilledBetween(64, 128));
REQUIRE_THAT (myAudioBuffer, isFilledBetween(64, 128));
Passes when sample values 64 and 128 have a non zero value for all channels.
REQUIRE_THAT (myAudioBlock, isEmpty);
REQUIRE_THAT (myAudioBuffer, isEmpty);
The block only has 0s.
REQUIRE_THAT (myAudioBlock, isEmptyUntil(256));
REQUIRE_THAT (myAudioBuffer, isEmptyUntil(256));
Passes when the block has only zeros up to and including this sample number.
REQUIRE_THAT (myAudioBlock, isEmptyAfter(256));
REQUIRE_THAT (myAudioBuffer, isEmptyAfter(256));
Passes when the block only contains zeros after this sample number, or when the block ends at this point.
The matchers above call out to free functions test helpers (prepended with block
) which can be used seperately.
So for example you can do use the Catch2 matcher style:
REQUIRE_THAT (myAudioBlock, isFilledUntil(256));
Or the free function style:
REQUIRE (blockIsFilledUntil(myAudioBlock, 256);
REQUIRE (bufferIsFilledUntil(myAudioBuffer, 256);
Additionally, there are some other helpers:
Returns the peak absolute value (magnitude) from any channel in the block.
REQUIRE (maxMagnitude (myAudioBlock) <= Catch::Approx (1.0f));
REQUIRE (magnitudeOfFrequency (myAudioBlock, 440.f, sampleRate) < 0.005);
This one is my favorite.
Use this if you know the frequencies you expect to encounter in the AudioBlock. You'll need to pass it the sample rate you are using.
This uses frequency correlation and compares your AudioBlock against sine and cosine probes. It's essentially the same thing as what goes on under the hood with DFT, but for exactly the frequency you specify.
This is a much more accurate way to confirm the presence of a known frequency than FFT.
REQUIRE_THAT (normalized (myAudioBlock), isEqualTo(someOtherBlock));
Normalizes a block to a range of -1 to 1.
This is vector only.
REQUIRE_THAT (myStdVector, isBetween (0.0f, 1.0f));
There are various FFT related functions available which rely on creating an instance of the FFT class.
Tip: Prefer the magnitudeOfFrequency
helper if you know the frequency you are expecting and wish to somewhat
accurately confirm magnitude. FFT is inherently messy. You'll get better results when your expected frequencies are in
the middle of FFT bins.
However, you can still sorta sloppily check for the strongest frequency:
REQUIRE (FFT (myAudioBlock, 44100.0f).strongestFrequencyIs (200.f));
auto fft = FFT (myAudioBlock, 44100.f);
fft.printDebug(); // prints the bins
REQUIRE (fft.strongestFrequencyBin() == fft.frequencyBinFor (220.f));
auto fft = FFT (myAudioBlock, 44100.f);
REQUIRE_THAT (fft.strongFrequencyBins(), Catch::Matchers::Contains (fft.frequencyBinFor (220.f)));
Prerequisites:
- Catch2
- The melatonin_audio_sparklines JUCE module. It provides Catch2 with fancy descriptions of AudioBlocks when tests fail.
Set up with a git submodule tracking the main
branch:
git submodule add -b main https://github.com/sudara/melatonin_test_helpers modules/melatonin_test_helpers
git commit -m "Added melatonin_test_helpers submodule."
To update these test helpers, you can:
git submodule update --remote --merge modules/melatonin_test_helpers
If you use CMake, inform JUCE about the module in your CMakeLists.txt
:
juce_add_module("modules/melatonin_test_helpers")
And link your target against it (using PRIVATE
, as juce recommends for modules):
target_link_libraries(my_target PRIVATE melatonin_test_helpers)
Add the following to any .cpp you'd like to use the helpers:
#include "melatonin_test_helpers/melatonin_test_helpers.h"
// make your life easier while you are at it...
using namespace melatonin;
The repo has been renamed now that we support AudioBuffers
. You'll want to do 2 things:
git submodule set-url -- modules/melatonin_audio_block_test_helpers https://github.com/sudara/melatonin_test_helpers
- Rename it locally (this will update .gitmodules)
git mv modules/melatonin_audio_block_test_helpers modules/melatonin_test_helpers
- The matchers are the "new style" and require Catch2 v3.x.
- This is just a random assortment of things I actually use for my tests! I'm open to more things being added, submit a PR!
- It's possible some multi-channel, float/double or block/buffer variants are missing. Open a PR!
- Thanks to @chrhaase for improving module compatibility and performance.