Added DaisySP

This commit is contained in:
2026-04-24 14:46:05 +02:00
parent 82190216c3
commit dd63b3aed4
96 changed files with 10613 additions and 0 deletions
+351
View File
@@ -0,0 +1,351 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Alexander Petrov-Savchenko
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_FIRFILTER_H
#define DSY_FIRFILTER_H
#include <cstdint>
#include <cstring> // for memset
#include <cassert>
#include <utility>
#ifdef USE_ARM_DSP
#include <arm_math.h> // required for platform-optimized version
#endif
/** @brief FIR Filter implementation, generic and ARM CMSIS DSP based
* @author Alexander Petrov-Savchenko (axp@soft-amp.com)
* @date February 2021
*/
namespace daisysp
{
/* use this as a template parameter to indicate user-provided memory storage */
#define FIRFILTER_USER_MEMORY 0, 0
/** Helper class that defines the memory model - internal or user-provided
* \param max_size - maximal filter length
* \param max_block - maximal length of the block processing
* if both parameters are 0, does NOT allocate any memory and instead
* requires user-provided memory blocks to be passed as parameters.
*
* Not intended to be used directly, so constructor is not exposed
*/
template <size_t max_size, size_t max_block>
struct FIRMemory
{
/* Public part of the API to be passed through to the FIR users */
public:
/* Reset the internal filter state (but not the coefficients) */
void Reset() { memset(state_, 0, state_size_ * sizeof(state_[0])); }
protected:
FIRMemory() : state_{0}, coefs_{0}, size_(0) {}
/* Expression for the maximum block size */
static constexpr size_t MaxBlock() { return max_block; }
/** Configure the filter coefficients
* \param coefs - pointer to coefficients (tail-first order)
* \param size - number of coefficients pointed by coefs (filter length)
* \param reverse - flag to perform reversing of the filter
* \return true if all conditions are met and the filter is configured
*/
bool SetCoefs(const float coefs[], size_t size, bool reverse)
{
/* truncate silently */
size_ = DSY_MIN(size, max_size);
if(reverse)
{
/* reverse the IR */
for(size_t i = 0; i < size_; i++)
{
/* start from size, not size_! */
coefs_[i] = coefs[size - 1u - i];
}
}
else
{
/* just copy as is */
memcpy(coefs_, coefs, size_ * sizeof(coefs[0]));
}
return true;
}
static constexpr size_t state_size_ = max_size + max_block - 1u;
float state_[state_size_]; /*< Internal state buffer */
float coefs_[max_size]; /*< Filter coefficients */
size_t size_; /*< Active filter length (<= max_size) */
};
/* Specialization for user-provided memory */
template <>
struct FIRMemory<FIRFILTER_USER_MEMORY>
{
/* Public part of the API to be passed through to the FIRFilter user */
public:
/** Set user-provided state buffer
* \param state - pointer to the allocated memory block
* \param length - length of the provided memory block (in elements)
* The length should be determined as follows
* length >= max_filter_size + max_processing_block - 1
*/
void SetStateBuffer(float state[], size_t length)
{
state_ = state;
state_size_ = length;
}
/* Reset the internal filter state (but not the coefficients) */
void Reset()
{
assert(nullptr != state_);
assert(0 != state_size_);
if(nullptr != state_)
{
memset(state_, 0, state_size_ * sizeof(state_[0]));
}
}
protected:
FIRMemory() : state_(nullptr), coefs_(nullptr), size_(0), state_size_(0) {}
/* Expression for the maximum processing block size currently supported */
size_t MaxBlock() const
{
return state_size_ + 1u > size_ ? state_size_ + 1u - size_ : 0;
}
/** Configure the filter coefficients
* \param coefs - pointer to coefficients (tail-first order)
* \param size - number of coefficients pointed by coefs (filter length)
* \param reverse - flag to perform reversing of the filter
* \return true if all conditions are met and the filter is configured
*/
bool SetCoefs(const float coefs[], size_t size, bool reverse)
{
/* reversing of external IR is not supported*/
assert(false == reverse);
assert(nullptr != coefs || 0 == size);
if(false == reverse && (nullptr != coefs || 0 == size))
{
coefs_ = coefs;
size_ = size;
return true;
}
return false;
}
/* Internal member variables */
float* state_; /*< Internal state buffer */
const float* coefs_; /*< Filter coefficients */
size_t size_; /*< number of filter coefficients */
size_t state_size_; /*< length of the state buffer */
};
/** Generic FIR implementation is always available
* \param max_size - maximal filter length
* \param max_block - maximal block size for ProcessBlock()
* if both parameters are 0 (via FIRFILTER_USER_MEMORY macro)
* Assumes the user will provide own memory buffers
* via SetIR() and SetStateBuffer() functions
* Otherwise statically allocates the necessary buffers itself
*/
template <size_t max_size, size_t max_block>
class FIRFilterImplGeneric : public FIRMemory<max_size, max_block>
{
private:
using FIRMem = FIRMemory<max_size, max_block>; // just a shorthand
public:
/* Default constructor */
FIRFilterImplGeneric() {}
/* Reset filter state (but not the coefficients) */
using FIRMem::Reset;
/* FIR Latency is always 0, but API is unified with FFT and fast convolution */
static constexpr size_t GetLatency() { return 0; }
/* Process one sample at a time */
float Process(float in)
{
assert(size_ > 0u);
/* Feed data into the buffer */
state_[size_ - 1u] = in;
/* Convolution loop */
float acc(0);
for(size_t i = 0; i < size_ - 1; i++)
{
acc += state_[i] * coefs_[i];
/** Shift the state simulatenously
* (note: better solutions are available)
*/
state_[i] = state_[1 + i];
}
acc += in * coefs_[size_ - 1u];
return acc;
}
/* Process a block of data */
void ProcessBlock(const float* pSrc, float* pDst, size_t block)
{
/* be sure to run debug version from time to time */
assert(block <= FIRMem::MaxBlock());
assert(size_ > 0u);
assert(nullptr != pSrc);
assert(nullptr != pDst);
/* Process the block of data */
for(size_t j = 0; j < block; j++)
{
/* Feed data into the buffer */
state_[size_ - 1u + j] = pSrc[j];
/* Convolution loop */
float acc = 0.0f;
for(size_t i = 0; i < size_; i++)
{
acc += state_[j + i] * coefs_[i];
}
/* Write output */
pDst[j] = acc;
}
/* Copy data tail for the next block */
for(size_t i = 0; i < size_ - 1u; i++)
{
state_[i] = state_[block + i];
}
}
/** Set filter coefficients (aka Impulse Response)
* Coefficients need to be in reversed order (tail-first)
* If internal storage is used, makes a local copy
* and allows reversing the impulse response
*/
bool SetIR(const float* ir, size_t len, bool reverse)
{
/* Function order is important */
const bool result = FIRMem::SetCoefs(ir, len, reverse);
Reset();
return result;
}
/* Create an alias to comply with DaisySP API conventions */
template <typename... Args>
inline auto Init(Args&&... args)
-> decltype(SetIR(std::forward<Args>(args)...))
{
return SetIR(std::forward<Args>(args)...);
}
protected:
using FIRMem::coefs_; /*< FIR coefficients buffer or pointer */
using FIRMem::size_; /*< FIR length */
using FIRMem::state_; /*< FIR state buffer or pointer */
};
#if(defined(USE_ARM_DSP) && defined(__arm__))
/** ARM-specific FIR implementation, expose only on __arm__ platforms
* \param max_size - maximal filter length
* \param max_block - maximal block size for ProcessBlock()
* if both parameters are 0 (via FIRFILTER_USER_MEMORY macro)
* Assumes the user will provide own memory buffers
* Otherwise statically allocates the necessary buffers
*/
template <size_t max_size, size_t max_block>
class FIRFilterImplARM : public FIRMemory<max_size, max_block>
{
private:
using FIRMem = FIRMemory<max_size, max_block>; // just a shorthand
public:
/* Default constructor */
FIRFilterImplARM() : fir_{0} {}
/* Reset filter state (but not the coefficients) */
using FIRMem::Reset;
/* FIR Latency is always 0, but API is unified with FFT and FastConv */
static constexpr size_t GetLatency() { return 0; }
/* Process one sample at a time */
float Process(float in)
{
float out(0);
arm_fir_f32(&fir_, &in, &out, 1);
return out;
}
/* Process a block of data */
void ProcessBlock(float* pSrc, float* pDst, size_t block)
{
assert(block <= FIRMem::MaxBlock());
arm_fir_f32(&fir_, pSrc, pDst, block);
}
/** Set filter coefficients (aka Impulse Response)
* Coefficients need to be in reversed order (tail-first)
* If internal storage is used, makes a local copy
* and allows reversing the impulse response
*/
bool SetIR(const float* ir, size_t len, bool reverse)
{
/* Function order is important */
const bool result = FIRMem::SetCoefs(ir, len, reverse);
arm_fir_init_f32(&fir_, len, (float*)coefs_, state_, max_block);
return result;
}
/* Create an alias to comply with DaisySP API conventions */
template <typename... Args>
inline auto Init(Args&&... args)
-> decltype(SetIR(std::forward<Args>(args)...))
{
return SetIR(std::forward<Args>(args)...);
}
protected:
arm_fir_instance_f32 fir_; /*< ARM CMSIS DSP library FIR filter instance */
using FIRMem::coefs_; /*< FIR coefficients buffer or pointer */
using FIRMem::size_; /*< FIR length*/
using FIRMem::state_; /*< FIR state buffer or pointer */
};
/* default to ARM implementation */
template <size_t max_size, size_t max_block>
using FIR = FIRFilterImplARM<max_size, max_block>;
#else // USE_ARM_DSP
/* default to generic implementation */
template <size_t max_size, size_t max_block>
using FIR = FIRFilterImplGeneric<max_size, max_block>;
#endif // USE_ARM_DSP
} // namespace daisysp
#endif // DSY_FIRFILTER_H
+180
View File
@@ -0,0 +1,180 @@
/* Ported from Audio Library for Teensy, Ladder Filter
* Copyright (c) 2021, Richard van Hoesel
* Copyright (c) 2024, Infrasonic Audio LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//-----------------------------------------------------------
// Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Richard van Hoesel, v. 1.03, Feb. 14 2021
// v1.7 (Infrasonic/Daisy) add configurable filter mode
// v1.6 (Infrasonic/Daisy) removes polyphase FIR, uses 4x linear
// oversampling for performance reasons
// v1.5 adds polyphase FIR or Linear interpolation
// v1.4 FC extended to 18.7kHz, max res to 1.8, 4x oversampling,
// and a minor Q-tuning adjustment
// v.1.03 adds oversampling, extended resonance,
// and exposes parameters input_drive and passband_gain
// v.1.02 now includes both cutoff and resonance "CV" modulation inputs
// please retain this header if you use this code.
//-----------------------------------------------------------
#include "ladder.h"
#include "Utility/dsp.h"
using namespace daisysp;
static inline float fast_tanh(float x)
{
if(x > 3.0f)
return 1.0f;
if(x < -3.0f)
return -1.0f;
float x2 = x * x;
return x * (27.0f + x2) / (27.0f + 9.0f * x2);
}
void LadderFilter::Init(float sample_rate)
{
sample_rate_ = sample_rate;
sr_int_recip_ = 1.0f / (sample_rate * kInterpolation);
alpha_ = 1.0f;
K_ = 1.0f;
Fbase_ = 1000.0f;
Qadjust_ = 1.0f;
oldinput_ = 0.f;
mode_ = FilterMode::LP24;
SetPassbandGain(0.5f);
SetInputDrive(0.5f);
SetFreq(5000.f);
SetRes(0.2f);
}
float LadderFilter::Process(float in)
{
float input = in * drive_scaled_;
float total = 0.0f;
float interp = 0.0f;
for(size_t os = 0; os < kInterpolation; os++)
{
float in_interp = (interp * oldinput_ + (1.0f - interp) * input);
float u = in_interp - (z1_[3] - pbg_ * in_interp) * K_ * Qadjust_;
u = fast_tanh(u);
float stage1 = LPF(u, 0);
float stage2 = LPF(stage1, 1);
float stage3 = LPF(stage2, 2);
float stage4 = LPF(stage3, 3);
total += weightedSumForCurrentMode({u, stage1, stage2, stage3, stage4})
* kInterpolationRecip;
interp += kInterpolationRecip;
}
oldinput_ = input;
return total;
}
__attribute__((optimize("unroll-loops"))) void
LadderFilter::ProcessBlock(float* buf, size_t size)
{
for(size_t i = 0; i < size; i++)
{
buf[i] = Process(buf[i]);
}
}
void LadderFilter::SetFreq(float freq)
{
Fbase_ = freq;
compute_coeffs(freq);
}
void LadderFilter::SetRes(float res)
{
// maps resonance = 0->1 to K = 0 -> 4
res = daisysp::fclamp(res, 0.0f, kMaxResonance);
K_ = 4.0f * res;
}
void LadderFilter::SetPassbandGain(float pbg)
{
pbg_ = daisysp::fclamp(pbg, 0.0f, 0.5f);
SetInputDrive(drive_);
}
void LadderFilter::SetInputDrive(float odrv)
{
drive_ = daisysp::fmax(odrv, 0.0f);
if(drive_ > 1.0f)
{
drive_ = fmin(drive_, 4.0f);
// max is 4 when pbg = 0, and 2.5 when pbg is 0.5
drive_scaled_ = 1.0f + (drive_ - 1.0f) * (1.0f - pbg_);
}
else
{
drive_scaled_ = drive_;
}
}
float LadderFilter::LPF(float s, int i)
{
// (1.0 / 1.3) (0.3 / 1.3)
float ft = s * 0.76923077f + 0.23076923f * z0_[i] - z1_[i];
ft = ft * alpha_ + z1_[i];
z1_[i] = ft;
z0_[i] = s;
return ft;
}
void LadderFilter::compute_coeffs(float freq)
{
freq = daisysp::fclamp(freq, 5.0f, sample_rate_ * 0.425f);
float wc = freq * 2.0f * PI_F * sr_int_recip_;
float wc2 = wc * wc;
alpha_ = 0.9892f * wc - 0.4324f * wc2 + 0.1381f * wc * wc2
- 0.0202f * wc2 * wc2;
//Qadjust = 1.0029f + 0.0526f * wc - 0.0926 * wc2 + 0.0218* wc * wc2;
Qadjust_ = 1.006f + 0.0536f * wc - 0.095f * wc2 - 0.05f * wc2 * wc2;
// revised hfQ (rvh - feb 14 2021)
}
float LadderFilter::weightedSumForCurrentMode(
const std::array<float, 5>& stage_outs)
{
// Weighted filter stage mixing to achieve selected response
// as described in "Oscillator and Filter Algorithms for Virtual Analog Synthesis"
// Välimäki and Huovilainen, Computer Music Journal, vol 60, 2006
switch(mode_)
{
case FilterMode::LP24: return stage_outs[4];
case FilterMode::LP12: return stage_outs[2];
case FilterMode::BP24:
return (stage_outs[2] + stage_outs[4]) * 4.0f
- stage_outs[3] * 8.0f;
case FilterMode::BP12: return (stage_outs[1] - stage_outs[2]) * 2.0f;
case FilterMode::HP24:
return stage_outs[0] + stage_outs[4]
- ((stage_outs[1] + stage_outs[3]) * 4.0f)
+ stage_outs[2] * 6.0f;
case FilterMode::HP12:
return stage_outs[0] + stage_outs[2] - stage_outs[1] * 2.0f;
default: return 0.0f;
}
}
+144
View File
@@ -0,0 +1,144 @@
/* Ported from Audio Library for Teensy, Ladder Filter
* Copyright (c) 2021, Richard van Hoesel
* Copyright (c) 2024, Infrasonic Audio LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//-----------------------------------------------------------
// Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Richard van Hoesel, v. 1.03, Feb. 14 2021
// v1.7 (Infrasonic/Daisy) add configurable filter mode
// v1.6 (Infrasonic/Daisy) removes polyphase FIR, uses 4x linear
// oversampling for performance reasons
// v1.5 adds polyphase FIR or Linear interpolation
// v1.4 FC extended to 18.7kHz, max res to 1.8, 4x oversampling,
// and a minor Q-tuning adjustment
// v.1.03 adds oversampling, extended resonance,
// and exposes parameters input_drive and passband_gain
// v.1.02 now includes both cutoff and resonance "CV" modulation inputs
// please retain this header if you use this code.
//-----------------------------------------------------------
#pragma once
#ifndef DSY_LADDER_H
#define DSY_LADDER_H
#include <stdlib.h>
#include <stdint.h>
#include <array>
#ifdef __cplusplus
namespace daisysp
{
/**
* 4-pole ladder filter model with selectable filter type (LP/BP/HP 12 or 24 dB/oct),
* drive, passband gain compensation, and stable self-oscillation.
*
*
*/
class LadderFilter
{
public:
enum class FilterMode
{
LP24,
LP12,
BP24,
BP12,
HP24,
HP12
};
LadderFilter() = default;
~LadderFilter() = default;
/** Initializes the ladder filter module.
*/
void Init(float sample_rate);
/** Process single sample */
float Process(float in);
/** Process mono buffer/block of samples in place */
void ProcessBlock(float* buf, size_t size);
/**
Sets the cutoff frequency of the filter.
Units of hz, valid in range 5 - ~nyquist (samp_rate / 2)
Internally clamped to this range.
*/
void SetFreq(float freq);
/**
Sets the resonance of the filter.
Filter will stably self oscillate at higher values.
Valid in range 0 - 1.8
Internally clamped to this range.
*/
void SetRes(float res);
/**
Set "passband gain" compensation factor to mitigate
loss of energy in passband at higher resonance values.
Drive and passband gain have a dependent relationship.
Valid in range 0 - 0.5
Internally clamped to this range.
*/
void SetPassbandGain(float pbg);
/**
Sets drive of the input stage into the tanh clipper
Valid in range 0 - 4.0
*/
void SetInputDrive(float drv);
/**
Sets the filter mode/response
Defaults to classic lowpass 24dB/oct
*/
inline void SetFilterMode(FilterMode mode) { mode_ = mode; }
private:
static constexpr uint8_t kInterpolation = 4;
static constexpr float kInterpolationRecip = 1.0f / kInterpolation;
static constexpr float kMaxResonance = 1.8f;
float sample_rate_, sr_int_recip_;
float alpha_;
float beta_[4] = {0.0, 0.0, 0.0, 0.0};
float z0_[4] = {0.0, 0.0, 0.0, 0.0};
float z1_[4] = {0.0, 0.0, 0.0, 0.0};
float K_;
float Fbase_;
float Qadjust_;
float pbg_;
float drive_, drive_scaled_;
float oldinput_;
FilterMode mode_;
float LPF(float s, int i);
void compute_coeffs(float fc);
float weightedSumForCurrentMode(const std::array<float, 5>& stage_outs);
};
} // namespace daisysp
#endif
#endif
+110
View File
@@ -0,0 +1,110 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Emilie Gillet
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#ifndef DSY_ONEPOLE_H
#define DSY_ONEPOLE_H
#include "Utility/dsp.h"
#include <cmath>
#include <algorithm>
namespace daisysp
{
/**
@brief One Pole Lowpass / Highpass Filter
@author beserge
@date Dec 2023
Ported from pichenettes/stmlib/dsp/filter.h \n
\n to an independent module. \n
Original code written by Emilie Gillet. \n
*/
class OnePole
{
public:
OnePole() {}
~OnePole() {}
/**
* @brief Operational modes of the filter
* - LOW_PASS = cuts out high frequency sound, lets low frequency sound pass through
* - HIGH_PASS = cuts out low frequency sound, lets high frequency sound pass through
*/
enum FilterMode
{
FILTER_MODE_LOW_PASS,
FILTER_MODE_HIGH_PASS
};
/** Initializes the module */
void Init()
{
Reset();
mode_ = FILTER_MODE_LOW_PASS;
}
/** Reset the module to its default state */
inline void Reset() { state_ = 0.0f; }
/** Set the filter cutoff frequency
* \param freq Cutoff frequency. Valid range from 0 to .497f
*/
inline void SetFrequency(float freq)
{
// Clip coefficient to about 100.
freq = freq < 0.497f ? freq : 0.497f;
g_ = tanf(PI_F * freq);
gi_ = 1.f / (1.f + g_);
}
/** Set the filter mode
* \param mode Filter mode. Can be lowpass or highpass
*/
inline void SetFilterMode(FilterMode mode) { mode_ = mode; }
/** Process audio through the filter
* \param in The next sample to be processed
*/
inline float Process(float in)
{
float lp;
lp = (g_ * in + state_) * gi_;
state_ = g_ * (in - lp) + lp;
switch(mode_)
{
case FILTER_MODE_LOW_PASS: return lp;
case FILTER_MODE_HIGH_PASS: return in - lp;
}
return 0.0f;
}
/** Process a block of audio through the filter
* \param in_out Pointer to the block of samples to be processed
* \param size Size of the block of samples to be processed.
*/
inline void ProcessBlock(float* in_out, size_t size)
{
while(size--)
{
*in_out = Process(*in_out);
++in_out;
}
}
private:
float g_;
float gi_;
float state_;
FilterMode mode_;
};
} // namespace daisysp
#endif // DSY_ONEPOLE_H
+65
View File
@@ -0,0 +1,65 @@
#include "soap.h"
#include <cmath>
#define PI 3.141592653589793
using namespace daisysp;
void Soap::Init(float sample_rate)
{
soap_center_freq_ = 400.0;
soap_bandwidth_ = 50.0;
in_0_ = 0.0; // input x0
din_1_ = 0.0; // delayed input x1
din_2_ = 0.0; // delayed input x2
dout_1_ = 0.0; // delayed output y1
dout_2_ = 0.0; // delayed output y2
all_output_ = 0.0; // all pass output y0
out_bandpass_ = 0.0; // bandpass output
out_bandreject_ = 0.0; // bandreject output
sr_ = sample_rate;
return;
}
void Soap::Process(float in)
{
// recalculate the coefficients, later move this to a lookup table
float d = -std::cos(2.0 * PI * (soap_center_freq_ / sr_));
// tangent bandwidth
float tf = std::tan(PI * (soap_bandwidth_ / sr_));
// coefficient
float c = (tf - 1.0) / (tf + 1.0);
in_0_ = in;
all_output_ = -c * in_0_ + (d - d * c) * din_1_ + din_2_
- (d - d * c) * dout_1_ + c * dout_2_;
// move samples in delay for next sample
din_2_ = din_1_;
din_1_ = in_0_;
dout_2_ = dout_1_;
dout_1_ = all_output_;
// make factor -1.0 to create a bandpass
out_bandpass_ = (in_0_ + all_output_ * -1.0) * 0.5;
// make factor +1.0 to create a bandreject
out_bandreject_ = (in_0_ + all_output_ * 0.99) * 0.5;
return;
}
void Soap::SetCenterFreq(float f)
{
soap_center_freq_ = f;
return;
}
void Soap::SetFilterBandwidth(float b)
{
soap_bandwidth_ = b;
return;
}
+70
View File
@@ -0,0 +1,70 @@
/*
Copyright (c) 2020 Electrosmith, Corp, Brian Tice, Tom Erbe
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
namespace daisysp
{
/** Second Order All Pass Filter
This is from Tom Erbe's synth notes. The filter
can be configured to be a band pass or band reject
Ported by: Brian Tice
*/
class Soap
{
public:
Soap() {}
~Soap() {}
/** Initializes the filter
float sample_rate - sample rate of the audio engine being run, and the frequency that the Process function will be called.
*/
void Init(float sample_rate);
/**
Process the input signal, updating all of the outputs
*/
void Process(float in);
/**
Sets the center frequency of the filter.
*/
void SetCenterFreq(float f);
/**
Sets the low frequency threshold of the filter.
*/
void SetFilterBandwidth(float b);
/** Bandpass output
\return bandpass output of the filter
*/
inline float Bandpass() { return out_bandpass_; }
/** Bandreject output
\return bandreject output of the filter
*/
inline float Bandreject() { return out_bandreject_; }
private:
float soap_center_freq_;
float soap_bandwidth_;
float in_0_;
float din_1_;
float din_2_;
float dout_1_;
float dout_2_;
float all_output_;
float out_bandpass_;
float out_bandreject_;
float sr_;
};
} // namespace daisysp
+86
View File
@@ -0,0 +1,86 @@
#include <math.h>
#include "svf.h"
#include "dsp.h"
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
using namespace daisysp;
void Svf::Init(float sample_rate)
{
sr_ = sample_rate;
fc_ = 200.0f;
res_ = 0.5f;
drive_ = 0.5f;
pre_drive_ = 0.5f;
freq_ = 0.25f;
damp_ = 0.0f;
notch_ = 0.0f;
low_ = 0.0f;
high_ = 0.0f;
band_ = 0.0f;
peak_ = 0.0f;
input_ = 0.0f;
out_notch_ = 0.0f;
out_low_ = 0.0f;
out_high_ = 0.0f;
out_peak_ = 0.0f;
out_band_ = 0.0f;
fc_max_ = sr_ / 3.f;
}
void Svf::Process(float in)
{
input_ = in;
// first pass
notch_ = input_ - damp_ * band_;
low_ = low_ + freq_ * band_;
high_ = notch_ - low_;
band_ = freq_ * high_ + band_ - drive_ * band_ * band_ * band_;
// take first sample of output
out_low_ = 0.5f * low_;
out_high_ = 0.5f * high_;
out_band_ = 0.5f * band_;
out_peak_ = 0.5f * (low_ - high_);
out_notch_ = 0.5f * notch_;
// second pass
notch_ = input_ - damp_ * band_;
low_ = low_ + freq_ * band_;
high_ = notch_ - low_;
band_ = freq_ * high_ + band_ - drive_ * band_ * band_ * band_;
// average second pass outputs
out_low_ += 0.5f * low_;
out_high_ += 0.5f * high_;
out_band_ += 0.5f * band_;
out_peak_ += 0.5f * (low_ - high_);
out_notch_ += 0.5f * notch_;
}
void Svf::SetFreq(float f)
{
fc_ = fclamp(f, 1.0e-6, fc_max_);
// Set Internal Frequency for fc_
freq_ = 2.0f
* sinf(PI_F
* MIN(0.25f,
fc_ / (sr_ * 2.0f))); // fs*2 because double sampled
// recalculate damp
damp_ = MIN(2.0f * (1.0f - powf(res_, 0.25f)),
MIN(2.0f, 2.0f / freq_ - freq_ * 0.5f));
}
void Svf::SetRes(float r)
{
float res = fclamp(r, 0.f, 1.f);
res_ = res;
// recalculate damp
damp_ = MIN(2.0f * (1.0f - powf(res_, 0.25f)),
MIN(2.0f, 2.0f / freq_ - freq_ * 0.5f));
drive_ = pre_drive_ * res_;
}
void Svf::SetDrive(float d)
{
float drv = fclamp(d * 0.1f, 0.f, 1.f);
pre_drive_ = drv;
drive_ = pre_drive_ * res_;
}
+87
View File
@@ -0,0 +1,87 @@
/*
Copyright (c) 2020 Electrosmith, Corp
Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/
#pragma once
#ifndef DSY_SVF_H
#define DSY_SVF_H
namespace daisysp
{
/** Double Sampled, Stable State Variable Filter
Credit to Andrew Simper from musicdsp.org
This is his "State Variable Filter (Double Sampled, Stable)"
Additional thanks to Laurent de Soras for stability limit, and
Stefan Diedrichsen for the correct notch output
Ported by: Stephen Hensley
*/
class Svf
{
public:
Svf() {}
~Svf() {}
/** Initializes the filter
float sample_rate - sample rate of the audio engine being run, and the frequency that the Process function will be called.
*/
void Init(float sample_rate);
/**
Process the input signal, updating all of the outputs.
*/
void Process(float in);
/** sets the frequency of the cutoff frequency.
f must be between 0.0 and sample_rate / 3
*/
void SetFreq(float f);
/** sets the resonance of the filter.
Must be between 0.0 and 1.0 to ensure stability.
*/
void SetRes(float r);
/** sets the drive of the filter
affects the response of the resonance of the filter
*/
void SetDrive(float d);
/** lowpass output
\return low pass output of the filter
*/
inline float Low() { return out_low_; }
/** highpass output
\return high pass output of the filter
*/
inline float High() { return out_high_; }
/** bandpass output
\return band pass output of the filter
*/
inline float Band() { return out_band_; }
/** notchpass output
\return notch pass output of the filter
*/
inline float Notch() { return out_notch_; }
/** peak output
\return peak output of the filter
*/
inline float Peak() { return out_peak_; }
private:
float sr_, fc_, res_, drive_, freq_, damp_;
float notch_, low_, high_, band_, peak_;
float input_;
float out_low_, out_high_, out_band_, out_peak_, out_notch_;
float pre_drive_, fc_max_;
};
} // namespace daisysp
#endif