- only downsample a source wave generated at higher sample rates - switch to Pade approximation of sin - new panel SVG from Befaco - fix LED logic Moved all chowdsp library code into specific header. Updated .jsontags/v1.1.0^2
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"slug": "Befaco", | "slug": "Befaco", | ||||
"version": "1.0.2", | |||||
"version": "1.1.0", | |||||
"license": "GPL-3.0-or-later", | "license": "GPL-3.0-or-later", | ||||
"name": "Befaco", | "name": "Befaco", | ||||
"author": "VCV", | "author": "VCV", | ||||
@@ -97,6 +97,7 @@ | |||||
"tags": [ | "tags": [ | ||||
"Envelope generator", | "Envelope generator", | ||||
"Mixer", | "Mixer", | ||||
"Poly", | |||||
"Hardware clone" | "Hardware clone" | ||||
] | ] | ||||
}, | }, | ||||
@@ -108,20 +109,30 @@ | |||||
"tags": [ | "tags": [ | ||||
"Mixer", | "Mixer", | ||||
"Hardware clone", | "Hardware clone", | ||||
"Poly", | |||||
"VCA" | "VCA" | ||||
] | ] | ||||
}, | }, | ||||
{ | { | ||||
"slug": "ChoppingKinky", | "slug": "ChoppingKinky", | ||||
"name": "ChoppingKinky", | "name": "ChoppingKinky", | ||||
"description": "", | |||||
"tags": [] | |||||
"description": "Voltage controllable, dual channel wavefolder", | |||||
"tags": [ | |||||
"Dual", | |||||
"Hardware clone", | |||||
"Voltage-controlled amplifier", | |||||
"Waveshaper" | |||||
] | |||||
}, | }, | ||||
{ | { | ||||
"slug": "Kickall", | "slug": "Kickall", | ||||
"name": "Kickall", | "name": "Kickall", | ||||
"description": "", | "description": "", | ||||
"tags": [] | |||||
"tags": [ | |||||
"Drum", | |||||
"Hardware clone", | |||||
"Synth voice" | |||||
] | |||||
} | } | ||||
] | ] | ||||
} | } |
@@ -1,21 +1,10 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "Common.hpp" | #include "Common.hpp" | ||||
#include "ChowDSP.hpp" | |||||
static const size_t BUF_LEN = 32; | static const size_t BUF_LEN = 32; | ||||
template <typename T> | |||||
T sin2pi_pade_05_5_4(T x) { | |||||
x -= 0.5f; | |||||
return (T(-6.283185307) * x + T(33.19863968) * simd::pow(x, 3) - T(32.44191367) * simd::pow(x, 5)) | |||||
/ (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4)); | |||||
} | |||||
template <typename T> | |||||
T tanh_pade(T x) { | |||||
T x2 = x * x; | |||||
T q = 12.f + x2; | |||||
return 12.f * x * q / (36.f * x2 + q * q); | |||||
} | |||||
static float foldResponse(float inputGain, float G) { | static float foldResponse(float inputGain, float G) { | ||||
return std::tanh(inputGain) + G * std::sin(M_PI * inputGain); | return std::tanh(inputGain) + G * std::sin(M_PI * inputGain); | ||||
@@ -69,7 +58,7 @@ struct ChoppingKinky : Module { | |||||
bool outputAToChopp; | bool outputAToChopp; | ||||
float previousA = 0.0; | float previousA = 0.0; | ||||
VariableOversampling<> oversampler[NUM_CHANNELS]; | |||||
chowdsp::VariableOversampling<> oversampler[NUM_CHANNELS]; | |||||
int oversamplingIndex = 2; | int oversamplingIndex = 2; | ||||
dsp::BiquadFilter blockDCFilter; | dsp::BiquadFilter blockDCFilter; | ||||
@@ -0,0 +1,420 @@ | |||||
#pragma once | |||||
namespace chowdsp { | |||||
// code taken from https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/, commit 21701fb | |||||
// * AAFilter.hpp | |||||
// * VariableOversampling.hpp | |||||
// * oversampling.hpp | |||||
// * iir.hpp | |||||
template <int ORDER, typename T = float> | |||||
struct IIRFilter { | |||||
/** transfer function numerator coefficients: b_0, b_1, etc.*/ | |||||
T b[ORDER] = {}; | |||||
/** transfer function denominator coefficients: a_0, a_1, etc.*/ | |||||
T a[ORDER] = {}; | |||||
/** filter state */ | |||||
T z[ORDER]; | |||||
IIRFilter() { | |||||
reset(); | |||||
} | |||||
void reset() { | |||||
std::fill(z, &z[ORDER], 0.0f); | |||||
} | |||||
void setCoefficients(const T* b, const T* a) { | |||||
for (int i = 0; i < ORDER; i++) { | |||||
this->b[i] = b[i]; | |||||
} | |||||
for (int i = 1; i < ORDER; i++) { | |||||
this->a[i] = a[i]; | |||||
} | |||||
} | |||||
template <int N = ORDER> | |||||
inline typename std::enable_if <N == 2, T>::type process(T x) noexcept { | |||||
T y = z[1] + x * b[0]; | |||||
z[1] = x * b[1] - y * a[1]; | |||||
return y; | |||||
} | |||||
template <int N = ORDER> | |||||
inline typename std::enable_if <N == 3, T>::type process(T x) noexcept { | |||||
T y = z[1] + x * b[0]; | |||||
z[1] = z[2] + x * b[1] - y * a[1]; | |||||
z[2] = x * b[2] - y * a[2]; | |||||
return y; | |||||
} | |||||
template <int N = ORDER> | |||||
inline typename std::enable_if < (N > 3), T >::type process(T x) noexcept { | |||||
T y = z[1] + x * b[0]; | |||||
for (int i = 1; i < ORDER - 1; ++i) | |||||
z[i] = z[i + 1] + x * b[i] - y * a[i]; | |||||
z[ORDER - 1] = x * b[ORDER - 1] - y * a[ORDER - 1]; | |||||
return y; | |||||
} | |||||
/** Computes the complex transfer function $H(s)$ at a particular frequency | |||||
s: normalized angular frequency equal to $2 \pi f / f_{sr}$ ($\pi$ is the Nyquist frequency) | |||||
*/ | |||||
std::complex<T> getTransferFunction(T s) { | |||||
// Compute sum(a_k z^-k) / sum(b_k z^-k) where z = e^(i s) | |||||
std::complex<T> bSum(b[0], 0); | |||||
std::complex<T> aSum(1, 0); | |||||
for (int i = 1; i < ORDER; i++) { | |||||
T p = -i * s; | |||||
std::complex<T> z(simd::cos(p), simd::sin(p)); | |||||
bSum += b[i] * z; | |||||
aSum += a[i - 1] * z; | |||||
} | |||||
return bSum / aSum; | |||||
} | |||||
T getFrequencyResponse(T f) { | |||||
return simd::abs(getTransferFunction(2 * M_PI * f)); | |||||
} | |||||
T getFrequencyPhase(T f) { | |||||
return simd::arg(getTransferFunction(2 * M_PI * f)); | |||||
} | |||||
}; | |||||
template <typename T = float> | |||||
struct TBiquadFilter : IIRFilter<3, T> { | |||||
enum Type { | |||||
LOWPASS, | |||||
HIGHPASS, | |||||
LOWSHELF, | |||||
HIGHSHELF, | |||||
BANDPASS, | |||||
PEAK, | |||||
NOTCH, | |||||
NUM_TYPES | |||||
}; | |||||
TBiquadFilter() { | |||||
setParameters(LOWPASS, 0.f, 0.f, 1.f); | |||||
} | |||||
/** Calculates and sets the biquad transfer function coefficients. | |||||
f: normalized frequency (cutoff frequency / sample rate), must be less than 0.5 | |||||
Q: quality factor | |||||
V: gain | |||||
*/ | |||||
void setParameters(Type type, float f, float Q, float V) { | |||||
float K = std::tan(M_PI * f); | |||||
switch (type) { | |||||
case LOWPASS: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = K * K * norm; | |||||
this->b[1] = 2.f * this->b[0]; | |||||
this->b[2] = this->b[0]; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
case HIGHPASS: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = norm; | |||||
this->b[1] = -2.f * this->b[0]; | |||||
this->b[2] = this->b[0]; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
case LOWSHELF: { | |||||
float sqrtV = std::sqrt(V); | |||||
if (V >= 1.f) { | |||||
float norm = 1.f / (1.f + M_SQRT2 * K + K * K); | |||||
this->b[0] = (1.f + M_SQRT2 * sqrtV * K + V * K * K) * norm; | |||||
this->b[1] = 2.f * (V * K * K - 1.f) * norm; | |||||
this->b[2] = (1.f - M_SQRT2 * sqrtV * K + V * K * K) * norm; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
} | |||||
else { | |||||
float norm = 1.f / (1.f + M_SQRT2 / sqrtV * K + K * K / V); | |||||
this->b[0] = (1.f + M_SQRT2 * K + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - 1) * norm; | |||||
this->b[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
this->a[1] = 2.f * (K * K / V - 1.f) * norm; | |||||
this->a[2] = (1.f - M_SQRT2 / sqrtV * K + K * K / V) * norm; | |||||
} | |||||
} break; | |||||
case HIGHSHELF: { | |||||
float sqrtV = std::sqrt(V); | |||||
if (V >= 1.f) { | |||||
float norm = 1.f / (1.f + M_SQRT2 * K + K * K); | |||||
this->b[0] = (V + M_SQRT2 * sqrtV * K + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - V) * norm; | |||||
this->b[2] = (V - M_SQRT2 * sqrtV * K + K * K) * norm; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
} | |||||
else { | |||||
float norm = 1.f / (1.f / V + M_SQRT2 / sqrtV * K + K * K); | |||||
this->b[0] = (1.f + M_SQRT2 * K + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->b[2] = (1.f - M_SQRT2 * K + K * K) * norm; | |||||
this->a[1] = 2.f * (K * K - 1.f / V) * norm; | |||||
this->a[2] = (1.f / V - M_SQRT2 / sqrtV * K + K * K) * norm; | |||||
} | |||||
} break; | |||||
case BANDPASS: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = K / Q * norm; | |||||
this->b[1] = 0.f; | |||||
this->b[2] = -this->b[0]; | |||||
this->a[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
case PEAK: { | |||||
float c = 1.0f / K; | |||||
float phi = c * c; | |||||
float Knum = c / Q; | |||||
float Kdenom = Knum; | |||||
if (V > 1.0f) | |||||
Knum *= V; | |||||
else | |||||
Kdenom /= V; | |||||
float norm = phi + Kdenom + 1.0; | |||||
this->b[0] = (phi + Knum + 1.0f) / norm; | |||||
this->b[1] = 2.0f * (1.0f - phi) / norm; | |||||
this->b[2] = (phi - Knum + 1.0f) / norm; | |||||
this->a[1] = 2.0f * (1.0f - phi) / norm; | |||||
this->a[2] = (phi - Kdenom + 1.0f) / norm; | |||||
} break; | |||||
case NOTCH: { | |||||
float norm = 1.f / (1.f + K / Q + K * K); | |||||
this->b[0] = (1.f + K * K) * norm; | |||||
this->b[1] = 2.f * (K * K - 1.f) * norm; | |||||
this->b[2] = this->b[0]; | |||||
this->a[1] = this->b[1]; | |||||
this->a[2] = (1.f - K / Q + K * K) * norm; | |||||
} break; | |||||
default: break; | |||||
} | |||||
} | |||||
}; | |||||
typedef TBiquadFilter<> BiquadFilter; | |||||
/** | |||||
High-order filter to be used for anti-aliasing or anti-imaging. | |||||
The template parameter N should be 1/2 the desired filter order. | |||||
Currently uses an 2*N-th order Butterworth filter. | |||||
source: https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/AAFilter.hpp | |||||
*/ | |||||
template<int N> | |||||
class AAFilter { | |||||
public: | |||||
AAFilter() = default; | |||||
/** Calculate Q values for a Butterworth filter of a given order */ | |||||
static std::vector<float> calculateButterQs(int order) { | |||||
const int lim = int (order / 2); | |||||
std::vector<float> Qs; | |||||
for (int k = 1; k <= lim; ++k) { | |||||
auto b = -2.0f * std::cos((2.0f * k + order - 1) * 3.14159 / (2.0f * order)); | |||||
Qs.push_back(1.0f / b); | |||||
} | |||||
std::reverse(Qs.begin(), Qs.end()); | |||||
return Qs; | |||||
} | |||||
/** | |||||
* Resets the filter to process at a new sample rate. | |||||
* | |||||
* @param sampleRate: The base (i.e. pre-oversampling) sample rate of the audio being processed | |||||
* @param osRatio: The oversampling ratio at which the filter is being used | |||||
*/ | |||||
void reset(float sampleRate, int osRatio) { | |||||
float fc = 0.98f * (sampleRate / 2.0f); | |||||
auto Qs = calculateButterQs(2 * N); | |||||
for (int i = 0; i < N; ++i) | |||||
filters[i].setParameters(BiquadFilter::Type::LOWPASS, fc / (osRatio * sampleRate), Qs[i], 1.0f); | |||||
} | |||||
inline float process(float x) noexcept { | |||||
for (int i = 0; i < N; ++i) | |||||
x = filters[i].process(x); | |||||
return x; | |||||
} | |||||
private: | |||||
BiquadFilter filters[N]; | |||||
}; | |||||
/** | |||||
* Base class for oversampling of any order | |||||
* source: https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/oversampling.hpp | |||||
*/ | |||||
class BaseOversampling { | |||||
public: | |||||
BaseOversampling() = default; | |||||
virtual ~BaseOversampling() {} | |||||
/** Resets the oversampler for processing at some base sample rate */ | |||||
virtual void reset(float /*baseSampleRate*/) = 0; | |||||
/** Upsample a single input sample and update the oversampled buffer */ | |||||
virtual void upsample(float) noexcept = 0; | |||||
/** Output a downsampled output sample from the current oversampled buffer */ | |||||
virtual float downsample() noexcept = 0; | |||||
/** Returns a pointer to the oversampled buffer */ | |||||
virtual float* getOSBuffer() noexcept = 0; | |||||
}; | |||||
/** | |||||
Class to implement an oversampled process. | |||||
To use, create an object and prepare using `reset()`. | |||||
Then use the following code to process samples: | |||||
@code | |||||
oversample.upsample(x); | |||||
for(int k = 0; k < ratio; k++) | |||||
oversample.osBuffer[k] = processSample(oversample.osBuffer[k]); | |||||
float y = oversample.downsample(); | |||||
@endcode | |||||
*/ | |||||
template<int ratio, int filtN = 4> | |||||
class Oversampling : public BaseOversampling { | |||||
public: | |||||
Oversampling() = default; | |||||
virtual ~Oversampling() {} | |||||
void reset(float baseSampleRate) override { | |||||
aaFilter.reset(baseSampleRate, ratio); | |||||
aiFilter.reset(baseSampleRate, ratio); | |||||
std::fill(osBuffer, &osBuffer[ratio], 0.0f); | |||||
} | |||||
inline void upsample(float x) noexcept override { | |||||
osBuffer[0] = ratio * x; | |||||
std::fill(&osBuffer[1], &osBuffer[ratio], 0.0f); | |||||
for (int k = 0; k < ratio; k++) | |||||
osBuffer[k] = aiFilter.process(osBuffer[k]); | |||||
} | |||||
inline float downsample() noexcept override { | |||||
float y = 0.0f; | |||||
for (int k = 0; k < ratio; k++) | |||||
y = aaFilter.process(osBuffer[k]); | |||||
return y; | |||||
} | |||||
inline float* getOSBuffer() noexcept override { | |||||
return osBuffer; | |||||
} | |||||
float osBuffer[ratio]; | |||||
private: | |||||
AAFilter<filtN> aaFilter; // anti-aliasing filter | |||||
AAFilter<filtN> aiFilter; // anti-imaging filter | |||||
}; | |||||
/** | |||||
Class to implement an oversampled process, with variable | |||||
oversampling factor. To use, create an object, set the oversampling | |||||
factor using `setOversamplingindex()` and prepare using `reset()`. | |||||
Then use the following code to process samples: | |||||
@code | |||||
oversample.upsample(x); | |||||
float* osBuffer = oversample.getOSBuffer(); | |||||
for(int k = 0; k < ratio; k++) | |||||
osBuffer[k] = processSample(osBuffer[k]); | |||||
float y = oversample.downsample(); | |||||
@endcode | |||||
source (modified): https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/VariableOversampling.hpp | |||||
*/ | |||||
template<int filtN = 4> | |||||
class VariableOversampling { | |||||
public: | |||||
VariableOversampling() = default; | |||||
/** Prepare the oversampler to process audio at a given sample rate */ | |||||
void reset(float sampleRate) { | |||||
for (auto* os : oss) | |||||
os->reset(sampleRate); | |||||
} | |||||
/** Sets the oversampling factor as 2^idx */ | |||||
void setOversamplingIndex(int newIdx) { | |||||
osIdx = newIdx; | |||||
} | |||||
/** Returns the oversampling index */ | |||||
int getOversamplingIndex() const noexcept { | |||||
return osIdx; | |||||
} | |||||
/** Upsample a single input sample and update the oversampled buffer */ | |||||
inline void upsample(float x) noexcept { | |||||
oss[osIdx]->upsample(x); | |||||
} | |||||
/** Output a downsampled output sample from the current oversampled buffer */ | |||||
inline float downsample() noexcept { | |||||
return oss[osIdx]->downsample(); | |||||
} | |||||
/** Returns a pointer to the oversampled buffer */ | |||||
inline float* getOSBuffer() noexcept { | |||||
return oss[osIdx]->getOSBuffer(); | |||||
} | |||||
/** Returns the current oversampling factor */ | |||||
int getOversamplingRatio() const noexcept { | |||||
return 1 << osIdx; | |||||
} | |||||
private: | |||||
enum { | |||||
NumOS = 5, // number of oversampling options | |||||
}; | |||||
int osIdx = 0; | |||||
Oversampling < 1 << 0, filtN > os0; // 1x | |||||
Oversampling < 1 << 1, filtN > os1; // 2x | |||||
Oversampling < 1 << 2, filtN > os2; // 4x | |||||
Oversampling < 1 << 3, filtN > os3; // 8x | |||||
Oversampling < 1 << 4, filtN > os4; // 16x | |||||
BaseOversampling* oss[NumOS] = { &os0, &os1, &os2, &os3, &os4 }; | |||||
}; | |||||
} |
@@ -45,203 +45,16 @@ struct BefacoInputPort : app::SvgPort { | |||||
} | } | ||||
}; | }; | ||||
/** | |||||
High-order filter to be used for anti-aliasing or anti-imaging. | |||||
The template parameter N should be 1/2 the desired filter order. | |||||
Currently uses an 2*N-th order Butterworth filter. | |||||
source: https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/AAFilter.hpp | |||||
*/ | |||||
template<int N> | |||||
class AAFilter { | |||||
public: | |||||
AAFilter() = default; | |||||
/** Calculate Q values for a Butterworth filter of a given order */ | |||||
static std::vector<float> calculateButterQs(int order) { | |||||
const int lim = int (order / 2); | |||||
std::vector<float> Qs; | |||||
for (int k = 1; k <= lim; ++k) { | |||||
auto b = -2.0f * std::cos((2.0f * k + order - 1) * 3.14159 / (2.0f * order)); | |||||
Qs.push_back(1.0f / b); | |||||
} | |||||
std::reverse(Qs.begin(), Qs.end()); | |||||
return Qs; | |||||
} | |||||
/** | |||||
* Resets the filter to process at a new sample rate. | |||||
* | |||||
* @param sampleRate: The base (i.e. pre-oversampling) sample rate of the audio being processed | |||||
* @param osRatio: The oversampling ratio at which the filter is being used | |||||
*/ | |||||
void reset(float sampleRate, int osRatio) { | |||||
float fc = 0.98f * (sampleRate / 2.0f); | |||||
auto Qs = calculateButterQs(2 * N); | |||||
for (int i = 0; i < N; ++i) | |||||
filters[i].setParameters(dsp::BiquadFilter::Type::LOWPASS, fc / (osRatio * sampleRate), Qs[i], 1.0f); | |||||
} | |||||
inline float process(float x) noexcept { | |||||
for (int i = 0; i < N; ++i) | |||||
x = filters[i].process(x); | |||||
return x; | |||||
} | |||||
private: | |||||
dsp::BiquadFilter filters[N]; | |||||
}; | |||||
/** | |||||
* Base class for oversampling of any order | |||||
* source: https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/oversampling.hpp | |||||
*/ | |||||
class BaseOversampling { | |||||
public: | |||||
BaseOversampling() = default; | |||||
virtual ~BaseOversampling() {} | |||||
/** Resets the oversampler for processing at some base sample rate */ | |||||
virtual void reset(float /*baseSampleRate*/) = 0; | |||||
/** Upsample a single input sample and update the oversampled buffer */ | |||||
virtual void upsample(float) noexcept = 0; | |||||
/** Output a downsampled output sample from the current oversampled buffer */ | |||||
virtual float downsample() noexcept = 0; | |||||
/** Returns a pointer to the oversampled buffer */ | |||||
virtual float* getOSBuffer() noexcept = 0; | |||||
}; | |||||
/** | |||||
Class to implement an oversampled process. | |||||
To use, create an object and prepare using `reset()`. | |||||
Then use the following code to process samples: | |||||
@code | |||||
oversample.upsample(x); | |||||
for(int k = 0; k < ratio; k++) | |||||
oversample.osBuffer[k] = processSample(oversample.osBuffer[k]); | |||||
float y = oversample.downsample(); | |||||
@endcode | |||||
*/ | |||||
template<int ratio, int filtN = 4> | |||||
class Oversampling : public BaseOversampling { | |||||
public: | |||||
Oversampling() = default; | |||||
virtual ~Oversampling() {} | |||||
void reset(float baseSampleRate) override { | |||||
aaFilter.reset(baseSampleRate, ratio); | |||||
aiFilter.reset(baseSampleRate, ratio); | |||||
std::fill(osBuffer, &osBuffer[ratio], 0.0f); | |||||
} | |||||
inline void upsample(float x) noexcept override { | |||||
osBuffer[0] = ratio * x; | |||||
std::fill(&osBuffer[1], &osBuffer[ratio], 0.0f); | |||||
for (int k = 0; k < ratio; k++) | |||||
osBuffer[k] = aiFilter.process(osBuffer[k]); | |||||
} | |||||
inline float downsample() noexcept override { | |||||
float y = 0.0f; | |||||
for (int k = 0; k < ratio; k++) | |||||
y = aaFilter.process(osBuffer[k]); | |||||
return y; | |||||
} | |||||
inline float* getOSBuffer() noexcept override { | |||||
return osBuffer; | |||||
} | |||||
float osBuffer[ratio]; | |||||
private: | |||||
AAFilter<filtN> aaFilter; // anti-aliasing filter | |||||
AAFilter<filtN> aiFilter; // anti-imaging filter | |||||
}; | |||||
/** | |||||
Class to implement an oversampled process, with variable | |||||
oversampling factor. To use, create an object, set the oversampling | |||||
factor using `setOversamplingindex()` and prepare using `reset()`. | |||||
Then use the following code to process samples: | |||||
@code | |||||
oversample.upsample(x); | |||||
float* osBuffer = oversample.getOSBuffer(); | |||||
for(int k = 0; k < ratio; k++) | |||||
osBuffer[k] = processSample(osBuffer[k]); | |||||
float y = oversample.downsample(); | |||||
@endcode | |||||
source (modified): https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/VariableOversampling.hpp | |||||
*/ | |||||
template<int filtN = 4> | |||||
class VariableOversampling { | |||||
public: | |||||
VariableOversampling() = default; | |||||
/** Prepare the oversampler to process audio at a given sample rate */ | |||||
void reset(float sampleRate) { | |||||
for (auto* os : oss) | |||||
os->reset(sampleRate); | |||||
} | |||||
/** Sets the oversampling factor as 2^idx */ | |||||
void setOversamplingIndex(int newIdx) { | |||||
osIdx = newIdx; | |||||
} | |||||
/** Returns the oversampling index */ | |||||
int getOversamplingIndex() const noexcept { | |||||
return osIdx; | |||||
} | |||||
/** Upsample a single input sample and update the oversampled buffer */ | |||||
inline void upsample(float x) noexcept { | |||||
oss[osIdx]->upsample(x); | |||||
} | |||||
/** Output a downsampled output sample from the current oversampled buffer */ | |||||
inline float downsample() noexcept { | |||||
return oss[osIdx]->downsample(); | |||||
} | |||||
/** Returns a pointer to the oversampled buffer */ | |||||
inline float* getOSBuffer() noexcept { | |||||
return oss[osIdx]->getOSBuffer(); | |||||
} | |||||
/** Returns the current oversampling factor */ | |||||
int getOversamplingRatio() const noexcept { | |||||
return 1 << osIdx; | |||||
} | |||||
private: | |||||
enum { | |||||
NumOS = 5, // number of oversampling options | |||||
}; | |||||
int osIdx = 0; | |||||
Oversampling < 1 << 0, filtN > os0; // 1x | |||||
Oversampling < 1 << 1, filtN > os1; // 2x | |||||
Oversampling < 1 << 2, filtN > os2; // 4x | |||||
Oversampling < 1 << 3, filtN > os3; // 8x | |||||
Oversampling < 1 << 4, filtN > os4; // 16x | |||||
BaseOversampling* oss[NumOS] = { &os0, &os1, &os2, &os3, &os4 }; | |||||
}; | |||||
template <typename T> | |||||
T sin2pi_pade_05_5_4(T x) { | |||||
x -= 0.5f; | |||||
return (T(-6.283185307) * x + T(33.19863968) * simd::pow(x, 3) - T(32.44191367) * simd::pow(x, 5)) | |||||
/ (1 + T(1.296008659) * simd::pow(x, 2) + T(0.7028072946) * simd::pow(x, 4)); | |||||
} | |||||
template <typename T> | |||||
T tanh_pade(T x) { | |||||
T x2 = x * x; | |||||
T q = 12.f + x2; | |||||
return 12.f * x * q / (36.f * x2 + q * q); | |||||
} |
@@ -1,5 +1,6 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "Common.hpp" | #include "Common.hpp" | ||||
#include "ChowDSP.hpp" | |||||
struct ADEnvelope { | struct ADEnvelope { | ||||
@@ -13,6 +14,7 @@ struct ADEnvelope { | |||||
float env = 0.f; | float env = 0.f; | ||||
float attackTime = 0.1, decayTime = 0.1; | float attackTime = 0.1, decayTime = 0.1; | ||||
// TODO: add shape, and generlise to use with Percall | |||||
ADEnvelope() { }; | ADEnvelope() { }; | ||||
void process(const float& sampleTime) { | void process(const float& sampleTime) { | ||||
@@ -98,8 +100,8 @@ struct Kickall : Module { | |||||
static constexpr float FREQ_A0 = 27.5f; | static constexpr float FREQ_A0 = 27.5f; | ||||
static constexpr float FREQ_B2 = 123.471f; | static constexpr float FREQ_B2 = 123.471f; | ||||
static const int UPSAMPLE = 4; | |||||
Oversampling<UPSAMPLE> oversampler; | |||||
static const int UPSAMPLE = 8; | |||||
chowdsp::Oversampling<UPSAMPLE> oversampler; | |||||
float shaperBuf[UPSAMPLE]; | float shaperBuf[UPSAMPLE]; | ||||
void process(const ProcessArgs& args) override { | void process(const ProcessArgs& args) override { | ||||
@@ -124,19 +126,8 @@ struct Kickall : Module { | |||||
float freq = params[TUNE_PARAM].getValue(); | float freq = params[TUNE_PARAM].getValue(); | ||||
freq *= std::pow(2, inputs[TUNE_INPUT].getVoltage()); | freq *= std::pow(2, inputs[TUNE_INPUT].getVoltage()); | ||||
//float kickFrequency = std::max(50.0f, params[TUNE_PARAM].getValue() + bendRange * bend * pitch.env); | |||||
float kickFrequency = std::max(50.0f, freq + bendRange * bend * pitch.env); | |||||
phase += args.sampleTime * kickFrequency; | |||||
if (phase > 1.0f) { | |||||
phase -= 1.0f; | |||||
} | |||||
// TODO: faster approximation | |||||
float wave = std::sin(2.0 * M_PI * phase); | |||||
oversampler.upsample(wave); | |||||
float* inputBuf = oversampler.getOSBuffer(); | |||||
const float kickFrequency = std::max(10.0f, freq + bendRange * bend * pitch.env); | |||||
const float phaseInc = args.sampleTime * kickFrequency / UPSAMPLE; | |||||
float shape = clamp(inputs[SHAPE_INPUT].getVoltage() / 2.f + params[SHAPE_PARAM].getValue() * 5.0f, 0.0f, 10.0f); | float shape = clamp(inputs[SHAPE_INPUT].getVoltage() / 2.f + params[SHAPE_PARAM].getValue() * 5.0f, 0.0f, 10.0f); | ||||
shape = clamp(shape, 0.f, 5.0f) * 0.2f; | shape = clamp(shape, 0.f, 5.0f) * 0.2f; | ||||
@@ -144,14 +135,19 @@ struct Kickall : Module { | |||||
const float shapeB = (1.0f - shape) / (1.0f + shape); | const float shapeB = (1.0f - shape) / (1.0f + shape); | ||||
const float shapeA = (4.0f * shape) / ((1.0f - shape) * (1.0f + shape)); | const float shapeA = (4.0f * shape) / ((1.0f - shape) * (1.0f + shape)); | ||||
float* inputBuf = oversampler.getOSBuffer(); | |||||
for (int i = 0; i < UPSAMPLE; ++i) { | for (int i = 0; i < UPSAMPLE; ++i) { | ||||
phase += phaseInc; | |||||
phase -= std::floor(phase); | |||||
inputBuf[i] = sin2pi_pade_05_5_4(phase); | |||||
inputBuf[i] = inputBuf[i] * (shapeA + shapeB) / ((std::abs(inputBuf[i]) * shapeA) + shapeB); | inputBuf[i] = inputBuf[i] * (shapeA + shapeB) / ((std::abs(inputBuf[i]) * shapeA) + shapeB); | ||||
} | } | ||||
float out = volume.env * oversampler.downsample() * 5.0f * vcaGain; | float out = volume.env * oversampler.downsample() * 5.0f * vcaGain; | ||||
outputs[OUT_OUTPUT].setVoltage(out); | outputs[OUT_OUTPUT].setVoltage(out); | ||||
lights[ENV_LIGHT].setBrightness(volume.stage); | |||||
lights[ENV_LIGHT].setBrightness(volume.env); | |||||
} | } | ||||
}; | }; | ||||
@@ -164,22 +160,22 @@ struct KickallWidget : ModuleWidget { | |||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0))); | ||||
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | ||||
addParam(createParamCentered<BefacoTinyKnobGrey>(mm2px(Vec(8.76, 29.068)), module, Kickall::TUNE_PARAM)); | |||||
addParam(createParamCentered<BefacoPush>(mm2px(Vec(22.651, 29.068)), module, Kickall::TRIGG_BUTTON_PARAM)); | |||||
addParam(createParamCentered<Davies1900hLargeGreyKnob>(mm2px(Vec(15.781, 49.278)), module, Kickall::SHAPE_PARAM)); | |||||
addParam(createParam<BefacoSlidePot>(mm2px(Vec(19.913, 64.004)), module, Kickall::DECAY_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(8.977, 71.626)), module, Kickall::TIME_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnobWhite>(mm2px(Vec(8.977, 93.549)), module, Kickall::BEND_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnobGrey>(mm2px(Vec(8.472, 28.97)), module, Kickall::TUNE_PARAM)); | |||||
addParam(createParamCentered<BefacoPush>(mm2px(Vec(22.409, 29.159)), module, Kickall::TRIGG_BUTTON_PARAM)); | |||||
addParam(createParamCentered<Davies1900hLargeGreyKnob>(mm2px(Vec(15.526, 49.292)), module, Kickall::SHAPE_PARAM)); | |||||
addParam(createParam<BefacoSlidePot>(mm2px(Vec(19.667, 63.897)), module, Kickall::DECAY_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 71.803)), module, Kickall::TIME_PARAM)); | |||||
addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(8.521, 93.517)), module, Kickall::BEND_PARAM)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.715, 14.651)), module, Kickall::TRIGG_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.748, 14.651)), module, Kickall::VOLUME_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.697, 113.286)), module, Kickall::TUNE_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.794, 113.286)), module, Kickall::SHAPE_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.891, 113.286)), module, Kickall::DECAY_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.501, 14.508)), module, Kickall::VOLUME_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 14.536)), module, Kickall::TRIGG_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.525, 113.191)), module, Kickall::DECAY_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.499, 113.208)), module, Kickall::TUNE_INPUT)); | |||||
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.485, 113.208)), module, Kickall::SHAPE_INPUT)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.781, 14.651)), module, Kickall::OUT_OUTPUT)); | |||||
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.525, 14.52)), module, Kickall::OUT_OUTPUT)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(15.723, 34.971)), module, Kickall::ENV_LIGHT)); | |||||
addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(15.535, 34.943)), module, Kickall::ENV_LIGHT)); | |||||
} | } | ||||
}; | }; | ||||