| @@ -87,9 +87,9 @@ T exponentialBipolar(T b, T x) { | |||||
| /** Useful for storing arrays of samples in ring buffers and casting them to `float*` to be used by interleaved processors, like SampleRateConverter */ | /** 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> | |||||
| template <size_t CHANNELS, typename T = float> | |||||
| struct Frame { | struct Frame { | ||||
| float samples[CHANNELS]; | |||||
| T samples[CHANNELS]; | |||||
| }; | }; | ||||
| @@ -6,128 +6,168 @@ namespace rack { | |||||
| namespace dsp { | namespace dsp { | ||||
| struct RCFilter { | |||||
| float c = 0.f; | |||||
| float xstate[1] = {}; | |||||
| float ystate[1] = {}; | |||||
| /** The simplest possible analog filter using an Euler solver. | |||||
| https://en.wikipedia.org/wiki/RC_circuit | |||||
| Use two RC filters in series for a bandpass filter. | |||||
| */ | |||||
| template <typename T = float> | |||||
| struct TRCFilter { | |||||
| T c = 0.f; | |||||
| T xstate[1]; | |||||
| T ystate[1]; | |||||
| TRCFilter() { | |||||
| reset(); | |||||
| } | |||||
| void reset() { | |||||
| xstate[0] = 0.f; | |||||
| ystate[0] = 0.f; | |||||
| } | |||||
| // `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s | |||||
| void setCutoff(float r) { | |||||
| /** Sets the cutoff frequency. | |||||
| `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s | |||||
| */ | |||||
| void setCutoff(T r) { | |||||
| c = 2.f / r; | c = 2.f / r; | ||||
| } | } | ||||
| void process(float x) { | |||||
| float y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c); | |||||
| void process(T x) { | |||||
| T y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c); | |||||
| xstate[0] = x; | xstate[0] = x; | ||||
| ystate[0] = y; | ystate[0] = y; | ||||
| } | } | ||||
| float lowpass() { | |||||
| T lowpass() { | |||||
| return ystate[0]; | return ystate[0]; | ||||
| } | } | ||||
| float highpass() { | |||||
| T highpass() { | |||||
| return xstate[0] - ystate[0]; | return xstate[0] - ystate[0]; | ||||
| } | } | ||||
| }; | }; | ||||
| typedef TRCFilter<> RCFilter; | |||||
| struct PeakFilter { | |||||
| float state = 0.f; | |||||
| float c = 0.f; | |||||
| /** Applies exponential smoothing to a signal with the ODE | |||||
| \f$ \frac{dy}{dt} = x \lambda \f$. | |||||
| */ | |||||
| template <typename T = float> | |||||
| struct TExponentialFilter { | |||||
| T out = 0.f; | |||||
| T lambda = 0.f; | |||||
| void reset() { | |||||
| out = 0.f; | |||||
| } | |||||
| /** Rate is lambda / sampleRate */ | |||||
| void setRate(float r) { | |||||
| c = 1.f - r; | |||||
| void setLambda(T lambda) { | |||||
| this->lambda = lambda; | |||||
| } | } | ||||
| void process(float x) { | |||||
| if (x > state) | |||||
| state = x; | |||||
| state *= c; | |||||
| T process(T deltaTime, T in) { | |||||
| T y = out + (in - out) * lambda * deltaTime; | |||||
| // If no change was made between the old and new output, assume T granularity is too small and snap output to input | |||||
| out = simd::ifelse(out == y, in, y); | |||||
| return out; | |||||
| } | } | ||||
| float peak() { | |||||
| return state; | |||||
| DEPRECATED T process(T in) { | |||||
| return process(1.f, in); | |||||
| } | } | ||||
| }; | }; | ||||
| typedef TExponentialFilter<> ExponentialFilter; | |||||
| /** Like ExponentialFilter but jumps immediately to higher values. | |||||
| */ | |||||
| template <typename T = float> | |||||
| struct TPeakFilter { | |||||
| T out = 0.f; | |||||
| T lambda = 0.f; | |||||
| void reset() { | |||||
| out = 0.f; | |||||
| } | |||||
| struct SlewLimiter { | |||||
| float rise = 0.f; | |||||
| float fall = 0.f; | |||||
| float out = NAN; | |||||
| float process(float deltaTime, float in) { | |||||
| if (std::isnan(out)) { | |||||
| out = in; | |||||
| } | |||||
| else if (out < in) { | |||||
| float y = out + rise * deltaTime; | |||||
| out = std::fmin(y, in); | |||||
| } | |||||
| else if (out > in) { | |||||
| float y = out - fall * deltaTime; | |||||
| out = std::fmax(y, in); | |||||
| } | |||||
| void setLambda(T lambda) { | |||||
| this->lambda = lambda; | |||||
| } | |||||
| T process(T deltaTime, T in) { | |||||
| T y = out + (in - out) * lambda * deltaTime; | |||||
| out = simd::fmax(y, in); | |||||
| return out; | return out; | ||||
| } | } | ||||
| DEPRECATED float process(float in) { | |||||
| return process(1.f, in); | |||||
| /** Use the return value of process() instead. */ | |||||
| DEPRECATED T peak() { | |||||
| return out; | |||||
| } | } | ||||
| DEPRECATED void setRiseFall(float rise, float fall) { | |||||
| this->rise = rise; | |||||
| this->fall = fall; | |||||
| /** Use setLambda() instead. */ | |||||
| DEPRECATED void setRate(T r) { | |||||
| lambda = 1.f - r; | |||||
| } | |||||
| DEPRECATED T process(T x) { | |||||
| return process(1.f, x); | |||||
| } | } | ||||
| }; | }; | ||||
| typedef TPeakFilter<> PeakFilter; | |||||
| template <typename T = float> | |||||
| struct TSlewLimiter { | |||||
| T out = 0.f; | |||||
| T rise = 0.f; | |||||
| T fall = 0.f; | |||||
| struct ExponentialSlewLimiter { | |||||
| float riseLambda = 0.f; | |||||
| float fallLambda = 0.f; | |||||
| float out = NAN; | |||||
| float process(float deltaTime, float in) { | |||||
| if (std::isnan(out)) { | |||||
| out = in; | |||||
| } | |||||
| else if (out < in) { | |||||
| float y = out + (in - out) * riseLambda * deltaTime; | |||||
| out = (out == y) ? in : y; | |||||
| } | |||||
| else if (out > in) { | |||||
| float y = out + (in - out) * fallLambda * deltaTime; | |||||
| out = (out == y) ? in : y; | |||||
| } | |||||
| void reset() { | |||||
| out = 0.f; | |||||
| } | |||||
| void setRiseFall(T rise, T fall) { | |||||
| this->rise = rise; | |||||
| this->fall = fall; | |||||
| } | |||||
| T process(T deltaTime, T in) { | |||||
| out = simd::clamp(in, out - fall * deltaTime, out + rise * deltaTime); | |||||
| return out; | return out; | ||||
| } | } | ||||
| DEPRECATED float process(float in) { | |||||
| DEPRECATED T process(T in) { | |||||
| return process(1.f, in); | return process(1.f, in); | ||||
| } | } | ||||
| }; | }; | ||||
| typedef TSlewLimiter<> SlewLimiter; | |||||
| /** Applies exponential smoothing to a signal with the ODE | |||||
| \f$ \frac{dy}{dt} = x \lambda \f$. | |||||
| */ | |||||
| struct ExponentialFilter { | |||||
| float out = 0.f; | |||||
| float lambda = 0.f; | |||||
| template <typename T = float> | |||||
| struct TExponentialSlewLimiter { | |||||
| T out = 0.f; | |||||
| T riseLambda = 0.f; | |||||
| T fallLambda = 0.f; | |||||
| void reset() { | void reset() { | ||||
| out = 0.f; | out = 0.f; | ||||
| } | } | ||||
| float process(float deltaTime, float in) { | |||||
| float y = out + (in - out) * lambda * deltaTime; | |||||
| // If no change was made between the old and new output, assume float granularity is too small and snap output to input | |||||
| if (out == y) | |||||
| out = in; | |||||
| else | |||||
| out = y; | |||||
| void setRiseFall(T riseLambda, T fallLambda) { | |||||
| this->riseLambda = riseLambda; | |||||
| this->fallLambda = fallLambda; | |||||
| } | |||||
| T process(T deltaTime, T in) { | |||||
| T lambda = simd::ifelse(in > out, riseLambda, fallLambda); | |||||
| T y = out + (in - out) * lambda * deltaTime; | |||||
| // If the change from the old out to the new out is too small for floats, set `in` directly. | |||||
| out = simd::ifelse(out == y, in, y); | |||||
| return out; | return out; | ||||
| } | } | ||||
| DEPRECATED float process(float in) { | |||||
| DEPRECATED T process(T in) { | |||||
| return process(1.f, in); | return process(1.f, in); | ||||
| } | } | ||||
| }; | }; | ||||
| typedef TExponentialSlewLimiter<> ExponentialSlewLimiter; | |||||
| } // namespace dsp | } // namespace dsp | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -26,9 +26,9 @@ For example, the following solves the system x''(t) = -x(t) using a fixed timest | |||||
| */ | */ | ||||
| /** Solves an ODE system using the 1st order Euler method */ | /** Solves an ODE system using the 1st order Euler method */ | ||||
| template <typename F> | |||||
| void stepEuler(float t, float dt, float x[], int len, F f) { | |||||
| float k[len]; | |||||
| template <typename T, typename F> | |||||
| void stepEuler(T t, T dt, T x[], int len, F f) { | |||||
| T k[len]; | |||||
| f(t, x, k); | f(t, x, k); | ||||
| for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
| @@ -37,11 +37,11 @@ void stepEuler(float t, float dt, float x[], int len, F f) { | |||||
| } | } | ||||
| /** Solves an ODE system using the 2nd order Runge-Kutta method */ | /** Solves an ODE system using the 2nd order Runge-Kutta method */ | ||||
| template <typename F> | |||||
| void stepRK2(float t, float dt, float x[], int len, F f) { | |||||
| float k1[len]; | |||||
| float k2[len]; | |||||
| float yi[len]; | |||||
| template <typename T, typename F> | |||||
| void stepRK2(T t, T dt, T x[], int len, F f) { | |||||
| T k1[len]; | |||||
| T k2[len]; | |||||
| T yi[len]; | |||||
| f(t, x, k1); | f(t, x, k1); | ||||
| @@ -56,13 +56,13 @@ void stepRK2(float t, float dt, float x[], int len, F f) { | |||||
| } | } | ||||
| /** Solves an ODE system using the 4th order Runge-Kutta method */ | /** Solves an ODE system using the 4th order Runge-Kutta method */ | ||||
| template <typename F> | |||||
| void stepRK4(float t, float dt, float x[], int len, F f) { | |||||
| float k1[len]; | |||||
| float k2[len]; | |||||
| float k3[len]; | |||||
| float k4[len]; | |||||
| float yi[len]; | |||||
| template <typename T, typename F> | |||||
| void stepRK4(T t, T dt, T x[], int len, F f) { | |||||
| T k1[len]; | |||||
| T k2[len]; | |||||
| T k3[len]; | |||||
| T k4[len]; | |||||
| T yi[len]; | |||||
| f(t, x, k1); | f(t, x, k1); | ||||
| @@ -10,8 +10,9 @@ namespace dsp { | |||||
| p: proportion from [0, 1], usually `i / (len - 1)` | p: proportion from [0, 1], usually `i / (len - 1)` | ||||
| https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows | https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows | ||||
| */ | */ | ||||
| inline float hann(float p) { | |||||
| return 0.5f * (1.f - std::cos(2*M_PI * p)); | |||||
| template <typename T> | |||||
| inline T hann(T p) { | |||||
| return 0.5f * (1.f - simd::cos(2*M_PI * p)); | |||||
| } | } | ||||
| /** Multiplies the Hann window by a signal `x` of length `len` in-place. */ | /** Multiplies the Hann window by a signal `x` of length `len` in-place. */ | ||||
| @@ -25,11 +26,12 @@ inline void hannWindow(float *x, int len) { | |||||
| https://en.wikipedia.org/wiki/Window_function#Blackman_window | https://en.wikipedia.org/wiki/Window_function#Blackman_window | ||||
| A typical alpha value is 0.16. | A typical alpha value is 0.16. | ||||
| */ | */ | ||||
| inline float blackman(float alpha, float p) { | |||||
| template <typename T> | |||||
| inline T blackman(T alpha, T p) { | |||||
| return | return | ||||
| + (1 - alpha) / 2.f | + (1 - alpha) / 2.f | ||||
| - 1 / 2.f * std::cos(2*M_PI * p) | |||||
| + alpha / 2.f * std::cos(4*M_PI * p); | |||||
| - 1 / 2.f * simd::cos(2*M_PI * p) | |||||
| + alpha / 2.f * simd::cos(4*M_PI * p); | |||||
| } | } | ||||
| inline void blackmanWindow(float alpha, float *x, int len) { | inline void blackmanWindow(float alpha, float *x, int len) { | ||||
| @@ -42,12 +44,13 @@ inline void blackmanWindow(float alpha, float *x, int len) { | |||||
| /** Blackman-Nuttall window function. | /** Blackman-Nuttall window function. | ||||
| https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Nuttall_window | https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Nuttall_window | ||||
| */ | */ | ||||
| inline float blackmanNuttall(float p) { | |||||
| template <typename T> | |||||
| inline T blackmanNuttall(T p) { | |||||
| return | return | ||||
| + 0.3635819f | + 0.3635819f | ||||
| - 0.4891775f * std::cos(2*M_PI * p) | |||||
| + 0.1365995f * std::cos(4*M_PI * p) | |||||
| - 0.0106411f * std::cos(6*M_PI * p); | |||||
| - 0.4891775f * simd::cos(2*M_PI * p) | |||||
| + 0.1365995f * simd::cos(4*M_PI * p) | |||||
| - 0.0106411f * simd::cos(6*M_PI * p); | |||||
| } | } | ||||
| inline void blackmanNuttallWindow(float *x, int len) { | inline void blackmanNuttallWindow(float *x, int len) { | ||||
| @@ -59,12 +62,13 @@ inline void blackmanNuttallWindow(float *x, int len) { | |||||
| /** Blackman-Harris window function. | /** Blackman-Harris window function. | ||||
| https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window | https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window | ||||
| */ | */ | ||||
| inline float blackmanHarris(float p) { | |||||
| template <typename T> | |||||
| inline T blackmanHarris(T p) { | |||||
| return | return | ||||
| + 0.35875f | + 0.35875f | ||||
| - 0.48829f * std::cos(2*M_PI * p) | |||||
| + 0.14128f * std::cos(4*M_PI * p) | |||||
| - 0.01168f * std::cos(6*M_PI * p); | |||||
| - 0.48829f * simd::cos(2*M_PI * p) | |||||
| + 0.14128f * simd::cos(4*M_PI * p) | |||||
| - 0.01168f * simd::cos(6*M_PI * p); | |||||
| } | } | ||||
| inline void blackmanHarrisWindow(float *x, int len) { | inline void blackmanHarrisWindow(float *x, int len) { | ||||
| @@ -52,6 +52,12 @@ inline f32_4 log10(f32_4 x) { | |||||
| return f32_4(sse_mathfun_log_ps(x.v)) / std::log(10.f); | return f32_4(sse_mathfun_log_ps(x.v)) / std::log(10.f); | ||||
| } | } | ||||
| using std::log2; | |||||
| inline f32_4 log2(f32_4 x) { | |||||
| return f32_4(sse_mathfun_log_ps(x.v)) / std::log(2.f); | |||||
| } | |||||
| using std::exp; | using std::exp; | ||||
| inline f32_4 exp(f32_4 x) { | inline f32_4 exp(f32_4 x) { | ||||
| @@ -118,6 +124,16 @@ inline f32_4 pow(float a, f32_4 b) { | |||||
| // Nonstandard functions | // Nonstandard functions | ||||
| inline float ifelse(bool cond, float a, float b) { | |||||
| return cond ? a : b; | |||||
| } | |||||
| /** Given a mask, returns a if mask is 0xffffffff per element, b if mask is 0x00000000 */ | |||||
| inline f32_4 ifelse(f32_4 mask, f32_4 a, f32_4 b) { | |||||
| return (a & mask) | andnot(mask, b); | |||||
| } | |||||
| /** Returns the approximate reciprocal square root. | /** Returns the approximate reciprocal square root. | ||||
| Much faster than `1/sqrt(x)`. | Much faster than `1/sqrt(x)`. | ||||
| */ | */ | ||||
| @@ -187,11 +187,6 @@ inline f32_4 andnot(const f32_4 &a, const f32_4 &b) { | |||||
| return f32_4(_mm_andnot_ps(a.v, b.v)); | return f32_4(_mm_andnot_ps(a.v, b.v)); | ||||
| } | } | ||||
| /** Given a mask, returns a if mask is 0xffffffff per element, b if mask is 0x00000000 */ | |||||
| inline f32_4 ifelse(const f32_4 &mask, const f32_4 &a, const f32_4 &b) { | |||||
| return (a & mask) | andnot(mask, b); | |||||
| } | |||||
| } // namespace simd | } // namespace simd | ||||
| } // namespace rack | } // namespace rack | ||||