@@ -1,39 +1,2 @@ | |||||
#pragma once | #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 { | 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++) { | for (int i = 0; i < len; i++) { | ||||
y += x[-i] * kernel[i]; | |||||
y += in[len - 1 - i] * kernel[i]; | |||||
} | } | ||||
return y; | 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 { | namespace rack { | ||||
/** The normalized sinc function. https://en.wikipedia.org/wiki/Sinc_function */ | |||||
inline float sinc(float x) { | inline float sinc(float x) { | ||||
if (x == 0.f) | if (x == 0.f) | ||||
return 1.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 | #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 <condition_variable> | ||||
#include "Core.hpp" | #include "Core.hpp" | ||||
#include "audio.hpp" | #include "audio.hpp" | ||||
#include "dsp/samplerate.hpp" | |||||
#include "dsp/resampler.hpp" | |||||
#include "dsp/ringbuffer.hpp" | #include "dsp/ringbuffer.hpp" | ||||