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