| @@ -1,487 +1,16 @@ | |||
| #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 <string.h> | |||
| #include <samplerate.h> | |||
| #include <complex> | |||
| #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 | |||
| #include <stdint.h> | |||
| #include <stdlib.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) { | |||
| return (1.0 - frac) * a + frac * b; | |||
| return a + frac * (b - a); | |||
| } | |||
| inline float quadraticBipolar(float x) { | |||