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 "SfzFilter.h"
#include "ScopedFTZ.h"
#include "SfzHelpers.h"
#include <benchmark/benchmark.h>
#include <random>
#include <numeric>
@ -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<float>::normalizedGain(*cutoffPtr, sampleRate);
const auto gain = sfz::vaGain(*cutoffPtr, sampleRate);
filter.setGain(gain);
filter.processLowpass({ inputPtr, step }, { outputPtr, step } );
cutoffPtr += step;

View file

@ -340,6 +340,66 @@ bool isValidAudio(absl::Span<Type> span)
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 {
public:
ScopedRoundingMode() = delete;

View file

@ -6,12 +6,14 @@
#pragma once
#include "Config.h"
#include "Debug.h"
#include "MathHelpers.h"
#include "Macros.h"
#include <absl/types/span.h>
#include <cmath>
namespace sfz
{
namespace sfz {
/**
* @brief An implementation of a one pole filter. This is a scalar
* implementation.
@ -22,108 +24,82 @@ template <class Type = float>
class OnePoleFilter {
public:
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)
{
this->gain = gain;
G = gain / (1 + gain);
}
Type getGain() const { return gain; }
size_t processLowpass(absl::Span<const Type> input, absl::Span<Type> lowpass)
void processLowpass(absl::Span<const Type> input, absl::Span<Type> 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<const Type> input, absl::Span<Type> highpass)
void processHighpass(absl::Span<const Type> input, absl::Span<Type> 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<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();
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<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();
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 };
};
}

View file

@ -204,6 +204,12 @@ namespace literals {
*/
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
* 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::fill(gains.begin(), gains.end(), gain);
sfz::OnePoleFilter<Type> filter { gain };
sfz::OnePoleFilter<Type> 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<Type, N>& input, const std::array<Type, N>& exp
REQUIRE(approxEqual(output, expectedHigh));
filter.reset();
filter.processHighpassVariableGain(input, outputSpan, gains);
filter.processHighpass(input, outputSpan, gains);
REQUIRE(approxEqual(output, expectedHigh));
}