| @@ -1,487 +1,16 @@ | |||||
| #pragma once | #pragma once | ||||
| #include "dsp/frame.hpp" | |||||
| #include "dsp/fft.hpp" | |||||
| #include "dsp/ode.hpp" | |||||
| #include "dsp/ringbuffer.hpp" | |||||
| #include "dsp/samplerate.hpp" | |||||
| #include "dsp/fir.hpp" | |||||
| #include "dsp/decimator.hpp" | |||||
| #include "dsp/filter.hpp" | |||||
| #include <assert.h> | #include <assert.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include <samplerate.h> | |||||
| #include <complex> | #include <complex> | ||||
| #include "math.hpp" | #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 <size_t CHANNELS> | |||||
| 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<float> *tw; | |||||
| SimpleFFT(int N, bool inverse) : N(N) { | |||||
| tw = new std::complex<float>[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<float>(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<float> *x, std::complex<float> *y, int s=1) { | |||||
| for (int k = 0; k < N/s; k++) { | |||||
| std::complex<float> 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<float> *x, std::complex<float> *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<float> *e = new std::complex<float>[N/(2*s)]; // Even inputs | |||||
| std::complex<float> *o = new std::complex<float>[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 <typename T, int S> | |||||
| 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 <typename T, int S> | |||||
| 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 <typename T, size_t S, size_t N> | |||||
| 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<int CHANNELS> | |||||
| 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<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *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<int OVERSAMPLE, int QUALITY> | |||||
| struct Decimator { | |||||
| DoubleRingBuffer<float, OVERSAMPLE*QUALITY> 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<int ZERO_CROSSINGS> | |||||
| 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 | |||||
| @@ -0,0 +1,34 @@ | |||||
| #pragma once | |||||
| #include "dsp/fir.hpp" | |||||
| namespace rack { | |||||
| template<int OVERSAMPLE, int QUALITY> | |||||
| struct Decimator { | |||||
| DoubleRingBuffer<float, OVERSAMPLE*QUALITY> 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; | |||||
| } | |||||
| }; | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,63 @@ | |||||
| #pragma once | |||||
| #include <complex> | |||||
| namespace rack { | |||||
| /** 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<float> *tw; | |||||
| SimpleFFT(int N, bool inverse) : N(N) { | |||||
| tw = new std::complex<float>[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<float>(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<float> *x, std::complex<float> *y, int s=1) { | |||||
| for (int k = 0; k < N/s; k++) { | |||||
| std::complex<float> 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<float> *x, std::complex<float> *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<float> *e = new std::complex<float>[N/(2*s)]; // Even inputs | |||||
| std::complex<float> *o = new std::complex<float>[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; | |||||
| } | |||||
| }; | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,89 @@ | |||||
| #pragma once | |||||
| #include "math.hpp" | |||||
| namespace rack { | |||||
| 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 | |||||
| @@ -0,0 +1,37 @@ | |||||
| #pragma once | |||||
| namespace rack { | |||||
| /** 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); | |||||
| } | |||||
| } | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,14 @@ | |||||
| #pragma once | |||||
| #include <stdlib.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 <size_t CHANNELS> | |||||
| struct Frame { | |||||
| float samples[CHANNELS]; | |||||
| }; | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,37 @@ | |||||
| #pragma once | |||||
| #include "math.hpp" | |||||
| namespace rack { | |||||
| // Pre-made minBLEP samples in minBLEP.cpp | |||||
| extern const float minblep_16_32[]; | |||||
| template<int ZERO_CROSSINGS> | |||||
| 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; | |||||
| } | |||||
| }; | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,48 @@ | |||||
| #pragma once | |||||
| namespace rack { | |||||
| 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; | |||||
| } | |||||
| } | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,163 @@ | |||||
| #pragma once | |||||
| #include <string.h> | |||||
| #include "math.hpp" | |||||
| namespace rack { | |||||
| /** A simple cyclic buffer. | |||||
| S must be a power of 2. | |||||
| push() is constant time O(1) | |||||
| */ | |||||
| template <typename T, int S> | |||||
| 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 <typename T, int S> | |||||
| 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 <typename T, size_t S, size_t N> | |||||
| 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; | |||||
| } | |||||
| }; | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,49 @@ | |||||
| #pragma once | |||||
| #include <assert.h> | |||||
| #include <samplerate.h> | |||||
| namespace rack { | |||||
| template<int CHANNELS> | |||||
| 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<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *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); | |||||
| } | |||||
| }; | |||||
| } // namespace rack | |||||
| @@ -1,5 +1,7 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <stdint.h> | |||||
| #include <stdlib.h> | |||||
| #include <math.h> | #include <math.h> | ||||
| @@ -76,7 +78,7 @@ inline float rescalef(float x, float xMin, float xMax, float yMin, float yMax) { | |||||
| } | } | ||||
| inline float crossf(float a, float b, float frac) { | inline float crossf(float a, float b, float frac) { | ||||
| return (1.0 - frac) * a + frac * b; | |||||
| return a + frac * (b - a); | |||||
| } | } | ||||
| inline float quadraticBipolar(float x) { | inline float quadraticBipolar(float x) { | ||||