Linear smoother

This commit is contained in:
Jean Pierre Cimalando 2021-03-13 12:37:41 +01:00
parent d2b9eb7f56
commit 75ca9b7d82
3 changed files with 184 additions and 17 deletions

View file

@ -34,24 +34,24 @@ public:
std::vector<float> output; std::vector<float> output;
}; };
BENCHMARK_DEFINE_F(SmootherFixture, Linear) (benchmark::State& state) BENCHMARK_DEFINE_F(SmootherFixture, OnePole) (benchmark::State& state)
{ {
sfz::Smoother smoother; sfz::OnePoleSmoother smoother;
smoother.setSmoothing(10, sfz::config::defaultSampleRate); smoother.setSmoothing(10, sfz::config::defaultSampleRate);
for (auto _ : state) { for (auto _ : state) {
smoother.process(input, absl::MakeSpan(output)); smoother.process(input, absl::MakeSpan(output));
} }
} }
// BENCHMARK_DEFINE_F(SmootherFixture, Multiplicative)(benchmark::State& state) { BENCHMARK_DEFINE_F(SmootherFixture, Linear) (benchmark::State& state)
// sfz::MultiplicativeSmoother smoother; {
// smoother.setSmoothing(10, sfz::config::defaultSampleRate); sfz::LinearSmoother smoother;
// for (auto _ : state) smoother.setSmoothing(10, sfz::config::defaultSampleRate);
// { for (auto _ : state) {
// smoother.process(input, absl::MakeSpan(output)); smoother.process(input, absl::MakeSpan(output));
// } }
// } }
BENCHMARK_REGISTER_F(SmootherFixture, OnePole)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(SmootherFixture, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); BENCHMARK_REGISTER_F(SmootherFixture, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
// BENCHMARK_REGISTER_F(SmootherFixture, Multiplicative)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_MAIN(); BENCHMARK_MAIN();

View file

@ -9,14 +9,15 @@
#include "MathHelpers.h" #include "MathHelpers.h"
#include "SfzHelpers.h" #include "SfzHelpers.h"
#include "SIMDHelpers.h" #include "SIMDHelpers.h"
#include <simde/x86/sse.h>
namespace sfz { namespace sfz {
Smoother::Smoother() OnePoleSmoother::OnePoleSmoother()
{ {
} }
void Smoother::setSmoothing(uint8_t smoothValue, float sampleRate) void OnePoleSmoother::setSmoothing(uint8_t smoothValue, float sampleRate)
{ {
smoothing = (smoothValue > 0); smoothing = (smoothValue > 0);
if (smoothing) { if (smoothing) {
@ -24,12 +25,12 @@ void Smoother::setSmoothing(uint8_t smoothValue, float sampleRate)
} }
} }
void Smoother::reset(float value) void OnePoleSmoother::reset(float value)
{ {
filter.reset(value); filter.reset(value);
} }
void Smoother::process(absl::Span<const float> input, absl::Span<float> output, bool canShortcut) void OnePoleSmoother::process(absl::Span<const float> input, absl::Span<float> output, bool canShortcut)
{ {
CHECK_SPAN_SIZES(input, output); CHECK_SPAN_SIZES(input, output);
if (input.size() == 0) if (input.size() == 0)
@ -53,4 +54,123 @@ void Smoother::process(absl::Span<const float> input, absl::Span<float> output,
} }
} }
///
LinearSmoother::LinearSmoother()
{
}
void LinearSmoother::setSmoothing(uint8_t smoothValue, float sampleRate)
{
const float smoothTime = 1e-3f * smoothValue;
smoothFrames_ = static_cast<int32_t>(smoothTime * sampleRate);
}
void LinearSmoother::reset(float value)
{
current_ = value;
target_ = value;
step_ = 0.0;
framesToTarget_ = 0;
}
void LinearSmoother::process(absl::Span<const float> input, absl::Span<float> output, bool canShortcut)
{
CHECK_SPAN_SIZES(input, output);
uint32_t i = 0;
const uint32_t count = static_cast<uint32_t>(input.size());
if (count == 0)
return;
float current = current_;
float target = target_;
if (canShortcut && current == target && current == input.front()) {
if (input.data() != output.data())
copy<float>(input, output);
reset(input.back());
return;
}
float step = step_;
int32_t framesToTarget = framesToTarget_;
const int32_t smoothFrames = smoothFrames_;
for (; i + 15 < count; i += 16) {
const float nextTarget = input[i + 15];
if (target != nextTarget) {
target = nextTarget;
//framesToTarget = (framesToTarget > 0) ? framesToTarget : smoothFrames;
framesToTarget = smoothFrames;
step = (target - current) / max(1, framesToTarget);
}
const simde__m128 targetX4 = simde_mm_set1_ps(target);
if (target > current) {
simde__m128 stepX4 = simde_mm_set1_ps(step);
simde__m128 tmp1X4 = simde_mm_mul_ps(stepX4, simde_mm_setr_ps(1.0f, 2.0f, 3.0f, 4.0f));
simde__m128 tmp2X4 = simde_mm_shuffle_ps(tmp1X4, tmp1X4, SIMDE_MM_SHUFFLE(3, 3, 3, 3));
simde__m128 current1X4 = simde_mm_add_ps(simde_mm_set1_ps(current), tmp1X4);
simde_mm_storeu_ps(&output[i], simde_mm_min_ps(current1X4, targetX4));
simde__m128 current2X4 = simde_mm_add_ps(current1X4, tmp2X4);
simde_mm_storeu_ps(&output[i + 4], simde_mm_min_ps(current2X4, targetX4));
simde__m128 current3X4 = simde_mm_add_ps(current2X4, tmp2X4);
simde_mm_storeu_ps(&output[i + 8], simde_mm_min_ps(current3X4, targetX4));
simde__m128 current4X4 = simde_mm_add_ps(current3X4, tmp2X4);
simde__m128 limited4X4 = simde_mm_min_ps(current4X4, targetX4);
simde_mm_storeu_ps(&output[i + 12], limited4X4);
current = simde_mm_cvtss_f32(simde_mm_shuffle_ps(limited4X4, limited4X4, SIMDE_MM_SHUFFLE(3, 3, 3, 3)));
}
else if (target < current) {
simde__m128 stepX4 = simde_mm_set1_ps(step);
simde__m128 tmp1X4 = simde_mm_mul_ps(stepX4, simde_mm_setr_ps(1.0f, 2.0f, 3.0f, 4.0f));
simde__m128 tmp2X4 = simde_mm_shuffle_ps(tmp1X4, tmp1X4, SIMDE_MM_SHUFFLE(3, 3, 3, 3));
simde__m128 current1X4 = simde_mm_add_ps(simde_mm_set1_ps(current), tmp1X4);
simde_mm_storeu_ps(&output[i], simde_mm_max_ps(current1X4, targetX4));
simde__m128 current2X4 = simde_mm_add_ps(current1X4, tmp2X4);
simde_mm_storeu_ps(&output[i + 4], simde_mm_max_ps(current2X4, targetX4));
simde__m128 current3X4 = simde_mm_add_ps(current2X4, tmp2X4);
simde_mm_storeu_ps(&output[i + 8], simde_mm_max_ps(current3X4, targetX4));
simde__m128 current4X4 = simde_mm_add_ps(current3X4, tmp2X4);
simde__m128 limited4X4 = simde_mm_max_ps(current4X4, targetX4);
simde_mm_storeu_ps(&output[i + 12], limited4X4);
current = simde_mm_cvtss_f32(simde_mm_shuffle_ps(limited4X4, limited4X4, SIMDE_MM_SHUFFLE(3, 3, 3, 3)));
}
else {
simde_mm_storeu_ps(&output[i], targetX4);
simde_mm_storeu_ps(&output[i + 4], targetX4);
simde_mm_storeu_ps(&output[i + 8], targetX4);
simde_mm_storeu_ps(&output[i + 12], targetX4);
}
framesToTarget -= 16;
}
if (i < count) {
const float nextTarget = input[count - 1];
if (target != nextTarget) {
target = nextTarget;
//framesToTarget = (framesToTarget > 0) ? framesToTarget : smoothFrames;
framesToTarget = smoothFrames;
step = (target - current) / max(1, framesToTarget);
}
if (target > current) {
for (; i < count; ++i)
output[i] = current = min(target, current + step);
}
else if (target < current) {
for (; i < count; ++i)
output[i] = current = max(target, current + step);
}
else {
for (; i < count; ++i)
output[i] = target;
}
framesToTarget -= count;
}
current_ = current;
target_ = target;
step_ = step;
framesToTarget_ = max(0, framesToTarget);
}
} }

