Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linear smoother #703

Merged
merged 2 commits into from
Mar 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions benchmarks/BM_smoothers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,24 @@ class SmootherFixture : public benchmark::Fixture {
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);
for (auto _ : state) {
smoother.process(input, absl::MakeSpan(output));
}
}

// BENCHMARK_DEFINE_F(SmootherFixture, Multiplicative)(benchmark::State& state) {
// sfz::MultiplicativeSmoother smoother;
// smoother.setSmoothing(10, sfz::config::defaultSampleRate);
// for (auto _ : state)
// {
// smoother.process(input, absl::MakeSpan(output));
// }
// }
BENCHMARK_DEFINE_F(SmootherFixture, Linear) (benchmark::State& state)
{
sfz::LinearSmoother smoother;
smoother.setSmoothing(10, sfz::config::defaultSampleRate);
for (auto _ : state) {
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, Multiplicative)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_MAIN();
128 changes: 124 additions & 4 deletions src/sfizz/Smoothers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@
#include "MathHelpers.h"
#include "SfzHelpers.h"
#include "SIMDHelpers.h"
#include <simde/x86/sse.h>

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);
if (smoothing) {
filter.setGain(std::tan(1.0f / (2 * config::smoothTauPerStep * smoothValue * sampleRate)));
}
}

void Smoother::reset(float value)
void OnePoleSmoother::reset(float 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);
if (input.size() == 0)
Expand All @@ -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;
//step = (target - current) / max(1, framesToTarget);
step = (target - current) / max(1, smoothFrames);
}
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;
// step = (target - current) / max(1, framesToTarget);
step = (target - current) / max(1, smoothFrames);
}
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);
}

}
51 changes: 49 additions & 2 deletions src/sfizz/Smoothers.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ namespace sfz {
* @brief Wrapper class for a one pole filter smoother
*
*/
class Smoother {
class OnePoleSmoother {
public:
Smoother();
OnePoleSmoother();
/**
* @brief Set the filter cutoff based on the sfz smoothing value
* and the sample rate.
Expand Down Expand Up @@ -49,4 +49,51 @@ class Smoother {
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;

}