Browse Source

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

and DoubleRingBuffer
tags/v0.3.0
Andrew Belt 7 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 \
-Wl,--export-all-symbols,--out-implib,libRack.a -mwindows
TARGET = Rack.exe
# OBJECTS = Rack.res

%.res: %.rc
windres $^ -O coff -o $@
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))




+ 48
- 41
include/dsp.hpp View File

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


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




+ 7
- 5
src/util.cpp View File

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



Loading…
Cancel
Save