- 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", | |||
"version": "1.0.2", | |||
"version": "1.1.0", | |||
"license": "GPL-3.0-or-later", | |||
"name": "Befaco", | |||
"author": "VCV", | |||
@@ -97,6 +97,7 @@ | |||
"tags": [ | |||
"Envelope generator", | |||
"Mixer", | |||
"Poly", | |||
"Hardware clone" | |||
] | |||
}, | |||
@@ -108,20 +109,30 @@ | |||
"tags": [ | |||
"Mixer", | |||
"Hardware clone", | |||
"Poly", | |||
"VCA" | |||
] | |||
}, | |||
{ | |||
"slug": "ChoppingKinky", | |||
"name": "ChoppingKinky", | |||
"description": "", | |||
"tags": [] | |||
"description": "Voltage controllable, dual channel wavefolder", | |||
"tags": [ | |||
"Dual", | |||
"Hardware clone", | |||
"Voltage-controlled amplifier", | |||
"Waveshaper" | |||
] | |||
}, | |||
{ | |||
"slug": "Kickall", | |||
"name": "Kickall", | |||
"description": "", | |||
"tags": [] | |||
"tags": [ | |||
"Drum", | |||
"Hardware clone", | |||
"Synth voice" | |||
] | |||
} | |||
] | |||
} |
@@ -1,21 +1,10 @@ | |||
#include "plugin.hpp" | |||
#include "Common.hpp" | |||
#include "ChowDSP.hpp" | |||
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) { | |||
return std::tanh(inputGain) + G * std::sin(M_PI * inputGain); | |||
@@ -69,7 +58,7 @@ struct ChoppingKinky : Module { | |||
bool outputAToChopp; | |||
float previousA = 0.0; | |||
VariableOversampling<> oversampler[NUM_CHANNELS]; | |||
chowdsp::VariableOversampling<> oversampler[NUM_CHANNELS]; | |||
int oversamplingIndex = 2; | |||
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 "Common.hpp" | |||
#include "ChowDSP.hpp" | |||
struct ADEnvelope { | |||
@@ -13,6 +14,7 @@ struct ADEnvelope { | |||
float env = 0.f; | |||
float attackTime = 0.1, decayTime = 0.1; | |||
// TODO: add shape, and generlise to use with Percall | |||
ADEnvelope() { }; | |||
void process(const float& sampleTime) { | |||
@@ -98,8 +100,8 @@ struct Kickall : Module { | |||
static constexpr float FREQ_A0 = 27.5f; | |||
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]; | |||
void process(const ProcessArgs& args) override { | |||
@@ -124,19 +126,8 @@ struct Kickall : Module { | |||
float freq = params[TUNE_PARAM].getValue(); | |||
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); | |||
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 shapeA = (4.0f * shape) / ((1.0f - shape) * (1.0f + shape)); | |||
float* inputBuf = oversampler.getOSBuffer(); | |||
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); | |||
} | |||
float out = volume.env * oversampler.downsample() * 5.0f * vcaGain; | |||
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, 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)); | |||
} | |||
}; | |||