#pragma once #include #include #include #include #include "math.hpp" #include "../ext/dr_libs/dr_wav.h" namespace rack { /** Useful for storing arrays of samples in ring buffers and casting them to `float*` to be used by interleaved processors, like SampleRateConverter */ template struct Frame { float samples[CHANNELS]; }; /** Simple FFT implementation If you need something fast, use pffft, KissFFT, etc instead. The size N must be a power of 2 */ struct SimpleFFT { int N; /** Twiddle factors e^(2pi k/N), interleaved complex numbers */ std::complex *tw; SimpleFFT(int N, bool inverse) : N(N) { tw = new std::complex[N]; for (int i = 0; i < N; i++) { float phase = 2*M_PI * (float)i / N; if (inverse) phase *= -1.0; tw[i] = std::exp(std::complex(0.0, phase)); } } ~SimpleFFT() { delete[] tw; } /** Reference naive implementation x and y are arrays of interleaved complex numbers y must be size N/s s is the stride factor for the x array which divides the size N */ void dft(const std::complex *x, std::complex *y, int s=1) { for (int k = 0; k < N/s; k++) { std::complex yk = 0.0; for (int n = 0; n < N; n += s) { int m = (n*k) % N; yk += x[n] * tw[m]; } y[k] = yk; } } void fft(const std::complex *x, std::complex *y, int s=1) { if (N/s <= 2) { // Naive DFT is faster than further FFT recursions at this point dft(x, y, s); return; } std::complex *e = new std::complex[N/(2*s)]; // Even inputs std::complex *o = new std::complex[N/(2*s)]; // Odd inputs fft(x, e, 2*s); fft(x + s, o, 2*s); for (int k = 0; k < N/(2*s); k++) { int m = (k*s) % N; y[k] = e[k] + tw[m] * o[k]; y[k + N/(2*s)] = e[k] - tw[m] * o[k]; } delete[] e; delete[] o; } }; typedef void (*stepCallback)(float x, const float y[], float dydt[]); /** Solve an ODE system using the 1st order Euler method */ inline void stepEuler(stepCallback f, float x, float dx, float y[], int len) { float k[len]; f(x, y, k); for (int i = 0; i < len; i++) { y[i] += dx * k[i]; } } /** Solve an ODE system using the 4th order Runge-Kutta method */ inline void stepRK4(stepCallback f, float x, float dx, float y[], int len) { float k1[len]; float k2[len]; float k3[len]; float k4[len]; float yi[len]; f(x, y, k1); for (int i = 0; i < len; i++) { yi[i] = y[i] + k1[i] * dx / 2.0; } f(x + dx / 2.0, yi, k2); for (int i = 0; i < len; i++) { yi[i] = y[i] + k2[i] * dx / 2.0; } f(x + dx / 2.0, yi, k3); for (int i = 0; i < len; i++) { yi[i] = y[i] + k3[i] * dx; } f(x + dx, yi, k4); for (int i = 0; i < len; i++) { y[i] += dx * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0; } } /** A simple cyclic buffer. S must be a power of 2. push() is constant time O(1) */ template struct RingBuffer { T data[S]; int start = 0; int end = 0; int mask(int i) const { return i & (S - 1); } void push(T t) { int i = mask(end++); data[i] = t; } T shift() { return data[mask(start++)]; } void clear() { start = end; } bool empty() const { return start >= end; } bool full() const { return end - start >= S; } int size() const { return end - start; } int capacity() const { return S - size(); } }; /** A cyclic buffer which maintains a valid linear array of size S by keeping a copy of the buffer in adjacent memory. S must be a power of 2. push() is constant time O(2) relative to RingBuffer */ template struct DoubleRingBuffer { T data[S*2]; int start = 0; int end = 0; int mask(int i) const { return i & (S - 1); } void push(T t) { int i = mask(end++); data[i] = t; data[i + S] = t; } T shift() { return data[mask(start++)]; } void clear() { start = end; } bool empty() const { return start >= end; } bool full() const { return end - start >= S; } int size() const { return end - start; } int capacity() const { return S - size(); } /** Returns a pointer to S consecutive elements for appending. If any data is appended, you must call endIncr afterwards. Pointer is invalidated when any other method is called. */ T *endData() { return &data[mask(end)]; } void endIncr(int n) { int e = mask(end); int e1 = e + n; int e2 = mini(e1, S); // Copy data forward memcpy(data + S + e, data + e, sizeof(T) * (e2 - e)); if (e1 > S) { // Copy data backward from the doubled block to the main block memcpy(data, data + S, sizeof(T) * (e1 - S)); } end += n; } /** Returns a pointer to S consecutive elements for consumption If any data is consumed, call startIncr afterwards. */ const T *startData() const { return &data[mask(start)]; } void startIncr(int n) { start += n; } }; /** A cyclic buffer which maintains a valid linear array of size S by sliding along a larger block of size N. The linear array of S elements are moved back to the start of the block once it outgrows past the end. This happens every N - S pushes, so the push() time is O(1 + S / (N - S)). For example, a float buffer of size 64 in a block of size 1024 is nearly as efficient as RingBuffer. */ template struct AppleRingBuffer { T data[N]; size_t start = 0; size_t end = 0; void push(T t) { data[end++] = t; if (end >= N) { // move end block to beginning memmove(data, &data[N - S], sizeof(T) * S); start -= N - S; end = S; } } T shift() { return data[start++]; } bool empty() const { return start >= end; } bool full() const { return end - start >= S; } size_t size() const { return end - start; } /** Returns a pointer to S consecutive elements for appending, requesting to append n elements. */ T *endData(size_t n) { // TODO return &data[end]; } /** Returns a pointer to S consecutive elements for consumption If any data is consumed, call startIncr afterwards. */ const T *startData() const { return &data[start]; } void startIncr(size_t n) { // This is valid as long as n < S start += n; } }; template struct SampleRateConverter { SRC_STATE *state; SRC_DATA data; SampleRateConverter() { int error; state = src_new(SRC_SINC_FASTEST, CHANNELS, &error); assert(!error); data.src_ratio = 1.0; data.end_of_input = false; } ~SampleRateConverter() { src_delete(state); } /** output_sample_rate / input_sample_rate */ void setRatio(float r) { src_set_ratio(state, r); data.src_ratio = r; } void setRatioSmooth(float r) { data.src_ratio = r; } /** `in` and `out` are interlaced with the number of channels */ void process(const Frame *in, int *inFrames, Frame *out, int *outFrames) { // Old versions of libsamplerate use float* here instead of const float* data.data_in = (float*) in; data.input_frames = *inFrames; data.data_out = (float*) out; data.output_frames = *outFrames; src_process(state, &data); *inFrames = data.input_frames_used; *outFrames = data.output_frames_gen; } void reset() { src_reset(state); } }; /** Perform a direct convolution x[-len + 1] to x[0] must be defined */ inline float convolve(const float *x, const float *kernel, int len) { float y = 0.0; for (int i = 0; i < len; i++) { y += x[-i] * kernel[i]; } return y; } inline void blackmanHarrisWindow(float *x, int n) { const float a0 = 0.35875; const float a1 = 0.48829; const float a2 = 0.14128; const float a3 = 0.01168; for (int i = 0; i < n; i++) { x[i] *= a0 - a1 * cosf(2 * M_PI * i / (n - 1)) + a2 * cosf(4 * M_PI * i / (n - 1)) - a3 * cosf(6 * M_PI * i / (n - 1)); } } inline void boxcarFIR(float *x, int n, float cutoff) { for (int i = 0; i < n; i++) { float t = (float)i / (n - 1) * 2.0 - 1.0; x[i] = sincf(t * n * cutoff); } } template struct Decimator { DoubleRingBuffer inBuffer; float kernel[OVERSAMPLE*QUALITY]; Decimator(float cutoff = 0.9) { boxcarFIR(kernel, OVERSAMPLE*QUALITY, cutoff * 0.5 / OVERSAMPLE); blackmanHarrisWindow(kernel, OVERSAMPLE*QUALITY); // The sum of the kernel should be 1 float sum = 0.0; for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { sum += kernel[i]; } for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { kernel[i] /= sum; } } float process(float *in) { memcpy(inBuffer.endData(), in, OVERSAMPLE*sizeof(float)); inBuffer.endIncr(OVERSAMPLE); float out = convolve(inBuffer.endData() + OVERSAMPLE*QUALITY, kernel, OVERSAMPLE*QUALITY); // Ignore the ring buffer's start position return out; } }; // Pre-made minBLEP samples in minBLEP.cpp extern const float minblep_16_32[]; template struct MinBLEP { float buf[2*ZERO_CROSSINGS] = {}; int pos = 0; const float *minblep; int oversample; /** Places a discontinuity with magnitude dx at -1 < p <= 0 relative to the current frame */ void jump(float p, float dx) { if (p <= -1 || 0 < p) return; for (int j = 0; j < 2*ZERO_CROSSINGS; j++) { float minblepIndex = ((float)j - p) * oversample; int index = (pos + j) % (2*ZERO_CROSSINGS); buf[index] += dx * (-1.0 + interpf(minblep, minblepIndex)); } } float shift() { float v = buf[pos]; buf[pos] = 0.0; pos = (pos + 1) % (2*ZERO_CROSSINGS); return v; } }; struct RCFilter { float c = 0.0; float xstate[1] = {}; float ystate[1] = {}; // `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s void setCutoff(float r) { c = 2.0 / r; } void process(float x) { float y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c); xstate[0] = x; ystate[0] = y; } float lowpass() { return ystate[0]; } float highpass() { return xstate[0] - ystate[0]; } }; struct PeakFilter { float state = 0.0; float c = 0.0; /** Rate is lambda / sampleRate */ void setRate(float r) { c = 1.0 - r; } void process(float x) { if (x > state) state = x; state *= c; } float peak() { return state; } }; struct SlewLimiter { float rise = 1.0; float fall = 1.0; float out = 0.0; float process(float in) { float delta = clampf(in - out, -fall, rise); out += delta; return out; } }; struct SchmittTrigger { /** 0 unknown, 1 low, 2 high */ int state = 0; float low = 0.0; float high = 1.0; void setThresholds(float low, float high) { this->low = low; this->high = high; } /** Returns true if triggered */ bool process(float in) { bool triggered = false; if (in >= high) { if (state == 1) triggered = true; state = 2; } else if (in <= low) { state = 1; } return triggered; } void reset() { state = 0; } }; } // namespace rack