From a18e4a4d6e44a5df7c6b5f9d1f11f0e3c784db8e Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Mon, 16 Jan 2017 01:51:45 -0500 Subject: [PATCH] Added ring buffers, made Rack a class, added stringf helper function, added VIPMutex/VIPLock, added sample rate and block size options to AudioInterface --- include/dsp.hpp | 95 +++++++++++---- include/{Rack.hpp => rack.hpp} | 56 +++++---- include/util.hpp | 8 ++ src/Rack.cpp | 216 +++++++++++++++++++++++++++++++++ src/core/AudioInterface.cpp | 195 +++++++++++++++++++++++------ src/core/MidiInterface.cpp | 4 +- src/core/core.hpp | 10 +- src/gui.cpp | 2 +- src/main.cpp | 15 +-- src/plugin.cpp | 2 +- src/rack.cpp | 202 ------------------------------ src/util.cpp | 17 +++ src/widgets/Button.cpp | 2 +- src/widgets/ChoiceButton.cpp | 2 +- src/widgets/InputPort.cpp | 2 +- src/widgets/Knob.cpp | 2 +- src/widgets/Label.cpp | 2 +- src/widgets/Light.cpp | 2 +- src/widgets/Menu.cpp | 2 +- src/widgets/MenuEntry.cpp | 2 +- src/widgets/MenuItem.cpp | 2 +- src/widgets/MenuLabel.cpp | 2 +- src/widgets/MenuOverlay.cpp | 2 +- src/widgets/ModulePanel.cpp | 2 +- src/widgets/ModuleWidget.cpp | 11 +- src/widgets/OutputPort.cpp | 2 +- src/widgets/ParamWidget.cpp | 4 +- src/widgets/Port.cpp | 2 +- src/widgets/QuantityWidget.cpp | 2 +- src/widgets/RackWidget.cpp | 2 +- src/widgets/Scene.cpp | 2 +- src/widgets/Screw.cpp | 2 +- src/widgets/ScrollBar.cpp | 2 +- src/widgets/ScrollWidget.cpp | 2 +- src/widgets/Slider.cpp | 2 +- src/widgets/SpriteWidget.cpp | 2 +- src/widgets/Toolbar.cpp | 2 +- src/widgets/Tooltip.cpp | 2 +- src/widgets/Widget.cpp | 2 +- src/widgets/WireWidget.cpp | 6 +- 40 files changed, 555 insertions(+), 338 deletions(-) rename include/{Rack.hpp => rack.hpp} (87%) create mode 100644 src/Rack.cpp delete mode 100644 src/rack.cpp diff --git a/include/dsp.hpp b/include/dsp.hpp index 3806b6e4..dda07723 100644 --- a/include/dsp.hpp +++ b/include/dsp.hpp @@ -8,14 +8,17 @@ namespace rack { -/** S must be a power of 2 */ +/** A simple cyclic buffer. +S must be a power of 2. +push() is constant time O(1) +*/ template struct RingBuffer { - T data[S] = {}; + T data[S]; size_t start = 0; size_t end = 0; - size_t mask(size_t i) { + size_t mask(size_t i) const { return i & (S - 1); } void push(T t) { @@ -25,26 +28,29 @@ struct RingBuffer { T shift() { return data[mask(start++)]; } - bool empty() { + bool empty() const { return start >= end; } - bool full() { + bool full() const { return end - start >= S; } - size_t size() { + size_t size() const { return end - start; } }; -/** S must be a power of 2 */ +/** A cyclic buffer which maintains a valid linear array of size S by keeping a copy of the buffer in adjacent memory. +S must be a power of 2. +push() is constant time O(2) relative to RingBuffer +*/ template struct DoubleRingBuffer { - T data[S*2] = {}; + T data[S*2]; size_t start = 0; size_t end = 0; - size_t mask(size_t i) { + size_t mask(size_t i) const { return i & (S - 1); } void push(T t) { @@ -55,21 +61,51 @@ struct DoubleRingBuffer { T shift() { return data[mask(start++)]; } - bool empty() { + bool empty() const { return start >= end; } - bool full() { + bool full() const { return end - start >= S; } - size_t size() { + size_t size() const { return end - start; } + /** 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. + */ + T *endData() { + return &data[mask(end)]; + } + void endIncr(size_t n) { + size_t mend = mask(end) + n; + if (mend > 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 + } + end += n; + } + /** Returns a pointer to S consecutive elements for consumption + If any data is consumed, call startIncr afterwards. + */ + const T *startData() const { + return &data[mask(start)]; + } + void startIncr(size_t n) { + start += n; + } }; +/** A cyclic buffer which maintains a valid linear array of size S by sliding along a larger block of size N. +The linear array of S elements are moved back to the start of the block once it outgrows past the end. +This happens every N - S pushes, so the push() time is O(1 + S / (N - S)). +For example, a float buffer of size 64 in a block of size 1024 is nearly as efficient as RingBuffer. +*/ template struct AppleRingBuffer { - T data[N] = {}; + T data[N]; size_t start = 0; size_t end = 0; @@ -85,19 +121,34 @@ struct AppleRingBuffer { T shift() { return data[start++]; } - bool empty() { + bool empty() const { return start >= end; } - bool full() { + bool full() const { return end - start >= S; } - size_t size() { + size_t size() const { return end - start; } + /** Returns a pointer to S consecutive elements for appending, requesting to append n elements. + */ + T *endData(size_t n) { + // TODO + return &data[end]; + } + /** Returns a pointer to S consecutive elements for consumption + If any data is consumed, call startIncr afterwards. + */ + const T *startData() const { + return &data[start]; + } + void startIncr(size_t n) { + // This is valid as long as n < S + start += n; + } }; -template struct SampleRateConverter { SRC_STATE *state; SRC_DATA data; @@ -116,17 +167,13 @@ struct SampleRateConverter { void setRatio(float r) { data.src_ratio = r; } - void push(const float *in, int length) { - float out[S]; + void process(float *in, int inLength, float *out, int outLength) { data.data_in = in; - data.input_frames = length; + data.input_frames = inLength; data.data_out = out; - data.output_frames = S; + data.output_frames = outLength; src_process(state, &data); } - void push(float in) { - push(&in, 1); - } }; diff --git a/include/Rack.hpp b/include/rack.hpp similarity index 87% rename from include/Rack.hpp rename to include/rack.hpp index 44f576bc..cc06cfea 100644 --- a/include/Rack.hpp +++ b/include/rack.hpp @@ -4,17 +4,14 @@ #include #include #include +#include +#include +#include #include "widgets.hpp" namespace rack { -extern std::string gApplicationName; -extern std::string gApplicationVersion; - -extern Scene *gScene; -extern RackWidget *gRackWidget; - //////////////////// // Plugin manager //////////////////// @@ -75,10 +72,6 @@ void drawImage(NVGcontext *vg, Vec pos, int imageId); // rack.cpp //////////////////// -// TODO Find a clean way to make this a variable -#define SAMPLE_RATE 44100 - - struct Wire; struct Module { @@ -106,17 +99,27 @@ struct Wire { float value = 0.0; }; -void rackInit(); -void rackDestroy(); -void rackStart(); -void rackStop(); -// Does not transfer ownership -void rackAddModule(Module *module); -void rackRemoveModule(Module *module); -// Does not transfer ownership -void rackConnectWire(Wire *wire); -void rackDisconnectWire(Wire *wire); -void rackSetParamSmooth(Module *module, int paramId, float value); +struct Rack { + Rack(); + ~Rack(); + /** Launches rack thread */ + void start(); + void stop(); + void run(); + void step(); + /** Does not transfer pointer ownership */ + void addModule(Module *module); + void removeModule(Module *module); + /** Does not transfer pointer ownership */ + void addWire(Wire *wire); + void removeWire(Wire *wire); + void setParamSmooth(Module *module, int paramId, float value); + + float sampleRate; + + struct Impl; + Impl *impl; +}; //////////////////// // Optional helpers for plugins @@ -186,6 +189,17 @@ Screw *createScrew(Vec pos) { return screw; } +//////////////////// +// Globals +//////////////////// + +extern std::string gApplicationName; +extern std::string gApplicationVersion; + +extern Scene *gScene; +extern RackWidget *gRackWidget; + +extern Rack *gRack; } // namespace rack diff --git a/include/util.hpp b/include/util.hpp index ee1b4a4a..c19ffccc 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace rack { @@ -183,5 +184,12 @@ struct Rect { } }; +//////////////////// +// Helper functions +//////////////////// + +/** Converts a printf format string and optional arguments into a std::string */ +std::string stringf(const char *format, ...); + } // namespace rack diff --git a/src/Rack.cpp b/src/Rack.cpp new file mode 100644 index 00000000..b14df79f --- /dev/null +++ b/src/Rack.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include + +#include "rack.hpp" + + +namespace rack { + +/** 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(); + } +}; + + +struct Rack::Impl { + bool running = false; + + std::mutex mutex; + std::thread audioThread; + VIPMutex vipMutex; + + std::set modules; + // Merely used for keeping track of which module inputs point to which module outputs, to prevent pointer mistakes and make the rack API more rigorous + std::set wires; + + // Parameter interpolation + Module *smoothModule = NULL; + int smoothParamId; + float smoothValue; +}; + + +Rack::Rack() { + impl = new Rack::Impl(); + sampleRate = 44100.0; +} + +Rack::~Rack() { + // Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the GUI was destroyed. + assert(impl->wires.empty()); + assert(impl->modules.empty()); + delete impl; +} + +void Rack::start() { + impl->running = true; + impl->audioThread = std::thread(&Rack::run, this); +} + +void Rack::stop() { + impl->running = false; + impl->audioThread.join(); +} + +void Rack::run() { + // Every time the rack waits and locks a mutex, it steps this many frames + const int stepSize = 32; + + while (impl->running) { + impl->vipMutex.wait(); + + auto start = std::chrono::high_resolution_clock::now(); + { + std::lock_guard lock(impl->mutex); + for (int i = 0; i < stepSize; i++) { + step(); + } + } + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::nanoseconds((long) (0.9 * 1e9 * stepSize / sampleRate)) - (end - start); + // Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate + std::this_thread::sleep_for(duration); + } +} + +void Rack::step() { + // Param interpolation + if (impl->smoothModule) { + float value = impl->smoothModule->params[impl->smoothParamId]; + const float lambda = 60.0; // decay rate is 1 graphics frame + const float snap = 0.0001; + float delta = impl->smoothValue - value; + if (fabsf(delta) < snap) { + impl->smoothModule->params[impl->smoothParamId] = impl->smoothValue; + impl->smoothModule = NULL; + } + else { + value += delta * lambda / sampleRate; + impl->smoothModule->params[impl->smoothParamId] = value; + } + } + // Step all modules + std::chrono::time_point start, end; + for (Module *module : impl->modules) { + // Start clock for CPU usage + start = std::chrono::high_resolution_clock::now(); + // Step module by one frame + module->step(); + // Stop clock and smooth step time value + end = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = end - start; + float elapsed = diff.count() * sampleRate; + const float lambda = 1.0; + module->cpuTime += (elapsed - module->cpuTime) * lambda / sampleRate; + } +} + +void Rack::addModule(Module *module) { + assert(module); + VIPLock vipLock(impl->vipMutex); + std::lock_guard lock(impl->mutex); + // Check that the module is not already added + assert(impl->modules.find(module) == impl->modules.end()); + impl->modules.insert(module); +} + +void Rack::removeModule(Module *module) { + assert(module); + VIPLock vipLock(impl->vipMutex); + std::lock_guard lock(impl->mutex); + // Remove parameter interpolation which point to this module + if (module == impl->smoothModule) { + impl->smoothModule = NULL; + } + // Check that all wires are disconnected + for (Wire *wire : impl->wires) { + assert(wire->outputModule != module); + assert(wire->inputModule != module); + } + auto it = impl->modules.find(module); + if (it != impl->modules.end()) { + impl->modules.erase(it); + } +} + +void Rack::addWire(Wire *wire) { + assert(wire); + VIPLock vipLock(impl->vipMutex); + std::lock_guard lock(impl->mutex); + // It would probably be good to reset the wire voltage + wire->value = 0.0; + // Check that the wire is not already added + assert(impl->wires.find(wire) == impl->wires.end()); + assert(wire->outputModule); + assert(wire->inputModule); + // Check that the inputs/outputs are not already used by another cable + for (Wire *wire2 : impl->wires) { + assert(wire2 != wire); + assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId)); + assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); + } + // Connect the wire to inputModule + impl->wires.insert(wire); + wire->inputModule->inputs[wire->inputId] = &wire->value; + wire->outputModule->outputs[wire->outputId] = &wire->value; +} + +void Rack::removeWire(Wire *wire) { + assert(wire); + VIPLock vipLock(impl->vipMutex); + std::lock_guard lock(impl->mutex); + // Disconnect wire from inputModule + wire->inputModule->inputs[wire->inputId] = NULL; + wire->outputModule->outputs[wire->outputId] = NULL; + + auto it = impl->wires.find(wire); + assert(it != impl->wires.end()); + impl->wires.erase(it); +} + +void Rack::setParamSmooth(Module *module, int paramId, float value) { + VIPLock vipLock(impl->vipMutex); + std::lock_guard lock(impl->mutex); + // Check existing parameter interpolation + if (impl->smoothModule) { + if (!(impl->smoothModule == module && impl->smoothParamId == paramId)) { + // Jump param value to smooth value + impl->smoothModule->params[impl->smoothParamId] = impl->smoothValue; + } + } + impl->smoothModule = module; + impl->smoothParamId = paramId; + impl->smoothValue = value; +} + + +} // namespace rack diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index a02ec7a5..f21f514c 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -37,21 +37,25 @@ struct AudioInterface : Module { PaStream *stream = NULL; int numOutputs; int numInputs; - int numFrames; + int bufferFrame; float outputBuffer[1<<14] = {}; float inputBuffer[1<<14] = {}; - int bufferFrame; // Used because the GUI thread and Rack thread can both interact with this class std::mutex mutex; + // Set these and call openDevice() + int deviceId = -1; + float sampleRate = 44100.0; + int blockSize = 256; + AudioInterface(); ~AudioInterface(); void step(); int getDeviceCount(); std::string getDeviceName(int deviceId); - // Use -1 as the deviceId to close the current device - void openDevice(int deviceId); + void openDevice(); + void closeDevice(); }; @@ -59,10 +63,11 @@ AudioInterface::AudioInterface() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); + closeDevice(); } AudioInterface::~AudioInterface() { - openDevice(-1); + closeDevice(); } void AudioInterface::step() { @@ -85,14 +90,14 @@ void AudioInterface::step() { setf(outputs[i], inputBuffer[numOutputs * bufferFrame + i] * 5.0); } - if (++bufferFrame >= numFrames) { + if (++bufferFrame >= blockSize) { bufferFrame = 0; PaError err; // Input // (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) if (numInputs > 0) { - err = Pa_ReadStream(stream, inputBuffer, numFrames); + err = Pa_ReadStream(stream, inputBuffer, blockSize); if (err) { // Ignore buffer underflows if (err != paInputOverflowed) { @@ -103,7 +108,7 @@ void AudioInterface::step() { // Output if (numOutputs > 0) { - err = Pa_WriteStream(stream, outputBuffer, numFrames); + err = Pa_WriteStream(stream, outputBuffer, blockSize); if (err) { // Ignore buffer underflows if (err != paOutputUnderflowed) { @@ -123,29 +128,13 @@ std::string AudioInterface::getDeviceName(int deviceId) { if (!info) return ""; const PaHostApiInfo *apiInfo = Pa_GetHostApiInfo(info->hostApi); - char name[1024]; - snprintf(name, sizeof(name), "%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels); - return name; + return stringf("%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels); } -void AudioInterface::openDevice(int deviceId) { +void AudioInterface::openDevice() { + closeDevice(); std::lock_guard lock(mutex); - // Close existing device - if (stream) { - PaError err; - err = Pa_CloseStream(stream); - if (err) { - // This shouldn't happen: - printf("Failed to close audio stream: %s\n", Pa_GetErrorText(err)); - } - stream = NULL; - } - numOutputs = 0; - numInputs = 0; - - numFrames = 256; - bufferFrame = 0; // Open new device if (deviceId >= 0) { PaError err; @@ -173,10 +162,10 @@ void AudioInterface::openDevice(int deviceId) { inputParameters.hostApiSpecificStreamInfo = NULL; // Don't use stream parameters if 0 input or output channels - PaStreamParameters *outputP = numOutputs == 0 ? NULL : &outputParameters; - PaStreamParameters *inputP = numInputs == 0 ? NULL : &inputParameters; - - err = Pa_OpenStream(&stream, inputP, outputP, SAMPLE_RATE, numFrames, paNoFlag, NULL, NULL); + err = Pa_OpenStream(&stream, + numInputs == 0 ? NULL : &outputParameters, + numOutputs == 0 ? NULL : &inputParameters, + sampleRate, blockSize, paNoFlag, NULL, NULL); if (err) { printf("Failed to open audio stream: %s\n", Pa_GetErrorText(err)); return; @@ -190,12 +179,30 @@ void AudioInterface::openDevice(int deviceId) { } } +void AudioInterface::closeDevice() { + std::lock_guard lock(mutex); + + if (stream) { + PaError err; + err = Pa_CloseStream(stream); + if (err) { + // This shouldn't happen: + printf("Failed to close audio stream: %s\n", Pa_GetErrorText(err)); + } + stream = NULL; + } + numOutputs = 0; + numInputs = 0; + bufferFrame = 0; +} + struct AudioItem : MenuItem { AudioInterface *audioInterface; int deviceId; void onAction() { - audioInterface->openDevice(deviceId); + audioInterface->deviceId = deviceId; + audioInterface->openDevice(); } }; @@ -224,6 +231,81 @@ struct AudioChoice : ChoiceButton { overlay->addChild(menu); gScene->setOverlay(overlay); } + void step() { + std::string name = audioInterface->getDeviceName(audioInterface->deviceId); + if (name.empty()) + text = "(no device)"; + else + text = name; + } +}; + + +struct SampleRateItem : MenuItem { + AudioInterface *audioInterface; + float sampleRate; + void onAction() { + audioInterface->sampleRate = sampleRate; + audioInterface->openDevice(); + } +}; + +struct SampleRateChoice : ChoiceButton { + AudioInterface *audioInterface; + void onAction() { + MenuOverlay *overlay = new MenuOverlay(); + Menu *menu = new Menu(); + menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); + + const float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000}; + for (int i = 0; i < 6; i++) { + SampleRateItem *item = new SampleRateItem(); + item->audioInterface = audioInterface; + item->sampleRate = sampleRates[i]; + item->text = stringf("%.0f", sampleRates[i]); + menu->pushChild(item); + } + + overlay->addChild(menu); + gScene->setOverlay(overlay); + } + void step() { + this->text = stringf("%.0f", audioInterface->sampleRate); + } +}; + + +struct BlockSizeItem : MenuItem { + AudioInterface *audioInterface; + int blockSize; + void onAction() { + audioInterface->blockSize = blockSize; + audioInterface->openDevice(); + } +}; + +struct BlockSizeChoice : ChoiceButton { + AudioInterface *audioInterface; + void onAction() { + MenuOverlay *overlay = new MenuOverlay(); + Menu *menu = new Menu(); + menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); + + const int blockSizes[6] = {128, 256, 512, 1024, 2048, 4096}; + for (int i = 0; i < 6; i++) { + BlockSizeItem *item = new BlockSizeItem(); + item->audioInterface = audioInterface; + item->blockSize = blockSizes[i]; + item->text = stringf("%d", blockSizes[i]); + menu->pushChild(item); + } + + overlay->addChild(menu); + gScene->setOverlay(overlay); + } + void step() { + this->text = stringf("%d", audioInterface->blockSize); + } }; @@ -242,13 +324,48 @@ AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface() } { - AudioChoice *audioChoice = new AudioChoice(); - audioChoice->audioInterface = dynamic_cast(module); - audioChoice->text = "Audio device"; - audioChoice->box.pos = Vec(margin, yPos); - audioChoice->box.size.x = box.size.x - 10; - addChild(audioChoice); - yPos += audioChoice->box.size.y + 2*margin; + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Audio device"; + addChild(label); + yPos += label->box.size.y + margin; + + AudioChoice *choice = new AudioChoice(); + choice->audioInterface = dynamic_cast(module); + choice->box.pos = Vec(margin, yPos); + choice->box.size.x = box.size.x - 10; + addChild(choice); + yPos += choice->box.size.y + 2*margin; + } + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Sample rate"; + addChild(label); + yPos += label->box.size.y + margin; + + SampleRateChoice *choice = new SampleRateChoice(); + choice->audioInterface = dynamic_cast(module); + choice->box.pos = Vec(margin, yPos); + choice->box.size.x = box.size.x - 10; + addChild(choice); + yPos += choice->box.size.y + 2*margin; + } + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Block size"; + addChild(label); + yPos += label->box.size.y + margin; + + BlockSizeChoice *choice = new BlockSizeChoice(); + choice->audioInterface = dynamic_cast(module); + choice->box.pos = Vec(margin, yPos); + choice->box.size.x = box.size.x - 10; + addChild(choice); + yPos += choice->box.size.y + 2*margin; } { diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 67227939..0c72504d 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -92,9 +92,7 @@ std::string MidiInterface::getPortName(int portId) { const PmDeviceInfo *info = Pm_GetDeviceInfo(portId); if (!info) return ""; - char name[1024]; - snprintf(name, sizeof(name), "%s: %s (%s)", info->interf, info->name, info->input ? "input" : "output"); - return name; + return stringf("%s: %s (%s)", info->interf, info->name, info->input ? "input" : "output"); } void MidiInterface::openPort(int portId) { diff --git a/src/core/core.hpp b/src/core/core.hpp index d99ad211..179a15dd 100644 --- a/src/core/core.hpp +++ b/src/core/core.hpp @@ -1,7 +1,9 @@ -#include "Rack.hpp" +#include "rack.hpp" -rack::Plugin *coreInit(); +using namespace rack; + +Plugin *coreInit(); void audioInit(); void midiInit(); @@ -10,12 +12,12 @@ void midiInit(); // module widgets //////////////////// -struct AudioInterfaceWidget : rack::ModuleWidget { +struct AudioInterfaceWidget : ModuleWidget { AudioInterfaceWidget(); void draw(NVGcontext *vg); }; -struct MidiInterfaceWidget : rack::ModuleWidget { +struct MidiInterfaceWidget : ModuleWidget { MidiInterfaceWidget(); void draw(NVGcontext *vg); }; diff --git a/src/gui.cpp b/src/gui.cpp index 4313abb0..fea68a12 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,5 +1,5 @@ #include -#include "Rack.hpp" +#include "rack.hpp" #include #include diff --git a/src/main.cpp b/src/main.cpp index 9ada1bac..800fed4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,7 @@ #include #endif -#include "Rack.hpp" +#include "rack.hpp" namespace rack { @@ -12,14 +12,16 @@ namespace rack { std::string gApplicationName = "Virtuoso Rack"; std::string gApplicationVersion = "v0.0.0 alpha"; +Rack *gRack; + } // namespace rack using namespace rack; int main() { - // Set working directory #if defined(APPLE) + // macOS workaround for setting the working directory to the location of the .app { CFBundleRef bundle = CFBundleGetMainBundle(); CFURLRef bundleURL = CFBundleCopyBundleURL(bundle); @@ -32,19 +34,18 @@ int main() { } #endif - - rackInit(); + gRack = new Rack(); guiInit(); pluginInit(); gRackWidget->loadPatch("autosave.json"); - rackStart(); + gRack->start(); guiRun(); - rackStop(); + gRack->stop(); gRackWidget->savePatch("autosave.json"); guiDestroy(); - rackDestroy(); + delete gRack; pluginDestroy(); return 0; } diff --git a/src/plugin.cpp b/src/plugin.cpp index 41e46f31..3870c76f 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -7,7 +7,7 @@ #include #endif -#include "Rack.hpp" +#include "rack.hpp" #include "core/core.hpp" diff --git a/src/rack.cpp b/src/rack.cpp deleted file mode 100644 index 11d5947b..00000000 --- a/src/rack.cpp +++ /dev/null @@ -1,202 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Rack.hpp" - - -namespace rack { - -static std::thread thread; -static std::mutex mutex; -static bool running; - -static std::set modules; -// Merely used for keeping track of which module inputs point to which module outputs, to prevent pointer mistakes and make the rack API rigorous -static std::set wires; - -// Parameter interpolation -static Module *smoothModule = NULL; -static int smoothParamId; -static float smoothValue; - - -// HACK -static std::mutex requestMutex; -static std::condition_variable requestCv; -static bool request = false; - -struct RackRequest { - RackRequest() { - std::unique_lock lock(requestMutex); - request = true; - } - ~RackRequest() { - std::unique_lock lock(requestMutex); - request = false; - lock.unlock(); - requestCv.notify_all(); - } -}; -// END HACK - - -void rackInit() { -} - -void rackDestroy() { - // Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the GUI was destroyed. - assert(wires.empty()); - assert(modules.empty()); -} - -void rackStep() { - // Param interpolation - if (smoothModule) { - float value = smoothModule->params[smoothParamId]; - const float lambda = 60.0; // decay rate is 1 graphics frame - const float snap = 0.0001; - float delta = smoothValue - value; - if (fabsf(delta) < snap) { - smoothModule->params[smoothParamId] = smoothValue; - smoothModule = NULL; - } - else { - value += delta * lambda / SAMPLE_RATE; - smoothModule->params[smoothParamId] = value; - } - } - // Step all modules - std::chrono::time_point start, end; - for (Module *module : modules) { - // Start clock for CPU usage - start = std::chrono::high_resolution_clock::now(); - // Step module by one frame - module->step(); - // Stop clock and smooth step time value - end = std::chrono::high_resolution_clock::now(); - std::chrono::duration diff = end - start; - float elapsed = diff.count() * SAMPLE_RATE; - const float lambda = 1.0; - module->cpuTime += (elapsed - module->cpuTime) * lambda / SAMPLE_RATE; - } -} - -static void rackRun() { - // Every time the rack waits and locks a mutex, it steps this many frames - const int stepSize = 32; - - while (running) { - auto start = std::chrono::high_resolution_clock::now(); - // This lock is to make sure the GUI gets higher priority than this thread - { - std::unique_lock lock(requestMutex); - while (request) - requestCv.wait(lock); - } - { - std::lock_guard lock(mutex); - for (int i = 0; i < stepSize; i++) { - rackStep(); - } - } - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::nanoseconds((long) (0.9 * 1e9 * stepSize / SAMPLE_RATE)) - (end - start); - std::this_thread::sleep_for(duration); - } -} - -void rackStart() { - running = true; - thread = std::thread(rackRun); -} - -void rackStop() { - running = false; - thread.join(); -} - -void rackAddModule(Module *module) { - assert(module); - RackRequest rm; - std::lock_guard lock(mutex); - // Check that the module is not already added - assert(modules.find(module) == modules.end()); - modules.insert(module); -} - -void rackRemoveModule(Module *module) { - assert(module); - RackRequest rm; - std::lock_guard lock(mutex); - // Remove parameter interpolation which point to this module - if (module == smoothModule) { - smoothModule = NULL; - } - // Check that all wires are disconnected - for (Wire *wire : wires) { - assert(wire->outputModule != module); - assert(wire->inputModule != module); - } - auto it = modules.find(module); - if (it != modules.end()) { - modules.erase(it); - } -} - -void rackConnectWire(Wire *wire) { - assert(wire); - RackRequest rm; - std::lock_guard lock(mutex); - // It would probably be good to reset the wire voltage - wire->value = 0.0; - // Check that the wire is not already added - assert(wires.find(wire) == wires.end()); - assert(wire->outputModule); - assert(wire->inputModule); - // Check that the inputs/outputs are not already used by another cable - for (Wire *wire2 : wires) { - assert(wire2 != wire); - assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId)); - assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); - } - // Connect the wire to inputModule - wires.insert(wire); - wire->inputModule->inputs[wire->inputId] = &wire->value; - wire->outputModule->outputs[wire->outputId] = &wire->value; -} - -void rackDisconnectWire(Wire *wire) { - assert(wire); - RackRequest rm; - std::lock_guard lock(mutex); - // Disconnect wire from inputModule - wire->inputModule->inputs[wire->inputId] = NULL; - wire->outputModule->outputs[wire->outputId] = NULL; - - auto it = wires.find(wire); - assert(it != wires.end()); - wires.erase(it); -} - -void rackSetParamSmooth(Module *module, int paramId, float value) { - // Check existing parameter interpolation - if (smoothModule) { - if (!(smoothModule == module && smoothParamId == paramId)) { - // Jump param value to smooth value - smoothModule->params[smoothParamId] = smoothValue; - } - } - smoothModule = module; - smoothParamId = paramId; - smoothValue = value; -} - - -} // namespace rack diff --git a/src/util.cpp b/src/util.cpp index 38c89f49..becac658 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,4 +1,6 @@ #include "util.hpp" +#include +#include namespace rack { @@ -23,5 +25,20 @@ float randomNormal(){ return normalDist(rng); } +std::string stringf(const char *format, ...) { + va_list ap; + va_start(ap, format); + size_t size = vsnprintf(NULL, 0, format, ap); + if (size < 0) + return ""; + size++; + char *buf = new char[size]; + vsnprintf(buf, size, format, ap); + va_end(ap); + std::string s = buf; + delete[] buf; + return s; +} + } // namespace rack diff --git a/src/widgets/Button.cpp b/src/widgets/Button.cpp index 4a286453..23a49903 100644 --- a/src/widgets/Button.cpp +++ b/src/widgets/Button.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/ChoiceButton.cpp b/src/widgets/ChoiceButton.cpp index c126af8f..3e8fc75a 100644 --- a/src/widgets/ChoiceButton.cpp +++ b/src/widgets/ChoiceButton.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/InputPort.cpp b/src/widgets/InputPort.cpp index ec8909a3..04c7c931 100644 --- a/src/widgets/InputPort.cpp +++ b/src/widgets/InputPort.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Knob.cpp b/src/widgets/Knob.cpp index 55247807..d85299e5 100644 --- a/src/widgets/Knob.cpp +++ b/src/widgets/Knob.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Label.cpp b/src/widgets/Label.cpp index 740708f2..aeaa32a2 100644 --- a/src/widgets/Label.cpp +++ b/src/widgets/Label.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Light.cpp b/src/widgets/Light.cpp index 1e9edefe..16e079bd 100644 --- a/src/widgets/Light.cpp +++ b/src/widgets/Light.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Menu.cpp b/src/widgets/Menu.cpp index bd950358..38ba4283 100644 --- a/src/widgets/Menu.cpp +++ b/src/widgets/Menu.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/MenuEntry.cpp b/src/widgets/MenuEntry.cpp index 15cfd560..84ef8d8e 100644 --- a/src/widgets/MenuEntry.cpp +++ b/src/widgets/MenuEntry.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/MenuItem.cpp b/src/widgets/MenuItem.cpp index 23538ac5..4b37bf51 100644 --- a/src/widgets/MenuItem.cpp +++ b/src/widgets/MenuItem.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/MenuLabel.cpp b/src/widgets/MenuLabel.cpp index e62691c5..eb6903ed 100644 --- a/src/widgets/MenuLabel.cpp +++ b/src/widgets/MenuLabel.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/MenuOverlay.cpp b/src/widgets/MenuOverlay.cpp index fc67c7d8..2d2f2a92 100644 --- a/src/widgets/MenuOverlay.cpp +++ b/src/widgets/MenuOverlay.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/ModulePanel.cpp b/src/widgets/ModulePanel.cpp index 413725f4..7a4b0e60 100644 --- a/src/widgets/ModulePanel.cpp +++ b/src/widgets/ModulePanel.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/ModuleWidget.cpp b/src/widgets/ModuleWidget.cpp index 91b2af1d..4d7349be 100644 --- a/src/widgets/ModuleWidget.cpp +++ b/src/widgets/ModuleWidget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { @@ -6,7 +6,7 @@ namespace rack { ModuleWidget::ModuleWidget(Module *module) { this->module = module; if (module) { - rackAddModule(module); + gRack->addModule(module); } } @@ -14,7 +14,7 @@ ModuleWidget::~ModuleWidget() { // Make sure WireWidget destructors are called *before* removing `module` from the rack. disconnectPorts(); if (module) { - rackRemoveModule(module); + gRack->removeModule(module); delete module; } } @@ -101,10 +101,9 @@ void ModuleWidget::draw(NVGcontext *vg) { // CPU usage text if (module) { - char text[32]; - snprintf(text, sizeof(text), "%.2f%%", module->cpuTime * 10); + std::string text = stringf("%.1f%%", module->cpuTime * 100.0); nvgSave(vg); - bndSlider(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, module->cpuTime, text, NULL); + bndSlider(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, module->cpuTime, text.c_str(), NULL); nvgRestore(vg); } } diff --git a/src/widgets/OutputPort.cpp b/src/widgets/OutputPort.cpp index 7cc74cfa..52a69c75 100644 --- a/src/widgets/OutputPort.cpp +++ b/src/widgets/OutputPort.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/ParamWidget.cpp b/src/widgets/ParamWidget.cpp index 902ec278..233c8829 100644 --- a/src/widgets/ParamWidget.cpp +++ b/src/widgets/ParamWidget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { @@ -23,7 +23,7 @@ void ParamWidget::onChange() { return; // moduleWidget->module->params[paramId] = value; - rackSetParamSmooth(module, paramId, value); + gRack->setParamSmooth(module, paramId, value); } diff --git a/src/widgets/Port.cpp b/src/widgets/Port.cpp index 122e506b..8d5608e5 100644 --- a/src/widgets/Port.cpp +++ b/src/widgets/Port.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/QuantityWidget.cpp b/src/widgets/QuantityWidget.cpp index 483b4ff4..07794005 100644 --- a/src/widgets/QuantityWidget.cpp +++ b/src/widgets/QuantityWidget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/RackWidget.cpp b/src/widgets/RackWidget.cpp index de9d6d83..0db0b99a 100644 --- a/src/widgets/RackWidget.cpp +++ b/src/widgets/RackWidget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" #include diff --git a/src/widgets/Scene.cpp b/src/widgets/Scene.cpp index 8f2dfa43..727d0111 100644 --- a/src/widgets/Scene.cpp +++ b/src/widgets/Scene.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Screw.cpp b/src/widgets/Screw.cpp index 0cf9caeb..ac7e154d 100644 --- a/src/widgets/Screw.cpp +++ b/src/widgets/Screw.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/ScrollBar.cpp b/src/widgets/ScrollBar.cpp index b013fad1..b66de879 100644 --- a/src/widgets/ScrollBar.cpp +++ b/src/widgets/ScrollBar.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/ScrollWidget.cpp b/src/widgets/ScrollWidget.cpp index 78c6fc17..c4a33632 100644 --- a/src/widgets/ScrollWidget.cpp +++ b/src/widgets/ScrollWidget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Slider.cpp b/src/widgets/Slider.cpp index 65db5f02..29328b44 100644 --- a/src/widgets/Slider.cpp +++ b/src/widgets/Slider.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/SpriteWidget.cpp b/src/widgets/SpriteWidget.cpp index dc1d6f52..d1f97eb0 100644 --- a/src/widgets/SpriteWidget.cpp +++ b/src/widgets/SpriteWidget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Toolbar.cpp b/src/widgets/Toolbar.cpp index 27c1920f..9f8cdd80 100644 --- a/src/widgets/Toolbar.cpp +++ b/src/widgets/Toolbar.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Tooltip.cpp b/src/widgets/Tooltip.cpp index baea5703..3bf8de6b 100644 --- a/src/widgets/Tooltip.cpp +++ b/src/widgets/Tooltip.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { diff --git a/src/widgets/Widget.cpp b/src/widgets/Widget.cpp index 1f8d9569..6c661d8d 100644 --- a/src/widgets/Widget.cpp +++ b/src/widgets/Widget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" #include diff --git a/src/widgets/WireWidget.cpp b/src/widgets/WireWidget.cpp index 63e1860a..95106eb9 100644 --- a/src/widgets/WireWidget.cpp +++ b/src/widgets/WireWidget.cpp @@ -1,4 +1,4 @@ -#include "Rack.hpp" +#include "rack.hpp" namespace rack { @@ -70,7 +70,7 @@ WireWidget::~WireWidget() { void WireWidget::updateWire() { if (wire) { - rackDisconnectWire(wire); + gRack->removeWire(wire); delete wire; wire = NULL; } @@ -80,7 +80,7 @@ void WireWidget::updateWire() { wire->outputId = outputPort->outputId; wire->inputModule = inputPort->module; wire->inputId = inputPort->inputId; - rackConnectWire(wire); + gRack->addWire(wire); } }