From 70e3ab97b65af2b05380af44aa6e900f9022bcb7 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Mon, 6 Feb 2017 02:32:07 -0500 Subject: [PATCH] fix Linux audio by using asynchronous Portaudio instead of synchronous --- Makefile | 9 ++- include/components.hpp | 60 ++++++++++++------ include/dsp.hpp | 8 +-- include/util.hpp | 34 ++++++++++ src/core/AudioInterface.cpp | 122 ++++++++++++++++++++++-------------- src/engine.cpp | 33 +--------- src/main.cpp | 2 +- src/scene.cpp | 2 +- src/widgets/Toolbar.cpp | 6 +- src/widgets/WireWidget.cpp | 12 ++-- 10 files changed, 174 insertions(+), 114 deletions(-) diff --git a/Makefile b/Makefile index 9881623e..6347f50f 100644 --- a/Makefile +++ b/Makefile @@ -49,13 +49,16 @@ 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 - -all: $(TARGET) +# OBJECTS = Rack.res %.res: %.rc windres $^ -O coff -o $@ endif +all: $(TARGET) + +clean: + rm -rf $(TARGET) build + include Makefile.inc \ No newline at end of file diff --git a/include/components.hpp b/include/components.hpp index 7f8f06fa..bea9bed5 100644 --- a/include/components.hpp +++ b/include/components.hpp @@ -10,30 +10,37 @@ namespace rack { struct KnobDavies1900h : SpriteKnob { KnobDavies1900h() { - box.size = Vec(36, 36); - spriteOffset = Vec(-8, -8); - spriteSize = Vec(64, 64); - minIndex = 44; - maxIndex = -46; + // box.size = Vec(36, 36); + // spriteOffset = Vec(-8, -8); + // spriteSize = Vec(64, 64); + // minIndex = 44; + // maxIndex = -46; + // spriteCount = 120; + box.size = Vec(42, 42); + spriteOffset = Vec(-9, -9); + spriteSize = Vec(60, 60); + minIndex = 0; + maxIndex = 119; spriteCount = 120; + spriteImage = Image::load("res/Black Plastic small 01.png"); } }; struct KnobDavies1900hWhite : KnobDavies1900h { KnobDavies1900hWhite() { - spriteImage = Image::load("res/ComponentLibrary/Davies1900hWhite.png"); + // spriteImage = Image::load("res/ComponentLibrary/Davies1900hWhite.png"); } }; struct KnobDavies1900hBlack : KnobDavies1900h { KnobDavies1900hBlack() { - spriteImage = Image::load("res/ComponentLibrary/Davies1900hBlack.png"); + // spriteImage = Image::load("res/ComponentLibrary/Davies1900hBlack.png"); } }; struct KnobDavies1900hRed : KnobDavies1900h { KnobDavies1900hRed() { - spriteImage = Image::load("res/ComponentLibrary/Davies1900hRed.png"); + // spriteImage = Image::load("res/ComponentLibrary/Davies1900hRed.png"); } }; @@ -43,10 +50,14 @@ struct KnobDavies1900hRed : KnobDavies1900h { struct PJ301M : SpriteWidget { PJ301M() { - box.size = Vec(24, 24); - spriteOffset = Vec(-10, -10); - spriteSize = Vec(48, 48); - spriteImage = Image::load("res/ComponentLibrary/PJ301M.png"); + // box.size = Vec(24, 24); + // spriteOffset = Vec(-10, -10); + // spriteSize = Vec(48, 48); + // spriteImage = Image::load("res/ComponentLibrary/PJ301M.png"); + box.size = Vec(26, 26); + spriteOffset = Vec(-16, -16); + spriteSize = Vec(56, 56); + spriteImage = Image::load("res/port.png"); } }; struct InputPortPJ301M : InputPort, PJ301M {}; @@ -54,10 +65,14 @@ struct OutputPortPJ301M: OutputPort, PJ301M {}; struct PJ3410 : SpriteWidget { PJ3410() { - box.size = Vec(31, 31); - spriteOffset = Vec(-9, -9); - spriteSize = Vec(54, 54); - spriteImage = Image::load("res/ComponentLibrary/PJ3410.png"); + // box.size = Vec(31, 31); + // spriteOffset = Vec(-9, -9); + // spriteSize = Vec(54, 54); + // spriteImage = Image::load("res/ComponentLibrary/PJ3410.png"); + box.size = Vec(26, 26); + spriteOffset = Vec(-12, -12); + spriteSize = Vec(56, 56); + spriteImage = Image::load("res/port.png"); } }; struct InputPortPJ3410 : InputPort, PJ3410 {}; @@ -65,10 +80,15 @@ struct OutputPortPJ3410: OutputPort, PJ3410 {}; struct CL1362 : SpriteWidget { CL1362() { - box.size = Vec(33, 29); - spriteOffset = Vec(-10, -10); - spriteSize = Vec(57, 54); - spriteImage = Image::load("res/ComponentLibrary/CL1362.png"); + // box.size = Vec(33, 29); + // spriteOffset = Vec(-10, -10); + // spriteSize = Vec(57, 54); + // spriteImage = Image::load("res/ComponentLibrary/CL1362.png"); + box.size = Vec(26, 26); + spriteOffset = Vec(-12, -12); + spriteSize = Vec(56, 56); + spriteImage = Image::load("res/port.png"); + } }; struct InputPortCL1362 : InputPort, CL1362 {}; diff --git a/include/dsp.hpp b/include/dsp.hpp index b97d156d..8e9569f8 100644 --- a/include/dsp.hpp +++ b/include/dsp.hpp @@ -251,11 +251,11 @@ struct SampleRateConverter { data.src_ratio = r; } /** `in` and `out` are interlaced with the number of channels */ - 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(in); + void process(const Frame *in, int *inFrames, Frame *out, int *outFrames) { + // Old versions of libsamplerate use float* here instead of const float* + data.data_in = (float*) in; data.input_frames = *inFrames; - data.data_out = out; + data.data_out = (float*) out; data.output_frames = *outFrames; src_process(state, &data); *inFrames = data.input_frames_used; diff --git a/include/util.hpp b/include/util.hpp index a351d7c4..d63a765a 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -8,6 +8,8 @@ #include #include +#include +#include namespace rack { @@ -33,4 +35,36 @@ std::string stringf(const char *format, ...); /** Truncates and adds "..." to a string, not exceeding `len` characters */ std::string ellipsize(std::string s, size_t len); + +/** Threads which obtain a VIPLock will cause wait() to block for other less important threads. +This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. +*/ +struct VIPMutex { + int count = 0; + std::condition_variable cv; + std::mutex countMutex; + + /** Blocks until there are no remaining VIPLocks */ + void wait() { + std::unique_lock lock(countMutex); + while (count > 0) + cv.wait(lock); + } +}; + +struct VIPLock { + VIPMutex &m; + VIPLock(VIPMutex &m) : m(m) { + std::unique_lock lock(m.countMutex); + m.count++; + } + ~VIPLock() { + std::unique_lock lock(m.countMutex); + m.count--; + lock.unlock(); + m.cv.notify_all(); + } +}; + + } // namespace rack diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 53990f10..80083cfa 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "core.hpp" #include "dsp.hpp" @@ -40,7 +41,8 @@ struct AudioInterface : Module { int numInputs = 0; // Used because the GUI thread and Rack thread can both interact with this class - std::mutex mutex; + std::mutex bufferMutex; + bool streamRunning; SampleRateConverter<2> inputSrc; SampleRateConverter<2> outputSrc; @@ -54,6 +56,7 @@ struct AudioInterface : Module { AudioInterface(); ~AudioInterface(); void step(); + void stepStream(const float *input, float *output, int numFrames); int getDeviceCount(); std::string getDeviceName(int deviceId); @@ -84,10 +87,23 @@ AudioInterface::~AudioInterface() { } void AudioInterface::step() { - std::lock_guard lock(mutex); if (!stream) return; + // Read/write stream if we have enough input, OR the output buffer is empty if we have no input + if (numOutputs > 0) { + while (inputSrcBuffer.size() >= blockSize && streamRunning) { + std::this_thread::sleep_for(std::chrono::duration(100e-6)); + } + } + else if (numInputs > 0) { + while (outputBuffer.empty() && streamRunning) { + std::this_thread::sleep_for(std::chrono::duration(100e-6)); + } + } + + std::lock_guard lock(bufferMutex); + // Get input and pass it through the sample rate converter if (numOutputs > 0) { if (!inputBuffer.full()) { @@ -98,63 +114,63 @@ void AudioInterface::step() { } // Once full, sample rate convert the input + // inputBuffer -> SRC -> inputSrcBuffer if (inputBuffer.full()) { inputSrc.setRatio(sampleRate / gSampleRate); int inLen = inputBuffer.size(); int outLen = inputSrcBuffer.capacity(); - inputSrc.process((const float*) inputBuffer.startData(), &inLen, (float*) inputSrcBuffer.endData(), &outLen); + inputSrc.process(inputBuffer.startData(), &inLen, inputSrcBuffer.endData(), &outLen); inputBuffer.startIncr(inLen); inputSrcBuffer.endIncr(outLen); } } - // Read/write stream if we have enough input, OR the output buffer is empty if we have no input - bool streamReady = (numOutputs > 0) ? (inputSrcBuffer.size() >= blockSize) : (outputBuffer.empty()); - if (streamReady) { - // printf("%p\t%d\t%d\n", this, inputSrcBuffer.size(), outputBuffer.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) { - Frame<2> *buf = new Frame<2>[blockSize]; - err = Pa_ReadStream(stream, (float*) buf, blockSize); - if (err) { - // Ignore buffer underflows - if (err == paInputOverflowed) { - fprintf(stderr, "Audio input buffer underflow\n"); - } - } + // Set output + 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]); + } +} - // Pass output through sample rate converter - outputSrc.setRatio(gSampleRate / sampleRate); - int inLen = blockSize; - int outLen = outputBuffer.capacity(); - outputSrc.process((float*) buf, &inLen, (float*) outputBuffer.endData(), &outLen); - outputBuffer.endIncr(outLen); - delete[] buf; +void AudioInterface::stepStream(const float *input, float *output, int numFrames) { + if (numOutputs > 0) { + // Wait for enough input before proceeding + while (inputSrcBuffer.size() < numFrames) { + if (!streamRunning) + return; + std::this_thread::sleep_for(std::chrono::duration(100e-6)); } + } - - // Write input to output stream - if (numOutputs > 0) { - assert(inputSrcBuffer.size() >= blockSize); - err = Pa_WriteStream(stream, (const float*) inputSrcBuffer.startData(), blockSize); - inputSrcBuffer.startIncr(blockSize); - if (err) { - // Ignore buffer underflows - if (err == paOutputUnderflowed) { - fprintf(stderr, "Audio output buffer underflow\n"); - } + std::lock_guard lock(bufferMutex); + // input stream -> output buffer + if (numInputs > 0) { + Frame<2> inputFrames[numFrames]; + for (int i = 0; i < numFrames; i++) { + for (int c = 0; c < 2; c++) { + inputFrames[i].samples[c] = (numInputs > c) ? input[i*numInputs + c] : 0.0; } } + + // Pass output through sample rate converter + outputSrc.setRatio(gSampleRate / sampleRate); + int inLen = numFrames; + int outLen = outputBuffer.capacity(); + outputSrc.process(inputFrames, &inLen, outputBuffer.endData(), &outLen); + outputBuffer.endIncr(outLen); } - // Set output - 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]); + // input buffer -> output stream + if (numOutputs > 0) { + for (int i = 0; i < numFrames; i++) { + if (inputSrcBuffer.empty()) + break; + Frame<2> f = inputSrcBuffer.shift(); + for (int c = 0; c < numOutputs; c++) { + output[i*numOutputs + c] = f.samples[c]; + } + } } } @@ -170,9 +186,15 @@ std::string AudioInterface::getDeviceName(int deviceId) { return stringf("%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels); } +static int paCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { + AudioInterface *p = (AudioInterface *) userData; + p->stepStream((const float *) inputBuffer, (float *) outputBuffer, framesPerBuffer); + return paContinue; +} + void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) { closeDevice(); - std::lock_guard lock(mutex); + std::lock_guard lock(bufferMutex); this->sampleRate = sampleRate; this->blockSize = blockSize; @@ -207,7 +229,7 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) { err = Pa_OpenStream(&stream, numInputs == 0 ? NULL : &inputParameters, numOutputs == 0 ? NULL : &outputParameters, - sampleRate, blockSize, paNoFlag, NULL, NULL); + sampleRate, blockSize, paNoFlag, paCallback, this); if (err) { fprintf(stderr, "Failed to open audio stream: %s\n", Pa_GetErrorText(err)); return; @@ -218,6 +240,8 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) { fprintf(stderr, "Failed to start audio stream: %s\n", Pa_GetErrorText(err)); return; } + // This should go after Pa_StartStream because sometimes it will call the callback once synchronously, and that time it should return early + streamRunning = true; // Correct sample rate const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream); @@ -227,10 +251,16 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) { } void AudioInterface::closeDevice() { - std::lock_guard lock(mutex); + std::lock_guard lock(bufferMutex); if (stream) { PaError err; + streamRunning = false; + err = Pa_StopStream(stream); + if (err) { + fprintf(stderr, "Failed to stop audio stream: %s\n", Pa_GetErrorText(err)); + } + err = Pa_CloseStream(stream); if (err) { fprintf(stderr, "Failed to close audio stream: %s\n", Pa_GetErrorText(err)); diff --git a/src/engine.cpp b/src/engine.cpp index 697da5a1..c80b77b6 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -5,47 +5,16 @@ #include #include #include -#include -#include #include #include "engine.hpp" +#include "util.hpp" namespace rack { float gSampleRate; -/** Threads which obtain a VIPLock will cause wait() to block for other less important threads. -This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. -*/ -struct VIPMutex { - int count = 0; - std::condition_variable cv; - std::mutex countMutex; - - /** Blocks until there are no remaining VIPLocks */ - void wait() { - std::unique_lock lock(countMutex); - while (count > 0) - cv.wait(lock); - } -}; - -struct VIPLock { - VIPMutex &m; - VIPLock(VIPMutex &m) : m(m) { - std::unique_lock lock(m.countMutex); - m.count++; - } - ~VIPLock() { - std::unique_lock lock(m.countMutex); - m.count--; - lock.unlock(); - m.cv.notify_all(); - } -}; - static bool running = false; diff --git a/src/main.cpp b/src/main.cpp index c2bcea07..2612d491 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,7 @@ int main() { assert(success); CFRelease(bundleURL); - // chdir(dirname(path)); + chdir(dirname(path)); } #endif diff --git a/src/scene.cpp b/src/scene.cpp index 1a7214e1..7e1f30ef 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -4,7 +4,7 @@ namespace rack { std::string gApplicationName = "VCV Rack"; -std::string gApplicationVersion = "v0.2.0 alpha"; +std::string gApplicationVersion = "v0.1.1 alpha"; RackWidget *gRackWidget = NULL; diff --git a/src/widgets/Toolbar.cpp b/src/widgets/Toolbar.cpp index c0596763..1a9abd06 100644 --- a/src/widgets/Toolbar.cpp +++ b/src/widgets/Toolbar.cpp @@ -47,9 +47,9 @@ struct FileChoice : ChoiceButton { openItem->text = "Open"; menu->pushChild(openItem); - MenuItem *saveItem = new SaveItem(); - saveItem->text = "Save"; - menu->pushChild(saveItem); + // MenuItem *saveItem = new SaveItem(); + // saveItem->text = "Save"; + // menu->pushChild(saveItem); MenuItem *saveAsItem = new SaveItem(); saveAsItem->text = "Save As"; diff --git a/src/widgets/WireWidget.cpp b/src/widgets/WireWidget.cpp index ee0269d4..e635136a 100644 --- a/src/widgets/WireWidget.cpp +++ b/src/widgets/WireWidget.cpp @@ -76,13 +76,17 @@ static NVGcolor wireColors[8] = { nvgRGB(0x88, 0x88, 0x88), // light gray nvgRGB(0xaa, 0xaa, 0xaa), // white }; -static int nextWireColorId = 1; - +static int lastWireColorId = -1; WireWidget::WireWidget() { - color = wireColors[nextWireColorId]; - nextWireColorId = (nextWireColorId + 1) % 8; + int wireColorId; + do { + wireColorId = randomu32() % 8; + } while (wireColorId == lastWireColorId); + lastWireColorId = wireColorId; + + color = wireColors[wireColorId]; } WireWidget::~WireWidget() {