diff --git a/benchmarks/BM_filterModulation.cpp b/benchmarks/BM_filterModulation.cpp index 9326e21f..f97a9b54 100644 --- a/benchmarks/BM_filterModulation.cpp +++ b/benchmarks/BM_filterModulation.cpp @@ -8,6 +8,7 @@ #include "OnePoleFilter.h" #include "SfzFilter.h" #include "ScopedFTZ.h" +#include "SfzHelpers.h" #include #include #include @@ -57,7 +58,7 @@ BENCHMARK_DEFINE_F(FilterFixture, OnePole_VA)(benchmark::State& state) { const auto sentinel = cutoff.data() + blockSize; while (cutoffPtr < sentinel) { - const auto gain = sfz::OnePoleFilter::normalizedGain(*cutoffPtr, sampleRate); + const auto gain = sfz::vaGain(*cutoffPtr, sampleRate); filter.setGain(gain); filter.processLowpass({ inputPtr, step }, { outputPtr, step } ); cutoffPtr += step; diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index a70446ca..6567d5b5 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -340,6 +340,66 @@ bool isValidAudio(absl::Span span) return true; } +/** + * @brief Finds the minimum size of 2 spans + * + * @tparam T + * @tparam U + * @param span1 + * @param span2 + * @return constexpr size_t + */ +template +constexpr size_t minSpanSize(absl::Span& span1, absl::Span& span2) +{ + return min(span1.size(), span2.size()); +} + +/** + * @brief Finds the minimum size of a list of spans. + * + * @tparam T + * @tparam Others + * @param first + * @param others + * @return constexpr size_t + */ +template +constexpr size_t minSpanSize(absl::Span& first, Others... others) +{ + return min(first.size(), minSpanSize(others...)); +} + +template +constexpr bool _checkSpanSizes(size_t size, absl::Span& span1) +{ + return span1.size() == size; +} + +template +constexpr bool _checkSpanSizes(size_t size, absl::Span& span1, Others... others) +{ + return span1.size() == size && _checkSpanSizes(size, others...); +} + +/** + * @brief Check that all spans of a compile time list have the same size + * + * @tparam T + * @tparam Others + * @param first + * @param others + * @return constexpr size_t + */ +template +constexpr bool checkSpanSizes(const absl::Span& span1, Others... others) +{ + return _checkSpanSizes(span1.size(), others...); +} + +#define CHECK_SPAN_SIZES(...) ASSERT(checkSpanSizes(__VA_ARGS__)) + + class ScopedRoundingMode { public: ScopedRoundingMode() = delete; diff --git a/src/sfizz/OnePoleFilter.h b/src/sfizz/OnePoleFilter.h index 99ddc754..3e0ea5fc 100644 --- a/src/sfizz/OnePoleFilter.h +++ b/src/sfizz/OnePoleFilter.h @@ -6,12 +6,14 @@ #pragma once #include "Config.h" +#include "Debug.h" #include "MathHelpers.h" +#include "Macros.h" #include #include -namespace sfz -{ +namespace sfz { + /** * @brief An implementation of a one pole filter. This is a scalar * implementation. @@ -22,108 +24,82 @@ template class OnePoleFilter { public: OnePoleFilter() = default; - // Normalized cutoff with respect to the sampling rate - template - static Type normalizedGain(Type cutoff, C sampleRate) - { - return std::tan(cutoff / static_cast(sampleRate) * pi()); - } - - OnePoleFilter(Type gain) - { - setGain(gain); - } void setGain(Type gain) { - this->gain = gain; G = gain / (1 + gain); } - Type getGain() const { return gain; } - - size_t processLowpass(absl::Span input, absl::Span lowpass) + void processLowpass(absl::Span input, absl::Span output) { - auto in = input.begin(); - auto out = lowpass.begin(); - auto size = std::min(input.size(), lowpass.size()); - auto sentinel = in + size; - while (in < sentinel) { - oneLowpass(in, out); - in++; - out++; - } - return size; + CHECK_SPAN_SIZES(input, output); + processLowpass(input.data(), output.data(), minSpanSize(input, output)); } - size_t processHighpass(absl::Span input, absl::Span highpass) + void processHighpass(absl::Span input, absl::Span output) { - auto in = input.begin(); - auto out = highpass.begin(); - auto size = std::min(input.size(), highpass.size()); - auto sentinel = in + size; - while (in < sentinel) { - oneHighpass(in, out); - in++; - out++; - } - return size; + CHECK_SPAN_SIZES(input, output); + processHighpass(input.data(), output.data(), minSpanSize(input, output)); } - size_t processLowpassVariableGain(absl::Span input, absl::Span lowpass, absl::Span gain) + void processLowpass(absl::Span input, absl::Span output, absl::Span gain) { - auto in = input.begin(); - auto out = lowpass.begin(); - auto g = gain.begin(); - auto size = min(input.size(), lowpass.size(), gain.size()); - auto sentinel = in + size; - while (in < sentinel) { - setGain(*g); - oneLowpass(in, out); - in++; - out++; - g++; - } - return size; + CHECK_SPAN_SIZES(input, output, gain); + processLowpass(input.data(), output.data(), gain.data(), minSpanSize(input, output, gain)); } - size_t processHighpassVariableGain(absl::Span input, absl::Span highpass, absl::Span gain) + void processHighpass(absl::Span input, absl::Span output, absl::Span gain) { - auto in = input.begin(); - auto out = highpass.begin(); - auto g = gain.begin(); - auto size = min(input.size(), highpass.size(), gain.size()); - auto sentinel = in + size; - while (in < sentinel) { - setGain(*g); - oneHighpass(in, out); - in++; - out++; - g++; - } - return size; + CHECK_SPAN_SIZES(input, output, gain); + processHighpass(input.data(), output.data(), gain.data(), minSpanSize(input, output, gain)); } - void reset() { state = 0.0; } + void processLowpass(const Type* input, Type* output, unsigned size) + { + for (unsigned i = 0; i < size; ++i) { + const Type intermediate = G * (input[i] - state); + output[i] = intermediate + state; + state = output[i] + intermediate; + } + } + + void processHighpass(const Type* input, Type* output, unsigned size) + { + for (unsigned i = 0; i < size; ++i) { + const Type intermediate = G * (input[i] - state); + output[i] = input[i] - intermediate - state; + state += 2 * intermediate; + } + } + + void processLowpass(const Type* input, Type* output, const Type* gain, unsigned size) + { + for (unsigned i = 0; i < size; ++i) { + setGain(gain[i]); + const Type intermediate = G * (input[i] - state); + output[i] = intermediate + state; + state = output[i] + intermediate; + } + } + + void processHighpass(const Type* input, Type* output, const Type* gain, unsigned size) + { + for (unsigned i = 0; i < size; ++i) { + setGain(gain[i]); + const Type intermediate = G * (input[i] - state); + output[i] = input[i] - intermediate - state; + state += 2 * intermediate; + } + } + + void reset(Type value = 0.0) + { + state = value; + } private: Type state { 0.0 }; - Type gain { 0.25 }; - Type intermediate { 0.0 }; - Type G { gain / (1 + gain) }; - - inline void oneLowpass(const Type* in, Type* out) - { - intermediate = G * (*in - state); - *out = intermediate + state; - state = *out + intermediate; - } - - inline void oneHighpass(const Type* in, Type* out) - { - intermediate = G * (*in - state); - *out = *in - intermediate - state; - state += 2 * intermediate; - } + Type G { 0.5 }; }; + } diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 5d581a74..5cd49aad 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -204,6 +204,12 @@ namespace literals { */ absl::optional readNoteValue(const absl::string_view& value); +template +inline CXX14_CONSTEXPR Type vaGain(Type cutoff, Type sampleRate) +{ + return std::tan(cutoff / sampleRate * pi()); +} + /** * @brief From a source view, find the next sfz header and its members and * return them, while updating the source by removing this header diff --git a/tests/OnePoleFilterT.cpp b/tests/OnePoleFilterT.cpp index 3350fa7d..f44e65cc 100644 --- a/tests/OnePoleFilterT.cpp +++ b/tests/OnePoleFilterT.cpp @@ -40,12 +40,13 @@ void testFilter(const std::array& input, const std::array& exp std::array gains; std::fill(gains.begin(), gains.end(), gain); - sfz::OnePoleFilter filter { gain }; + sfz::OnePoleFilter filter; + filter.setGain(gain); filter.processLowpass(input, outputSpan); REQUIRE(approxEqual(output, expectedLow)); filter.reset(); - filter.processLowpassVariableGain(input, outputSpan, gains); + filter.processLowpass(input, outputSpan, gains); REQUIRE(approxEqual(output, expectedLow)); filter.reset(); @@ -53,7 +54,7 @@ void testFilter(const std::array& input, const std::array& exp REQUIRE(approxEqual(output, expectedHigh)); filter.reset(); - filter.processHighpassVariableGain(input, outputSpan, gains); + filter.processHighpass(input, outputSpan, gains); REQUIRE(approxEqual(output, expectedHigh)); }