@@ -1,39 +1,2 @@ | |||
#pragma once | |||
#include "string.h" | |||
#include "dsp/ringbuffer.hpp" | |||
#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; | |||
} | |||
// Zero input buffer | |||
memset(inBuffer.data, 0, sizeof(inBuffer.data)); | |||
} | |||
/** `in` must be OVERSAMPLE floats long */ | |||
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 | |||
#include "resampler.hpp" |
@@ -5,34 +5,36 @@ | |||
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; | |||
/** Performs a direct sum convolution */ | |||
inline float convolveNaive(const float *in, const float *kernel, int len) { | |||
float y = 0.f; | |||
for (int i = 0; i < len; i++) { | |||
y += x[-i] * kernel[i]; | |||
y += in[len - 1 - 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)); | |||
/** Computes the impulse response of a boxcar lowpass filter */ | |||
inline void boxcarLowpassIR(float *out, int len, float cutoff = 0.5f) { | |||
for (int i = 0; i < len; i++) { | |||
float t = i - (len - 1) / 2.f; | |||
out[i] = 2 * cutoff * sinc(2 * cutoff * t); | |||
} | |||
} | |||
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] = sinc(t * n * cutoff); | |||
inline void blackmanHarrisWindow(float *x, int len) { | |||
// Constants from https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window | |||
const float a0 = 0.35875f; | |||
const float a1 = 0.48829f; | |||
const float a2 = 0.14128f; | |||
const float a3 = 0.01168f; | |||
float factor = 2*M_PI / (len - 1); | |||
for (int i = 0; i < len; i++) { | |||
x[i] *= | |||
+ a0 | |||
- a1 * cosf(1*factor * i) | |||
+ a2 * cosf(2*factor * i) | |||
- a3 * cosf(3*factor * i); | |||
} | |||
} | |||
@@ -6,6 +6,7 @@ | |||
namespace rack { | |||
/** The normalized sinc function. https://en.wikipedia.org/wiki/Sinc_function */ | |||
inline float sinc(float x) { | |||
if (x == 0.f) | |||
return 1.f; | |||
@@ -0,0 +1,145 @@ | |||
#pragma once | |||
#include <assert.h> | |||
#include <string.h> | |||
#include <speex/speex_resampler.h> | |||
#include "frame.hpp" | |||
#include "ringbuffer.hpp" | |||
#include "fir.hpp" | |||
namespace rack { | |||
template<int CHANNELS> | |||
struct SampleRateConverter { | |||
SpeexResamplerState *st = NULL; | |||
int channels = CHANNELS; | |||
int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; | |||
int inRate = 44100; | |||
int outRate = 44100; | |||
SampleRateConverter() { | |||
refreshState(); | |||
} | |||
~SampleRateConverter() { | |||
if (st) { | |||
speex_resampler_destroy(st); | |||
} | |||
} | |||
/** Sets the number of channels to actually process. This can be at most CHANNELS. */ | |||
void setChannels(int channels) { | |||
assert(channels <= CHANNELS); | |||
if (channels == this->channels) | |||
return; | |||
this->channels = channels; | |||
refreshState(); | |||
} | |||
/** From 0 (worst, fastest) to 10 (best, slowest) */ | |||
void setQuality(int quality) { | |||
if (quality == this->quality) | |||
return; | |||
this->quality = quality; | |||
refreshState(); | |||
} | |||
void setRates(int inRate, int outRate) { | |||
if (inRate == this->inRate && outRate == this->outRate) | |||
return; | |||
this->inRate = inRate; | |||
this->outRate = outRate; | |||
refreshState(); | |||
} | |||
void refreshState() { | |||
if (st) { | |||
speex_resampler_destroy(st); | |||
st = NULL; | |||
} | |||
if (channels > 0 && inRate != outRate) { | |||
int err; | |||
st = speex_resampler_init(channels, inRate, outRate, quality, &err); | |||
assert(st); | |||
assert(err == RESAMPLER_ERR_SUCCESS); | |||
speex_resampler_set_input_stride(st, CHANNELS); | |||
speex_resampler_set_output_stride(st, CHANNELS); | |||
} | |||
} | |||
/** `in` and `out` are interlaced with the number of channels */ | |||
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | |||
assert(in); | |||
assert(inFrames); | |||
assert(out); | |||
assert(outFrames); | |||
if (st) { | |||
// Resample each channel at a time | |||
spx_uint32_t inLen; | |||
spx_uint32_t outLen; | |||
for (int i = 0; i < channels; i++) { | |||
inLen = *inFrames; | |||
outLen = *outFrames; | |||
int err = speex_resampler_process_float(st, i, ((const float*) in) + i, &inLen, ((float*) out) + i, &outLen); | |||
assert(err == RESAMPLER_ERR_SUCCESS); | |||
} | |||
*inFrames = inLen; | |||
*outFrames = outLen; | |||
} | |||
else { | |||
// Simply copy the buffer without conversion | |||
int frames = min(*inFrames, *outFrames); | |||
memcpy(out, in, frames * sizeof(Frame<CHANNELS>)); | |||
*inFrames = frames; | |||
*outFrames = frames; | |||
} | |||
} | |||
}; | |||
template<int OVERSAMPLE, int QUALITY> | |||
struct Decimator { | |||
float inBuffer[OVERSAMPLE*QUALITY]; | |||
float kernel[OVERSAMPLE*QUALITY]; | |||
int inIndex = 0; | |||
Decimator(float cutoff = 0.9f) { | |||
boxcarLowpassIR(kernel, OVERSAMPLE*QUALITY, cutoff * 0.5f / OVERSAMPLE); | |||
blackmanHarrisWindow(kernel, OVERSAMPLE*QUALITY); | |||
reset(); | |||
} | |||
void reset() { | |||
// Zero input buffer | |||
memset(inBuffer, 0, sizeof(inBuffer)); | |||
} | |||
/** `in` must be length OVERSAMPLE */ | |||
float process(float *in) { | |||
// Copy input to buffer | |||
memcpy(&inBuffer[inIndex], in, OVERSAMPLE*sizeof(float)); | |||
inIndex += OVERSAMPLE; | |||
inIndex %= OVERSAMPLE*QUALITY; | |||
// Perform naive convolution | |||
float out = 0.f; | |||
for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { | |||
int index = inIndex - 1 - i; | |||
index = (index + OVERSAMPLE*QUALITY) % (OVERSAMPLE*QUALITY); | |||
out += kernel[i] * inBuffer[index]; | |||
} | |||
return out; | |||
} | |||
}; | |||
template<int OVERSAMPLE, int QUALITY> | |||
struct Upsampler { | |||
/** `out` must be length OVERSAMPLE */ | |||
void process(float in, float *out) { | |||
// TODO | |||
} | |||
}; | |||
} // namespace rack |
@@ -1,99 +1,2 @@ | |||
#pragma once | |||
#include <assert.h> | |||
#include <string.h> | |||
#include <speex/speex_resampler.h> | |||
#include "frame.hpp" | |||
namespace rack { | |||
template<int CHANNELS> | |||
struct SampleRateConverter { | |||
SpeexResamplerState *st = NULL; | |||
int channels = CHANNELS; | |||
int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; | |||
int inRate = 44100; | |||
int outRate = 44100; | |||
SampleRateConverter() { | |||
refreshState(); | |||
} | |||
~SampleRateConverter() { | |||
if (st) { | |||
speex_resampler_destroy(st); | |||
} | |||
} | |||
/** Sets the number of channels to actually process. This can be at most CHANNELS. */ | |||
void setChannels(int channels) { | |||
assert(channels <= CHANNELS); | |||
if (channels == this->channels) | |||
return; | |||
this->channels = channels; | |||
refreshState(); | |||
} | |||
/** From 0 (worst, fastest) to 10 (best, slowest) */ | |||
void setQuality(int quality) { | |||
if (quality == this->quality) | |||
return; | |||
this->quality = quality; | |||
refreshState(); | |||
} | |||
void setRates(int inRate, int outRate) { | |||
if (inRate == this->inRate && outRate == this->outRate) | |||
return; | |||
this->inRate = inRate; | |||
this->outRate = outRate; | |||
refreshState(); | |||
} | |||
void refreshState() { | |||
if (st) { | |||
speex_resampler_destroy(st); | |||
st = NULL; | |||
} | |||
if (channels > 0 && inRate != outRate) { | |||
int err; | |||
st = speex_resampler_init(channels, inRate, outRate, quality, &err); | |||
assert(st); | |||
assert(err == RESAMPLER_ERR_SUCCESS); | |||
speex_resampler_set_input_stride(st, CHANNELS); | |||
speex_resampler_set_output_stride(st, CHANNELS); | |||
} | |||
} | |||
/** `in` and `out` are interlaced with the number of channels */ | |||
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | |||
assert(in); | |||
assert(inFrames); | |||
assert(out); | |||
assert(outFrames); | |||
if (st) { | |||
// Resample each channel at a time | |||
spx_uint32_t inLen; | |||
spx_uint32_t outLen; | |||
for (int i = 0; i < channels; i++) { | |||
inLen = *inFrames; | |||
outLen = *outFrames; | |||
int err = speex_resampler_process_float(st, i, ((const float*) in) + i, &inLen, ((float*) out) + i, &outLen); | |||
assert(err == RESAMPLER_ERR_SUCCESS); | |||
} | |||
*inFrames = inLen; | |||
*outFrames = outLen; | |||
} | |||
else { | |||
// Simply copy the buffer without conversion | |||
int frames = min(*inFrames, *outFrames); | |||
memcpy(out, in, frames * sizeof(Frame<CHANNELS>)); | |||
*inFrames = frames; | |||
*outFrames = frames; | |||
} | |||
} | |||
}; | |||
} // namespace rack | |||
#include "resampler.hpp" |
@@ -6,7 +6,7 @@ | |||
#include <condition_variable> | |||
#include "Core.hpp" | |||
#include "audio.hpp" | |||
#include "dsp/samplerate.hpp" | |||
#include "dsp/resampler.hpp" | |||
#include "dsp/ringbuffer.hpp" | |||