View file

@ -14,9 +14,9 @@ namespace sfz {
* @brief Wrapper class for a one pole filter smoother * @brief Wrapper class for a one pole filter smoother
* *
*/ */
class Smoother { class OnePoleSmoother {
public: public:
Smoother(); OnePoleSmoother();
/** /**
* @brief Set the filter cutoff based on the sfz smoothing value * @brief Set the filter cutoff based on the sfz smoothing value
* and the sample rate. * and the sample rate.
@ -49,4 +49,51 @@ private:
OnePoleFilter<float> filter {}; OnePoleFilter<float> filter {};
}; };
/**
* @brief Linear smoother
*
*/
class LinearSmoother {
public:
LinearSmoother();
/**
* @brief Set the filter cutoff based on the sfz smoothing value
* and the sample rate.
*
* @param smoothValue
* @param sampleRate
*/
void setSmoothing(uint8_t smoothValue, float sampleRate);
/**
* @brief Reset the filter state to a given value
*
* @param value
*/
void reset(float value = 0.0f);
/**
* @brief Process a span of data. Input and output can refer to the same
* memory.
*
* @param input
* @param output
* @param canShortcut whether we can have a fast path if the filter is within
* a reasonable range around the first value of the input
* span.
*/
void process(absl::Span<const float> input, absl::Span<float> output, bool canShortcut = false);
float current() const { return current_; }
private:
float current_ = 0.0;
float target_ = 0.0;
float step_ = 0.0;
int32_t framesToTarget_ = 0;
int32_t smoothFrames_ = 0;
};
/**
* @brief Default smoother
*/
using Smoother = LinearSmoother;
} }