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; | ||||
} | } | ||||