@@ -5,7 +5,7 @@ | |||
namespace rack { | |||
/** Digital signal processing routines for plugins | |||
/** Digital signal processing routines | |||
*/ | |||
namespace dsp { | |||
@@ -69,5 +69,12 @@ inline float exponentialBipolar(float b, float x) { | |||
} | |||
/** 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 dsp | |||
} // namespace rack |
@@ -6,87 +6,64 @@ namespace rack { | |||
namespace dsp { | |||
/** Detects when a boolean changes from false to true */ | |||
struct BooleanTrigger { | |||
bool state = true; | |||
void reset() { | |||
state = true; | |||
} | |||
bool process(bool state) { | |||
bool triggered = (state && !this->state); | |||
this->state = state; | |||
return triggered; | |||
} | |||
}; | |||
/** Turns HIGH when value reaches 1.f, turns LOW when value reaches 0.f. */ | |||
struct SchmittTrigger { | |||
enum State { | |||
LOW, | |||
HIGH, | |||
UNKNOWN | |||
}; | |||
State state; | |||
SchmittTrigger() { | |||
reset(); | |||
} | |||
bool state = true; | |||
void reset() { | |||
state = UNKNOWN; | |||
state = true; | |||
} | |||
/** Updates the state of the Schmitt Trigger given a value. | |||
Returns true if triggered, i.e. the value increases from 0 to 1. | |||
If different trigger thresholds are needed, use | |||
process(math::rescale(in, low, high, 0.f, 1.f)) | |||
process(rescale(in, low, high, 0.f, 1.f)) | |||
for example. | |||
*/ | |||
bool process(float in) { | |||
switch (state) { | |||
case LOW: | |||
if (in >= 1.f) { | |||
state = HIGH; | |||
return true; | |||
} | |||
break; | |||
case HIGH: | |||
if (in <= 0.f) { | |||
state = LOW; | |||
} | |||
break; | |||
default: | |||
if (in >= 1.f) { | |||
state = HIGH; | |||
} | |||
else if (in <= 0.f) { | |||
state = LOW; | |||
} | |||
break; | |||
if (state) { | |||
// HIGH to LOW | |||
if (in <= 0.f) { | |||
state = false; | |||
} | |||
} | |||
else { | |||
// LOW to HIGH | |||
if (in >= 1.f) { | |||
state = true; | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
bool isHigh() { | |||
return state == HIGH; | |||
} | |||
}; | |||
/** Detects when a boolean changes from false to true */ | |||
struct BooleanTrigger { | |||
bool state; | |||
BooleanTrigger() { | |||
reset(); | |||
} | |||
void reset() { | |||
state = true; | |||
} | |||
bool process(bool state) { | |||
bool triggered = (state && !this->state); | |||
this->state = state; | |||
return triggered; | |||
return state; | |||
} | |||
}; | |||
/** When triggered, holds a high value for a specified time before going low again */ | |||
struct PulseGenerator { | |||
float remaining; | |||
PulseGenerator() { | |||
reset(); | |||
} | |||
float remaining = 0.f; | |||
/** Immediately disables the pulse */ | |||
void reset() { | |||
@@ -113,16 +90,13 @@ struct PulseGenerator { | |||
struct Timer { | |||
float time; | |||
Timer() { | |||
reset(); | |||
} | |||
float time = 0.f; | |||
void reset() { | |||
time = 0.f; | |||
} | |||
/** Returns the time since last reset or initialization. */ | |||
float process(float deltaTime) { | |||
time += deltaTime; | |||
return time; | |||
@@ -131,26 +105,22 @@ struct Timer { | |||
struct ClockDivider { | |||
int clock; | |||
int division = 1; | |||
ClockDivider() { | |||
reset(); | |||
} | |||
uint32_t clock = 0; | |||
uint32_t division = 1; | |||
void reset() { | |||
clock = 0; | |||
} | |||
void setDivision(int division) { | |||
void setDivision(uint32_t division) { | |||
this->division = division; | |||
} | |||
int getDivision() { | |||
uint32_t getDivision() { | |||
return division; | |||
} | |||
int getClock() { | |||
uint32_t getClock() { | |||
return clock; | |||
} | |||
@@ -7,13 +7,16 @@ namespace rack { | |||
namespace dsp { | |||
template<typename T> | |||
T *alignedNew(size_t len) { | |||
/** Allocates an array to 64-byte boundaries. | |||
Must call alignedFree() on the buffer to free. | |||
*/ | |||
template <typename T> | |||
T *alignedMalloc(size_t len) { | |||
return (T*) pffft_aligned_malloc(len * sizeof(T)); | |||
} | |||
template<typename T> | |||
void alignedDelete(T *p) { | |||
template <typename T> | |||
void alignedFree(T *p) { | |||
pffft_aligned_free(p); | |||
} | |||
@@ -21,6 +24,7 @@ void alignedDelete(T *p) { | |||
/** Real-valued FFT context. | |||
Wrapper for [PFFFT](https://bitbucket.org/jpommier/pffft/) | |||
`length` must be a multiple of 32. | |||
Buffers must be aligned to 16-byte boundaries, e.g. with alignedMalloc(). | |||
*/ | |||
struct RealFFT { | |||
PFFFT_Setup *setup; | |||
@@ -106,29 +106,20 @@ struct ExponentialSlewLimiter { | |||
\f$ \frac{dy}{dt} = x \lambda \f$. | |||
*/ | |||
struct ExponentialFilter { | |||
float out; | |||
float out = 0.f; | |||
float lambda = 0.f; | |||
ExponentialFilter() { | |||
reset(); | |||
} | |||
void reset() { | |||
out = NAN; | |||
out = 0.f; | |||
} | |||
float process(float deltaTime, float in) { | |||
if (std::isnan(out)) { | |||
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 { | |||
float y = out + (in - out) * lambda * deltaTime; | |||
// If no change was detected, assume float granularity is too small and snap output to input | |||
if (out == y) | |||
out = in; | |||
else | |||
out = y; | |||
} | |||
else | |||
out = y; | |||
return out; | |||
} | |||
@@ -1,17 +0,0 @@ | |||
#pragma once | |||
#include "dsp/common.hpp" | |||
namespace rack { | |||
namespace dsp { | |||
/** 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 dsp | |||
} // namespace rack |
@@ -15,7 +15,7 @@ https://www.cs.cmu.edu/~eli/papers/icmc01-hardsync.pdf | |||
void minBlepImpulse(int z, int o, float *output); | |||
template<int Z, int O> | |||
template <int Z, int O> | |||
struct MinBlepGenerator { | |||
float buf[2 * Z] = {}; | |||
int pos = 0; | |||
@@ -26,7 +26,7 @@ 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 */ | |||
template<typename F> | |||
template <typename F> | |||
void stepEuler(float t, float dt, float x[], int len, F f) { | |||
float k[len]; | |||
@@ -37,7 +37,7 @@ void stepEuler(float t, float dt, float x[], int len, F f) { | |||
} | |||
/** Solves an ODE system using the 2nd order Runge-Kutta method */ | |||
template<typename F> | |||
template <typename F> | |||
void stepRK2(float t, float dt, float x[], int len, F f) { | |||
float k1[len]; | |||
float k2[len]; | |||
@@ -56,7 +56,7 @@ void stepRK2(float t, float dt, float x[], int len, F f) { | |||
} | |||
/** Solves an ODE system using the 4th order Runge-Kutta method */ | |||
template<typename F> | |||
template <typename F> | |||
void stepRK4(float t, float dt, float x[], int len, F f) { | |||
float k1[len]; | |||
float k2[len]; | |||
@@ -1,6 +1,5 @@ | |||
#pragma once | |||
#include "dsp/common.hpp" | |||
#include "dsp/frame.hpp" | |||
#include "dsp/ringbuffer.hpp" | |||
#include "dsp/fir.hpp" | |||
#include "dsp/window.hpp" | |||
@@ -14,10 +13,10 @@ namespace dsp { | |||
/** Resamples by a fixed rational factor. */ | |||
template<int CHANNELS> | |||
template <int MAX_CHANNELS> | |||
struct SampleRateConverter { | |||
SpeexResamplerState *st = NULL; | |||
int channels = CHANNELS; | |||
int channels = MAX_CHANNELS; | |||
int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; | |||
int inRate = 44100; | |||
int outRate = 44100; | |||
@@ -31,9 +30,9 @@ struct SampleRateConverter { | |||
} | |||
} | |||
/** Sets the number of channels to actually process. This can be at most CHANNELS. */ | |||
/** Sets the number of channels to actually process. This can be at most MAX_CHANNELS. */ | |||
void setChannels(int channels) { | |||
assert(channels <= CHANNELS); | |||
assert(channels <= MAX_CHANNELS); | |||
if (channels == this->channels) | |||
return; | |||
this->channels = channels; | |||
@@ -68,13 +67,13 @@ struct SampleRateConverter { | |||
assert(st); | |||
assert(err == RESAMPLER_ERR_SUCCESS); | |||
speex_resampler_set_input_stride(st, CHANNELS); | |||
speex_resampler_set_output_stride(st, CHANNELS); | |||
speex_resampler_set_input_stride(st, MAX_CHANNELS); | |||
speex_resampler_set_output_stride(st, MAX_CHANNELS); | |||
} | |||
} | |||
/** `in` and `out` are interlaced with the number of channels */ | |||
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | |||
void process(const Frame<MAX_CHANNELS> *in, int *inFrames, Frame<MAX_CHANNELS> *out, int *outFrames) { | |||
assert(in); | |||
assert(inFrames); | |||
assert(out); | |||
@@ -95,7 +94,7 @@ struct SampleRateConverter { | |||
else { | |||
// Simply copy the buffer without conversion | |||
int frames = std::min(*inFrames, *outFrames); | |||
std::memcpy(out, in, frames * sizeof(Frame<CHANNELS>)); | |||
std::memcpy(out, in, frames * sizeof(Frame<MAX_CHANNELS>)); | |||
*inFrames = frames; | |||
*outFrames = frames; | |||
} | |||
@@ -104,7 +103,7 @@ struct SampleRateConverter { | |||
/** Downsamples by an integer factor. */ | |||
template<int OVERSAMPLE, int QUALITY> | |||
template <int OVERSAMPLE, int QUALITY> | |||
struct Decimator { | |||
float inBuffer[OVERSAMPLE*QUALITY]; | |||
float kernel[OVERSAMPLE*QUALITY]; | |||
@@ -139,7 +138,7 @@ struct Decimator { | |||
/** Upsamples by an integer factor. */ | |||
template<int OVERSAMPLE, int QUALITY> | |||
template <int OVERSAMPLE, int QUALITY> | |||
struct Upsampler { | |||
float inBuffer[QUALITY]; | |||
float kernel[OVERSAMPLE*QUALITY]; | |||
@@ -54,14 +54,10 @@ struct VuMeter2 { | |||
}; | |||
Mode mode = PEAK; | |||
/** Either the smoothed peak or the mean-square of the brightness, depending on the mode. */ | |||
float v; | |||
float v = 0.f; | |||
/** Inverse time constant in 1/seconds */ | |||
float lambda = 30.f; | |||
VuMeter2() { | |||
reset(); | |||
} | |||
void reset() { | |||
v = 0.f; | |||
} | |||
@@ -14,7 +14,7 @@ inline float hann(float p) { | |||
return 0.5f * (1.f - std::cos(2*M_PI * p)); | |||
} | |||
/** Applies the Hann window to a signal `x`. */ | |||
/** Multiplies the Hann window by a signal `x` of length `len` in-place. */ | |||
inline void hannWindow(float *x, int len) { | |||
for (int i = 0; i < len; i++) { | |||
x[i] *= hann((float) i / (len - 1)); | |||
@@ -36,7 +36,7 @@ inline int clamp(int x, int a, int b) { | |||
If `b < a`, switches the two values. | |||
*/ | |||
inline int clampSafe(int x, int a, int b) { | |||
return clamp(x, std::min(a, b), std::max(a, b)); | |||
return (a <= b) ? clamp(x, a, b) : clamp(x, b, a); | |||
} | |||
/** Euclidean modulus. Always returns `0 <= mod < b`. | |||
@@ -101,14 +101,14 @@ inline float clamp(float x, float a, float b) { | |||
If `b < a`, switches the two values. | |||
*/ | |||
inline float clampSafe(float x, float a, float b) { | |||
return clamp(x, std::fmin(a, b), std::fmax(a, b)); | |||
return (a <= b) ? clamp(x, a, b) : clamp(x, b, a); | |||
} | |||
/** Returns 1 for positive numbers, -1 for negative numbers, and 0 for zero. | |||
See https://en.wikipedia.org/wiki/Sign_function. | |||
*/ | |||
inline float sgn(float x) { | |||
return x > 0.f ? 1.f : x < 0.f ? -1.f : 0.f; | |||
return x > 0.f ? 1.f : (x < 0.f ? -1.f : 0.f); | |||
} | |||
/** Converts -0.f to 0.f. Leaves all other values unchanged. */ | |||
@@ -119,9 +119,12 @@ inline float normalizeZero(float x) { | |||
/** Euclidean modulus. Always returns `0 <= mod < b`. | |||
See https://en.wikipedia.org/wiki/Euclidean_division. | |||
*/ | |||
inline float eucMod(float a, float base) { | |||
float mod = std::fmod(a, base); | |||
return (mod >= 0.f) ? mod : mod + base; | |||
inline float eucMod(float a, float b) { | |||
int mod = std::fmod(a, b); | |||
if (mod < 0.f) { | |||
mod += b; | |||
} | |||
return mod; | |||
} | |||
/** Returns whether `a` is within epsilon distance from `b`. */ | |||
@@ -131,7 +134,7 @@ inline bool isNear(float a, float b, float epsilon = 1e-6f) { | |||
/** If the magnitude of `x` if less than epsilon, return 0. */ | |||
inline float chop(float x, float epsilon = 1e-6f) { | |||
return isNear(x, 0.f, epsilon) ? 0.f : x; | |||
return std::fabs(x) <= epsilon ? 0.f : x; | |||
} | |||
inline float rescale(float x, float xMin, float xMax, float yMin, float yMax) { | |||
@@ -155,9 +158,9 @@ inline float interpolateLinear(const float *p, float x) { | |||
Arguments may be the same pointers. | |||
Example: | |||
cmultf(&ar, &ai, ar, ai, br, bi); | |||
cmultf(ar, ai, br, bi, &ar, &ai); | |||
*/ | |||
inline void complexMult(float *cr, float *ci, float ar, float ai, float br, float bi) { | |||
inline void complexMult(float ar, float ai, float br, float bi, float *cr, float *ci) { | |||
*cr = ar * br - ai * bi; | |||
*ci = ar * bi + ai * br; | |||
} | |||
@@ -202,6 +205,9 @@ struct Vec { | |||
float dot(Vec b) const { | |||
return x * b.x + y * b.y; | |||
} | |||
float arg() const { | |||
return std::atan2(y, x); | |||
} | |||
float norm() const { | |||
return std::hypot(x, y); | |||
} | |||
@@ -229,6 +235,9 @@ struct Vec { | |||
Vec max(Vec b) const { | |||
return Vec(std::fmax(x, b.x), std::fmax(y, b.y)); | |||
} | |||
Vec abs() const { | |||
return Vec(std::fabs(x), std::fabs(y)); | |||
} | |||
Vec round() const { | |||
return Vec(std::round(x), std::round(y)); | |||
} | |||
@@ -323,7 +332,7 @@ struct Rect { | |||
r.pos.y = math::clampSafe(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y); | |||
return r; | |||
} | |||
/** Expands this Rect to contain `b`. */ | |||
/** Returns the bounding box of the union of `this` and `b`. */ | |||
Rect expand(Rect b) const { | |||
Rect r; | |||
r.pos.x = std::fmin(pos.x, b.pos.x); | |||
@@ -84,7 +84,6 @@ | |||
#include "dsp/fft.hpp" | |||
#include "dsp/filter.hpp" | |||
#include "dsp/fir.hpp" | |||
#include "dsp/frame.hpp" | |||
#include "dsp/minblep.hpp" | |||
#include "dsp/ode.hpp" | |||
#include "dsp/resampler.hpp" | |||
@@ -45,6 +45,7 @@ DEPRECATED inline float rescalef(float x, float a, float b, float yMin, float yM | |||
DEPRECATED inline float crossf(float a, float b, float frac) {return crossfade(a, b, frac);} | |||
DEPRECATED inline float interpf(const float *p, float x) {return interpolateLinear(p, x);} | |||
DEPRECATED inline void cmultf(float *cr, float *ci, float ar, float ai, float br, float bi) {return complexMult(cr, ci, ar, ai, br, bi);} | |||
DEPRECATED inline void complexMult(float *cr, float *ci, float ar, float ai, float br, float bi) {complexMult(ar, ai, br, bi, cr, ci);} | |||
//////////////////// | |||
// random | |||
@@ -10,7 +10,7 @@ namespace dsp { | |||
void minBlepImpulse(int z, int o, float *output) { | |||
// Symmetric sinc array with `z` zero-crossings on each side | |||
int n = 2 * z * o; | |||
float *x = alignedNew<float>(n); | |||
float *x = alignedMalloc<float>(n); | |||
for (int i = 0; i < n; i++) { | |||
float p = math::rescale((float) i, 0.f, (float) (n - 1), (float) -z, (float) z); | |||
x[i] = sinc(p); | |||
@@ -20,7 +20,7 @@ void minBlepImpulse(int z, int o, float *output) { | |||
blackmanHarrisWindow(x, n); | |||
// Real cepstrum | |||
float *fx = alignedNew<float>(2*n); | |||
float *fx = alignedMalloc<float>(2*n); | |||
RealFFT rfft(n); | |||
rfft.rfft(x, fx); | |||
// fx = log(abs(fx)) | |||
@@ -73,8 +73,8 @@ void minBlepImpulse(int z, int o, float *output) { | |||
std::memcpy(output, x, n * sizeof(float)); | |||
// Cleanup | |||
alignedDelete(x); | |||
alignedDelete(fx); | |||
alignedFree(x); | |||
alignedFree(fx); | |||
} | |||