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;
  }
};
}