Smoothing values
Supported bindings: ossia
It is common to require controls to be smoothed over time, in order to prevent clicks and pops in the sound.
Avendish allows this, by defining a simple smoother
struct which specifies over how many milliseconds the control changes must be smoothed.
struct {
static consteval auto name() { return "foobar"; }
struct smoother {
float milliseconds = 10.;
};
struct range {
float min = 20.;
float max = 20000.;
float init = 100.;
};
float value{};
} foobar;
It is also possible to get precise control over the smoothing ratio, depending on the control update rate (sample rate or buffer rate).
Helpers
Helper types are provided:
// #include: <halp/smoothers.hpp>
#pragma once
#include <cmath>
#include <halp/modules.hpp>
HALP_MODULE_EXPORT
namespace halp
{
// A basic smooth specification
template <int T>
struct milliseconds_smooth
{
static constexpr float milliseconds{T};
// Alternative:
// static constexpr std::chrono::milliseconds duration{T};
};
// Same thing but explicit control over the smoothing
// ratio
template <int T>
struct exp_smooth
{
static const constexpr double pi = 3.141592653589793238462643383279502884;
static constexpr auto ratio(double sample_rate) noexcept
{
return std::exp(-2. * pi / (T * 1e-3 * sample_rate));
}
};
}
Usage example
These examples smooth the gain control parameter, either over a buffer in the first case, or for each sample in the second case.
#pragma once
/* SPDX-License-Identifier: GPL-3.0-or-later */
#include <halp/audio.hpp>
#include <halp/controls.hpp>
#include <halp/meta.hpp>
#include <halp/smoothers.hpp>
#include <vector>
namespace examples::helpers
{
/**
* Smooth gain
*/
class SmoothGainPoly
{
public:
halp_meta(name, "Smooth Gain")
halp_meta(c_name, "avnd_helpers_smooth_gain")
halp_meta(uuid, "032e1734-f84a-4eb2-9d14-01fc3dea4c14")
using setup = halp::setup;
using tick = halp::tick;
struct
{
halp::dynamic_audio_bus<"Input", double> audio;
struct : halp::hslider_f32<"Gain", halp::range{.min = 0., .max = 1., .init = 0.5}>
{
struct smoother
{
float milliseconds = 20.;
};
} gain;
} inputs;
struct
{
halp::dynamic_audio_bus<"Output", double> audio;
} outputs;
void prepare(halp::setup info) { }
// Do our processing for N samples
void operator()(halp::tick t)
{
// Process the input buffer
for(int i = 0; i < inputs.audio.channels; i++)
{
auto* in = inputs.audio[i];
auto* out = outputs.audio[i];
for(int j = 0; j < t.frames; j++)
{
out[j] = inputs.gain * in[j];
}
}
}
};
class SmoothGainPerSample
{
public:
halp_meta(name, "Smooth Gain")
halp_meta(c_name, "avnd_helpers_smooth_gain")
halp_meta(uuid, "032e1734-f84a-4eb2-9d14-01fc3dea4c14")
struct inputs
{
halp::audio_sample<"Input", double> audio;
struct : halp::hslider_f32<"Gain", halp::range{.min = 0., .max = 1., .init = 0.5}>
{
using smooth = halp::milliseconds_smooth<20>;
} gain;
};
struct outputs
{
halp::audio_sample<"Output", double> audio;
};
// Do our processing for N samples
void operator()(const inputs& inputs, outputs& outputs)
{
outputs.audio.sample = inputs.audio.sample * inputs.gain;
}
};
}