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