and DoubleRingBuffertags/v0.3.0
| @@ -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 | |||
| @@ -1,5 +1,5 @@ | |||
| OBJECTS = $(patsubst %, build/%.o, $(SOURCES)) | |||
| OBJECTS += $(patsubst %, build/%.o, $(SOURCES)) | |||
| DEPS = $(patsubst %, build/%.d, $(SOURCES)) | |||
| @@ -3,25 +3,19 @@ | |||
| #include <assert.h> | |||
| #include <string.h> | |||
| #include <samplerate.h> | |||
| #include <complex.h> | |||
| #include <complex> | |||
| #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 <size_t CHANNELS> | |||
| 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<float> *tw; | |||
| 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++) { | |||
| 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<float>(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<float> *x, std::complex<float> *y, int s=1) { | |||
| 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) { | |||
| 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<float> *x, std::complex<float> *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<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 + 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 <typename T, size_t S> | |||
| template <typename T, int S> | |||
| 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 <typename T, size_t S> | |||
| template <typename T, int S> | |||
| 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<float*>(in); | |||
| data.input_frames = *inFrames; | |||
| data.data_out = out; | |||
| 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 | |||
| 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<Frame, (1<<15)> inBuffer; | |||
| DoubleRingBuffer<Frame<2>, (1<<15)> inputBuffer; | |||
| // in rack's sample rate | |||
| RingBuffer<Frame, (1<<15)> outBuffer; | |||
| DoubleRingBuffer<Frame<2>, (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(); | |||
| } | |||
| @@ -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; | |||
| } | |||