1+ //
2+ // Created by Syl Morrison on 03/05/2025.
3+ //
4+ #include " catch2/benchmark/catch_benchmark.hpp"
5+
6+
7+ #include < iostream>
8+ #include < marvin/dsp/filters/biquad/marvin_Biquad.h>
9+ #include < marvin/dsp/filters/biquad/marvin_RBJCoefficients.h>
10+ #include < marvin/dsp/filters/biquad/marvin_SIMDBiquad.h>
11+ #include < marvin/dsp/oscillators/marvin_Oscillator.h>
12+ #include < fmt/core.h>
13+ #include < catch2/catch_test_macros.hpp>
14+ #include < catch2/matchers/catch_matchers_floating_point.hpp>
15+ namespace marvin ::testing {
16+ static std::random_device s_rd;
17+ template <marvin::FloatType SampleType>
18+ std::vector<SampleType> generateImpulse (size_t len) {
19+ std::vector<SampleType> impulse;
20+ impulse.resize (len);
21+ std::fill (impulse.begin (), impulse.end (), static_cast <SampleType>(0.0 ));
22+ impulse[0 ] = static_cast <SampleType>(1.0 );
23+ return impulse;
24+ }
25+ template <FloatType T, size_t N>
26+ std::vector<T> generateNoise () {
27+ marvin::dsp::oscillators::NoiseOscillator<T> osc{ s_rd };
28+ std::vector<T> vec (N, static_cast <T>(0.0 ));
29+ for (auto i = 0 ; i < N; ++i) {
30+ vec[i] = osc ();
31+ }
32+ return vec;
33+ }
34+
35+ TEST_CASE (" Test parity with single biquad" ) {
36+ constexpr static auto sampleRate{ 44100.0 };
37+ constexpr static auto cutoff{ 200.0 };
38+ constexpr static auto q{ 0.7070 };
39+ marvin::dsp::filters::Biquad<double , 1 > singleBiquad;
40+ marvin::dsp::filters::SIMDBiquad<double , 1 > simdBiquad;
41+ auto lowpassCoeffs = dsp::filters::rbj::lowpass (sampleRate, cutoff, q);
42+ singleBiquad.setCoeffs (0 , lowpassCoeffs);
43+ simdBiquad.setCoeffs (lowpassCoeffs);
44+ auto impulse = generateImpulse<double >(100 );
45+ for (auto i = 0 ; i < impulse.size (); ++i) {
46+ const auto singleFiltered = singleBiquad (impulse[i]);
47+ std::array<double , 1 > simdInput{ impulse[i] };
48+ simdBiquad (simdInput);
49+ std::cout << i;
50+ REQUIRE_THAT (simdInput[0 ], Catch::Matchers::WithinRel (singleFiltered, 0.1 ));
51+ }
52+ }
53+
54+ template <NumericType T>
55+ [[nodiscard]] std::string getTypeName () {
56+ if constexpr (std::is_same_v<T, float >) {
57+ return " float" ;
58+ } else if constexpr (std::is_same_v<T, double >) {
59+ return " double" ;
60+ }
61+ }
62+
63+ template <marvin::FloatType SampleType, size_t N, size_t NumSamples>
64+ auto benchmarkSIMD () -> void {
65+ constexpr static auto sampleRate{ 44100.0 };
66+ constexpr static auto cutoff{ 200.0 };
67+ constexpr static auto q{ 0.7070 };
68+ const auto coeffs = marvin::dsp::filters::rbj::lowpass<SampleType>(sampleRate, cutoff, q);
69+ std::array<marvin::dsp::filters::Biquad<SampleType, 1 >, N> normalFilters;
70+ marvin::dsp::filters::SIMDBiquad<SampleType, N> simdFilters;
71+ for (auto & f : normalFilters) {
72+ f.setCoeffs (0 , coeffs);
73+ }
74+ simdFilters.setCoeffs (coeffs);
75+ auto impulse = generateNoise<SampleType, NumSamples>();
76+ std::vector<std::array<SampleType, N>> simdInputs;
77+ for (auto & x : impulse) {
78+ std::array<SampleType, N> current;
79+ std::fill (current.begin (), current.end (), x);
80+ simdInputs.emplace_back (current);
81+ }
82+ BENCHMARK (fmt::format (" Biquad, N = {}, NSamples = {}, Type = {}" , N, NumSamples, getTypeName<SampleType>(), N)) {
83+ for (auto i = 0 ; i < impulse.size (); ++i) {
84+ const auto coeffs = marvin::dsp::filters::rbj::lowpass<SampleType>(sampleRate, cutoff + static_cast <SampleType>(i), q);
85+
86+ for (auto & f : normalFilters) {
87+ f.setCoeffs (0 , coeffs);
88+ [[maybe_unused]] const auto _ = f (impulse[i]);
89+ }
90+ }
91+ };
92+ BENCHMARK (fmt::format (" SIMDBiquad<{}>, NSamples = {}, Type = {}" , N, NumSamples, getTypeName<SampleType>(), N)) {
93+ for (auto i = 0 ; i < impulse.size (); ++i) {
94+ const auto coeffs = marvin::dsp::filters::rbj::lowpass<SampleType>(sampleRate, cutoff + static_cast <SampleType>(i), q);
95+ simdFilters.setCoeffs (coeffs);
96+ simdFilters (simdInputs[i]);
97+ }
98+ };
99+ }
100+
101+ TEST_CASE (" Benchmark Biquads" ) {
102+ benchmarkSIMD<float , 2 , 32 >();
103+ benchmarkSIMD<float , 3 , 32 >();
104+ benchmarkSIMD<float , 4 , 32 >();
105+ benchmarkSIMD<float , 5 , 32 >();
106+ benchmarkSIMD<float , 6 , 32 >();
107+ benchmarkSIMD<float , 7 , 32 >();
108+ benchmarkSIMD<float , 8 , 32 >();
109+ benchmarkSIMD<float , 9 , 32 >();
110+ benchmarkSIMD<float , 10 , 32 >();
111+ benchmarkSIMD<float , 11 , 32 >();
112+ benchmarkSIMD<float , 12 , 32 >();
113+ benchmarkSIMD<float , 13 , 32 >();
114+ benchmarkSIMD<float , 14 , 32 >();
115+ benchmarkSIMD<float , 15 , 32 >();
116+ benchmarkSIMD<float , 16 , 32 >();
117+ }
118+
119+ } // namespace marvin::testing
0 commit comments