Linear smoother
This commit is contained in:
parent
d2b9eb7f56
commit
75ca9b7d82
3 changed files with 184 additions and 17 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue