std1.0.0-beta-7 SafeC docs

Source dsp/comb.h

View in the repository

// SafeC Standard Library — Comb filters + Karplus-Strong string synthesis
//
// CombFF (feedforward comb): y[n] = x[n] + gain*x[n-delay] — an FIR delay-
// and-add; produces evenly-spaced notches in the frequency response.
// CombFB (feedback comb): y[n] = x[n] + gain*y[n-delay] — an IIR delay-
// and-add; produces evenly-spaced resonant peaks instead (the basis of
// most artificial reverb algorithms — a bank of feedback combs at
// different delays/gains, often followed by allpass filters).
//
// KarplusStrong is the classic plucked-string/percussion synthesis
// algorithm (Karplus & Strong, 1983): a feedback comb whose feedback path
// is itself a 2-tap averaging lowpass (delayLine[n] and delayLine[n-1]
// averaged, then scaled by 'damping') instead of a plain scalar gain —
// the averaging progressively removes high-frequency energy each time
// around the loop, which is what makes a plucked string's timbre brighten-
// then-decay the way it audibly does. Seed the delay line with an
// excitation buffer (e.g. white noise, or a single impulse) via
// KarplusStrong::new/pluck, then call next() repeatedly — the string is
// self-sustaining except for the built-in decay.
//
// The comb filters are SampleFilters and the Karplus-Strong strings are
// SampleGenerators (see sample_filter.h): a string takes no input, so its
// methods are next() and pluck(), not process() and reset().
#pragma once
#include <std/dsp/fixed.h>
#include <std/dsp/sample_filter.h>

namespace std {

struct CombFF {
    &double history; // ring buffer, length 'delay'
    unsigned long delay;
    unsigned long pos;
    double gain;

    double process(double x);
    void   reset();
    void   process_block([]const double in, []double out);   // out may alias in

    // 'history' is the caller's ring buffer of 'delay' samples, zeroed.
    static struct CombFF new([]double history, double gain);   // delay = history.len
};

struct CombFB {
    &double history;
    unsigned long delay;
    unsigned long pos;
    double gain;

    double process(double x);
    void   reset();
    void   process_block([]const double in, []double out);   // out may alias in

    static struct CombFB new([]double history, double gain);   // delay = history.len
};

struct KarplusStrong {
    &double buffer; // ring buffer, length 'length' — doubles as the delay line and the pitch period
    unsigned long length;
    unsigned long pos;
    double damping; // 0..1; closer to 1 = slower decay (typical: 0.99-0.999)

    // The next output sample.
    double next();
    // Refill the delay line with a new excitation (n samples copied in;
    // if n < length the remainder is left at its previous contents —
    // callers doing a fresh pluck should size the excitation to 'length').
    void   pluck([]const double excitation);

    // 'buffer' is the caller's delay line of 'length' samples; seed it
    // (or call pluck) with an excitation before next().
    static struct KarplusStrong new([]double buffer, double damping);   // length = buffer.len
};

// Single precision: the three filters above on float buffers.
struct CombFF32 {
    &float history;
    unsigned long delay;
    unsigned long pos;
    float gain;

    float process(float x);
    void  reset();
    void  process_block([]const float in, []float out);   // out may alias in

    static struct CombFF32 new([]float history, float gain);   // delay = history.len
};

struct CombFB32 {
    &float history;
    unsigned long delay;
    unsigned long pos;
    float gain;

    float process(float x);
    void  reset();
    void  process_block([]const float in, []float out);   // out may alias in

    static struct CombFB32 new([]float history, float gain);   // delay = history.len
};

struct KarplusStrong32 {
    &float buffer;
    unsigned long length;
    unsigned long pos;
    float damping;

    float next();
    void  pluck([]const float excitation);

    static struct KarplusStrong32 new([]float buffer, float damping);   // length = buffer.len
};

struct FCombFF {
    &Fixed history;
    unsigned long delay;
    unsigned long pos;
    Fixed gain;

    Fixed process(Fixed x);
    void  reset();
    void  process_block([]const Fixed in, []Fixed out);   // out may alias in

    static struct FCombFF new([]Fixed history, Fixed gain);   // delay = history.len
};

struct FCombFB {
    &Fixed history;
    unsigned long delay;
    unsigned long pos;
    Fixed gain;

    Fixed process(Fixed x);
    void  reset();
    void  process_block([]const Fixed in, []Fixed out);   // out may alias in

    static struct FCombFB new([]Fixed history, Fixed gain);   // delay = history.len
};

struct FKarplusStrong {
    &Fixed buffer;
    unsigned long length;
    unsigned long pos;
    Fixed damping;

    Fixed next();
    void  pluck([]const Fixed excitation);

    static struct FKarplusStrong new([]Fixed buffer, Fixed damping);   // length = buffer.len
};

} // namespace std