Cherry pick the OPF refactor from the smoothers

This commit is contained in:
Paul Ferrand 2020-05-07 01:02:35 +02:00
parent 68abedae36
commit d740f1c32b
5 changed files with 132 additions and 88 deletions

View file

@ -8,6 +8,7 @@
#include "OnePoleFilter.h" #include "OnePoleFilter.h"
#include "SfzFilter.h" #include "SfzFilter.h"
#include "ScopedFTZ.h" #include "ScopedFTZ.h"
#include "SfzHelpers.h"
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <random> #include <random>
#include <numeric> #include <numeric>
@ -57,7 +58,7 @@ BENCHMARK_DEFINE_F(FilterFixture, OnePole_VA)(benchmark::State& state) {
const auto sentinel = cutoff.data() + blockSize; const auto sentinel = cutoff.data() + blockSize;
while (cutoffPtr < sentinel) while (cutoffPtr < sentinel)
{ {
const auto gain = sfz::OnePoleFilter<float>::normalizedGain(*cutoffPtr, sampleRate); const auto gain = sfz::vaGain(*cutoffPtr, sampleRate);
filter.setGain(gain); filter.setGain(gain);
filter.processLowpass({ inputPtr, step }, { outputPtr, step } ); filter.processLowpass({ inputPtr, step }, { outputPtr, step } );
cutoffPtr += step; cutoffPtr += step;

View file

@ -340,6 +340,66 @@ bool isValidAudio(absl::Span<Type> span)
return true; return true;
} }
/**
* @brief Finds the minimum size of 2 spans
*
* @tparam T
* @tparam U
* @param span1
* @param span2
* @return constexpr size_t
*/
template <class T, class U>
constexpr size_t minSpanSize(absl::Span<T>& span1, absl::Span<U>& 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 <class T, class... Others>
constexpr size_t minSpanSize(absl::Span<T>& first, Others... others)
{
return min(first.size(), minSpanSize(others...));
}
template <class T>
constexpr bool _checkSpanSizes(size_t size, absl::Span<T>& span1)
{
return span1.size() == size;
}
template <class T, class... Others>
constexpr bool _checkSpanSizes(size_t size, absl::Span<T>& 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 <class T, class... Others>
constexpr bool checkSpanSizes(const absl::Span<T>& span1, Others... others)
{
return _checkSpanSizes(span1.size(), others...);
}
#define CHECK_SPAN_SIZES(...) ASSERT(checkSpanSizes(__VA_ARGS__))
class ScopedRoundingMode { class ScopedRoundingMode {
public: public:
ScopedRoundingMode() = delete; ScopedRoundingMode() = delete;

View file

@ -6,12 +6,14 @@
#pragma once #pragma once
#include "Config.h" #include "Config.h"
#include "Debug.h"
#include "MathHelpers.h" #include "MathHelpers.h"
#include "Macros.h"
#include <absl/types/span.h> #include <absl/types/span.h>
#include <cmath> #include <cmath>
namespace sfz namespace sfz {
{
/** /**
* @brief An implementation of a one pole filter. This is a scalar * @brief An implementation of a one pole filter. This is a scalar
* implementation. * implementation.
@ -22,108 +24,82 @@ template <class Type = float>
class OnePoleFilter { class OnePoleFilter {
public: public:
OnePoleFilter() = default; OnePoleFilter() = default;
// Normalized cutoff with respect to the sampling rate
template <class C>
static Type normalizedGain(Type cutoff, C sampleRate)
{
return std::tan(cutoff / static_cast<Type>(sampleRate) * pi<float>());
}
OnePoleFilter(Type gain)
{
setGain(gain);
}
void setGain(Type gain) void setGain(Type gain)
{ {
this->gain = gain;
G = gain / (1 + gain); G = gain / (1 + gain);
} }
Type getGain() const { return gain; } void processLowpass(absl::Span<const Type> input, absl::Span<Type> output)
size_t processLowpass(absl::Span<const Type> input, absl::Span<Type> lowpass)
{ {
auto in = input.begin(); CHECK_SPAN_SIZES(input, output);
auto out = lowpass.begin(); processLowpass(input.data(), output.data(), minSpanSize(input, output));
auto size = std::min(input.size(), lowpass.size());
auto sentinel = in + size;
while (in < sentinel) {
oneLowpass(in, out);
in++;
out++;
}
return size;
} }
size_t processHighpass(absl::Span<const Type> input, absl::Span<Type> highpass) void processHighpass(absl::Span<const Type> input, absl::Span<Type> output)
{ {
auto in = input.begin(); CHECK_SPAN_SIZES(input, output);
auto out = highpass.begin(); processHighpass(input.data(), output.data(), minSpanSize(input, output));
auto size = std::min(input.size(), highpass.size());
auto sentinel = in + size;
while (in < sentinel) {
oneHighpass(in, out);
in++;
out++;
}
return size;
} }
size_t processLowpassVariableGain(absl::Span<const Type> input, absl::Span<Type> lowpass, absl::Span<const Type> gain) void processLowpass(absl::Span<const Type> input, absl::Span<Type> output, absl::Span<const Type> gain)
{ {
auto in = input.begin(); CHECK_SPAN_SIZES(input, output, gain);
auto out = lowpass.begin(); processLowpass(input.data(), output.data(), gain.data(), minSpanSize(input, output, gain));
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;
} }
size_t processHighpassVariableGain(absl::Span<const Type> input, absl::Span<Type> highpass, absl::Span<const Type> gain) void processHighpass(absl::Span<const Type> input, absl::Span<Type> output, absl::Span<const Type> gain)
{ {
auto in = input.begin(); CHECK_SPAN_SIZES(input, output, gain);
auto out = highpass.begin(); processHighpass(input.data(), output.data(), gain.data(), minSpanSize(input, output, gain));
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;
} }
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: private:
Type state { 0.0 }; Type state { 0.0 };
Type gain { 0.25 }; Type G { 0.5 };
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;
}
}; };
} }

View file

@ -204,6 +204,12 @@ namespace literals {
*/ */
absl::optional<uint8_t> readNoteValue(const absl::string_view& value); absl::optional<uint8_t> readNoteValue(const absl::string_view& value);
template <class Type>
inline CXX14_CONSTEXPR Type vaGain(Type cutoff, Type sampleRate)
{
return std::tan(cutoff / sampleRate * pi<Type>());
}
/** /**
* @brief From a source view, find the next sfz header and its members and * @brief From a source view, find the next sfz header and its members and
* return them, while updating the source by removing this header * return them, while updating the source by removing this header

View file

@ -40,12 +40,13 @@ void testFilter(const std::array<Type, N>& input, const std::array<Type, N>& exp
std::array<Type, N> gains; std::array<Type, N> gains;
std::fill(gains.begin(), gains.end(), gain); std::fill(gains.begin(), gains.end(), gain);
sfz::OnePoleFilter<Type> filter { gain }; sfz::OnePoleFilter<Type> filter;
filter.setGain(gain);
filter.processLowpass(input, outputSpan); filter.processLowpass(input, outputSpan);
REQUIRE(approxEqual(output, expectedLow)); REQUIRE(approxEqual(output, expectedLow));
filter.reset(); filter.reset();
filter.processLowpassVariableGain(input, outputSpan, gains); filter.processLowpass(input, outputSpan, gains);
REQUIRE(approxEqual(output, expectedLow)); REQUIRE(approxEqual(output, expectedLow));
filter.reset(); filter.reset();
@ -53,7 +54,7 @@ void testFilter(const std::array<Type, N>& input, const std::array<Type, N>& exp
REQUIRE(approxEqual(output, expectedHigh)); REQUIRE(approxEqual(output, expectedHigh));
filter.reset(); filter.reset();
filter.processHighpassVariableGain(input, outputSpan, gains); filter.processHighpass(input, outputSpan, gains);
REQUIRE(approxEqual(output, expectedHigh)); REQUIRE(approxEqual(output, expectedHigh));
} }