- 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)); | |||
| } | |||
| }; | |||