Added DaisySP
This commit is contained in:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user