From a8e82624fcca4c4773188c306d96ebb189c6ab8b Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Mon, 30 Jan 2017 02:36:45 -0500 Subject: [PATCH] Add sample rate conversion to AudioInterface input/output, fix bugs in stringf and DoubleRingBuffer --- Makefile | 4 ++ Makefile.inc | 2 +- include/dsp.hpp | 89 ++++++++++++++++--------------- src/core/AudioInterface.cpp | 101 +++++++++++++----------------------- src/util.cpp | 12 +++-- 5 files changed, 97 insertions(+), 111 deletions(-) diff --git a/Makefile b/Makefile index cc86228e..06ebd028 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,10 @@ LDFLAGS += \ -L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \ -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows TARGET = Rack.exe +# OBJECTS = Rack.res + +%.res: %.rc + windres $^ -O coff -o $@ endif diff --git a/Makefile.inc b/Makefile.inc index b87f7969..f1f76c7f 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -1,5 +1,5 @@ -OBJECTS = $(patsubst %, build/%.o, $(SOURCES)) +OBJECTS += $(patsubst %, build/%.o, $(SOURCES)) DEPS = $(patsubst %, build/%.d, $(SOURCES)) diff --git a/include/dsp.hpp b/include/dsp.hpp index 729a18c8..b97d156d 100644 --- a/include/dsp.hpp +++ b/include/dsp.hpp @@ -3,25 +3,19 @@ #include #include #include -#include +#include #include "math.hpp" namespace rack { -/** Construct a C-style complex float -With -O3 this is as fast as the 1.0 + 1.0*I syntax but it stops the compiler from complaining -*/ -inline float _Complex complexf(float r, float i) { - union { - float x[2]; - float _Complex c; - } v; - v.x[0] = r; - v.x[1] = i; - return v.c; -} +/** Useful for storing arrays of samples in ring buffers and casting them to `float*` to be used by interleaved processors, like SampleRateConverter */ +template +struct Frame { + float samples[CHANNELS]; +}; + /** Simple FFT implementation If you need something fast, use pffft, KissFFT, etc instead. @@ -30,14 +24,14 @@ The size N must be a power of 2 struct SimpleFFT { int N; /** Twiddle factors e^(2pi k/N), interleaved complex numbers */ - float _Complex *tw; + std::complex *tw; SimpleFFT(int N, bool inverse) : N(N) { - tw = new float _Complex[N]; + tw = new std::complex[N]; for (int i = 0; i < N; i++) { float phase = 2*M_PI * (float)i / N; if (inverse) phase *= -1.0; - tw[i] = cexpf(phase * complexf(0.0, 1.0)); + tw[i] = std::exp(std::complex(0.0, phase)); } } ~SimpleFFT() { @@ -48,9 +42,9 @@ struct SimpleFFT { y must be size N/s s is the stride factor for the x array which divides the size N */ - void dft(const float _Complex *x, float _Complex *y, int s=1) { + void dft(const std::complex *x, std::complex *y, int s=1) { for (int k = 0; k < N/s; k++) { - float _Complex yk = 0.0; + std::complex yk = 0.0; for (int n = 0; n < N; n += s) { int m = (n*k) % N; yk += x[n] * tw[m]; @@ -58,14 +52,14 @@ struct SimpleFFT { y[k] = yk; } } - void fft(const float _Complex *x, float _Complex *y, int s=1) { + void fft(const std::complex *x, std::complex *y, int s=1) { if (N/s <= 2) { // Naive DFT is faster than further FFT recursions at this point dft(x, y, s); return; } - float _Complex e[N/(2*s)]; // Even inputs - float _Complex o[N/(2*s)]; // Odd inputs + std::complex *e = new std::complex[N/(2*s)]; // Even inputs + std::complex *o = new std::complex[N/(2*s)]; // Odd inputs fft(x, e, 2*s); fft(x + s, o, 2*s); for (int k = 0; k < N/(2*s); k++) { @@ -73,6 +67,8 @@ struct SimpleFFT { y[k] = e[k] + tw[m] * o[k]; y[k + N/(2*s)] = e[k] - tw[m] * o[k]; } + delete[] e; + delete[] o; } }; @@ -81,17 +77,17 @@ struct SimpleFFT { S must be a power of 2. push() is constant time O(1) */ -template +template struct RingBuffer { T data[S]; - size_t start = 0; - size_t end = 0; + int start = 0; + int end = 0; - size_t mask(size_t i) const { + int mask(int i) const { return i & (S - 1); } void push(T t) { - size_t i = mask(end++); + int i = mask(end++); data[i] = t; } T shift() { @@ -106,9 +102,12 @@ struct RingBuffer { bool full() const { return end - start >= S; } - size_t size() const { + int size() const { return end - start; } + int capacity() const { + return S - size(); + } }; @@ -116,17 +115,17 @@ struct RingBuffer { S must be a power of 2. push() is constant time O(2) relative to RingBuffer */ -template +template struct DoubleRingBuffer { T data[S*2]; - size_t start = 0; - size_t end = 0; + int start = 0; + int end = 0; - size_t mask(size_t i) const { + int mask(int i) const { return i & (S - 1); } void push(T t) { - size_t i = mask(end++); + int i = mask(end++); data[i] = t; data[i + S] = t; } @@ -142,9 +141,12 @@ struct DoubleRingBuffer { bool full() const { return end - start >= S; } - size_t size() const { + int size() const { return end - start; } + int capacity() const { + return S - size(); + } /** Returns a pointer to S consecutive elements for appending. If any data is appended, you must call endIncr afterwards. Pointer is invalidated when any other method is called. @@ -152,12 +154,16 @@ struct DoubleRingBuffer { T *endData() { return &data[mask(end)]; } - void endIncr(size_t n) { - size_t mend = mask(end) + n; - if (mend > S) { + void endIncr(int n) { + int e = mask(end); + int e1 = e + n; + int e2 = mini(e1, S); + // Copy data forward + memcpy(data + S + e, data + e, sizeof(T) * (e2 - e)); + + if (e1 > S) { // Copy data backward from the doubled block to the main block - memcpy(data, &data[S], sizeof(T) * (mend - S)); - // Don't bother copying forward + memcpy(data, data + S, sizeof(T) * (e1 - S)); } end += n; } @@ -167,7 +173,7 @@ struct DoubleRingBuffer { const T *startData() const { return &data[mask(start)]; } - void startIncr(size_t n) { + void startIncr(int n) { start += n; } }; @@ -245,8 +251,9 @@ struct SampleRateConverter { data.src_ratio = r; } /** `in` and `out` are interlaced with the number of channels */ - void process(float *in, int *inFrames, float *out, int *outFrames) { - data.data_in = in; + void process(const float *in, int *inFrames, float *out, int *outFrames) { + // The const cast is okay since src_process does not modify it + data.data_in = const_cast(in); data.input_frames = *inFrames; data.data_out = out; data.output_frames = *outFrames; diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index c9da0e06..d709ffe9 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -42,16 +42,13 @@ struct AudioInterface : Module { // Used because the GUI thread and Rack thread can both interact with this class std::mutex mutex; - SampleRateConverter<2> inSrc; - SampleRateConverter<2> outSrc; + SampleRateConverter<2> inputSrc; + SampleRateConverter<2> outputSrc; - struct Frame { - float samples[2]; - }; // in device's sample rate - RingBuffer inBuffer; + DoubleRingBuffer, (1<<15)> inputBuffer; // in rack's sample rate - RingBuffer outBuffer; + DoubleRingBuffer, (1<<15)> outputBuffer; AudioInterface(); ~AudioInterface(); @@ -92,37 +89,29 @@ void AudioInterface::step() { // Get input and pass it through the sample rate converter if (numOutputs > 0) { - Frame f; - f.samples[0] = getf(inputs[AUDIO1_INPUT]); - f.samples[1] = getf(inputs[AUDIO2_INPUT]); - - inSrc.setRatio(sampleRate / gRack->sampleRate); - int inLength = 1; - int outLength = 16; - float buf[2*outLength]; - inSrc.process(f.samples, &inLength, buf, &outLength); - for (int i = 0; i < outLength; i++) { - if (inBuffer.full()) - break; - Frame f; - f.samples[0] = buf[2*i + 0]; - f.samples[1] = buf[2*i + 1]; - inBuffer.push(f); - } + Frame<2> f; + f.samples[0] = getf(inputs[AUDIO1_INPUT]) / 5.0; + f.samples[1] = getf(inputs[AUDIO2_INPUT]) / 5.0; + + inputSrc.setRatio(sampleRate / gRack->sampleRate); + int inLen = 1; + int outLen = inputBuffer.capacity(); + inputSrc.process((const float*) &f, &inLen, (float*) inputBuffer.endData(), &outLen); + inputBuffer.endIncr(outLen); } // Read/write stream if we have enough input - // TODO If numOutputs == 0, call this when outBuffer.empty() - bool streamReady = (numOutputs > 0) ? ((int)inBuffer.size() >= blockSize) : (outBuffer.empty()); + bool streamReady = (numOutputs > 0) ? (inputBuffer.size() >= blockSize) : (outputBuffer.empty()); if (streamReady) { - // printf("%d %d\n", inBuffer.size(), outBuffer.size()); PaError err; // Read output from input stream // (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) if (numInputs > 0) { - float *buf = new float[numInputs * blockSize]; - err = Pa_ReadStream(stream, buf, blockSize); + Frame<2> *buf = new Frame<2>[blockSize]; + printf("read %d\n", blockSize); + err = Pa_ReadStream(stream, (float*) buf, blockSize); + printf("read done\n"); if (err) { // Ignore buffer underflows if (err != paInputOverflowed) { @@ -131,52 +120,36 @@ void AudioInterface::step() { } // Pass output through sample rate converter - outSrc.setRatio(gRack->sampleRate / sampleRate); - int inLength = blockSize; - int outLength = 8*blockSize; - float *outBuf = new float[2*outLength]; - outSrc.process(buf, &inLength, outBuf, &outLength); - // Add to output ring buffer - for (int i = 0; i < outLength; i++) { - if (outBuffer.full()) - break; - Frame f; - f.samples[0] = outBuf[2*i + 0]; - f.samples[1] = outBuf[2*i + 1]; - outBuffer.push(f); - } - delete[] outBuf; + outputSrc.setRatio(gRack->sampleRate / sampleRate); + int inLen = blockSize; + int outLen = outputBuffer.capacity(); + outputSrc.process((float*) buf, &inLen, (float*) outputBuffer.endData(), &outLen); + outputBuffer.endIncr(outLen); delete[] buf; } // Write input to output stream if (numOutputs > 0) { - float *buf = new float[numOutputs * blockSize](); - for (int i = 0; i < blockSize; i++) { - assert(!inBuffer.empty()); - Frame f = inBuffer.shift(); - for (int channel = 0; channel < numOutputs; channel++) { - buf[i * numOutputs + channel] = f.samples[channel]; - } - } - - err = Pa_WriteStream(stream, buf, blockSize); + assert(inputBuffer.size() >= blockSize); + printf("write %d\n", blockSize); + err = Pa_WriteStream(stream, (const float*) inputBuffer.startData(), blockSize); + printf("write done\n"); + inputBuffer.startIncr(blockSize); if (err) { // Ignore buffer underflows if (err != paOutputUnderflowed) { fprintf(stderr, "Audio output buffer underflow\n"); } } - delete[] buf; } } // Set output - if (!outBuffer.empty()) { - Frame f = outBuffer.shift(); - setf(outputs[AUDIO1_OUTPUT], f.samples[0]); - setf(outputs[AUDIO2_OUTPUT], f.samples[1]); + if (!outputBuffer.empty()) { + Frame<2> f = outputBuffer.shift(); + setf(outputs[AUDIO1_OUTPUT], 5.0 * f.samples[0]); + setf(outputs[AUDIO2_OUTPUT], 5.0 * f.samples[1]); } } @@ -244,9 +217,8 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) { // Correct sample rate const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream); this->sampleRate = streamInfo->sampleRate; + this->deviceId = deviceId; } - - this->deviceId = deviceId; } void AudioInterface::closeDevice() { @@ -256,7 +228,6 @@ void AudioInterface::closeDevice() { PaError err; err = Pa_CloseStream(stream); if (err) { - // This shouldn't happen: fprintf(stderr, "Failed to close audio stream: %s\n", Pa_GetErrorText(err)); } } @@ -268,8 +239,10 @@ void AudioInterface::closeDevice() { numInputs = 0; // Clear buffers - inBuffer.clear(); - outBuffer.clear(); + inputBuffer.clear(); + outputBuffer.clear(); + inputSrc.reset(); + outputSrc.reset(); } diff --git a/src/util.cpp b/src/util.cpp index b7fa8f70..fe7218fa 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -28,16 +28,18 @@ float randomNormal(){ std::string stringf(const char *format, ...) { - va_list ap; - va_start(ap, format); - size_t size = vsnprintf(NULL, 0, format, ap); + va_list args; + va_start(args, format); + int size = vsnprintf(NULL, 0, format, args); + va_end(args); if (size < 0) return ""; size++; std::string s; s.resize(size); - vsnprintf(&s[0], size, format, ap); - va_end(ap); + va_start(args, format); + vsnprintf(&s[0], size, format, args); + va_end(args); return s; }