and DoubleRingBuffertags/v0.3.0
| @@ -52,6 +52,10 @@ LDFLAGS += \ | |||||
| -L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \ | -L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \ | ||||
| -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows | -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows | ||||
| TARGET = Rack.exe | TARGET = Rack.exe | ||||
| # OBJECTS = Rack.res | |||||
| %.res: %.rc | |||||
| windres $^ -O coff -o $@ | |||||
| endif | endif | ||||
| @@ -1,5 +1,5 @@ | |||||
| OBJECTS = $(patsubst %, build/%.o, $(SOURCES)) | |||||
| OBJECTS += $(patsubst %, build/%.o, $(SOURCES)) | |||||
| DEPS = $(patsubst %, build/%.d, $(SOURCES)) | DEPS = $(patsubst %, build/%.d, $(SOURCES)) | ||||
| @@ -3,25 +3,19 @@ | |||||
| #include <assert.h> | #include <assert.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include <samplerate.h> | #include <samplerate.h> | ||||
| #include <complex.h> | |||||
| #include <complex> | |||||
| #include "math.hpp" | #include "math.hpp" | ||||
| namespace rack { | 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 <size_t CHANNELS> | |||||
| struct Frame { | |||||
| float samples[CHANNELS]; | |||||
| }; | |||||
| /** Simple FFT implementation | /** Simple FFT implementation | ||||
| If you need something fast, use pffft, KissFFT, etc instead. | 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 { | struct SimpleFFT { | ||||
| int N; | int N; | ||||
| /** Twiddle factors e^(2pi k/N), interleaved complex numbers */ | /** Twiddle factors e^(2pi k/N), interleaved complex numbers */ | ||||
| float _Complex *tw; | |||||
| std::complex<float> *tw; | |||||
| SimpleFFT(int N, bool inverse) : N(N) { | SimpleFFT(int N, bool inverse) : N(N) { | ||||
| tw = new float _Complex[N]; | |||||
| tw = new std::complex<float>[N]; | |||||
| for (int i = 0; i < N; i++) { | for (int i = 0; i < N; i++) { | ||||
| float phase = 2*M_PI * (float)i / N; | float phase = 2*M_PI * (float)i / N; | ||||
| if (inverse) | if (inverse) | ||||
| phase *= -1.0; | phase *= -1.0; | ||||
| tw[i] = cexpf(phase * complexf(0.0, 1.0)); | |||||
| tw[i] = std::exp(std::complex<float>(0.0, phase)); | |||||
| } | } | ||||
| } | } | ||||
| ~SimpleFFT() { | ~SimpleFFT() { | ||||
| @@ -48,9 +42,9 @@ struct SimpleFFT { | |||||
| y must be size N/s | y must be size N/s | ||||
| s is the stride factor for the x array which divides the size N | 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<float> *x, std::complex<float> *y, int s=1) { | |||||
| for (int k = 0; k < N/s; k++) { | for (int k = 0; k < N/s; k++) { | ||||
| float _Complex yk = 0.0; | |||||
| std::complex<float> yk = 0.0; | |||||
| for (int n = 0; n < N; n += s) { | for (int n = 0; n < N; n += s) { | ||||
| int m = (n*k) % N; | int m = (n*k) % N; | ||||
| yk += x[n] * tw[m]; | yk += x[n] * tw[m]; | ||||
| @@ -58,14 +52,14 @@ struct SimpleFFT { | |||||
| y[k] = yk; | y[k] = yk; | ||||
| } | } | ||||
| } | } | ||||
| void fft(const float _Complex *x, float _Complex *y, int s=1) { | |||||
| void fft(const std::complex<float> *x, std::complex<float> *y, int s=1) { | |||||
| if (N/s <= 2) { | if (N/s <= 2) { | ||||
| // Naive DFT is faster than further FFT recursions at this point | // Naive DFT is faster than further FFT recursions at this point | ||||
| dft(x, y, s); | dft(x, y, s); | ||||
| return; | return; | ||||
| } | } | ||||
| float _Complex e[N/(2*s)]; // Even inputs | |||||
| float _Complex o[N/(2*s)]; // Odd inputs | |||||
| std::complex<float> *e = new std::complex<float>[N/(2*s)]; // Even inputs | |||||
| std::complex<float> *o = new std::complex<float>[N/(2*s)]; // Odd inputs | |||||
| fft(x, e, 2*s); | fft(x, e, 2*s); | ||||
| fft(x + s, o, 2*s); | fft(x + s, o, 2*s); | ||||
| for (int k = 0; k < N/(2*s); k++) { | 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] = e[k] + tw[m] * o[k]; | ||||
| y[k + N/(2*s)] = 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. | S must be a power of 2. | ||||
| push() is constant time O(1) | push() is constant time O(1) | ||||
| */ | */ | ||||
| template <typename T, size_t S> | |||||
| template <typename T, int S> | |||||
| struct RingBuffer { | struct RingBuffer { | ||||
| T data[S]; | 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); | return i & (S - 1); | ||||
| } | } | ||||
| void push(T t) { | void push(T t) { | ||||
| size_t i = mask(end++); | |||||
| int i = mask(end++); | |||||
| data[i] = t; | data[i] = t; | ||||
| } | } | ||||
| T shift() { | T shift() { | ||||
| @@ -106,9 +102,12 @@ struct RingBuffer { | |||||
| bool full() const { | bool full() const { | ||||
| return end - start >= S; | return end - start >= S; | ||||
| } | } | ||||
| size_t size() const { | |||||
| int size() const { | |||||
| return end - start; | return end - start; | ||||
| } | } | ||||
| int capacity() const { | |||||
| return S - size(); | |||||
| } | |||||
| }; | }; | ||||
| @@ -116,17 +115,17 @@ struct RingBuffer { | |||||
| S must be a power of 2. | S must be a power of 2. | ||||
| push() is constant time O(2) relative to RingBuffer | push() is constant time O(2) relative to RingBuffer | ||||
| */ | */ | ||||
| template <typename T, size_t S> | |||||
| template <typename T, int S> | |||||
| struct DoubleRingBuffer { | struct DoubleRingBuffer { | ||||
| T data[S*2]; | 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); | return i & (S - 1); | ||||
| } | } | ||||
| void push(T t) { | void push(T t) { | ||||
| size_t i = mask(end++); | |||||
| int i = mask(end++); | |||||
| data[i] = t; | data[i] = t; | ||||
| data[i + S] = t; | data[i + S] = t; | ||||
| } | } | ||||
| @@ -142,9 +141,12 @@ struct DoubleRingBuffer { | |||||
| bool full() const { | bool full() const { | ||||
| return end - start >= S; | return end - start >= S; | ||||
| } | } | ||||
| size_t size() const { | |||||
| int size() const { | |||||
| return end - start; | return end - start; | ||||
| } | } | ||||
| int capacity() const { | |||||
| return S - size(); | |||||
| } | |||||
| /** Returns a pointer to S consecutive elements for appending. | /** Returns a pointer to S consecutive elements for appending. | ||||
| If any data is appended, you must call endIncr afterwards. | If any data is appended, you must call endIncr afterwards. | ||||
| Pointer is invalidated when any other method is called. | Pointer is invalidated when any other method is called. | ||||
| @@ -152,12 +154,16 @@ struct DoubleRingBuffer { | |||||
| T *endData() { | T *endData() { | ||||
| return &data[mask(end)]; | 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 | // 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; | end += n; | ||||
| } | } | ||||
| @@ -167,7 +173,7 @@ struct DoubleRingBuffer { | |||||
| const T *startData() const { | const T *startData() const { | ||||
| return &data[mask(start)]; | return &data[mask(start)]; | ||||
| } | } | ||||
| void startIncr(size_t n) { | |||||
| void startIncr(int n) { | |||||
| start += n; | start += n; | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -245,8 +251,9 @@ struct SampleRateConverter { | |||||
| data.src_ratio = r; | data.src_ratio = r; | ||||
| } | } | ||||
| /** `in` and `out` are interlaced with the number of channels */ | /** `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<float*>(in); | |||||
| data.input_frames = *inFrames; | data.input_frames = *inFrames; | ||||
| data.data_out = out; | data.data_out = out; | ||||
| data.output_frames = *outFrames; | data.output_frames = *outFrames; | ||||
| @@ -42,16 +42,13 @@ struct AudioInterface : Module { | |||||
| // Used because the GUI thread and Rack thread can both interact with this class | // Used because the GUI thread and Rack thread can both interact with this class | ||||
| std::mutex mutex; | 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 | // in device's sample rate | ||||
| RingBuffer<Frame, (1<<15)> inBuffer; | |||||
| DoubleRingBuffer<Frame<2>, (1<<15)> inputBuffer; | |||||
| // in rack's sample rate | // in rack's sample rate | ||||
| RingBuffer<Frame, (1<<15)> outBuffer; | |||||
| DoubleRingBuffer<Frame<2>, (1<<15)> outputBuffer; | |||||
| AudioInterface(); | AudioInterface(); | ||||
| ~AudioInterface(); | ~AudioInterface(); | ||||
| @@ -92,37 +89,29 @@ void AudioInterface::step() { | |||||
| // Get input and pass it through the sample rate converter | // Get input and pass it through the sample rate converter | ||||
| if (numOutputs > 0) { | 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 | // 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) { | if (streamReady) { | ||||
| // printf("%d %d\n", inBuffer.size(), outBuffer.size()); | |||||
| PaError err; | PaError err; | ||||
| // Read output from input stream | // 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.) | // (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) | ||||
| if (numInputs > 0) { | 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) { | if (err) { | ||||
| // Ignore buffer underflows | // Ignore buffer underflows | ||||
| if (err != paInputOverflowed) { | if (err != paInputOverflowed) { | ||||
| @@ -131,52 +120,36 @@ void AudioInterface::step() { | |||||
| } | } | ||||
| // Pass output through sample rate converter | // 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; | delete[] buf; | ||||
| } | } | ||||
| // Write input to output stream | // Write input to output stream | ||||
| if (numOutputs > 0) { | 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) { | if (err) { | ||||
| // Ignore buffer underflows | // Ignore buffer underflows | ||||
| if (err != paOutputUnderflowed) { | if (err != paOutputUnderflowed) { | ||||
| fprintf(stderr, "Audio output buffer underflow\n"); | fprintf(stderr, "Audio output buffer underflow\n"); | ||||
| } | } | ||||
| } | } | ||||
| delete[] buf; | |||||
| } | } | ||||
| } | } | ||||
| // Set output | // 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 | // Correct sample rate | ||||
| const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream); | const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream); | ||||
| this->sampleRate = streamInfo->sampleRate; | this->sampleRate = streamInfo->sampleRate; | ||||
| this->deviceId = deviceId; | |||||
| } | } | ||||
| this->deviceId = deviceId; | |||||
| } | } | ||||
| void AudioInterface::closeDevice() { | void AudioInterface::closeDevice() { | ||||
| @@ -256,7 +228,6 @@ void AudioInterface::closeDevice() { | |||||
| PaError err; | PaError err; | ||||
| err = Pa_CloseStream(stream); | err = Pa_CloseStream(stream); | ||||
| if (err) { | if (err) { | ||||
| // This shouldn't happen: | |||||
| fprintf(stderr, "Failed to close audio stream: %s\n", Pa_GetErrorText(err)); | fprintf(stderr, "Failed to close audio stream: %s\n", Pa_GetErrorText(err)); | ||||
| } | } | ||||
| } | } | ||||
| @@ -268,8 +239,10 @@ void AudioInterface::closeDevice() { | |||||
| numInputs = 0; | numInputs = 0; | ||||
| // Clear buffers | // Clear buffers | ||||
| inBuffer.clear(); | |||||
| outBuffer.clear(); | |||||
| inputBuffer.clear(); | |||||
| outputBuffer.clear(); | |||||
| inputSrc.reset(); | |||||
| outputSrc.reset(); | |||||
| } | } | ||||
| @@ -28,16 +28,18 @@ float randomNormal(){ | |||||
| std::string stringf(const char *format, ...) { | 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) | if (size < 0) | ||||
| return ""; | return ""; | ||||
| size++; | size++; | ||||
| std::string s; | std::string s; | ||||
| s.resize(size); | 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; | return s; | ||||
| } | } | ||||