Skip to content

Commit

Permalink
Add filters and mixing utilities
Browse files Browse the repository at this point in the history
  • Loading branch information
geraintluff committed Oct 8, 2021
1 parent a4f21c2 commit d2793ad
Show file tree
Hide file tree
Showing 10 changed files with 518 additions and 52 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A C++11 header-only library, providing classes/templates for (mostly audio) sign
git clone https://signalsmith-audio.co.uk/code/dsp.git
```

It's fairly early in development, so it's far from comprehensive, and APIs might change.
⚠️ It's fairly early in development, so (while the parts which exist should be well-tested) the APIs might change.

### Documentation

Expand All @@ -22,7 +22,6 @@ git clone https://signalsmith-audio.co.uk/code/dsp-doc.git

The goal (where possible) is to measure/test the actual audio characteristics of the tools (e.g. frequency responses and aliasing levels).


### License

This code is [MIT licensed](LICENSE.txt). If you'd prefer something else, get in touch.
37 changes: 37 additions & 0 deletions common.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,41 @@
#ifndef SIGNALSMITH_DSP_COMMON_H
#define SIGNALSMITH_DSP_COMMON_H

#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif

namespace signalsmith {
/** @defgroup Common Common
@brief helper classes used by the rest of the library
@{
@file
*/
//
// /// Helpful base-class for anything which could be either fixed-size or variable size (`-1`)
// template<int size=-1>
// class Size {
// public:
// Size() {}
//
// static constexpr int size() {
// return size;
// }
// }
//
// /// Variable-size specialisation
// template<>
// class Size<-1> {
// int _size;
// public:
// Size(int size=0) : _size(size) {}
//
// int size() const {
// return _size;
// }
// }

/** @} */
} // signalsmith::
#endif // include guard
45 changes: 39 additions & 6 deletions delay.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ namespace delay {
// We shouldn't accidentally copy a delay buffer
Buffer(const Buffer &other) = delete;
Buffer & operator =(const Buffer &other) = delete;
// But moving one is fine
Buffer(Buffer &&other) = default;
Buffer & operator =(Buffer &&other) = default;

void resize(int minCapacity, Sample value=Sample()) {
int bufferLength = 1;
Expand Down Expand Up @@ -430,7 +433,7 @@ namespace delay {

int subSampleSteps;
std::vector<Sample> coefficients;

InterpolatorKaiserSincN(double bandwidthTarget=0) {
subSampleSteps = 2*n; // Heuristic again. Really it depends on the bandwidth as well.
if (bandwidthTarget == 0) {
Expand Down Expand Up @@ -616,14 +619,14 @@ namespace delay {
struct ChannelView {
static constexpr Sample latency = Super::latency;

Super &reader;
typename MultiBuffer<Sample>::ConstChannel &channel;
const Super &reader;
typename MultiBuffer<Sample>::ConstChannel channel;

Sample read(Sample delaySamples) const {
return reader.read(channel, delaySamples);
}
};
ChannelView operator [](int channel) {
ChannelView operator [](int channel) const {
return ChannelView{*this, multiBuffer[channel]};
}

Expand All @@ -641,6 +644,20 @@ namespace delay {
DelayView read(Sample delaySamples) {
return DelayView{*this, multiBuffer.constView(), delaySamples};
}
/// Reads into the provided output structure
template<class Output>
void read(Sample delaySamples, Output &output) {
for (int c = 0; c < channels; ++c) {
output[c] = Super::read(multiBuffer[c], delaySamples);
}
}
/// Reads separate delays for each channel
template<class Delays, class Output>
void readMulti(const Delays &delays, Output &output) {
for (int c = 0; c < channels; ++c) {
output[c] = Super::read(multiBuffer[c], delays[c]);
}
}
template<class Data>
void write(const Data &data) {
for (int c = 0; c < channels; ++c) {
Expand All @@ -654,11 +671,27 @@ namespace delay {
multiBuffer[c][0] = data[c];
}
DelayView result = read(delaySamples);
write(data);
++multiBuffer;
return result;
}
template<class Data, class Output>
void readWrite(const Data &data, Sample delaySamples, Output &output) {
for (int c = 0; c < channels; ++c) {
multiBuffer[c][0] = data[c];
}
read(delaySamples, output);
++multiBuffer;
}
template<class Data, class Delays, class Output>
void readWriteMulti(const Data &data, const Delays &delays, Output &output) {
for (int c = 0; c < channels; ++c) {
multiBuffer[c][0] = data[c];
}
readMulti(delays, output);
++multiBuffer;
}
};

/** @} */
}} // signalsmith::delay::
#endif // SIGNALSMITH_DSP_DELAY_H
#endif // include guard
113 changes: 113 additions & 0 deletions envelopes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#ifndef SIGNALSMITH_DSP_ENVELOPES_H
#define SIGNALSMITH_DSP_ENVELOPES_H

#include "./common.h"

#include <cmath>
#include <random>

namespace signalsmith {
namespace envelopes {
/** @defgroup Envelopes Envelopes and LFOs
@brief Envelopes, mostly fast and approximate
@{
@file
*/

/** A cheap vaguely-sine-like LFO with random speed variation.
Currently based on a cubic polynomial, but may change in future.
It supports random variation in the rate, and the depth. It will always remain bounded by the limits you specify (once it updates) so randomising the depth will produce smaller oscillations on average.
*/
class CheapLfo {
float ratio = 0;
float ratioStep = 0;

float valueFrom = 0, valueTo = 1, valueRange = 1;
float targetLow = 0, targetHigh = 1;
float targetRate = 0;
float rateRandom = 0.5, depthRandom = 0;
bool freshReset = true;

std::default_random_engine randomEngine;
std::uniform_real_distribution<float> randomUnit;
float random() {
return randomUnit(randomEngine);
}
float randomRate() {
return targetRate*exp(rateRandom*(random() - 0.5));
}
float randomTarget(float previous) {
float randomOffset = depthRandom*random()*(targetLow - targetHigh);
if (previous < (targetLow + targetHigh)*0.5f) {
return targetHigh + randomOffset;
} else {
return targetLow - randomOffset;
}
}
public:
CheapLfo() : randomEngine(std::random_device()()), randomUnit(0, 1) {
reset();
}

/// Resets the LFO state, with random phase.
void reset() {
ratio = random();
ratioStep = randomRate();
if (random() < 0.5) {
valueFrom = targetLow;
valueTo = targetHigh;
} else {
valueFrom = targetHigh;
valueTo = targetLow;
}
valueRange = valueTo - valueFrom;
freshReset = true;
}
/** Smoothly updates the LFO parameters.
If called directly after `.reset()`, oscillation will immediately start within the specified range. Otherwise, it will follow the new parameters after at most one cycle.
The LFO will complete a full oscillation in (approximately) `1/rate` samples. `rateVariation` can be any number, but 0-1 is a good range. `depthVariation` must be in [0, 1], where 0.5 has random amplitude but still alternates up/down.
*/
void set(float low, float high, float rate, float rateVariation=0.5, float depthVariation=0) {
rate *= 2; // We want to go up and down during this period
targetRate = rate;
targetLow = std::min(low, high);
targetHigh = std::max(low, high);
rateRandom = rateVariation;
depthRandom = depthVariation;

// If we haven't called .next() yet, don't bother being smooth.
if (freshReset) return reset();

// Only update the current rate if it's outside our new random-variation range
float maxRandomRatio = exp((float)0.5*rateRandom);
if (ratioStep > rate*maxRandomRatio || ratioStep < rate/maxRandomRatio) {
ratioStep = randomRate();
}
}

/// get the next output sample
float next() {
freshReset = false;
float result = ratio*ratio*(3 - 2*ratio)*valueRange + valueFrom;

ratio += ratioStep;
if (ratio >= 1) {
ratio = 0;
ratioStep = randomRate();
valueFrom = valueTo;
valueTo = randomTarget(valueFrom);
valueRange = valueTo - valueFrom;
}
return result;
}
};


/** @} */
}} // signalsmith::envelopes::
#endif // include guard
47 changes: 26 additions & 21 deletions fft.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,50 @@
#define SIGNALSMITH_FFT_V5

#include "./common.h"
#include "./perf.h"

#include <vector>
#include <complex>
#include <cmath>

#ifndef SIGNALSMITH_INLINE
#ifdef __GNUC__
#define SIGNALSMITH_INLINE __attribute__((always_inline)) inline
#elif defined(__MSVC__)
#define SIGNALSMITH_INLINE __forceinline inline
#else
#define SIGNALSMITH_INLINE inline
#endif
#endif

namespace signalsmith {

#ifndef DOXYGEN_SHOULD_SKIP_THIS
namespace _fft_impl {

template <typename V>
SIGNALSMITH_INLINE V complexReal(const std::complex<V> &c) {
return ((V*)(&c))[0];
}
template <typename V>
SIGNALSMITH_INLINE V complexImag(const std::complex<V> &c) {
return ((V*)(&c))[1];
}

// Complex multiplication has edge-cases around Inf/NaN - handling those properly makes std::complex non-inlineable, so we use our own
template <bool conjugateSecond, typename V>
SIGNALSMITH_INLINE std::complex<V> complexMul(const std::complex<V> &a, const std::complex<V> &b) {
V aReal = complexReal(a), aImag = complexImag(a);
V bReal = complexReal(b), bImag = complexImag(b);
return conjugateSecond ? std::complex<V>{
b.real()*a.real() + b.imag()*a.imag(),
b.real()*a.imag() - b.imag()*a.real()
bReal*aReal + bImag*aImag,
bReal*aImag - bImag*aReal
} : std::complex<V>{
a.real()*b.real() - a.imag()*b.imag(),
a.real()*b.imag() + a.imag()*b.real()
aReal*bReal - aImag*bImag,
aReal*bImag + aImag*bReal
};
}

template<bool flipped, typename V>
SIGNALSMITH_INLINE std::complex<V> complexAddI(const std::complex<V> &a, const std::complex<V> &b) {
V aReal = complexReal(a), aImag = complexImag(a);
V bReal = complexReal(b), bImag = complexImag(b);
return flipped ? std::complex<V>{
a.real() + b.imag(),
a.imag() - b.real()
aReal + bImag,
aImag - bReal
} : std::complex<V>{
a.real() - b.imag(),
a.imag() + b.real()
aReal - bImag,
aImag + bReal
};
}

Expand Down Expand Up @@ -209,7 +214,7 @@ namespace signalsmith {
}

template<bool inverse, typename RandomAccessIterator>
void fftStep2(RandomAccessIterator &&origData, const Step &step) {
SIGNALSMITH_INLINE void fftStep2(RandomAccessIterator &&origData, const Step &step) {
const size_t stride = step.innerRepeats;
const complex *origTwiddles = twiddleVector.data() + step.twiddleIndex;
for (size_t outerRepeat = 0; outerRepeat < step.outerRepeats; ++outerRepeat) {
Expand All @@ -227,7 +232,7 @@ namespace signalsmith {
}

template<bool inverse, typename RandomAccessIterator>
void fftStep3(RandomAccessIterator &&origData, const Step &step) {
SIGNALSMITH_INLINE void fftStep3(RandomAccessIterator &&origData, const Step &step) {
constexpr complex factor3 = {-0.5, inverse ? 0.8660254037844386 : -0.8660254037844386};
const size_t stride = step.innerRepeats;
const complex *origTwiddles = twiddleVector.data() + step.twiddleIndex;
Expand All @@ -253,7 +258,7 @@ namespace signalsmith {
}

template<bool inverse, typename RandomAccessIterator>
void fftStep4(RandomAccessIterator &&origData, const Step &step) {
SIGNALSMITH_INLINE void fftStep4(RandomAccessIterator &&origData, const Step &step) {
const size_t stride = step.innerRepeats;
const complex *origTwiddles = twiddleVector.data() + step.twiddleIndex;

Expand Down
Loading

0 comments on commit d2793ad

Please sign in to comment.