From 2d31ea054395f555b1f8021cdf409c7c44bb79c8 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Fri, 28 Jan 2022 18:04:30 +0000 Subject: [PATCH 01/14] Working port of SC NoveltySlice tests --- tests/CMakeLists.txt | 10 + .../public/TestNoveltySegmentation.cpp | 355 ++++++++++++++++++ tests/test_signals/CMakeLists.txt | 6 + tests/test_signals/Signals.cpp | 98 +++++ tests/test_signals/Signals.hpp | 54 +++ 5 files changed, 523 insertions(+) create mode 100644 tests/algorithms/public/TestNoveltySegmentation.cpp create mode 100644 tests/test_signals/CMakeLists.txt create mode 100644 tests/test_signals/Signals.cpp create mode 100644 tests/test_signals/Signals.hpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c0f3e3f32..79ac84d95 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required (VERSION 3.11) ###### Utils +add_subdirectory(test_signals) ##### Assert Death Testing @@ -131,6 +132,11 @@ add_test_executable(TestFluidSource clients/common/TestFluidSource.cpp) add_test_executable(TestFluidSink clients/common/TestFluidSink.cpp) add_test_executable(TestBufferedProcess clients/common/TestBufferedProcess.cpp) +add_test_executable(TestNoveltySeg algorithms/public/TestNoveltySegmentation.cpp) + + +target_link_libraries(TestNoveltySeg PRIVATE TestSignals) + include(CTest) include(Catch) @@ -144,6 +150,10 @@ catch_discover_tests(TestFluidTensorDeath catch_discover_tests(TestFluidTensorView WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidTensorSupport WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidDataSet WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") +catch_discover_tests(TestNoveltySeg + EXTRA_ARGS --audio=${CMAKE_CURRENT_SOURCE_DIR}/../AudioFiles + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" +) catch_discover_tests(TestFluidSource WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSink WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/tests/algorithms/public/TestNoveltySegmentation.cpp b/tests/algorithms/public/TestNoveltySegmentation.cpp new file mode 100644 index 000000000..a348ede87 --- /dev/null +++ b/tests/algorithms/public/TestNoveltySegmentation.cpp @@ -0,0 +1,355 @@ +//#define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_RUNNER +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +using fluid::Slice; +using fluid::FluidTensor; +using fluid::FluidTensorView; +using fluid::algorithm::STFT; +using fluid::algorithm::NoveltySegmentation; + + +static std::string audio_path; + +struct Params{ + fluid::index window; + fluid::index hop; + fluid::index fft; + fluid::index minSlice; + fluid::index kernel; + fluid::index filter; + fluid::index dims; + double threshold; +}; + + +namespace fluid { + +template +std::vector NoveltyTestHarness(FluidTensorView testSignal, Params p, F&& f) +{ + + // NB: This has a lot of arcana about padding amounts and adjustments to get the same results as the equivalent + // tests already implemented in SC. However, I think the latency calculation in NoveltySlice, and the padding assumptions + // in NRTWrapper could do with another look, as I think we can get closer than we do + + const index filt = p.filter % 2 ? p.filter + 1 : p.filter; + const index halfWindow = p.window;// >> 1; + const index padding = p.hop * (((p.kernel + 1) >> 1) + (filt >> 1)); + FluidTensor padded(p.window + halfWindow + padding + testSignal.size()); + padded.fill(0); + padded(Slice(halfWindow,testSignal.size())) = testSignal; + const fluid::index nHops = std::floor((padded.size() - p.window)/p.hop); + + auto slicer = NoveltySegmentation(p.kernel, p.filter); + slicer.init(p.kernel,p.filter,p.dims); // sigh + + std::vector spikePositions; + + for(index i = 0; i < nHops; ++i) + { + auto input = f(padded(Slice(i * p.hop, p.window))); + if(slicer.processFrame(input,p.threshold,p.minSlice) > 0){ + spikePositions.push_back((i * p.hop) - padding - p.hop); + } + } + + //This reproduces what the NRT wrapper does (and hence the result that the existing test in SC sees). I'm dubious that + // it really ought to be needed though. I think we're adjusting the latency by a hop too much + std::transform(spikePositions.begin(),spikePositions.end(), spikePositions.begin(), [&p](index x){ + return std::max(0,x); + }); + + spikePositions.erase(std::unique(spikePositions.begin(),spikePositions.end()),spikePositions.end()); + + return spikePositions; +} +} + +std::vector NoveltySTFTTest(fluid::FluidTensorView testSignal, Params p) +{ + FluidTensor,1> stftFrame(p.dims); + FluidTensor magnitudes(p.dims); + + auto stft = STFT{p.window, p.fft, p.hop}; + + auto makeInput = [&stft,&stftFrame,&magnitudes](auto source) + { + stft.processFrame(source, stftFrame); + stft.magnitude(stftFrame, magnitudes); + return fluid::FluidTensorView(magnitudes); + }; + + return NoveltyTestHarness(testSignal, p, makeInput); +} + + +std::vector NoveltyMFCCTest(fluid::FluidTensorView testSignal, Params p) +{ + FluidTensor,1> stftFrame((p.fft / 2) + 1); + FluidTensor magnitudes((p.fft / 2) + 1); + FluidTensor melFrame(40); + FluidTensor mfccFrame(13); + + auto stft = STFT{p.window, p.fft, p.hop}; + auto mels = fluid::algorithm::MelBands(40,p.fft); + auto dct = fluid::algorithm::DCT(40,13); + + //The NoveltySliceClient inits mels only up to 2k, which I'm not is correct + mels.init(20, 2000, 40, (p.fft / 2) + 1, 44100, p.window); + dct.init(40, 13); + auto makeInput = [&stft,&mels,&dct,&stftFrame,&magnitudes,&melFrame,&mfccFrame](auto source) + { + stft.processFrame(source, stftFrame); + stft.magnitude(stftFrame, magnitudes); + mels.processFrame(magnitudes, melFrame, false, false, true); + dct.processFrame(melFrame, mfccFrame); + return fluid::FluidTensorView(mfccFrame); + }; + + return NoveltyTestHarness(testSignal, p, makeInput); +} + +std::vector NoveltyPitchTest(fluid::FluidTensorView testSignal, Params p) +{ + FluidTensor,1> stftFrame((p.fft / 2) + 1); + FluidTensor magnitudes((p.fft / 2) + 1); + FluidTensor pitchFrame(2); + + auto stft = STFT{p.window, p.fft, p.hop}; + auto pitch = fluid::algorithm::YINFFT(); + + auto makeInput = [&stft,&pitch,&stftFrame,&magnitudes,&pitchFrame](auto source) + { + stft.processFrame(source, stftFrame); + stft.magnitude(stftFrame, magnitudes); + pitch.processFrame(magnitudes, pitchFrame, 20, 5000, 44100); + return fluid::FluidTensorView(pitchFrame); + }; + + return NoveltyTestHarness(testSignal, p, makeInput); +} + +std::vector NoveltyLoudnessTest(fluid::FluidTensorView testSignal, Params p) +{ + FluidTensor loudnessFrame(2); + + auto loudness = fluid::algorithm::Loudness(p.fft); + loudness.init(p.window, 44100); + + auto makeInput = [&loudness,&loudnessFrame](auto source) + { + loudness.processFrame(source, loudnessFrame, true, true); + return fluid::FluidTensorView(loudnessFrame); + }; + + return NoveltyTestHarness(testSignal, p, makeInput); +} + +TEST_CASE("NoveltySegmentation will segment on clicks with some predictability","[Novelty][slicers]"){ + + using fluid::index; + + auto testSignal = fluid::testsignals::stereoImpulses(); + + Params p; + p.window = 128; + p.fft = 128; + p.hop = 64; + p.threshold = 0.5; + p.minSlice = 2; + p.kernel = 3; + p.filter = 1; + p.dims = (p.fft/2) + 1; + + FluidTensor monoInput(testSignal.cols()); + monoInput = testSignal.row(0); + monoInput.apply(testSignal.row(1),[](double& x, double y){ + x += y; + }); + + std::vector spikePositions = NoveltySTFTTest(monoInput, p); + const std::vector expected{1000,12025,23051,34076}; + + auto matcher = Catch::Matchers::Approx(expected); + index margin = 128; + matcher.margin(margin); + + REQUIRE(spikePositions.size() == 4); + REQUIRE_THAT(spikePositions, matcher); +} + +TEST_CASE("NoveltySegmentation will segment sine bursts STFT mags accurately","[Novelty][slicers]"){ + using fluid::index; + Params p; + p.window = 512; + p.fft = 1024; + p.hop = 256; + p.threshold = 0.38; + p.minSlice = 4; + p.kernel = 3; + p.filter = 1; + p.dims = (p.fft/2) + 1; + + auto testSignal = fluid::testsignals::sharpSines(); + std::vector spikePositions = NoveltySTFTTest(testSignal, p); + const std::vector expected{512, 11008, 22016, 33024}; + REQUIRE(spikePositions.size() == 4); + REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); +} + +TEST_CASE("NoveltySegmentation will do something predictable with a smooth AM sine","[Novelty][slicers]"){ + using fluid::index; + Params p; + p.window = 512; + p.fft = 1024; + p.hop = 256; + p.threshold = 0.34; + p.minSlice = 30; + p.kernel = 3; + p.filter = 1; + p.dims = (p.fft/2) + 1; + + auto testSignal = fluid::testsignals::smoothSine(); + std::vector spikePositions = NoveltySTFTTest(testSignal, p); + const std::vector expected{0, 22016}; + REQUIRE(spikePositions.size() == 2); + REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); +} + +TEST_CASE("NoveltySegmentation behaves with different filter sizes","[Novelty][slicers]"){ + + using fluid::index; + + struct Settings{ + index filterSize; + std::vector expected; + }; + + auto settings = GENERATE( + Settings{1,{0, 292352, 558592, 563712, 617984, 669696, 722432, 774656, 826368, 973824, 1000960}}, + Settings{4,{0, 292352, 564224, 617984, 670208, 722944, 774656, 826880, 974848, 1000960 }}, + Settings{12,{512, 292352, 564224, 617984, 723456, 774656, 827392, 1000960 }} + ); + + Params p; + p.window = 1024; + p.fft = 1024; + p.hop = 512; + p.threshold = 0.1; + p.minSlice = 2; + p.kernel = 31; + p.filter = settings.filterSize; + p.dims = (p.fft/2) + 1; + + auto testSignal = fluid::testsignals::guitarStrums(audio_path); + std::vector spikePositions = NoveltySTFTTest(testSignal.row(0), p); + CHECK(spikePositions.size() == settings.expected.size()); + REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(settings.expected)); +} + +TEST_CASE("NoveltySegmentation works with MFCC feature","[Novelty][slicers]"){ + + using fluid::index; + + std::vector expected{320, 34880, 105856, 117504, 179200, 186496, 205248, 223936, 238208, 256448, 346944, 352512, 368512, 401088, 414016, 455424, 465600, 481728, 494784, 512640}; + + Params p; + p.window = 2048; + p.fft = 2048; + p.hop = 64; + p.threshold = 0.6; + p.minSlice = 50; + p.kernel = 17; + p.filter = 5; + p.dims = 13; + + auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + std::vector spikePositions = NoveltyMFCCTest(testSignal.row(0), p); + CHECK(spikePositions.size() == expected.size()); + REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); +} + +TEST_CASE("NoveltySegmentation works with pitch feature","[Novelty][slicers]"){ + + using fluid::index; + + std::vector expected{128, 34880, 47360, 145280, 181888, 186496, 191040, 195648, 200320, 204928, 230976, 266880, 349056, 354944, 358784, 362688, 367552, 371456, 375360, 414080, 425728, 465600, 471616, 481664, 487744, 492992}; + + Params p; + p.window = 2048; + p.fft = 2048; + p.hop = 64; + p.threshold = 0.2; + p.minSlice = 50; + p.kernel = 9; + p.filter = 5; + p.dims = 2; + + auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + std::vector spikePositions = NoveltyPitchTest(testSignal.row(0), p); + CHECK(spikePositions.size() == expected.size()); + REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); +} + +TEST_CASE("NoveltySegmentation works with loudness feature","[Novelty][slicers]"){ + + using fluid::index; + + std::vector expected{0, 19008, 24640, 34624, 58240, 117696, 122048, 179392, 229376, 256832, 260288, 265536, 287488, 306752, 335616, 401280, 413888, 464896, 471936, 477184, 483456, 488064, 493376, 513664}; + + Params p; + p.window = 2048; + p.fft = 2048; + p.hop = 64; + p.threshold = 0.0145; + p.minSlice = 50; + p.kernel = 17; + p.filter = 5; + p.dims = 2; + + auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + std::vector spikePositions = NoveltyLoudnessTest(testSignal.row(0), p); + CHECK(spikePositions.size() == expected.size()); + REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); +} + +int main( int argc, char* argv[] ) { + Catch::Session session; // There must be exactly one instance + + // Build a new parser on top of Catch2's + using namespace Catch::clara; + auto cli + = session.cli() // Get Catch2's command line parser + | Opt( audio_path, "path to demo audio files" ) // bind variable to a new option, with a hint string + ["-A"]["--audio"] // the option names it will respond to + ("path to audio"); // description string for the help output + + // Now pass the new composite back to Catch2 so it uses that + session.cli( cli ); + + // Let Catch2 (using Clara) parse the command line + int returnCode = session.applyCommandLine( argc, argv ); + if( returnCode != 0 ) // Indicates a command line error + return returnCode; + + + return session.run(); +} diff --git a/tests/test_signals/CMakeLists.txt b/tests/test_signals/CMakeLists.txt new file mode 100644 index 000000000..dfbf597a4 --- /dev/null +++ b/tests/test_signals/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required (VERSION 3.11) + +add_library(TestSignals STATIC Signals.cpp) +target_include_directories(TestSignals PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(TestSignals PRIVATE FLUID_DECOMPOSITION HISSTools_AudioFile) +target_compile_features(TestSignals PUBLIC cxx_std_14) diff --git a/tests/test_signals/Signals.cpp b/tests/test_signals/Signals.cpp new file mode 100644 index 000000000..b393f8eee --- /dev/null +++ b/tests/test_signals/Signals.cpp @@ -0,0 +1,98 @@ +#include "Signals.hpp" +#include + +#include + +#include +#include +#include +#include + +namespace fluid { +namespace testsignals { + +constexpr size_t fs = 44100; + +std::vector oneImpulse() +{ + std::vector oneImpulse(fs); + std::fill(oneImpulse.begin(), oneImpulse.end(), 0); + oneImpulse[(fs / 2) - 1] = 1.0; + return oneImpulse; +} + +FluidTensor stereoImpulses() +{ + FluidTensor impulses(2, fs); + impulses.fill(0); + // 1000.0, 12025.0, 23051.0, 34076.0 + impulses.row(0)[1000] = 1; + impulses.row(0)[23051] = 1; + + impulses.row(1)[12025] = 1; + impulses.row(1)[34076] = 1; + return impulses; +} + +FluidTensor sharpSines() +{ + FluidTensor sharpSines(fs); + std::generate(sharpSines.begin() + 1000, sharpSines.end(), + [i = 1000]() mutable { + constexpr double freq = 640; + double sinx = sin(2 * M_PI * i * freq / fs - 1); + constexpr double nPeriods = 4; + double phasor = + (((fs - 1 - i) % index(fs / nPeriods)) / (fs / nPeriods)); + i++; + return sinx * phasor; + }); + return sharpSines; +} + +FluidTensor smoothSine() +{ + FluidTensor smoothSine(fs); + std::generate(smoothSine.begin(), smoothSine .end(), [i = 0]() mutable { + double res = sin(2 * M_PI * 320 * i / fs) * fabs(sin(2 * M_PI * i / fs)); + i++; + return res; + }); + return smoothSine; +} + +FluidTensor load(const std::string& audio_path, const std::string& file) +{ + HISSTools::IAudioFile f(audio_path + "/" + file); + auto e = f.getErrors(); + if(e.size()) + { + throw std::runtime_error(HISSTools::BaseAudioFile::getErrorString(e[0])); + } + + FluidTensor data(f.getChannels(),f.getFrames()); + f.readInterleaved(data.data(), f.getFrames()); + e = f.getErrors(); + if(e.size()) + { + throw std::runtime_error(HISSTools::BaseAudioFile::getErrorString(e[0])); + } + + return data; +} + +FluidTensor guitarStrums(const std::string& audio_path) +{ + static std::string file{"Tremblay-AaS-AcousticStrums-M.wav"}; + return load(audio_path,file); +} + +FluidTensor eurorackSynth(const std::string& audio_path) +{ + static std::string file{"Tremblay-AaS-SynthTwoVoices-M.wav"}; + return load(audio_path,file); +} + + +} // namespace testsignals +} // namespace fluid diff --git a/tests/test_signals/Signals.hpp b/tests/test_signals/Signals.hpp new file mode 100644 index 000000000..2b1961974 --- /dev/null +++ b/tests/test_signals/Signals.hpp @@ -0,0 +1,54 @@ +#include + +// #include + +#include +#include +#include + +namespace fluid { +namespace testsignals { + +// constexpr size_t fs = 44100; + +std::vector oneImpulse(); +FluidTensor stereoImpulses(); +FluidTensor sharpSines(); +FluidTensor smoothSine(); +FluidTensor guitarStrums(const std::string& audio_path); +FluidTensor eurorackSynth(const std::string& audio_path); + +// std::fill(oneImpulse.begin(), oneImpulse.end(), 0); +// oneImpulse[(fs / 2) - 1] = 1.0; + +// std::array sharpSines; +// std::generate(sharpSines.begin() + 1000, sharpSines.end(), [i = 1000]() mutable { +// constexpr double freq = 640; +// double sinx = sin(2 * M_PI * i * freq / fs - 1); +// +// constexpr double nPeriods = 4; +// double phasor = (((fs - 1 - i) % (fs / nPeriods)) / (fs / nPeriods)); +// i++; +// return sinx * phasor; +// }); +// +// std::array smoothSine; +// std::generate(smoothSines.begin(), smoothSines.end(), [i = 0]() mutable +// { +// double res = sin(2 * M_PI * 320 * i / fs) * fabs(sin(2 * M_PI * i / fs )); +// i++; +// return res; +// }); +// +// FluidTensor impulses(2,fs); +// impulses.fill(0); +// +// impulses.row(0)[1000] = 1; +// impulses.row(0)[23051] = 1; +// +// impulses.row(1)[11030] = 1; +// impules.row(1)[33080] = 1; + + +} // namespace testsignals +} // namespace flucoma From eecb3dc3f25a468f0a6ee83a537911a68d21bb52 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Fri, 28 Jan 2022 22:23:17 +0000 Subject: [PATCH 02/14] formatting and constifying --- .../public/TestNoveltySegmentation.cpp | 329 ++++++++++-------- 1 file changed, 187 insertions(+), 142 deletions(-) diff --git a/tests/algorithms/public/TestNoveltySegmentation.cpp b/tests/algorithms/public/TestNoveltySegmentation.cpp index a348ede87..834ac13ea 100644 --- a/tests/algorithms/public/TestNoveltySegmentation.cpp +++ b/tests/algorithms/public/TestNoveltySegmentation.cpp @@ -1,34 +1,34 @@ //#define CATCH_CONFIG_MAIN #define CATCH_CONFIG_RUNNER -#include +//#include +#include +#include +#include #include #include -#include -#include #include -#include -#include -#include -#include +#include #include - +#include +#include +#include #include +#include +#include #include -#include -#include -#include -using fluid::Slice; using fluid::FluidTensor; using fluid::FluidTensorView; -using fluid::algorithm::STFT; -using fluid::algorithm::NoveltySegmentation; +using fluid::Slice; +using fluid::algorithm::NoveltySegmentation; +using fluid::algorithm::STFT; static std::string audio_path; -struct Params{ +struct Params +{ fluid::index window; fluid::index hop; fluid::index fft; @@ -36,139 +36,153 @@ struct Params{ fluid::index kernel; fluid::index filter; fluid::index dims; - double threshold; + double threshold; }; namespace fluid { - -template -std::vector NoveltyTestHarness(FluidTensorView testSignal, Params p, F&& f) + +template +std::vector NoveltyTestHarness(FluidTensorView testSignal, + Params p, F&& f) { - // NB: This has a lot of arcana about padding amounts and adjustments to get the same results as the equivalent - // tests already implemented in SC. However, I think the latency calculation in NoveltySlice, and the padding assumptions - // in NRTWrapper could do with another look, as I think we can get closer than we do + // NB: This has a lot of arcana about padding amounts and adjustments to get + // the same results as the equivalent tests already implemented in SC. + // However, I think the latency calculation in NoveltySlice, and the padding + // assumptions in NRTWrapper could do with another look, as I think we can get + // closer than we do const index filt = p.filter % 2 ? p.filter + 1 : p.filter; - const index halfWindow = p.window;// >> 1; + const index halfWindow = p.window; // >> 1; const index padding = p.hop * (((p.kernel + 1) >> 1) + (filt >> 1)); - FluidTensor padded(p.window + halfWindow + padding + testSignal.size()); + FluidTensor padded(p.window + halfWindow + padding + + testSignal.size()); padded.fill(0); - padded(Slice(halfWindow,testSignal.size())) = testSignal; - const fluid::index nHops = std::floor((padded.size() - p.window)/p.hop); - + padded(Slice(halfWindow, testSignal.size())) = testSignal; + const fluid::index nHops = + std::floor((padded.size() - p.window) / p.hop); + auto slicer = NoveltySegmentation(p.kernel, p.filter); - slicer.init(p.kernel,p.filter,p.dims); // sigh - + slicer.init(p.kernel, p.filter, p.dims); // sigh + std::vector spikePositions; - - for(index i = 0; i < nHops; ++i) + + for (index i = 0; i < nHops; ++i) { auto input = f(padded(Slice(i * p.hop, p.window))); - if(slicer.processFrame(input,p.threshold,p.minSlice) > 0){ + if (slicer.processFrame(input, p.threshold, p.minSlice) > 0) + { spikePositions.push_back((i * p.hop) - padding - p.hop); } } - - //This reproduces what the NRT wrapper does (and hence the result that the existing test in SC sees). I'm dubious that - // it really ought to be needed though. I think we're adjusting the latency by a hop too much - std::transform(spikePositions.begin(),spikePositions.end(), spikePositions.begin(), [&p](index x){ - return std::max(0,x); - }); - - spikePositions.erase(std::unique(spikePositions.begin(),spikePositions.end()),spikePositions.end()); - + + // This reproduces what the NRT wrapper does (and hence the result that the + // existing test in SC sees). I'm dubious that + // it really ought to be needed though. I think we're adjusting the latency + // by a hop too much + std::transform(spikePositions.begin(), spikePositions.end(), + spikePositions.begin(), + [&p](index x) { return std::max(0, x); }); + + spikePositions.erase( + std::unique(spikePositions.begin(), spikePositions.end()), + spikePositions.end()); + return spikePositions; } -} +} // namespace fluid -std::vector NoveltySTFTTest(fluid::FluidTensorView testSignal, Params p) +std::vector +NoveltySTFTTest(fluid::FluidTensorView testSignal, Params p) { - FluidTensor,1> stftFrame(p.dims); - FluidTensor magnitudes(p.dims); - + FluidTensor, 1> stftFrame(p.dims); + FluidTensor magnitudes(p.dims); + auto stft = STFT{p.window, p.fft, p.hop}; - - auto makeInput = [&stft,&stftFrame,&magnitudes](auto source) - { + + auto makeInput = [&stft, &stftFrame, &magnitudes](auto source) { stft.processFrame(source, stftFrame); stft.magnitude(stftFrame, magnitudes); return fluid::FluidTensorView(magnitudes); }; - + return NoveltyTestHarness(testSignal, p, makeInput); } -std::vector NoveltyMFCCTest(fluid::FluidTensorView testSignal, Params p) +std::vector +NoveltyMFCCTest(fluid::FluidTensorView testSignal, Params p) { - FluidTensor,1> stftFrame((p.fft / 2) + 1); - FluidTensor magnitudes((p.fft / 2) + 1); - FluidTensor melFrame(40); - FluidTensor mfccFrame(13); - + FluidTensor, 1> stftFrame((p.fft / 2) + 1); + FluidTensor magnitudes((p.fft / 2) + 1); + FluidTensor melFrame(40); + FluidTensor mfccFrame(13); + auto stft = STFT{p.window, p.fft, p.hop}; - auto mels = fluid::algorithm::MelBands(40,p.fft); - auto dct = fluid::algorithm::DCT(40,13); - - //The NoveltySliceClient inits mels only up to 2k, which I'm not is correct + auto mels = fluid::algorithm::MelBands(40, p.fft); + auto dct = fluid::algorithm::DCT(40, 13); + + // The NoveltySliceClient inits mels only up to 2k, which I'm not is correct mels.init(20, 2000, 40, (p.fft / 2) + 1, 44100, p.window); dct.init(40, 13); - auto makeInput = [&stft,&mels,&dct,&stftFrame,&magnitudes,&melFrame,&mfccFrame](auto source) - { + auto makeInput = [&stft, &mels, &dct, &stftFrame, &magnitudes, &melFrame, + &mfccFrame](auto source) { stft.processFrame(source, stftFrame); stft.magnitude(stftFrame, magnitudes); mels.processFrame(magnitudes, melFrame, false, false, true); dct.processFrame(melFrame, mfccFrame); return fluid::FluidTensorView(mfccFrame); }; - + return NoveltyTestHarness(testSignal, p, makeInput); } -std::vector NoveltyPitchTest(fluid::FluidTensorView testSignal, Params p) +std::vector +NoveltyPitchTest(fluid::FluidTensorView testSignal, Params p) { - FluidTensor,1> stftFrame((p.fft / 2) + 1); - FluidTensor magnitudes((p.fft / 2) + 1); - FluidTensor pitchFrame(2); - + FluidTensor, 1> stftFrame((p.fft / 2) + 1); + FluidTensor magnitudes((p.fft / 2) + 1); + FluidTensor pitchFrame(2); + auto stft = STFT{p.window, p.fft, p.hop}; auto pitch = fluid::algorithm::YINFFT(); - - auto makeInput = [&stft,&pitch,&stftFrame,&magnitudes,&pitchFrame](auto source) - { + + auto makeInput = [&stft, &pitch, &stftFrame, &magnitudes, + &pitchFrame](auto source) { stft.processFrame(source, stftFrame); stft.magnitude(stftFrame, magnitudes); pitch.processFrame(magnitudes, pitchFrame, 20, 5000, 44100); return fluid::FluidTensorView(pitchFrame); }; - + return NoveltyTestHarness(testSignal, p, makeInput); } -std::vector NoveltyLoudnessTest(fluid::FluidTensorView testSignal, Params p) +std::vector +NoveltyLoudnessTest(fluid::FluidTensorView testSignal, Params p) { - FluidTensor loudnessFrame(2); - + FluidTensor loudnessFrame(2); + auto loudness = fluid::algorithm::Loudness(p.fft); loudness.init(p.window, 44100); - auto makeInput = [&loudness,&loudnessFrame](auto source) - { + auto makeInput = [&loudness, &loudnessFrame](auto source) { loudness.processFrame(source, loudnessFrame, true, true); return fluid::FluidTensorView(loudnessFrame); }; - + return NoveltyTestHarness(testSignal, p, makeInput); } -TEST_CASE("NoveltySegmentation will segment on clicks with some predictability","[Novelty][slicers]"){ +TEST_CASE("NoveltySegmentation will segment on clicks with some predictability", + "[Novelty][slicers]") +{ using fluid::index; - + auto testSignal = fluid::testsignals::stereoImpulses(); - + Params p; p.window = 128; p.fft = 128; @@ -177,26 +191,27 @@ TEST_CASE("NoveltySegmentation will segment on clicks with some predictability", p.minSlice = 2; p.kernel = 3; p.filter = 1; - p.dims = (p.fft/2) + 1; - - FluidTensor monoInput(testSignal.cols()); + p.dims = (p.fft / 2) + 1; + + FluidTensor monoInput(testSignal.cols()); monoInput = testSignal.row(0); - monoInput.apply(testSignal.row(1),[](double& x, double y){ - x += y; - }); - - std::vector spikePositions = NoveltySTFTTest(monoInput, p); - const std::vector expected{1000,12025,23051,34076}; - - auto matcher = Catch::Matchers::Approx(expected); + monoInput.apply(testSignal.row(1), [](double& x, double y) { x += y; }); + + const std::vector spikePositions = NoveltySTFTTest(monoInput, p); + + const std::vector expected{1000, 12025, 23051, 34076}; + + auto matcher = Catch::Matchers::Approx(expected); index margin = 128; matcher.margin(margin); - + REQUIRE(spikePositions.size() == 4); REQUIRE_THAT(spikePositions, matcher); } -TEST_CASE("NoveltySegmentation will segment sine bursts STFT mags accurately","[Novelty][slicers]"){ +TEST_CASE("NoveltySegmentation will segment sine bursts STFT mags accurately", + "[Novelty][slicers]") +{ using fluid::index; Params p; p.window = 512; @@ -206,16 +221,21 @@ TEST_CASE("NoveltySegmentation will segment sine bursts STFT mags accurately","[ p.minSlice = 4; p.kernel = 3; p.filter = 1; - p.dims = (p.fft/2) + 1; - - auto testSignal = fluid::testsignals::sharpSines(); - std::vector spikePositions = NoveltySTFTTest(testSignal, p); + p.dims = (p.fft / 2) + 1; + + const auto testSignal = fluid::testsignals::sharpSines(); + + const std::vector spikePositions = NoveltySTFTTest(testSignal, p); + const std::vector expected{512, 11008, 22016, 33024}; REQUIRE(spikePositions.size() == 4); REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); } -TEST_CASE("NoveltySegmentation will do something predictable with a smooth AM sine","[Novelty][slicers]"){ +TEST_CASE( + "NoveltySegmentation will do something predictable with a smooth AM sine", + "[Novelty][slicers]") +{ using fluid::index; Params p; p.window = 512; @@ -225,10 +245,12 @@ TEST_CASE("NoveltySegmentation will do something predictable with a smooth AM si p.minSlice = 30; p.kernel = 3; p.filter = 1; - p.dims = (p.fft/2) + 1; - - auto testSignal = fluid::testsignals::smoothSine(); - std::vector spikePositions = NoveltySTFTTest(testSignal, p); + p.dims = (p.fft / 2) + 1; + + const auto testSignal = fluid::testsignals::smoothSine(); + + const std::vector spikePositions = NoveltySTFTTest(testSignal, p); + const std::vector expected{0, 22016}; REQUIRE(spikePositions.size() == 2); REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); @@ -238,17 +260,22 @@ TEST_CASE("NoveltySegmentation behaves with different filter sizes","[Novelty][s using fluid::index; - struct Settings{ - index filterSize; + struct Settings + { + index filterSize; std::vector expected; }; auto settings = GENERATE( - Settings{1,{0, 292352, 558592, 563712, 617984, 669696, 722432, 774656, 826368, 973824, 1000960}}, - Settings{4,{0, 292352, 564224, 617984, 670208, 722944, 774656, 826880, 974848, 1000960 }}, - Settings{12,{512, 292352, 564224, 617984, 723456, 774656, 827392, 1000960 }} - ); - + Settings{1, + {0, 292352, 558592, 563712, 617984, 669696, 722432, 774656, + 826368, 973824, 1000960}}, + Settings{4, + {0, 292352, 564224, 617984, 670208, 722944, 774656, 826880, + 974848, 1000960}}, + Settings{12, + {512, 292352, 564224, 617984, 723456, 774656, 827392, 1000960}}); + Params p; p.window = 1024; p.fft = 1024; @@ -257,10 +284,11 @@ TEST_CASE("NoveltySegmentation behaves with different filter sizes","[Novelty][s p.minSlice = 2; p.kernel = 31; p.filter = settings.filterSize; - p.dims = (p.fft/2) + 1; - - auto testSignal = fluid::testsignals::guitarStrums(audio_path); - std::vector spikePositions = NoveltySTFTTest(testSignal.row(0), p); + p.dims = (p.fft / 2) + 1; + + const auto testSignal = fluid::testsignals::guitarStrums(audio_path); + + const std::vector spikePositions = NoveltySTFTTest(testSignal.row(0), p); CHECK(spikePositions.size() == settings.expected.size()); REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(settings.expected)); } @@ -268,9 +296,12 @@ TEST_CASE("NoveltySegmentation behaves with different filter sizes","[Novelty][s TEST_CASE("NoveltySegmentation works with MFCC feature","[Novelty][slicers]"){ using fluid::index; - - std::vector expected{320, 34880, 105856, 117504, 179200, 186496, 205248, 223936, 238208, 256448, 346944, 352512, 368512, 401088, 414016, 455424, 465600, 481728, 494784, 512640}; - + + std::vector expected{320, 34880, 105856, 117504, 179200, + 186496, 205248, 223936, 238208, 256448, + 346944, 352512, 368512, 401088, 414016, + 455424, 465600, 481728, 494784, 512640}; + Params p; p.window = 2048; p.fft = 2048; @@ -280,19 +311,24 @@ TEST_CASE("NoveltySegmentation works with MFCC feature","[Novelty][slicers]"){ p.kernel = 17; p.filter = 5; p.dims = 13; - - auto testSignal = fluid::testsignals::eurorackSynth(audio_path); - std::vector spikePositions = NoveltyMFCCTest(testSignal.row(0), p); + + const auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + + const std::vector spikePositions = NoveltyMFCCTest(testSignal.row(0), p); CHECK(spikePositions.size() == expected.size()); REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); } TEST_CASE("NoveltySegmentation works with pitch feature","[Novelty][slicers]"){ + using fluid::index; - - std::vector expected{128, 34880, 47360, 145280, 181888, 186496, 191040, 195648, 200320, 204928, 230976, 266880, 349056, 354944, 358784, 362688, 367552, 371456, 375360, 414080, 425728, 465600, 471616, 481664, 487744, 492992}; - + + std::vector expected{ + 128, 34880, 47360, 145280, 181888, 186496, 191040, 195648, 200320, + 204928, 230976, 266880, 349056, 354944, 358784, 362688, 367552, 371456, + 375360, 414080, 425728, 465600, 471616, 481664, 487744, 492992}; + Params p; p.window = 2048; p.fft = 2048; @@ -302,9 +338,10 @@ TEST_CASE("NoveltySegmentation works with pitch feature","[Novelty][slicers]"){ p.kernel = 9; p.filter = 5; p.dims = 2; - - auto testSignal = fluid::testsignals::eurorackSynth(audio_path); - std::vector spikePositions = NoveltyPitchTest(testSignal.row(0), p); + + const auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + + const std::vector spikePositions = NoveltyPitchTest(testSignal.row(0), p); CHECK(spikePositions.size() == expected.size()); REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); } @@ -312,9 +349,12 @@ TEST_CASE("NoveltySegmentation works with pitch feature","[Novelty][slicers]"){ TEST_CASE("NoveltySegmentation works with loudness feature","[Novelty][slicers]"){ using fluid::index; - - std::vector expected{0, 19008, 24640, 34624, 58240, 117696, 122048, 179392, 229376, 256832, 260288, 265536, 287488, 306752, 335616, 401280, 413888, 464896, 471936, 477184, 483456, 488064, 493376, 513664}; - + + std::vector expected{0, 19008, 24640, 34624, 58240, 117696, + 122048, 179392, 229376, 256832, 260288, 265536, + 287488, 306752, 335616, 401280, 413888, 464896, + 471936, 477184, 483456, 488064, 493376, 513664}; + Params p; p.window = 2048; p.fft = 2048; @@ -324,31 +364,36 @@ TEST_CASE("NoveltySegmentation works with loudness feature","[Novelty][slicers]" p.kernel = 17; p.filter = 5; p.dims = 2; - - auto testSignal = fluid::testsignals::eurorackSynth(audio_path); - std::vector spikePositions = NoveltyLoudnessTest(testSignal.row(0), p); + + const auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + + const std::vector spikePositions = NoveltyLoudnessTest(testSignal.row(0), p); CHECK(spikePositions.size() == expected.size()); REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); } -int main( int argc, char* argv[] ) { +int main(int argc, char* argv[]) +{ Catch::Session session; // There must be exactly one instance // Build a new parser on top of Catch2's using namespace Catch::clara; - auto cli - = session.cli() // Get Catch2's command line parser - | Opt( audio_path, "path to demo audio files" ) // bind variable to a new option, with a hint string - ["-A"]["--audio"] // the option names it will respond to - ("path to audio"); // description string for the help output + auto cli = + session.cli() // Get Catch2's command line parser + | Opt(audio_path, + "path to demo audio files") // bind variable to a new option, with a + // hint string + ["-A"]["--audio"] // the option names it + // will respond to + ("path to audio"); // description string for the help output // Now pass the new composite back to Catch2 so it uses that - session.cli( cli ); + session.cli(cli); // Let Catch2 (using Clara) parse the command line - int returnCode = session.applyCommandLine( argc, argv ); - if( returnCode != 0 ) // Indicates a command line error - return returnCode; + int returnCode = session.applyCommandLine(argc, argv); + if (returnCode != 0) // Indicates a command line error + return returnCode; return session.run(); From 65e27b7117349ad88dbb5be8d6a7d1a69f91377d Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 1 Feb 2022 13:13:49 +0000 Subject: [PATCH 03/14] use generated path for test signal loading --- tests/test_signals/Signals.cpp | 98 ------------------- tests/test_signals/Signals.cpp.in | 150 ++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 98 deletions(-) delete mode 100644 tests/test_signals/Signals.cpp create mode 100644 tests/test_signals/Signals.cpp.in diff --git a/tests/test_signals/Signals.cpp b/tests/test_signals/Signals.cpp deleted file mode 100644 index b393f8eee..000000000 --- a/tests/test_signals/Signals.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "Signals.hpp" -#include - -#include - -#include -#include -#include -#include - -namespace fluid { -namespace testsignals { - -constexpr size_t fs = 44100; - -std::vector oneImpulse() -{ - std::vector oneImpulse(fs); - std::fill(oneImpulse.begin(), oneImpulse.end(), 0); - oneImpulse[(fs / 2) - 1] = 1.0; - return oneImpulse; -} - -FluidTensor stereoImpulses() -{ - FluidTensor impulses(2, fs); - impulses.fill(0); - // 1000.0, 12025.0, 23051.0, 34076.0 - impulses.row(0)[1000] = 1; - impulses.row(0)[23051] = 1; - - impulses.row(1)[12025] = 1; - impulses.row(1)[34076] = 1; - return impulses; -} - -FluidTensor sharpSines() -{ - FluidTensor sharpSines(fs); - std::generate(sharpSines.begin() + 1000, sharpSines.end(), - [i = 1000]() mutable { - constexpr double freq = 640; - double sinx = sin(2 * M_PI * i * freq / fs - 1); - constexpr double nPeriods = 4; - double phasor = - (((fs - 1 - i) % index(fs / nPeriods)) / (fs / nPeriods)); - i++; - return sinx * phasor; - }); - return sharpSines; -} - -FluidTensor smoothSine() -{ - FluidTensor smoothSine(fs); - std::generate(smoothSine.begin(), smoothSine .end(), [i = 0]() mutable { - double res = sin(2 * M_PI * 320 * i / fs) * fabs(sin(2 * M_PI * i / fs)); - i++; - return res; - }); - return smoothSine; -} - -FluidTensor load(const std::string& audio_path, const std::string& file) -{ - HISSTools::IAudioFile f(audio_path + "/" + file); - auto e = f.getErrors(); - if(e.size()) - { - throw std::runtime_error(HISSTools::BaseAudioFile::getErrorString(e[0])); - } - - FluidTensor data(f.getChannels(),f.getFrames()); - f.readInterleaved(data.data(), f.getFrames()); - e = f.getErrors(); - if(e.size()) - { - throw std::runtime_error(HISSTools::BaseAudioFile::getErrorString(e[0])); - } - - return data; -} - -FluidTensor guitarStrums(const std::string& audio_path) -{ - static std::string file{"Tremblay-AaS-AcousticStrums-M.wav"}; - return load(audio_path,file); -} - -FluidTensor eurorackSynth(const std::string& audio_path) -{ - static std::string file{"Tremblay-AaS-SynthTwoVoices-M.wav"}; - return load(audio_path,file); -} - - -} // namespace testsignals -} // namespace fluid diff --git a/tests/test_signals/Signals.cpp.in b/tests/test_signals/Signals.cpp.in new file mode 100644 index 000000000..973596465 --- /dev/null +++ b/tests/test_signals/Signals.cpp.in @@ -0,0 +1,150 @@ +#include "Signals.hpp" +#include +#include +#include +#include +#include +#include + +namespace fluid { +namespace testsignals { + +constexpr size_t fs = 44100; + +const std::string audio_path("@FLUCOMA_CORE_AUDIO@"); + +FluidTensor mono(const FluidTensor& x) +{ + FluidTensor monoInput(x.cols()); + monoInput = x.row(0); + for (index i = 1; i < x.rows(); ++i) + monoInput.apply(x.row(i), [](double& x, double y) { x += y; }); + return monoInput; +} + +FluidTensor make_oneImpulse() +{ + FluidTensor oneImpulse(fs); + std::fill(oneImpulse.begin(), oneImpulse.end(), 0); + oneImpulse[(fs / 2) - 1] = 1.0; + return oneImpulse; +} + +FluidTensor make_stereoImpulses() +{ + FluidTensor impulses(2, fs); + impulses.fill(0); + // 1000.0, 12025.0, 23051.0, 34076.0 + impulses.row(0)[1000] = 1; + impulses.row(0)[23051] = 1; + + impulses.row(1)[12025] = 1; + impulses.row(1)[34076] = 1; + return impulses; +} + +FluidTensor make_sharpSines() +{ + FluidTensor sharpSines(fs); + std::generate(sharpSines.begin() + 1000, sharpSines.end(), + [i = 1000]() mutable { + constexpr double freq = 640; + double sinx = sin(2 * M_PI * i * freq / fs - 1); + constexpr double nPeriods = 4; + double phasor = + (((fs - 1 - i) % index(fs / nPeriods)) / (fs / nPeriods)); + i++; + return sinx * phasor; + }); + return sharpSines; +} + +FluidTensor make_smoothSine() +{ + FluidTensor smoothSine(fs); + std::generate(smoothSine.begin(), smoothSine.end(), [i = 0]() mutable { + double res = sin(2 * M_PI * 320 * i / fs) * fabs(sin(2 * M_PI * i / fs)); + i++; + return res; + }); + return smoothSine; +} + +FluidTensor load(const std::string& audio_path, + const std::string& file) +{ + HISSTools::IAudioFile f(audio_path + "/" + file); + auto e = f.getErrors(); + if (e.size()) + { + throw std::runtime_error(HISSTools::BaseAudioFile::getErrorString(e[0])); + } + + FluidTensor data(f.getChannels(), f.getFrames()); + f.readInterleaved(data.data(), f.getFrames()); + e = f.getErrors(); + if (e.size()) + { + throw std::runtime_error(HISSTools::BaseAudioFile::getErrorString(e[0])); + } + + return data; +} + +FluidTensor& oneImpulse() +{ + static auto resource = make_oneImpulse(); + return resource; +} + +FluidTensor& stereoImpulses() +{ + static auto resource = make_stereoImpulses(); + return resource; +} + +FluidTensor& sharpSines() +{ + static auto resource = make_sharpSines(); + return resource; +} + +FluidTensor& smoothSine() +{ + static auto resource = make_smoothSine(); + return resource; +} + +FluidTensor& guitarStrums() +{ + static auto resource = load(audio_path,"Tremblay-AaS-AcousticStrums-M.wav"); + return resource; +} + +FluidTensor& eurorackSynth() +{ + static auto resource = load(audio_path,"Tremblay-AaS-SynthTwoVoices-M.wav"); + return resource; +} + +FluidTensor& drums() +{ + static auto resource = load(audio_path,"Nicol-LoopE-M.wav"); + return resource; +} + +FluidTensor& monoDrums() +{ + static auto resource = mono(drums()); + return resource; +} + +FluidTensor& monoImpulses() +{ + static auto resource = mono(stereoImpulses()); + return resource; +} + + +} // namespace testsignals +} // namespace fluid From 944188dec158d3fd8230e7a2288b2eeafa4fe4a8 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 1 Feb 2022 13:15:31 +0000 Subject: [PATCH 04/14] update testsignals header and cmake --- tests/test_signals/CMakeLists.txt | 10 +++++- tests/test_signals/Signals.hpp | 56 +++++++------------------------ 2 files changed, 21 insertions(+), 45 deletions(-) diff --git a/tests/test_signals/CMakeLists.txt b/tests/test_signals/CMakeLists.txt index dfbf597a4..87429a107 100644 --- a/tests/test_signals/CMakeLists.txt +++ b/tests/test_signals/CMakeLists.txt @@ -1,6 +1,14 @@ cmake_minimum_required (VERSION 3.11) +get_filename_component(FLUCOMA_CORE_AUDIO + "${CMAKE_CURRENT_SOURCE_DIR}/../../AudioFiles" ABSOLUTE +) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Signals.cpp.in ${CMAKE_CURRENT_SOURCE_DIR}/Signals.cpp @ONLY) + add_library(TestSignals STATIC Signals.cpp) target_include_directories(TestSignals PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") -target_link_libraries(TestSignals PRIVATE FLUID_DECOMPOSITION HISSTools_AudioFile) +target_link_libraries(TestSignals PRIVATE + FLUID_DECOMPOSITION HISSTools_AudioFile +) target_compile_features(TestSignals PUBLIC cxx_std_14) diff --git a/tests/test_signals/Signals.hpp b/tests/test_signals/Signals.hpp index 2b1961974..394bbf58a 100644 --- a/tests/test_signals/Signals.hpp +++ b/tests/test_signals/Signals.hpp @@ -1,54 +1,22 @@ #include -// #include - #include -#include #include +#include +#include namespace fluid { namespace testsignals { - -// constexpr size_t fs = 44100; - -std::vector oneImpulse(); -FluidTensor stereoImpulses(); -FluidTensor sharpSines(); -FluidTensor smoothSine(); -FluidTensor guitarStrums(const std::string& audio_path); -FluidTensor eurorackSynth(const std::string& audio_path); - -// std::fill(oneImpulse.begin(), oneImpulse.end(), 0); -// oneImpulse[(fs / 2) - 1] = 1.0; - -// std::array sharpSines; -// std::generate(sharpSines.begin() + 1000, sharpSines.end(), [i = 1000]() mutable { -// constexpr double freq = 640; -// double sinx = sin(2 * M_PI * i * freq / fs - 1); -// -// constexpr double nPeriods = 4; -// double phasor = (((fs - 1 - i) % (fs / nPeriods)) / (fs / nPeriods)); -// i++; -// return sinx * phasor; -// }); -// -// std::array smoothSine; -// std::generate(smoothSines.begin(), smoothSines.end(), [i = 0]() mutable -// { -// double res = sin(2 * M_PI * 320 * i / fs) * fabs(sin(2 * M_PI * i / fs )); -// i++; -// return res; -// }); -// -// FluidTensor impulses(2,fs); -// impulses.fill(0); -// -// impulses.row(0)[1000] = 1; -// impulses.row(0)[23051] = 1; -// -// impulses.row(1)[11030] = 1; -// impules.row(1)[33080] = 1; +FluidTensor& oneImpulse(); +FluidTensor& stereoImpulses(); +FluidTensor& sharpSines(); +FluidTensor& smoothSine(); +FluidTensor& guitarStrums(); +FluidTensor& eurorackSynth(); +FluidTensor& drums(); +FluidTensor& monoDrums(); +FluidTensor& monoImpulses(); } // namespace testsignals -} // namespace flucoma +} // namespace fluid From e909815c3cdc004a569989b1087fa491d0680fdd Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 1 Feb 2022 13:20:54 +0000 Subject: [PATCH 05/14] Add TestOnsetSegmention, update TestNoveltySeg for new resource loading --- .../public/TestNoveltySegmentation.cpp | 47 +-- .../public/TestOnsetSegmentation.cpp | 309 ++++++++++++++++++ 2 files changed, 318 insertions(+), 38 deletions(-) create mode 100644 tests/algorithms/public/TestOnsetSegmentation.cpp diff --git a/tests/algorithms/public/TestNoveltySegmentation.cpp b/tests/algorithms/public/TestNoveltySegmentation.cpp index 834ac13ea..cc6e1f0c2 100644 --- a/tests/algorithms/public/TestNoveltySegmentation.cpp +++ b/tests/algorithms/public/TestNoveltySegmentation.cpp @@ -1,6 +1,4 @@ -//#define CATCH_CONFIG_MAIN -#define CATCH_CONFIG_RUNNER -//#include +#define CATCH_CONFIG_MAIN #include #include #include @@ -181,7 +179,7 @@ TEST_CASE("NoveltySegmentation will segment on clicks with some predictability", using fluid::index; - auto testSignal = fluid::testsignals::stereoImpulses(); + auto monoInput = fluid::testsignals::monoImpulses(); Params p; p.window = 128; @@ -193,9 +191,9 @@ TEST_CASE("NoveltySegmentation will segment on clicks with some predictability", p.filter = 1; p.dims = (p.fft / 2) + 1; - FluidTensor monoInput(testSignal.cols()); - monoInput = testSignal.row(0); - monoInput.apply(testSignal.row(1), [](double& x, double y) { x += y; }); + // FluidTensor monoInput(testSignal.cols()); + // monoInput = testSignal.row(0); + // monoInput.apply(testSignal.row(1), [](double& x, double y) { x += y; }); const std::vector spikePositions = NoveltySTFTTest(monoInput, p); @@ -286,7 +284,7 @@ TEST_CASE("NoveltySegmentation behaves with different filter sizes","[Novelty][s p.filter = settings.filterSize; p.dims = (p.fft / 2) + 1; - const auto testSignal = fluid::testsignals::guitarStrums(audio_path); + const auto testSignal = fluid::testsignals::guitarStrums(); const std::vector spikePositions = NoveltySTFTTest(testSignal.row(0), p); CHECK(spikePositions.size() == settings.expected.size()); @@ -312,7 +310,7 @@ TEST_CASE("NoveltySegmentation works with MFCC feature","[Novelty][slicers]"){ p.filter = 5; p.dims = 13; - const auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + const auto testSignal = fluid::testsignals::eurorackSynth(); const std::vector spikePositions = NoveltyMFCCTest(testSignal.row(0), p); CHECK(spikePositions.size() == expected.size()); @@ -339,7 +337,7 @@ TEST_CASE("NoveltySegmentation works with pitch feature","[Novelty][slicers]"){ p.filter = 5; p.dims = 2; - const auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + const auto testSignal = fluid::testsignals::eurorackSynth(); const std::vector spikePositions = NoveltyPitchTest(testSignal.row(0), p); CHECK(spikePositions.size() == expected.size()); @@ -365,36 +363,9 @@ TEST_CASE("NoveltySegmentation works with loudness feature","[Novelty][slicers]" p.filter = 5; p.dims = 2; - const auto testSignal = fluid::testsignals::eurorackSynth(audio_path); + const auto testSignal = fluid::testsignals::eurorackSynth(); const std::vector spikePositions = NoveltyLoudnessTest(testSignal.row(0), p); CHECK(spikePositions.size() == expected.size()); REQUIRE_THAT(spikePositions, Catch::Matchers::Equals(expected)); } - -int main(int argc, char* argv[]) -{ - Catch::Session session; // There must be exactly one instance - - // Build a new parser on top of Catch2's - using namespace Catch::clara; - auto cli = - session.cli() // Get Catch2's command line parser - | Opt(audio_path, - "path to demo audio files") // bind variable to a new option, with a - // hint string - ["-A"]["--audio"] // the option names it - // will respond to - ("path to audio"); // description string for the help output - - // Now pass the new composite back to Catch2 so it uses that - session.cli(cli); - - // Let Catch2 (using Clara) parse the command line - int returnCode = session.applyCommandLine(argc, argv); - if (returnCode != 0) // Indicates a command line error - return returnCode; - - - return session.run(); -} diff --git a/tests/algorithms/public/TestOnsetSegmentation.cpp b/tests/algorithms/public/TestOnsetSegmentation.cpp new file mode 100644 index 000000000..a965e4a7f --- /dev/null +++ b/tests/algorithms/public/TestOnsetSegmentation.cpp @@ -0,0 +1,309 @@ +#define CATCH_CONFIG_MAIN + +#include "SlicerTestHarness.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// std::string audio_path; + +namespace fluid { + +using testsignals::drums; +using testsignals::monoDrums; +using testsignals::monoImpulses; +using testsignals::oneImpulse; +using testsignals::stereoImpulses; + +std::vector spikeExpected{22050}; + +template +struct StrongType +{ + StrongType(T val) : mValue{val} {} + operator const T&() const { return mValue; } + const T& operator()() const { return mValue; } + +private: + T mValue; +}; + +using Window = StrongType; +using Hop = StrongType; +using FFT = StrongType; +using Metric = StrongType; +using MinSliceLen = StrongType; +using FilterSize = StrongType; +using Threshold = StrongType; +using FrameDelta = StrongType; +using Data = StrongType, struct DataTag>; +using Expected = StrongType, struct ExpectedTag>; +using Margin = StrongType; + +struct TestParams +{ + Window window; + Hop hop; + FFT fft; + Metric metric; + MinSliceLen minSlice; + FilterSize filterSize; + Threshold threshold; + FrameDelta frameDelta{1}; + Data data; + Expected expected; + Margin margin; +}; + + +std::vector runOneTest(const TestParams& params) +{ + auto makeSlicer = [](const TestParams& p) { + auto res = algorithm::OnsetSegmentation(p.fft); + res.init(p.window, p.fft, p.filterSize); + return res; + }; + + auto invokeSlicer = [](algorithm::OnsetSegmentation& slicer, auto source, + const TestParams& p) { + return slicer.processFrame(source, p.metric, p.filterSize, p.threshold, + p.minSlice, p.frameDelta); + }; + + auto inputFn = [](auto source) { return source; }; + return SlicerTestHarness(params.data, params, makeSlicer, inputFn, + invokeSlicer, 0); // params.hop - (params.window/2)); +} + + +TEST_CASE("OnsetSegmentation can produce the same results as SC tests", + "[OnsetSegmentation][slicers]") +{ + + SECTION("single impulse tests") + { + + + auto metric = GENERATE(as{}, 0, 1, 2, 3, 4, 8, 9); + auto params = + TestParams{Window(1024), + Hop(512), + FFT(1024), + metric, + MinSliceLen(2), + FilterSize(5), + Threshold(0.5), + FrameDelta(0), + Data(FluidTensorView(oneImpulse())), + Expected(spikeExpected), + Margin(34)}; + + INFO("Click Test with Metric " << index(params.metric)); + + auto result = runOneTest(params); + const std::vector& points = params.expected(); + auto matcher = Catch::Matchers::Approx(points); + index margin = params.margin; + matcher.margin(margin); + + CHECK(result.size() == points.size()); + CHECK_THAT(result, matcher); + } + + SECTION("stereo impulses") + { + + auto params = TestParams{Window(512), + Hop(64), + FFT(512), + Metric(9), + MinSliceLen(2), + FilterSize(5), + Threshold(0.1), + FrameDelta(0), + Data(monoImpulses()), + Expected({1000, 12025, 23051, 34076}), + Margin(64)}; + + auto result = runOneTest(params); + const std::vector& points = params.expected(); + auto matcher = Catch::Matchers::Approx(points); + index margin = params.margin; + matcher.margin(margin); + + INFO("stereo impulse test") + CHECK(result.size() == points.size()); + CHECK_THAT(result, matcher); + } + + + SECTION("drum tests") + { + + struct LabelledParams + { + std::string label; + TestParams p; + }; + + auto makeDrumParams = [](std::string label, Metric m, Threshold t, Window w, + Hop h, FFT f, MinSliceLen l, Expected e) { + return LabelledParams{ + label, TestParams{w, h, f, m, l, FilterSize(5), t, FrameDelta(0), + Data(FluidTensorView(monoDrums())), + e, Margin(1)}}; + }; + + auto params = GENERATE_REF( + makeDrumParams( + "test_drums_energy", Metric(0), Threshold(0.5), Window(1024), + Hop(512), FFT(1024), MinSliceLen(2), + Expected({1536, 8192, 38400, 51200, 69632, 88064, 114688, + 151552, 157184, 176640, 202240, 220672, 240128, 252416, + 259072, 276480, 283648, 289792, 296448, 302592, 327168, + 353280, 372736, 390144, 417280})), + makeDrumParams("test_drums_hfc", Metric(1), Threshold(20), Window(512), + Hop(128), FFT(512), MinSliceLen(20), + Expected({1792, 7936, 38784, 51200, 70016, 88320, + 114688, 151808, 157568, 176768, 202368, 221056, + 240256, 252800, 259328, 284032, 289792, 302976, + 327040, 353536, 372608, 390528, 417280})), + makeDrumParams( + "test_drums_SpectralFlux", Metric(2), Threshold(0.2), Window(1000), + Hop(220), FFT(1024), MinSliceLen(2), + Expected({1760, 8580, 38720, 51260, 69960, 88220, + 114620, 151800, 157520, 176660, 202400, 221100, + 240240, 252780, 259380, 284020, 289740, 296560, + 302940, 326920, 353540, 372680, 390500, 417340})), + makeDrumParams( + "test_drums_MKL", Metric(3), Threshold(2), Window(800), Hop(330), + FFT(1024), MinSliceLen(2), + Expected({0, 1650, 69960, 88110, 100650, 114510, 127050, + 146190, 151800, 189420, 202290, 220770, 239910, 252780, + 259380, 277530, 283800, 289740, 302940, 326700, 353430, + 372570, 378840, 403920, 417120, 429990, 449130})), + makeDrumParams( + "test_drums_cosine", Metric(5), Threshold(0.2), Window(1000), + Hop(200), FFT(1024), MinSliceLen(5), + Expected({0, 1600, 38400, 51200, 69800, 88000, + 146200, 151800, 157400, 176400, 202200, 240000, + 243000, 252600, 276400, 280600, 289800, 302800, + 326800, 353400, 390200, 417200, 449000, 453600})), + makeDrumParams( + "test_drums_phase_dev", Metric(6), Threshold(0.1), Window(2000), + Hop(200), FFT(2048), MinSliceLen(5), + Expected({2200, 8800, 40200, 51600, 70600, 115200, 152200, 158400, + 202800, 241000, 253400, 259800, 290200, 303400, 327600, + 354000, 373200, 417600})), + makeDrumParams( + "test_drums_Wphase_dev", Metric(7), Threshold(0.1), Window(1500), + Hop(300), FFT(2048), MinSliceLen(5), + Expected({1800, 8400, 38700, 51300, 70200, 88200, 114600, + 151800, 157500, 165000, 176700, 202500, 221100, 240300, + 252900, 259500, 276900, 284100, 289800, 296400, 303000, + 327300, 353700, 372600, 390600, 417300})), + makeDrumParams("test_drums_complex", Metric(8), Threshold(0.1), + Window(512), Hop(50), FFT(512), MinSliceLen(50), + Expected({1750, 5200, 7900, 38550, 51200, 54650, + 69900, 88150, 114600, 151800, 154550, 157300, + 176550, 202350, 206100, 220950, 240050, 252700, + 259350, 276800, 283900, 289750, 296350, 302850, + 326900, 353500, 372600, 390350, 417250})), + makeDrumParams("test_drums_Rcomplex", Metric(9), Threshold(0.2), + Window(1950), Hop(40), FFT(2048), MinSliceLen(50), + Expected({2040, 9000, 39560, 51760, 89200, 115000, + 152200, 158280, 177480, 202720, 240760, 253120, + 259800, 277840, 284880, 290200, 297560, 303360, + 327440, 354040, 373560, 391360, 417600}))); + + INFO("" << params.label); + + auto result = runOneTest(params.p); + + CHECK(result.size() == params.p.expected().size()); + CHECK_THAT(result, Catch::Equals(params.p.expected())); + } + + SECTION("Test Filtersize") + { + + auto makeDrumParams = [](FilterSize f, Expected e) { + return TestParams{Window(512), + Hop(50), + FFT(512), + Metric(8), + MinSliceLen(50), + f, + Threshold(0.1), + FrameDelta(0), + Data(FluidTensorView(monoDrums())), + e, + Margin(1)}; + }; + + auto params = GENERATE_REF( + makeDrumParams( + FilterSize(3), + Expected({1750, 5200, 8400, 38800, 51200, 53750, 69950, + 88200, 114600, 151800, 157300, 176750, 202350, 204950, + 220950, 240150, 252700, 259350, 276800, 284000, 289750, + 296550, 302850, 326950, 353500, 372600, 390450, 417250})), + makeDrumParams( + FilterSize(7), + Expected({1750, 5200, 7850, 38550, 51200, 54650, + 69900, 76800, 88150, 100800, 114600, 151800, + 157300, 166200, 176550, 202350, 206100, 220950, + 228450, 240050, 252700, 259350, 276800, 283900, + 289750, 296350, 302850, 326900, 332050, 353500, + 372600, 379000, 390350, 404050, 417250, 430250})), + makeDrumParams( + FilterSize(29), + Expected({1750, 7850, 13750, 38550, 46400, 51200, 69900, + 76800, 88150, 100800, 114600, 127300, 151800, 157300, + 164350, 176550, 189650, 202350, 220950, 228400, 240000, + 252700, 259350, 276600, 283900, 289750, 296350, 302850, + 326900, 332050, 353500, 372600, 379000, 390350, 404050, + 417250, 430200, 449350}))); + + INFO("Filter Size " << index(params.filterSize)); + auto result = runOneTest(params); + CHECK(result.size() == params.expected().size()); + CHECK_THAT(result, Catch::Equals(params.expected())); + } + + SECTION("frame delta") + { + auto params = TestParams{ + Window(1000), + Hop(200), + FFT(1024), + Metric(5), + MinSliceLen(5), + FilterSize(7), + Threshold(0.2), + FrameDelta(100), + Data(monoDrums()), + Expected({0, 1600, 38400, 51200, 69800, 88000, 114600, + 146200, 151800, 157400, 176400, 202200, 240000, 243000, + 252600, 276400, 278400, 280600, 289800, 302800, 326800, + 353400, 390200, 404000, 417200, 449000, 453600}), + Margin(1)}; + + INFO("frame delta test"); + auto result = runOneTest(params); + CHECK(result.size() == params.expected().size()); + CHECK_THAT(result, Catch::Equals(params.expected())); + } +} + +} // namespace fluid From e6738a31df84c5a74cf0e9f125fcee588f1f9b0c Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 1 Feb 2022 13:22:05 +0000 Subject: [PATCH 06/14] Add Onsets and update novelty in CMake --- tests/CMakeLists.txt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 79ac84d95..a7ab87cc9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -132,10 +132,13 @@ add_test_executable(TestFluidSource clients/common/TestFluidSource.cpp) add_test_executable(TestFluidSink clients/common/TestFluidSink.cpp) add_test_executable(TestBufferedProcess clients/common/TestBufferedProcess.cpp) -add_test_executable(TestNoveltySeg algorithms/public/TestNoveltySegmentation.cpp) - +add_test_executable(TestNoveltySeg + algorithms/public/TestNoveltySegmentation.cpp +) +add_test_executable(TestOnsetSeg algorithms/public/TestOnsetSegmentation.cpp) target_link_libraries(TestNoveltySeg PRIVATE TestSignals) +target_link_libraries(TestOnsetSeg PRIVATE TestSignals) include(CTest) include(Catch) @@ -150,10 +153,10 @@ catch_discover_tests(TestFluidTensorDeath catch_discover_tests(TestFluidTensorView WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidTensorSupport WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidDataSet WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") -catch_discover_tests(TestNoveltySeg - EXTRA_ARGS --audio=${CMAKE_CURRENT_SOURCE_DIR}/../AudioFiles - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" -) + +catch_discover_tests(TestNoveltySeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") + +catch_discover_tests(TestOnsetSeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSource WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSink WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") From 5721d90b19dbd88aa56f59801d1519b13048a816 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Mon, 7 Feb 2022 13:24:21 +0000 Subject: [PATCH 07/14] Add EnvelopeSeg tests and some missing headers --- tests/.gitignore | 1 + tests/CMakeLists.txt | 5 +- tests/algorithms/public/SlicerTestHarness.hpp | 76 ++++++++ .../public/TestEnvelopeSegmentation.cpp | 175 ++++++++++++++++++ .../public/TestOnsetSegmentation.cpp | 14 +- tests/include/CatchUtils.hpp | 5 +- tests/include/TestUtils.hpp | 14 ++ tests/test_signals/.gitignore | 1 + tests/test_signals/Signals.cpp.in | 7 +- tests/test_signals/Signals.hpp | 4 + 10 files changed, 286 insertions(+), 16 deletions(-) create mode 100644 tests/.gitignore create mode 100644 tests/algorithms/public/SlicerTestHarness.hpp create mode 100644 tests/algorithms/public/TestEnvelopeSegmentation.cpp create mode 100644 tests/include/TestUtils.hpp create mode 100644 tests/test_signals/.gitignore diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 000000000..1c9a99104 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +*.received.* diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a7ab87cc9..b01e0a18f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -136,9 +136,12 @@ add_test_executable(TestNoveltySeg algorithms/public/TestNoveltySegmentation.cpp ) add_test_executable(TestOnsetSeg algorithms/public/TestOnsetSegmentation.cpp) +add_test_executable(TestEnvelopeSeg algorithms/public/TestEnvelopeSegmentation.cpp) + target_link_libraries(TestNoveltySeg PRIVATE TestSignals) target_link_libraries(TestOnsetSeg PRIVATE TestSignals) +target_link_libraries(TestEnvelopeSeg PRIVATE TestSignals) include(CTest) include(Catch) @@ -155,8 +158,8 @@ catch_discover_tests(TestFluidTensorSupport WORKING_DIRECTORY "${CMAKE_BINARY_DI catch_discover_tests(TestFluidDataSet WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestNoveltySeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") - catch_discover_tests(TestOnsetSeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") +catch_discover_tests(TestEnvelopeSeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSource WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSink WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/tests/algorithms/public/SlicerTestHarness.hpp b/tests/algorithms/public/SlicerTestHarness.hpp new file mode 100644 index 000000000..12407f7db --- /dev/null +++ b/tests/algorithms/public/SlicerTestHarness.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace fluid { + +template +std::vector +SlicerTestHarness(FluidTensorView testSignal, Params p, + PrepareSlicerFn&& prepareSlicer, MakeInputsFn&& makeInputs, + InvokeSlicerFn&& invokeSlicer, index addedLatency) +{ + const index halfWindow = p.window; // >> 1; + const index padding = addedLatency; + FluidTensor padded(p.window + halfWindow + padding + + testSignal.size()); + padded.fill(0); + padded(Slice(halfWindow, testSignal.size())) = testSignal; + const fluid::index nHops = + std::floor((padded.size() - p.window) / p.hop); + auto slicer = prepareSlicer(p); + std::vector spikePositions; + for (index i = 0; i < nHops; ++i) + { + auto input = makeInputs(padded(Slice(i * p.hop, p.window))); + if (invokeSlicer(slicer, input, p) > 0) + { + spikePositions.push_back((i * p.hop) - padding - p.hop); + } + } + + // This reproduces what the NRT wrapper does (and hence the result that the + // existing test in SC sees). I'm dubious that + // it really ought to be needed though. I think we're adjusting the latency + // by a hop too much + std::transform(spikePositions.begin(), spikePositions.end(), + spikePositions.begin(), + [&p](index x) { return std::max(0, x); }); + + spikePositions.erase( + std::unique(spikePositions.begin(), spikePositions.end()), + spikePositions.end()); + + return spikePositions; +} + + +struct STFTMagnitudeInput +{ + + template + STFTMagnitudeInput(const Params& p) + : mSTFT(index(p.window), index(p.fft), index(p.hop)), + mSTFTFrame((index(p.fft) / 2) + 1), mMagnitudes((index(p.fft) / 2) + 1) + {} + + FluidTensorView operator()(FluidTensorView source) + { + mSTFT.processFrame(source, mSTFTFrame); + mSTFT.magnitude(mSTFTFrame, mMagnitudes); + return FluidTensorView(mMagnitudes); + } + +private: + algorithm::STFT mSTFT; + FluidTensor, 1> mSTFTFrame; + FluidTensor mMagnitudes; +}; + + +} // namespace fluid diff --git a/tests/algorithms/public/TestEnvelopeSegmentation.cpp b/tests/algorithms/public/TestEnvelopeSegmentation.cpp new file mode 100644 index 000000000..93c5305ff --- /dev/null +++ b/tests/algorithms/public/TestEnvelopeSegmentation.cpp @@ -0,0 +1,175 @@ +#define CATCH_CONFIG_MAIN + +#include "SlicerTestHarness.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fluid { + +using FastRampUp = StrongType; +using FastRampDown = StrongType; +using SlowRampUp = StrongType; +using SlowRampDown = StrongType; +using OnThreshold = StrongType; +using OffThreshold = StrongType; +using Floor = StrongType; +using MinSliceLength = StrongType; +using HighPassFreq = StrongType; +using Data = StrongType, struct DataTag>; +using Expected = StrongType, struct ExpectedTag>; +using Margin = StrongType; + +struct TestParams +{ + FastRampUp fastRampUp; + FastRampDown fastRampDown; + SlowRampUp slowRampUp; + SlowRampDown slowRampDown; + OnThreshold onThreshold; + OffThreshold offThreshold; + Floor floor; + MinSliceLength minSliceLength; + HighPassFreq highPassFreq; + Data data; + Expected expected; + Margin margin; +}; + +std::vector runTest(FluidTensorView testSignal, + TestParams const& p) +{ + + double hiPassFreq = std::min(p.highPassFreq / 44100, 0.5); + + auto algo = algorithm::EnvelopeSegmentation(); + algo.init(p.floor, hiPassFreq); + + std::vector spikePositions; + + index i{0}; + + for (auto&& x : testSignal) + { + if (algo.processSample(x, p.onThreshold, p.offThreshold, p.floor, + p.fastRampUp, p.slowRampUp, p.fastRampDown, + p.slowRampDown, hiPassFreq, p.minSliceLength) > 0) + { + spikePositions.push_back(i); + } + + i++; + } + + return spikePositions; +} + + +TEST_CASE("EnvSeg can be exactly precise with impulses", "[slicers][ampslice]") +{ + + auto data = testsignals::monoImpulses(); + auto exp = testsignals::stereoImpulsePositions(); + + auto params = + TestParams{FastRampUp(10), FastRampDown(2205), SlowRampUp(4410), + SlowRampDown(4410), OnThreshold(10), OffThreshold(5), + Floor(-144), MinSliceLength(2), HighPassFreq(85), + Data(data), Expected(exp), Margin(1)}; + + auto result = runTest(params.data(), params); + REQUIRE_THAT(result, Catch::Equals(exp)); +} + +TEST_CASE("EnvSeg is predictable with sharp sine bursts", "[slicers][ampslice]") +{ + + auto data = testsignals::sharpSines(); + auto exp = std::vector{1001, 1455, 1493, 11028, 11412, 11450, 22053, + 22399, 22437, 22475, 33078, 33462, 33500}; + + auto params = TestParams{ + FastRampUp(5), FastRampDown(50), SlowRampUp(220), SlowRampDown(220), + OnThreshold(10), OffThreshold(10), Floor(-60), MinSliceLength(2), + HighPassFreq(85), Data(data), Expected(exp), Margin(1)}; + + auto result = runTest(params.data(), params); + REQUIRE_THAT(result, Catch::Equals(exp)); +} + + +TEST_CASE("EnvSeg schmitt triggering is predictable", "[slicers][ampslice]") +{ + + auto data = testsignals::sharpSines(); + auto exp = std::vector{1001, 11028, 22053, 33078}; + + auto params = TestParams{ + FastRampUp(5), FastRampDown(50), SlowRampUp(220), SlowRampDown(220), + OnThreshold(10), OffThreshold(5), Floor(-60), MinSliceLength(2), + HighPassFreq(85), Data(data), Expected(exp), Margin(1)}; + + auto result = runTest(params.data(), params); + REQUIRE_THAT(result, Catch::Equals(exp)); +} + +TEST_CASE("EnvSeg debouncing is predictable", "[slicers][ampslice]") +{ + + auto data = testsignals::sharpSines(); + auto exp = std::vector{1001, 11028, 22053, 33078}; + + auto params = TestParams{ + FastRampUp(5), FastRampDown(50), SlowRampUp(220), SlowRampDown(220), + OnThreshold(10), OffThreshold(10), Floor(-60), MinSliceLength(800), + HighPassFreq(85), Data(data), Expected(exp), Margin(1)}; + + auto result = runTest(params.data(), params); + REQUIRE_THAT(result, Catch::Equals(exp)); +} + +TEST_CASE("EnvSeg debouncing and Schmitt trigger together are predictable", + "[slicers][ampslice]") +{ + + auto data = testsignals::sharpSines(); + auto exp = std::vector{1001, 22053}; + + auto params = + TestParams{FastRampUp(5), FastRampDown(50), SlowRampUp(220), + SlowRampDown(220), OnThreshold(10), OffThreshold(5), + Floor(-60), MinSliceLength(15000), HighPassFreq(85), + Data(data), Expected(exp), Margin(1)}; + + auto result = runTest(params.data(), params); + REQUIRE_THAT(result, Catch::Equals(exp)); +} + +TEST_CASE("EnvSeg is predictable on real meaterial", "[slicers][ampslice]") +{ + + auto data = testsignals::monoDrums(); + auto exp = std::vector{1685, 38411, 51140, 69840, 88051, 114540, + 151768, 176349, 202307, 220877, 239981, 252606, + 259266, 276511, 283738, 289695, 296181, 302794, + 326863, 353451, 372514, 390215, 417181}; + + auto params = + TestParams{FastRampUp(10), FastRampDown(2205), SlowRampUp(4410), + SlowRampDown(4410), OnThreshold(10), OffThreshold(5), + Floor(-40), MinSliceLength(4410), HighPassFreq(20), + Data(data), Expected(exp), Margin(1)}; + + auto result = runTest(params.data(), params); + REQUIRE_THAT(result, Catch::Equals(exp)); +} + + +} // namespace fluid diff --git a/tests/algorithms/public/TestOnsetSegmentation.cpp b/tests/algorithms/public/TestOnsetSegmentation.cpp index a965e4a7f..fc01df2b4 100644 --- a/tests/algorithms/public/TestOnsetSegmentation.cpp +++ b/tests/algorithms/public/TestOnsetSegmentation.cpp @@ -1,6 +1,7 @@ #define CATCH_CONFIG_MAIN #include "SlicerTestHarness.hpp" +#include #include #include #include @@ -15,8 +16,6 @@ #include -// std::string audio_path; - namespace fluid { using testsignals::drums; @@ -27,17 +26,6 @@ using testsignals::stereoImpulses; std::vector spikeExpected{22050}; -template -struct StrongType -{ - StrongType(T val) : mValue{val} {} - operator const T&() const { return mValue; } - const T& operator()() const { return mValue; } - -private: - T mValue; -}; - using Window = StrongType; using Hop = StrongType; using FFT = StrongType; diff --git a/tests/include/CatchUtils.hpp b/tests/include/CatchUtils.hpp index 274bf0fbb..1c7583044 100644 --- a/tests/include/CatchUtils.hpp +++ b/tests/include/CatchUtils.hpp @@ -1,5 +1,8 @@ // #include // #include + +#pragma once + #include namespace fluid { @@ -34,4 +37,4 @@ auto EqualsRange(Range&& range) -> EqualsRangeMatcher { return EqualsRangeMatcher{std::forward(range)}; } -} \ No newline at end of file +} diff --git a/tests/include/TestUtils.hpp b/tests/include/TestUtils.hpp new file mode 100644 index 000000000..fa21c9a9d --- /dev/null +++ b/tests/include/TestUtils.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace fluid { +template +struct StrongType +{ + StrongType(T val) : mValue{val} {} + operator const T&() const { return mValue; } + const T& operator()() const { return mValue; } + +private: + T mValue; +}; +} // namespace fluid diff --git a/tests/test_signals/.gitignore b/tests/test_signals/.gitignore new file mode 100644 index 000000000..19b97d72d --- /dev/null +++ b/tests/test_signals/.gitignore @@ -0,0 +1 @@ +Signals.cpp diff --git a/tests/test_signals/Signals.cpp.in b/tests/test_signals/Signals.cpp.in index 973596465..6f0333572 100644 --- a/tests/test_signals/Signals.cpp.in +++ b/tests/test_signals/Signals.cpp.in @@ -30,6 +30,11 @@ FluidTensor make_oneImpulse() return oneImpulse; } +std::vector stereoImpulsePositions() +{ + return {1000, 12025, 23051, 34076}; +} + FluidTensor make_stereoImpulses() { FluidTensor impulses(2, fs); @@ -105,7 +110,7 @@ FluidTensor& stereoImpulses() FluidTensor& sharpSines() { - static auto resource = make_sharpSines(); + static auto resource = load("/tmp/sharpSines.wav"); return resource; } diff --git a/tests/test_signals/Signals.hpp b/tests/test_signals/Signals.hpp index 394bbf58a..55284c7b3 100644 --- a/tests/test_signals/Signals.hpp +++ b/tests/test_signals/Signals.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include @@ -10,6 +12,7 @@ namespace testsignals { FluidTensor& oneImpulse(); FluidTensor& stereoImpulses(); + FluidTensor& sharpSines(); FluidTensor& smoothSine(); FluidTensor& guitarStrums(); @@ -18,5 +21,6 @@ FluidTensor& drums(); FluidTensor& monoDrums(); FluidTensor& monoImpulses(); +std::vector stereoImpulsePositions(); } // namespace testsignals } // namespace fluid From 91306e7288be0d36ccfc4be808c0c1ff1aa74793 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Mon, 7 Feb 2022 15:29:35 +0000 Subject: [PATCH 08/14] Add EnvelopeGate tests --- tests/CMakeLists.txt | 5 + tests/algorithms/public/TestEnvelopeGate.cpp | 429 ++++++++++++++++++ .../public/TestEnvelopeSegmentation.cpp | 12 +- 3 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 tests/algorithms/public/TestEnvelopeGate.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b01e0a18f..7beb0209b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -138,10 +138,14 @@ add_test_executable(TestNoveltySeg add_test_executable(TestOnsetSeg algorithms/public/TestOnsetSegmentation.cpp) add_test_executable(TestEnvelopeSeg algorithms/public/TestEnvelopeSegmentation.cpp) +add_test_executable(TestEnvelopeGate algorithms/public/TestEnvelopeGate.cpp) + + target_link_libraries(TestNoveltySeg PRIVATE TestSignals) target_link_libraries(TestOnsetSeg PRIVATE TestSignals) target_link_libraries(TestEnvelopeSeg PRIVATE TestSignals) +target_link_libraries(TestEnvelopeGate PRIVATE TestSignals) include(CTest) include(Catch) @@ -160,6 +164,7 @@ catch_discover_tests(TestFluidDataSet WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestNoveltySeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestOnsetSeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestEnvelopeSeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") +catch_discover_tests(TestEnvelopeGate WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSource WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSink WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/tests/algorithms/public/TestEnvelopeGate.cpp b/tests/algorithms/public/TestEnvelopeGate.cpp new file mode 100644 index 000000000..8d25460f3 --- /dev/null +++ b/tests/algorithms/public/TestEnvelopeGate.cpp @@ -0,0 +1,429 @@ +#define CATCH_CONFIG_MAIN + +#include "SlicerTestHarness.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fluid { + +using RampUp = StrongType; +using RampDown = StrongType; +using OnThreshold = StrongType; +using OffThreshold = StrongType; +using MinLengthAbove = StrongType; +using MinLengthBelow = StrongType; +using MinSliceLength = StrongType; +using MinSilenceLength = StrongType; +using HighPassFreq = StrongType; +using LookAhead = StrongType; +using LookBack = StrongType; +using Data = StrongType, struct DataTag>; +using Expected = StrongType, struct ExpectedTag>; +using Margin = StrongType; + + +struct TestParams +{ + RampUp rampUp; + RampDown rampDown; + OnThreshold onThreshold; + OffThreshold offThreshold; + MinLengthAbove minLengthAbove; + MinLengthBelow minLengthBelow; + MinSliceLength minSliceLength; + MinSilenceLength minSilenceLength; + LookAhead lookahead; + LookBack lookback; + HighPassFreq highPassFreq; +}; + +std::pair, std::vector> +runTest(FluidTensorView testSignal, TestParams const& p) +{ + + double hiPassFreq = std::min(p.highPassFreq / 44100, 0.5); + + auto algo = algorithm::EnvelopeGate(88200); + algo.init(p.onThreshold, p.offThreshold, hiPassFreq, p.minLengthAbove, + p.lookback, p.minLengthBelow, p.lookahead); + + + index latency = std::max( + p.minLengthAbove + p.lookback, + std::max(p.minLengthBelow, p.lookahead)); + + std::vector onsetPositions; + std::vector offsetPositions; + + index i{0}; + + bool state = false; + + for (auto&& x : testSignal) + { + + + double response = algo.processSample(x, p.onThreshold, p.offThreshold, + p.rampUp, p.rampDown, hiPassFreq, + p.minSliceLength, p.minSilenceLength); + if (response > 0 && !state){ + onsetPositions.push_back(i - latency); + state = true; + } + else if (response == 0 && state) + { + offsetPositions.push_back(i - latency); + state = false; + } + i++; + } + + return {onsetPositions, offsetPositions}; +} + + +TEST_CASE("EnvelopeGate is almost exact with impulses", "[AmpGate][slicers]") +{ + auto sig = testsignals::monoImpulses(); + auto expectedOnsets = testsignals::stereoImpulsePositions(); + auto expectedOffsets = testsignals::stereoImpulsePositions(); + + std::transform(expectedOffsets.begin(), expectedOffsets.end(), + expectedOffsets.begin(), [](index x) { return x + 596; }); + + auto params = + TestParams{RampUp(1), RampDown(10), OnThreshold(-30), + OffThreshold(-90), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1), LookAhead(0), + LookBack(0), HighPassFreq(85)}; + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 2; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{ 1876, 1942, 2009, 2076, 2144, 2212, 2279, 2347, 2415, 2483, 2551, 2619, 2687, 19431, 19501, 19571, 19641, 19711, 19781, 19851, 19921, 19991, 20062, 20133, 20205, 23926, 23992, 24059, 24126, 24194, 24262, 24329, 24397, 24465, 24533, 24601, 24669, 24737, 41481, 41551, 41621, 41691, 41761, 41831, 41901, 41971, 42041, 42112, 42183, 42255 }; + + auto expectedOffsets = std::vector{1890, 1965, 2039, 2113, 2185, 2257, 2329, 2400, 2471, 2542, 2613, 2684, 19430, 19497, 19564, 19631, 19698, 19764, 19831, 19897, 19963, 20028, 20092, 20156, 20219, 23940, 24015, 24089, 24163, 24235, 24307, 24379, 24450, 24521, 24592, 24663, 24734, 41480, 41547, 41614, 41681, 41748, 41814, 41881, 41947, 42013, 42078, 42142, 42206, 42269}; + + + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1), LookAhead(0), + LookBack(0), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and hysteresis", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{1878, 23928}; + + auto expectedOffsets = std::vector{20462, 42512}; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-16), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1), LookAhead(0), + LookBack(0), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and debouncing", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{1876, 2347, 19431, 19921, 23926, 24397, 41481, 41971}; + + auto expectedOffsets = std::vector{2329, 19430, 19897, 20362, 24379, 41480, 41947, 42412 }; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(441), MinSilenceLength(1), LookAhead(0), + LookBack(0), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and gap debouncing", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{1876, 2347, 2841, 19871, 23926, 24397, 24891, 41921}; + + auto expectedOffsets = std::vector{1890, 2400, 19430, 19897, 23940, 24450, 41480, 41947 }; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(441), LookAhead(0), + LookBack(0), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and min time above", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{2687, 24737 }; + + auto expectedOffsets = std::vector{19429, 41479 }; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(441), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1), LookAhead(0), + LookBack(0), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and min time below", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{1875, 23925 }; + + auto expectedOffsets = std::vector{20219, 42269 }; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(441), + MinSliceLength(1), MinSilenceLength(1), LookAhead(0), + LookBack(0), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookahead", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{1875, 23925 }; + + auto expectedOffsets = std::vector{20658, 42708}; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1), LookAhead(441), + LookBack(0), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookback", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{1435, 19499, 19568, 19638, 19707, 19776, 19846, 19915, 19985, 20055, 20125, 20195, 23485, 41549, 41618, 41688, 41757, 41826, 41896, 41965, 42035, 42105, 42175, 42245 }; + + auto expectedOffsets = std::vector{19496, 19563, 19630, 19697, 19763, 19830, 19896, 19962, 20027, 20091, 20155, 20218, 41546, 41613, 41680, 41747, 41813, 41880, 41946, 42012, 42077, 42141, 42205, 42268 }; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1), LookAhead(0), + LookBack(441), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and both lookahead and lookback", "[AmpGate][slicers]") +{ + auto sig = testsignals::smoothSine(); + auto expectedOnsets = std::vector{ 1654, 23704 }; + + auto expectedOffsets = std::vector{ 20658, 42708 }; + + auto params = + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1), LookAhead(441), + LookBack(221), HighPassFreq(85)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + +TEST_CASE("EnvelopeGate is predictable on a real signal", "[AmpGate][slicers]") +{ + auto sig = testsignals::monoDrums(); + auto expectedOnsets = std::vector{ 1269, 38394, 69830, 88034, 114533, 151761, 176327, 202300, 220866, 239779, 252598, 276315, 283982, 326755, 353444, 372504, 390197, 417174 }; + + auto expectedOffsets = std::vector{ 23328, 65751, 81905, 96307, 124975, 172742, 184798, 216023, 232738, 249671, 272516, 283322, 310995, 343433, 366663, 384191, 398299, 428473 }; + + auto params = + TestParams{RampUp(110), RampDown(2205), OnThreshold(-27), + OffThreshold(-31), MinLengthAbove(1), MinLengthBelow(1), + MinSliceLength(1), MinSilenceLength(1100), LookAhead(0), + LookBack(441), HighPassFreq(40)}; + + + auto result = runTest(sig,params); + + REQUIRE(result.first.size() == result.second.size()); + + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); + index margin = 1; + matcherOn.margin(margin); + + CHECK_THAT(result.first,matcherOn); + + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); + matcherOff.margin(margin); + + CHECK_THAT(result.second,matcherOff); +} + + +} // namespace fluid diff --git a/tests/algorithms/public/TestEnvelopeSegmentation.cpp b/tests/algorithms/public/TestEnvelopeSegmentation.cpp index 93c5305ff..fc58ef085 100644 --- a/tests/algorithms/public/TestEnvelopeSegmentation.cpp +++ b/tests/algorithms/public/TestEnvelopeSegmentation.cpp @@ -72,7 +72,7 @@ std::vector runTest(FluidTensorView testSignal, } -TEST_CASE("EnvSeg can be exactly precise with impulses", "[slicers][ampslice]") +TEST_CASE("EnvSeg can be exactly precise with impulses", "[slicers][AmpSlice]") { auto data = testsignals::monoImpulses(); @@ -88,7 +88,7 @@ TEST_CASE("EnvSeg can be exactly precise with impulses", "[slicers][ampslice]") REQUIRE_THAT(result, Catch::Equals(exp)); } -TEST_CASE("EnvSeg is predictable with sharp sine bursts", "[slicers][ampslice]") +TEST_CASE("EnvSeg is predictable with sharp sine bursts", "[slicers][AmpSlice]") { auto data = testsignals::sharpSines(); @@ -105,7 +105,7 @@ TEST_CASE("EnvSeg is predictable with sharp sine bursts", "[slicers][ampslice]") } -TEST_CASE("EnvSeg schmitt triggering is predictable", "[slicers][ampslice]") +TEST_CASE("EnvSeg schmitt triggering is predictable", "[slicers][AmpSlice]") { auto data = testsignals::sharpSines(); @@ -120,7 +120,7 @@ TEST_CASE("EnvSeg schmitt triggering is predictable", "[slicers][ampslice]") REQUIRE_THAT(result, Catch::Equals(exp)); } -TEST_CASE("EnvSeg debouncing is predictable", "[slicers][ampslice]") +TEST_CASE("EnvSeg debouncing is predictable", "[slicers][AmpSlice]") { auto data = testsignals::sharpSines(); @@ -136,7 +136,7 @@ TEST_CASE("EnvSeg debouncing is predictable", "[slicers][ampslice]") } TEST_CASE("EnvSeg debouncing and Schmitt trigger together are predictable", - "[slicers][ampslice]") + "[slicers][AmpSlice]") { auto data = testsignals::sharpSines(); @@ -152,7 +152,7 @@ TEST_CASE("EnvSeg debouncing and Schmitt trigger together are predictable", REQUIRE_THAT(result, Catch::Equals(exp)); } -TEST_CASE("EnvSeg is predictable on real meaterial", "[slicers][ampslice]") +TEST_CASE("EnvSeg is predictable on real meaterial", "[slicers][AmpSlice]") { auto data = testsignals::monoDrums(); From 7a271d0d1b739e1773bbfa7147ebdeb833d5b7ea Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Mon, 7 Feb 2022 23:13:42 +0000 Subject: [PATCH 09/14] Add TransientSlice tests --- tests/CMakeLists.txt | 3 + tests/algorithms/public/TestEnvelopeGate.cpp | 213 ++++++++++-------- .../algorithms/public/TestTransientSlice.cpp | 176 +++++++++++++++ tests/test_signals/Signals.cpp.in | 2 +- tests/test_signals/Signals.hpp | 1 + 5 files changed, 306 insertions(+), 89 deletions(-) create mode 100644 tests/algorithms/public/TestTransientSlice.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7beb0209b..f1b4c7954 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -140,12 +140,14 @@ add_test_executable(TestEnvelopeSeg algorithms/public/TestEnvelopeSegmentation.c add_test_executable(TestEnvelopeGate algorithms/public/TestEnvelopeGate.cpp) +add_test_executable(TestTransientSlice algorithms/public/TestTransientSlice.cpp) target_link_libraries(TestNoveltySeg PRIVATE TestSignals) target_link_libraries(TestOnsetSeg PRIVATE TestSignals) target_link_libraries(TestEnvelopeSeg PRIVATE TestSignals) target_link_libraries(TestEnvelopeGate PRIVATE TestSignals) +target_link_libraries(TestTransientSlice PRIVATE TestSignals) include(CTest) include(Catch) @@ -165,6 +167,7 @@ catch_discover_tests(TestNoveltySeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestOnsetSeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestEnvelopeSeg WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestEnvelopeGate WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") +catch_discover_tests(TestTransientSlice WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSource WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestFluidSink WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/tests/algorithms/public/TestEnvelopeGate.cpp b/tests/algorithms/public/TestEnvelopeGate.cpp index 8d25460f3..8b67ca58b 100644 --- a/tests/algorithms/public/TestEnvelopeGate.cpp +++ b/tests/algorithms/public/TestEnvelopeGate.cpp @@ -53,20 +53,20 @@ runTest(FluidTensorView testSignal, TestParams const& p) auto algo = algorithm::EnvelopeGate(88200); algo.init(p.onThreshold, p.offThreshold, hiPassFreq, p.minLengthAbove, - p.lookback, p.minLengthBelow, p.lookahead); + p.lookback, p.minLengthBelow, p.lookahead); - index latency = std::max( - p.minLengthAbove + p.lookback, - std::max(p.minLengthBelow, p.lookahead)); + index latency = + std::max(p.minLengthAbove + p.lookback, + std::max(p.minLengthBelow, p.lookahead)); std::vector onsetPositions; std::vector offsetPositions; index i{0}; - + bool state = false; - + for (auto&& x : testSignal) { @@ -74,7 +74,8 @@ runTest(FluidTensorView testSignal, TestParams const& p) double response = algo.processSample(x, p.onThreshold, p.offThreshold, p.rampUp, p.rampDown, hiPassFreq, p.minSliceLength, p.minSilenceLength); - if (response > 0 && !state){ + if (response > 0 && !state) + { onsetPositions.push_back(i - latency); state = true; } @@ -104,30 +105,40 @@ TEST_CASE("EnvelopeGate is almost exact with impulses", "[AmpGate][slicers]") OffThreshold(-90), MinLengthAbove(1), MinLengthBelow(1), MinSliceLength(1), MinSilenceLength(1), LookAhead(0), LookBack(0), HighPassFreq(85)}; - - auto result = runTest(sig,params); - + + auto result = runTest(sig, params); + REQUIRE(result.first.size() == result.second.size()); - + auto matcherOn = Catch::Matchers::Approx(expectedOnsets); index margin = 2; matcherOn.margin(margin); - - CHECK_THAT(result.first,matcherOn); - + + CHECK_THAT(result.first, matcherOn); + auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - - CHECK_THAT(result.second,matcherOff); + + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts", "[AmpGate][slicers]") +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{ 1876, 1942, 2009, 2076, 2144, 2212, 2279, 2347, 2415, 2483, 2551, 2619, 2687, 19431, 19501, 19571, 19641, 19711, 19781, 19851, 19921, 19991, 20062, 20133, 20205, 23926, 23992, 24059, 24126, 24194, 24262, 24329, 24397, 24465, 24533, 24601, 24669, 24737, 41481, 41551, 41621, 41691, 41761, 41831, 41901, 41971, 42041, 42112, 42183, 42255 }; - - auto expectedOffsets = std::vector{1890, 1965, 2039, 2113, 2185, 2257, 2329, 2400, 2471, 2542, 2613, 2684, 19430, 19497, 19564, 19631, 19698, 19764, 19831, 19897, 19963, 20028, 20092, 20156, 20219, 23940, 24015, 24089, 24163, 24235, 24307, 24379, 24450, 24521, 24592, 24663, 24734, 41480, 41547, 41614, 41681, 41748, 41814, 41881, 41947, 42013, 42078, 42142, 42206, 42269}; + auto expectedOnsets = std::vector{ + 1876, 1942, 2009, 2076, 2144, 2212, 2279, 2347, 2415, 2483, + 2551, 2619, 2687, 19431, 19501, 19571, 19641, 19711, 19781, 19851, + 19921, 19991, 20062, 20133, 20205, 23926, 23992, 24059, 24126, 24194, + 24262, 24329, 24397, 24465, 24533, 24601, 24669, 24737, 41481, 41551, + 41621, 41691, 41761, 41831, 41901, 41971, 42041, 42112, 42183, 42255}; + auto expectedOffsets = std::vector{ + 1890, 1965, 2039, 2113, 2185, 2257, 2329, 2400, 2471, 2542, + 2613, 2684, 19430, 19497, 19564, 19631, 19698, 19764, 19831, 19897, + 19963, 20028, 20092, 20156, 20219, 23940, 24015, 24089, 24163, 24235, + 24307, 24379, 24450, 24521, 24592, 24663, 24734, 41480, 41547, 41614, + 41681, 41748, 41814, 41881, 41947, 42013, 42078, 42142, 42206, 42269}; auto params = @@ -137,7 +148,7 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts", "[AmpGate][slic LookBack(0), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -145,15 +156,16 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts", "[AmpGate][slic index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and hysteresis", "[AmpGate][slicers]") +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and hysteresis", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); auto expectedOnsets = std::vector{1878, 23928}; @@ -167,7 +179,7 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and hysteresis", LookBack(0), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -175,29 +187,32 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and hysteresis", index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and debouncing", "[AmpGate][slicers]") +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and debouncing", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{1876, 2347, 19431, 19921, 23926, 24397, 41481, 41971}; + auto expectedOnsets = + std::vector{1876, 2347, 19431, 19921, 23926, 24397, 41481, 41971}; - auto expectedOffsets = std::vector{2329, 19430, 19897, 20362, 24379, 41480, 41947, 42412 }; + auto expectedOffsets = + std::vector{2329, 19430, 19897, 20362, 24379, 41480, 41947, 42412}; auto params = - TestParams{RampUp(5), RampDown(25), OnThreshold(-12), - OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), MinSliceLength(441), MinSilenceLength(1), LookAhead(0), - LookBack(0), HighPassFreq(85)}; + LookBack(0), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -205,29 +220,33 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and debouncing", index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and gap debouncing", "[AmpGate][slicers]") +TEST_CASE( + "EnvelopeGate is predictable with smooth sine bursts and gap debouncing", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{1876, 2347, 2841, 19871, 23926, 24397, 24891, 41921}; + auto expectedOnsets = + std::vector{1876, 2347, 2841, 19871, 23926, 24397, 24891, 41921}; - auto expectedOffsets = std::vector{1890, 2400, 19430, 19897, 23940, 24450, 41480, 41947 }; + auto expectedOffsets = + std::vector{1890, 2400, 19430, 19897, 23940, 24450, 41480, 41947}; auto params = - TestParams{RampUp(5), RampDown(25), OnThreshold(-12), - OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + TestParams{RampUp(5), RampDown(25), OnThreshold(-12), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), MinSliceLength(1), MinSilenceLength(441), LookAhead(0), LookBack(0), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -235,30 +254,32 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and gap debouncin index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and min time above", "[AmpGate][slicers]") +TEST_CASE( + "EnvelopeGate is predictable with smooth sine bursts and min time above", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{2687, 24737 }; + auto expectedOnsets = std::vector{2687, 24737}; - auto expectedOffsets = std::vector{19429, 41479 }; + auto expectedOffsets = std::vector{19429, 41479}; auto params = TestParams{RampUp(5), RampDown(25), OnThreshold(-12), - OffThreshold(-12), MinLengthAbove(441), MinLengthBelow(1), + OffThreshold(-12), MinLengthAbove(441), MinLengthBelow(1), MinSliceLength(1), MinSilenceLength(1), LookAhead(0), LookBack(0), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -266,29 +287,31 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and min time abov index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and min time below", "[AmpGate][slicers]") +TEST_CASE( + "EnvelopeGate is predictable with smooth sine bursts and min time below", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{1875, 23925 }; + auto expectedOnsets = std::vector{1875, 23925}; - auto expectedOffsets = std::vector{20219, 42269 }; + auto expectedOffsets = std::vector{20219, 42269}; auto params = TestParams{RampUp(5), RampDown(25), OnThreshold(-12), - OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(441), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(441), MinSliceLength(1), MinSilenceLength(1), LookAhead(0), LookBack(0), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -296,29 +319,30 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and min time belo index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookahead", "[AmpGate][slicers]") +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookahead", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{1875, 23925 }; + auto expectedOnsets = std::vector{1875, 23925}; auto expectedOffsets = std::vector{20658, 42708}; auto params = TestParams{RampUp(5), RampDown(25), OnThreshold(-12), - OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), MinSliceLength(1), MinSilenceLength(1), LookAhead(441), LookBack(0), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -326,30 +350,37 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookahead", " index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookback", "[AmpGate][slicers]") +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookback", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{1435, 19499, 19568, 19638, 19707, 19776, 19846, 19915, 19985, 20055, 20125, 20195, 23485, 41549, 41618, 41688, 41757, 41826, 41896, 41965, 42035, 42105, 42175, 42245 }; + auto expectedOnsets = std::vector{ + 1435, 19499, 19568, 19638, 19707, 19776, 19846, 19915, + 19985, 20055, 20125, 20195, 23485, 41549, 41618, 41688, + 41757, 41826, 41896, 41965, 42035, 42105, 42175, 42245}; - auto expectedOffsets = std::vector{19496, 19563, 19630, 19697, 19763, 19830, 19896, 19962, 20027, 20091, 20155, 20218, 41546, 41613, 41680, 41747, 41813, 41880, 41946, 42012, 42077, 42141, 42205, 42268 }; + auto expectedOffsets = std::vector{ + 19496, 19563, 19630, 19697, 19763, 19830, 19896, 19962, + 20027, 20091, 20155, 20218, 41546, 41613, 41680, 41747, + 41813, 41880, 41946, 42012, 42077, 42141, 42205, 42268}; auto params = TestParams{RampUp(5), RampDown(25), OnThreshold(-12), - OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), MinSliceLength(1), MinSilenceLength(1), LookAhead(0), - LookBack(441), HighPassFreq(85)}; + LookBack(441), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -357,29 +388,31 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and lookback", "[ index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } -TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and both lookahead and lookback", "[AmpGate][slicers]") +TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and both " + "lookahead and lookback", + "[AmpGate][slicers]") { auto sig = testsignals::smoothSine(); - auto expectedOnsets = std::vector{ 1654, 23704 }; + auto expectedOnsets = std::vector{1654, 23704}; - auto expectedOffsets = std::vector{ 20658, 42708 }; + auto expectedOffsets = std::vector{20658, 42708}; auto params = TestParams{RampUp(5), RampDown(25), OnThreshold(-12), - OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), + OffThreshold(-12), MinLengthAbove(1), MinLengthBelow(1), MinSliceLength(1), MinSilenceLength(1), LookAhead(441), - LookBack(221), HighPassFreq(85)}; + LookBack(221), HighPassFreq(85)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -387,29 +420,33 @@ TEST_CASE("EnvelopeGate is predictable with smooth sine bursts and both lookahea index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } TEST_CASE("EnvelopeGate is predictable on a real signal", "[AmpGate][slicers]") { auto sig = testsignals::monoDrums(); - auto expectedOnsets = std::vector{ 1269, 38394, 69830, 88034, 114533, 151761, 176327, 202300, 220866, 239779, 252598, 276315, 283982, 326755, 353444, 372504, 390197, 417174 }; + auto expectedOnsets = std::vector{ + 1269, 38394, 69830, 88034, 114533, 151761, 176327, 202300, 220866, + 239779, 252598, 276315, 283982, 326755, 353444, 372504, 390197, 417174}; - auto expectedOffsets = std::vector{ 23328, 65751, 81905, 96307, 124975, 172742, 184798, 216023, 232738, 249671, 272516, 283322, 310995, 343433, 366663, 384191, 398299, 428473 }; + auto expectedOffsets = std::vector{ + 23328, 65751, 81905, 96307, 124975, 172742, 184798, 216023, 232738, + 249671, 272516, 283322, 310995, 343433, 366663, 384191, 398299, 428473}; auto params = - TestParams{RampUp(110), RampDown(2205), OnThreshold(-27), - OffThreshold(-31), MinLengthAbove(1), MinLengthBelow(1), + TestParams{RampUp(110), RampDown(2205), OnThreshold(-27), + OffThreshold(-31), MinLengthAbove(1), MinLengthBelow(1), MinSliceLength(1), MinSilenceLength(1100), LookAhead(0), - LookBack(441), HighPassFreq(40)}; + LookBack(441), HighPassFreq(40)}; - auto result = runTest(sig,params); + auto result = runTest(sig, params); REQUIRE(result.first.size() == result.second.size()); @@ -417,12 +454,12 @@ TEST_CASE("EnvelopeGate is predictable on a real signal", "[AmpGate][slicers]") index margin = 1; matcherOn.margin(margin); - CHECK_THAT(result.first,matcherOn); + CHECK_THAT(result.first, matcherOn); auto matcherOff = Catch::Matchers::Approx(expectedOffsets); matcherOff.margin(margin); - CHECK_THAT(result.second,matcherOff); + CHECK_THAT(result.second, matcherOff); } diff --git a/tests/algorithms/public/TestTransientSlice.cpp b/tests/algorithms/public/TestTransientSlice.cpp new file mode 100644 index 000000000..b50f50104 --- /dev/null +++ b/tests/algorithms/public/TestTransientSlice.cpp @@ -0,0 +1,176 @@ +#define CATCH_CONFIG_MAIN + +#include "SlicerTestHarness.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fluid { + +using Order = StrongType; +using BlockSize = StrongType; +using Padding = StrongType; +using Skew = StrongType; +using ThreshFwd = StrongType; +using ThreshBack = StrongType; +using WindowSize = StrongType; +using ClumpLength = StrongType; +using MinSliceLength = StrongType; + +struct TestParams +{ + Order order; + BlockSize blocksize; + Padding padding; + Skew skew; + ThreshFwd threshfwd; + ThreshBack threshback; + WindowSize windowsize; + ClumpLength clumplength; + MinSliceLength minslicelength; +}; + +std::vector runTest(FluidTensorView testSignal, + TestParams const& p) +{ + auto algo = algorithm::TransientSegmentation(); + algo.init(p.order, p.blocksize, p.padding); + + const double skew = pow(2, p.skew); + + const index maxWinIn = 2 * p.blocksize + p.padding; + const index maxWinOut = maxWinIn; + const index halfWindow = lrint(p.windowsize / 2); + const index latency = p.padding + p.blocksize - p.order; + + algo.setDetectionParameters(skew, p.threshfwd, p.threshback, halfWindow, + p.clumplength, p.minslicelength); + + + const index hopSize = algo.hopSize(); + index nHops = std::ceil(testSignal.size() / hopSize); + FluidTensor paddedInput(testSignal.size() + latency + hopSize); + paddedInput(Slice(latency, testSignal.size())) = testSignal; + + FluidTensor output(hopSize); + + std::vector spikePositions; + + index i{0}; + + for (index i = 0; i < paddedInput.size(); i += hopSize) + { + algo.process(paddedInput(Slice(i, algo.inputSize())), output); + + auto it = std::find_if(output.begin(), output.end(), + [](double x) { return x > 0; }); + while (it != output.end()) + { + spikePositions.push_back(std::distance(output.begin(), it) + i - latency); + it = std::find_if(std::next(it), output.end(), + [](double x) { return x > 0; }); + } + } + + return spikePositions; +} + +TEST_CASE("TransientSlice is predictable on impulses", + "[TransientSlice][slicers]") +{ + auto source = testsignals::monoImpulses(); + auto expected = testsignals::stereoImpulsePositions(); + + auto params = + TestParams{Order(20), BlockSize(256), Padding(128), + Skew(0), ThreshFwd(2), ThreshBack(1.1), + WindowSize(14), ClumpLength(25), MinSliceLength(1000)}; + + auto matcher = Catch::Matchers::Approx(expected); + index margin = 8; + matcher.margin(margin); + + auto result = runTest(source, params); + + CHECK_THAT(result, matcher); +} + + +TEST_CASE("TransientSlice is predictable on sharp sine bursts", + "[TransientSlice][slicers]") +{ + auto source = testsignals::sharpSines(); + auto expected = std::vector{1000, 22050, 33075}; + + auto params = + TestParams{Order(20), BlockSize(256), Padding(128), + Skew(0), ThreshFwd(2), ThreshBack(1.1), + WindowSize(14), ClumpLength(25), MinSliceLength(1000)}; + + auto matcher = Catch::Matchers::Approx(expected); + index margin = 8; + matcher.margin(margin); + + auto result = runTest(source, params); + + CHECK_THAT(result, matcher); +} + + +TEST_CASE("TransientSlice is predictable on real material", + "[TransientSlice][slicers]") +{ + auto source = testsignals::monoEurorackSynth(); + auto expected = std::vector{ + 144, 19188, 34706, 47223, 49465, 58299, 68185, 86942, 105689, + 106751, 117438, 139521, 152879, 161525, 167573, 179045, 186295, 205049, + 223795, 248985, 250356, 256304, 263609, 280169, 297483, 306502, 310674, + 312505, 319114, 327659, 335217, 346778, 364673, 368356, 384718, 400937, + 431226, 433295, 434501, 435764, 439536, 441625, 444028, 445795, 452031, + 453392, 465467, 481514, 494518, 496119, 505754, 512477, 514270}; + + auto params = + TestParams{Order(20), BlockSize(256), Padding(128), + Skew(0), ThreshFwd(2), ThreshBack(1.1), + WindowSize(14), ClumpLength(25), MinSliceLength(1000)}; + + auto matcher = Catch::Matchers::Approx(expected); + index margin = 1; + matcher.margin(margin); + + auto result = runTest(source, params); + + CHECK_THAT(result, matcher); +} + +TEST_CASE("TransientSlice is predictable on real material with heavy settings", + "[TransientSlice][slicers]") +{ + auto source = testsignals::monoEurorackSynth(); + auto expected = std::vector{ + 140, 19182, 34704, 47217, 58297, 68182, 86941, 105688, 117356, + 122134, 139498, 150485, 161516, 167571, 179043, 186293, 205047, 220493}; + + auto params = + TestParams{Order(200), BlockSize(2048), Padding(1024), + Skew(1), ThreshFwd(3), ThreshBack(1), + WindowSize(15), ClumpLength(30), MinSliceLength(4410)}; + + auto matcher = Catch::Matchers::Approx(expected); + index margin = 1; + matcher.margin(margin); + + auto result = runTest(source(Slice(0, 220500)), params); + + CHECK_THAT(result, matcher); +} + + +} // namespace fluid diff --git a/tests/test_signals/Signals.cpp.in b/tests/test_signals/Signals.cpp.in index 6f0333572..707195211 100644 --- a/tests/test_signals/Signals.cpp.in +++ b/tests/test_signals/Signals.cpp.in @@ -110,7 +110,7 @@ FluidTensor& stereoImpulses() FluidTensor& sharpSines() { - static auto resource = load("/tmp/sharpSines.wav"); + static auto resource = make_sharpSines(); return resource; } diff --git a/tests/test_signals/Signals.hpp b/tests/test_signals/Signals.hpp index 55284c7b3..b6ecaf8e5 100644 --- a/tests/test_signals/Signals.hpp +++ b/tests/test_signals/Signals.hpp @@ -17,6 +17,7 @@ FluidTensor& sharpSines(); FluidTensor& smoothSine(); FluidTensor& guitarStrums(); FluidTensor& eurorackSynth(); +FluidTensor& monoEurorackSynth(); FluidTensor& drums(); FluidTensor& monoDrums(); FluidTensor& monoImpulses(); From 30ed8d7d421da3bbef5b28411933d381652acbd1 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 8 Feb 2022 14:37:01 +0000 Subject: [PATCH 10/14] update location of audio files --- tests/test_signals/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_signals/CMakeLists.txt b/tests/test_signals/CMakeLists.txt index 87429a107..30233e41a 100644 --- a/tests/test_signals/CMakeLists.txt +++ b/tests/test_signals/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required (VERSION 3.11) get_filename_component(FLUCOMA_CORE_AUDIO - "${CMAKE_CURRENT_SOURCE_DIR}/../../AudioFiles" ABSOLUTE + "${CMAKE_CURRENT_SOURCE_DIR}/../../Resources/AudioFiles" ABSOLUTE ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Signals.cpp.in ${CMAKE_CURRENT_SOURCE_DIR}/Signals.cpp @ONLY) From 32c9fcc6fee65b68b5889f59d063d1b8913280db Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 8 Feb 2022 15:02:52 +0000 Subject: [PATCH 11/14] add missing function to signals.cpp.in --- tests/test_signals/Signals.cpp.in | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_signals/Signals.cpp.in b/tests/test_signals/Signals.cpp.in index 707195211..4bb2e1ac0 100644 --- a/tests/test_signals/Signals.cpp.in +++ b/tests/test_signals/Signals.cpp.in @@ -132,6 +132,12 @@ FluidTensor& eurorackSynth() return resource; } +FluidTensor& monoEurorackSynth() +{ + static auto resource = mono(eurorackSynth()); + return resource; +} + FluidTensor& drums() { static auto resource = load(audio_path,"Nicol-LoopE-M.wav"); From 7023b9079aa12a359f6566bb084812de05a594e3 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 8 Feb 2022 15:57:58 +0000 Subject: [PATCH 12/14] Fix test signal for AmpSlice test --- .../algorithms/public/TestEnvelopeSegmentation.cpp | 14 ++++++++++---- tests/test_signals/Signals.cpp.in | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/algorithms/public/TestEnvelopeSegmentation.cpp b/tests/algorithms/public/TestEnvelopeSegmentation.cpp index fc58ef085..214372e47 100644 --- a/tests/algorithms/public/TestEnvelopeSegmentation.cpp +++ b/tests/algorithms/public/TestEnvelopeSegmentation.cpp @@ -101,7 +101,10 @@ TEST_CASE("EnvSeg is predictable with sharp sine bursts", "[slicers][AmpSlice]") HighPassFreq(85), Data(data), Expected(exp), Margin(1)}; auto result = runTest(params.data(), params); - REQUIRE_THAT(result, Catch::Equals(exp)); + + + + REQUIRE_THAT(result, Catch::Matchers::Approx(exp).margin(1)); } @@ -117,7 +120,8 @@ TEST_CASE("EnvSeg schmitt triggering is predictable", "[slicers][AmpSlice]") HighPassFreq(85), Data(data), Expected(exp), Margin(1)}; auto result = runTest(params.data(), params); - REQUIRE_THAT(result, Catch::Equals(exp)); +// REQUIRE_THAT(result, Catch::Equals(exp)); + REQUIRE_THAT(result, Catch::Matchers::Approx(exp).margin(1)); } TEST_CASE("EnvSeg debouncing is predictable", "[slicers][AmpSlice]") @@ -132,7 +136,8 @@ TEST_CASE("EnvSeg debouncing is predictable", "[slicers][AmpSlice]") HighPassFreq(85), Data(data), Expected(exp), Margin(1)}; auto result = runTest(params.data(), params); - REQUIRE_THAT(result, Catch::Equals(exp)); +// REQUIRE_THAT(result, Catch::Equals(exp)); +REQUIRE_THAT(result, Catch::Matchers::Approx(exp).margin(1)); } TEST_CASE("EnvSeg debouncing and Schmitt trigger together are predictable", @@ -149,7 +154,8 @@ TEST_CASE("EnvSeg debouncing and Schmitt trigger together are predictable", Data(data), Expected(exp), Margin(1)}; auto result = runTest(params.data(), params); - REQUIRE_THAT(result, Catch::Equals(exp)); +// REQUIRE_THAT(result, Catch::Equals(exp)); + REQUIRE_THAT(result, Catch::Matchers::Approx(exp).margin(1)); } TEST_CASE("EnvSeg is predictable on real meaterial", "[slicers][AmpSlice]") diff --git a/tests/test_signals/Signals.cpp.in b/tests/test_signals/Signals.cpp.in index 4bb2e1ac0..6af28f9dd 100644 --- a/tests/test_signals/Signals.cpp.in +++ b/tests/test_signals/Signals.cpp.in @@ -54,7 +54,7 @@ FluidTensor make_sharpSines() std::generate(sharpSines.begin() + 1000, sharpSines.end(), [i = 1000]() mutable { constexpr double freq = 640; - double sinx = sin(2 * M_PI * i * freq / fs - 1); + double sinx = sin(2 * M_PI * i * freq / (fs - 1)); constexpr double nPeriods = 4; double phasor = (((fs - 1 - i) % index(fs / nPeriods)) / (fs / nPeriods)); From bbd1012abf6e81c4d5ffb4ec77fee77bc88f57d1 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 8 Feb 2022 18:21:37 +0000 Subject: [PATCH 13/14] try and speed up test run --- .github/workflows/flucoma-core-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flucoma-core-ci.yml b/.github/workflows/flucoma-core-ci.yml index ec1e34b50..d56dda8ef 100644 --- a/.github/workflows/flucoma-core-ci.yml +++ b/.github/workflows/flucoma-core-ci.yml @@ -35,5 +35,5 @@ jobs: - name: Test working-directory: ${{github.workspace}}/build - run: ctest -C ${{env.BUILD_TYPE}} + run: ctest -C ${{env.BUILD_TYPE}} -j3 From ad59b2a78fefb7086be94207a7d0d8eaac3e7539 Mon Sep 17 00:00:00 2001 From: weefuzzy Date: Tue, 8 Feb 2022 22:59:48 +0000 Subject: [PATCH 14/14] FluidSource test blackholed on MSVC It doesn't like GENERATE_REF nested in GENERATE. Nosir. --- tests/clients/common/TestFluidSource.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/clients/common/TestFluidSource.cpp b/tests/clients/common/TestFluidSource.cpp index 1e59e360d..81241348c 100644 --- a/tests/clients/common/TestFluidSource.cpp +++ b/tests/clients/common/TestFluidSource.cpp @@ -28,34 +28,32 @@ TEMPLATE_TEST_CASE( std::array data; std::array output; -// std::array emptyFrame; -// emptyFrame.fill(0); std::iota(data.begin(), data.end(), 0); // run the test with each of these frame sizes auto frameSize = GENERATE(32, 43, 64, 96, 128, 512); - + // and for each frame size above, we test with these hops - auto hop = GENERATE_REF(int(frameSize / 4), int(frameSize / 3), - int(frameSize / 2), int(frameSize)); - + auto overlap = GENERATE(4, 3, 2, 1); + int hop = frameSize / overlap; FluidTensor expected(data.size() + frameSize); - expected(Slice(frameSize)) = FluidTensorView(data.data(),0,data.size()); + expected(Slice(frameSize)) = + FluidTensorView(data.data(), 0, data.size()); for (int i = 0, j = 0, k = 0; i < data.size() - hostSize; i += hostSize) { - auto input = FluidTensorView{ data.data(), i, 1, hostSize }; + auto input = FluidTensorView{data.data(), i, 1, hostSize}; framer.push(input); - auto outputView = FluidTensorView{ output.data(), 0, 1, frameSize }; - + auto outputView = + FluidTensorView{output.data(), 0, 1, frameSize}; for (; j < hostSize; j += hop, k += hop) { framer.pull(outputView, j); - CHECK_THAT(outputView, EqualsRange(expected(Slice(k,frameSize)))); + CHECK_THAT(outputView, EqualsRange(expected(Slice(k, frameSize)))); } j = j < hostSize ? j : j - hostSize;