Cherry pick the OPF refactor from the smoothers
This commit is contained in:
parent
68abedae36
commit
d740f1c32b
5 changed files with 132 additions and 88 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue