Browse Source

Add sample rate conversion to AudioInterface input/output, fix bugs in stringf

and DoubleRingBuffer
tags/v0.3.0
Andrew Belt 8 years ago
parent
commit
a8e82624fc
5 changed files with 97 additions and 111 deletions
  1. +4
    -0
      Makefile
  2. +1
    -1
      Makefile.inc
  3. +48
    -41
      include/dsp.hpp
  4. +37
    -64
      src/core/AudioInterface.cpp
  5. +7
    -5
      src/util.cpp

+ 4
- 0
Makefile View File

@@ -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
- 1
Makefile.inc View File

@@ -1,5 +1,5 @@


OBJECTS = $(patsubst %, build/%.o, $(SOURCES))
OBJECTS += $(patsubst %, build/%.o, $(SOURCES))
DEPS = $(patsubst %, build/%.d, $(SOURCES)) DEPS = $(patsubst %, build/%.d, $(SOURCES))






+ 48
- 41
include/dsp.hpp View File

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


+ 37
- 64
src/core/AudioInterface.cpp View File

@@ -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();
} }






+ 7
- 5
src/util.cpp View File

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




Loading…
Cancel
Save