Browse Source

Clean up DSP headers.

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
10fa872dc6
14 changed files with 105 additions and 146 deletions
  1. +8
    -1
      include/dsp/common.hpp
  2. +42
    -72
      include/dsp/digital.hpp
  3. +8
    -4
      include/dsp/fft.hpp
  4. +7
    -16
      include/dsp/filter.hpp
  5. +0
    -17
      include/dsp/frame.hpp
  6. +1
    -1
      include/dsp/minblep.hpp
  7. +3
    -3
      include/dsp/ode.hpp
  8. +10
    -11
      include/dsp/resampler.hpp
  9. +1
    -5
      include/dsp/vumeter.hpp
  10. +1
    -1
      include/dsp/window.hpp
  11. +19
    -10
      include/math.hpp
  12. +0
    -1
      include/rack.hpp
  13. +1
    -0
      include/rack0.hpp
  14. +4
    -4
      src/dsp/minblep.cpp

+ 8
- 1
include/dsp/common.hpp View File

@@ -5,7 +5,7 @@
namespace rack { namespace rack {




/** Digital signal processing routines for plugins
/** Digital signal processing routines
*/ */
namespace dsp { 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 dsp
} // namespace rack } // namespace rack

+ 42
- 72
include/dsp/digital.hpp View File

@@ -6,87 +6,64 @@ namespace rack {
namespace dsp { 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. */ /** Turns HIGH when value reaches 1.f, turns LOW when value reaches 0.f. */
struct SchmittTrigger { struct SchmittTrigger {
enum State {
LOW,
HIGH,
UNKNOWN
};
State state;

SchmittTrigger() {
reset();
}
bool state = true;


void reset() { void reset() {
state = UNKNOWN;
state = true;
} }


/** Updates the state of the Schmitt Trigger given a value. /** Updates the state of the Schmitt Trigger given a value.
Returns true if triggered, i.e. the value increases from 0 to 1. Returns true if triggered, i.e. the value increases from 0 to 1.
If different trigger thresholds are needed, use 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. for example.
*/ */
bool process(float in) { 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; return false;
} }


bool isHigh() { 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 */ /** When triggered, holds a high value for a specified time before going low again */
struct PulseGenerator { struct PulseGenerator {
float remaining;

PulseGenerator() {
reset();
}
float remaining = 0.f;


/** Immediately disables the pulse */ /** Immediately disables the pulse */
void reset() { void reset() {
@@ -113,16 +90,13 @@ struct PulseGenerator {




struct Timer { struct Timer {
float time;

Timer() {
reset();
}
float time = 0.f;


void reset() { void reset() {
time = 0.f; time = 0.f;
} }


/** Returns the time since last reset or initialization. */
float process(float deltaTime) { float process(float deltaTime) {
time += deltaTime; time += deltaTime;
return time; return time;
@@ -131,26 +105,22 @@ struct Timer {




struct ClockDivider { struct ClockDivider {
int clock;
int division = 1;

ClockDivider() {
reset();
}
uint32_t clock = 0;
uint32_t division = 1;


void reset() { void reset() {
clock = 0; clock = 0;
} }


void setDivision(int division) {
void setDivision(uint32_t division) {
this->division = division; this->division = division;
} }


int getDivision() {
uint32_t getDivision() {
return division; return division;
} }


int getClock() {
uint32_t getClock() {
return clock; return clock;
} }




+ 8
- 4
include/dsp/fft.hpp View File

@@ -7,13 +7,16 @@ namespace rack {
namespace dsp { 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)); 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); pffft_aligned_free(p);
} }


@@ -21,6 +24,7 @@ void alignedDelete(T *p) {
/** Real-valued FFT context. /** Real-valued FFT context.
Wrapper for [PFFFT](https://bitbucket.org/jpommier/pffft/) Wrapper for [PFFFT](https://bitbucket.org/jpommier/pffft/)
`length` must be a multiple of 32. `length` must be a multiple of 32.
Buffers must be aligned to 16-byte boundaries, e.g. with alignedMalloc().
*/ */
struct RealFFT { struct RealFFT {
PFFFT_Setup *setup; PFFFT_Setup *setup;


+ 7
- 16
include/dsp/filter.hpp View File

@@ -106,29 +106,20 @@ struct ExponentialSlewLimiter {
\f$ \frac{dy}{dt} = x \lambda \f$. \f$ \frac{dy}{dt} = x \lambda \f$.
*/ */
struct ExponentialFilter { struct ExponentialFilter {
float out;
float out = 0.f;
float lambda = 0.f; float lambda = 0.f;


ExponentialFilter() {
reset();
}

void reset() { void reset() {
out = NAN;
out = 0.f;
} }


float process(float deltaTime, float in) { 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; 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; return out;
} }




+ 0
- 17
include/dsp/frame.hpp View File

@@ -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

+ 1
- 1
include/dsp/minblep.hpp View File

@@ -15,7 +15,7 @@ https://www.cs.cmu.edu/~eli/papers/icmc01-hardsync.pdf
void minBlepImpulse(int z, int o, float *output); void minBlepImpulse(int z, int o, float *output);




template<int Z, int O>
template <int Z, int O>
struct MinBlepGenerator { struct MinBlepGenerator {
float buf[2 * Z] = {}; float buf[2 * Z] = {};
int pos = 0; int pos = 0;


+ 3
- 3
include/dsp/ode.hpp View File

@@ -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 */ /** 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) { void stepEuler(float t, float dt, float x[], int len, F f) {
float k[len]; 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 */ /** 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) { void stepRK2(float t, float dt, float x[], int len, F f) {
float k1[len]; float k1[len];
float k2[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 */ /** 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) { void stepRK4(float t, float dt, float x[], int len, F f) {
float k1[len]; float k1[len];
float k2[len]; float k2[len];


+ 10
- 11
include/dsp/resampler.hpp View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "dsp/common.hpp" #include "dsp/common.hpp"
#include "dsp/frame.hpp"
#include "dsp/ringbuffer.hpp" #include "dsp/ringbuffer.hpp"
#include "dsp/fir.hpp" #include "dsp/fir.hpp"
#include "dsp/window.hpp" #include "dsp/window.hpp"
@@ -14,10 +13,10 @@ namespace dsp {




/** Resamples by a fixed rational factor. */ /** Resamples by a fixed rational factor. */
template<int CHANNELS>
template <int MAX_CHANNELS>
struct SampleRateConverter { struct SampleRateConverter {
SpeexResamplerState *st = NULL; SpeexResamplerState *st = NULL;
int channels = CHANNELS;
int channels = MAX_CHANNELS;
int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT;
int inRate = 44100; int inRate = 44100;
int outRate = 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) { void setChannels(int channels) {
assert(channels <= CHANNELS);
assert(channels <= MAX_CHANNELS);
if (channels == this->channels) if (channels == this->channels)
return; return;
this->channels = channels; this->channels = channels;
@@ -68,13 +67,13 @@ struct SampleRateConverter {
assert(st); assert(st);
assert(err == RESAMPLER_ERR_SUCCESS); 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 */ /** `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(in);
assert(inFrames); assert(inFrames);
assert(out); assert(out);
@@ -95,7 +94,7 @@ struct SampleRateConverter {
else { else {
// Simply copy the buffer without conversion // Simply copy the buffer without conversion
int frames = std::min(*inFrames, *outFrames); 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; *inFrames = frames;
*outFrames = frames; *outFrames = frames;
} }
@@ -104,7 +103,7 @@ struct SampleRateConverter {




/** Downsamples by an integer factor. */ /** Downsamples by an integer factor. */
template<int OVERSAMPLE, int QUALITY>
template <int OVERSAMPLE, int QUALITY>
struct Decimator { struct Decimator {
float inBuffer[OVERSAMPLE*QUALITY]; float inBuffer[OVERSAMPLE*QUALITY];
float kernel[OVERSAMPLE*QUALITY]; float kernel[OVERSAMPLE*QUALITY];
@@ -139,7 +138,7 @@ struct Decimator {




/** Upsamples by an integer factor. */ /** Upsamples by an integer factor. */
template<int OVERSAMPLE, int QUALITY>
template <int OVERSAMPLE, int QUALITY>
struct Upsampler { struct Upsampler {
float inBuffer[QUALITY]; float inBuffer[QUALITY];
float kernel[OVERSAMPLE*QUALITY]; float kernel[OVERSAMPLE*QUALITY];


+ 1
- 5
include/dsp/vumeter.hpp View File

@@ -54,14 +54,10 @@ struct VuMeter2 {
}; };
Mode mode = PEAK; Mode mode = PEAK;
/** Either the smoothed peak or the mean-square of the brightness, depending on the mode. */ /** 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 */ /** Inverse time constant in 1/seconds */
float lambda = 30.f; float lambda = 30.f;


VuMeter2() {
reset();
}

void reset() { void reset() {
v = 0.f; v = 0.f;
} }


+ 1
- 1
include/dsp/window.hpp View File

@@ -14,7 +14,7 @@ inline float hann(float p) {
return 0.5f * (1.f - std::cos(2*M_PI * 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) { inline void hannWindow(float *x, int len) {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
x[i] *= hann((float) i / (len - 1)); x[i] *= hann((float) i / (len - 1));


+ 19
- 10
include/math.hpp View File

@@ -36,7 +36,7 @@ inline int clamp(int x, int a, int b) {
If `b < a`, switches the two values. If `b < a`, switches the two values.
*/ */
inline int clampSafe(int x, int a, int b) { 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`. /** 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. If `b < a`, switches the two values.
*/ */
inline float clampSafe(float x, float a, float b) { 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. /** Returns 1 for positive numbers, -1 for negative numbers, and 0 for zero.
See https://en.wikipedia.org/wiki/Sign_function. See https://en.wikipedia.org/wiki/Sign_function.
*/ */
inline float sgn(float x) { 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. */ /** 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`. /** Euclidean modulus. Always returns `0 <= mod < b`.
See https://en.wikipedia.org/wiki/Euclidean_division. 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`. */ /** 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. */ /** If the magnitude of `x` if less than epsilon, return 0. */
inline float chop(float x, float epsilon = 1e-6f) { 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) { 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. Arguments may be the same pointers.
Example: 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; *cr = ar * br - ai * bi;
*ci = ar * bi + ai * br; *ci = ar * bi + ai * br;
} }
@@ -202,6 +205,9 @@ struct Vec {
float dot(Vec b) const { float dot(Vec b) const {
return x * b.x + y * b.y; return x * b.x + y * b.y;
} }
float arg() const {
return std::atan2(y, x);
}
float norm() const { float norm() const {
return std::hypot(x, y); return std::hypot(x, y);
} }
@@ -229,6 +235,9 @@ struct Vec {
Vec max(Vec b) const { Vec max(Vec b) const {
return Vec(std::fmax(x, b.x), std::fmax(y, b.y)); 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 { Vec round() const {
return Vec(std::round(x), std::round(y)); 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); r.pos.y = math::clampSafe(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y);
return r; 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 expand(Rect b) const {
Rect r; Rect r;
r.pos.x = std::fmin(pos.x, b.pos.x); r.pos.x = std::fmin(pos.x, b.pos.x);


+ 0
- 1
include/rack.hpp View File

@@ -84,7 +84,6 @@
#include "dsp/fft.hpp" #include "dsp/fft.hpp"
#include "dsp/filter.hpp" #include "dsp/filter.hpp"
#include "dsp/fir.hpp" #include "dsp/fir.hpp"
#include "dsp/frame.hpp"
#include "dsp/minblep.hpp" #include "dsp/minblep.hpp"
#include "dsp/ode.hpp" #include "dsp/ode.hpp"
#include "dsp/resampler.hpp" #include "dsp/resampler.hpp"


+ 1
- 0
include/rack0.hpp View File

@@ -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 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 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 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 // random


+ 4
- 4
src/dsp/minblep.cpp View File

@@ -10,7 +10,7 @@ namespace dsp {
void minBlepImpulse(int z, int o, float *output) { void minBlepImpulse(int z, int o, float *output) {
// Symmetric sinc array with `z` zero-crossings on each side // Symmetric sinc array with `z` zero-crossings on each side
int n = 2 * z * o; int n = 2 * z * o;
float *x = alignedNew<float>(n);
float *x = alignedMalloc<float>(n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
float p = math::rescale((float) i, 0.f, (float) (n - 1), (float) -z, (float) z); float p = math::rescale((float) i, 0.f, (float) (n - 1), (float) -z, (float) z);
x[i] = sinc(p); x[i] = sinc(p);
@@ -20,7 +20,7 @@ void minBlepImpulse(int z, int o, float *output) {
blackmanHarrisWindow(x, n); blackmanHarrisWindow(x, n);


// Real cepstrum // Real cepstrum
float *fx = alignedNew<float>(2*n);
float *fx = alignedMalloc<float>(2*n);
RealFFT rfft(n); RealFFT rfft(n);
rfft.rfft(x, fx); rfft.rfft(x, fx);
// fx = log(abs(fx)) // fx = log(abs(fx))
@@ -73,8 +73,8 @@ void minBlepImpulse(int z, int o, float *output) {
std::memcpy(output, x, n * sizeof(float)); std::memcpy(output, x, n * sizeof(float));


// Cleanup // Cleanup
alignedDelete(x);
alignedDelete(fx);
alignedFree(x);
alignedFree(fx);
} }






Loading…
Cancel
Save