From d7b72c5b0f46b665934c58cc346281e3dec952a6 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Thu, 7 Feb 2019 18:21:58 -0500 Subject: [PATCH] Rewrite thread API. Add ParamMap::to/fromJson(). --- include/engine/Engine.hpp | 16 +-- include/engine/Param.hpp | 5 +- include/engine/ParamMap.hpp | 8 +- include/rack.hpp | 1 + include/system.hpp | 3 +- src/Core/MIDI_Map.cpp | 40 +++++++ src/app/Toolbar.cpp | 10 +- src/engine/Engine.cpp | 201 +++++++++++++++++------------------- src/engine/Param.cpp | 4 - src/engine/ParamMap.cpp | 27 +++++ src/system.cpp | 2 +- 11 files changed, 187 insertions(+), 130 deletions(-) create mode 100644 src/engine/ParamMap.cpp diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index 08500391..ee196b44 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -23,6 +23,12 @@ struct Engine { int getThreadCount(); void setPaused(bool paused); bool isPaused(); + void setSampleRate(float sampleRate); + float getSampleRate(); + /** Returns the inverse of the current sample rate. */ + float getSampleTime(); + + // Modules /** Does not transfer pointer ownership. */ void addModule(Module *module); void removeModule(Module *module); @@ -30,19 +36,17 @@ struct Engine { void resetModule(Module *module); void randomizeModule(Module *module); void bypassModule(Module *module, bool bypass); + + // Cables /** Does not transfer pointer ownership. */ void addCable(Cable *cable); void removeCable(Cable *cable); + + // Params void setParam(Module *module, int paramId, float value); float getParam(Module *module, int paramId); void setSmoothParam(Module *module, int paramId, float value); float getSmoothParam(Module *module, int paramId); - int getNextModuleId(); - - void setSampleRate(float sampleRate); - float getSampleRate(); - /** Returns the inverse of the current sample rate */ - float getSampleTime(); }; diff --git a/include/engine/Param.hpp b/include/engine/Param.hpp index d0488cc2..10e19b6a 100644 --- a/include/engine/Param.hpp +++ b/include/engine/Param.hpp @@ -86,7 +86,10 @@ struct Param { } /** Returns whether the Param has finite range between minValue and maxValue. */ - bool isBounded(); + bool isBounded() { + return std::isfinite(minValue) && std::isfinite(maxValue); + } + json_t *toJson(); void fromJson(json_t *rootJ); void reset(); diff --git a/include/engine/ParamMap.hpp b/include/engine/ParamMap.hpp index 7554ed05..10bb43b1 100644 --- a/include/engine/ParamMap.hpp +++ b/include/engine/ParamMap.hpp @@ -1,5 +1,6 @@ #pragma once #include "common.hpp" +#include namespace rack { @@ -7,8 +8,11 @@ namespace engine { struct ParamMap { - int moduleId; - int paramId; + int moduleId = -1; + int paramId = -1; + + json_t *toJson(); + void fromJson(json_t *rootJ); }; diff --git a/include/rack.hpp b/include/rack.hpp index 3d9a0ab7..b5233346 100644 --- a/include/rack.hpp +++ b/include/rack.hpp @@ -72,6 +72,7 @@ #include "engine/Module.hpp" #include "engine/Param.hpp" #include "engine/Cable.hpp" +#include "engine/ParamMap.hpp" #include "plugin/Plugin.hpp" #include "plugin/Model.hpp" diff --git a/include/system.hpp b/include/system.hpp index 4ba364f9..6ebcae56 100644 --- a/include/system.hpp +++ b/include/system.hpp @@ -13,8 +13,7 @@ bool isDirectory(const std::string &path); void copyFile(const std::string &srcPath, const std::string &destPath); void createDirectory(const std::string &path); -/** Currently this lies and returns the number of logical cores instead. */ -int getPhysicalCoreCount(); +int getLogicalCoreCount(); void setThreadName(const std::string &name); void setThreadRealTime(); std::string getStackTrace(); diff --git a/src/Core/MIDI_Map.cpp b/src/Core/MIDI_Map.cpp index 6eaa5fcd..d4021357 100644 --- a/src/Core/MIDI_Map.cpp +++ b/src/Core/MIDI_Map.cpp @@ -21,9 +21,13 @@ struct MIDI_Map : Module { int lastLearnedCc; int learnedCcs[8]; dsp::ExponentialFilter valueFilters[8]; + ParamMap paramMaps[8]; MIDI_Map() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + for (int i = 0; i < 8; i++) { + valueFilters[i].lambda = 40.f; + } onReset(); } @@ -41,6 +45,27 @@ struct MIDI_Map : Module { while (midiInput.shift(&msg)) { processMessage(msg); } + + float deltaTime = APP->engine->getSampleTime(); + + for (int i = 0; i < 8; i++) { + // Get module + int moduleId = paramMaps[i].moduleId; + if (moduleId < 0) + continue; + Module *module = APP->engine->getModule(moduleId); + if (!module) + continue; + // Get param + int paramId = paramMaps[i].paramId; + Param *param = &module->params[paramId]; + if (!param->isBounded()) + continue; + // Set param + float v = rescale(values[i], 0, 127, param->minValue, param->maxValue); + v = valueFilters[i].process(deltaTime, v); + module->params[paramId].setValue(v); + } } void processMessage(midi::Message msg) { @@ -76,6 +101,12 @@ struct MIDI_Map : Module { } json_object_set_new(rootJ, "ccs", ccsJ); + json_t *paramMapsJ = json_array(); + for (int i = 0; i < 8; i++) { + json_array_append_new(paramMapsJ, paramMaps[i].toJson()); + } + json_object_set_new(rootJ, "paramMaps", paramMapsJ); + json_object_set_new(rootJ, "midi", midiInput.toJson()); return rootJ; } @@ -90,6 +121,15 @@ struct MIDI_Map : Module { } } + json_t *paramMapsJ = json_object_get(rootJ, "paramMaps"); + if (paramMapsJ) { + for (int i = 0; i < 8; i++) { + json_t *paramMapJ = json_array_get(paramMapsJ, i); + if (paramMapJ) + paramMaps[i].fromJson(paramMapJ); + } + } + json_t *midiJ = json_object_get(rootJ, "midi"); if (midiJ) midiInput.fromJson(midiJ); diff --git a/src/app/Toolbar.cpp b/src/app/Toolbar.cpp index 0dd213eb..ae5e38a0 100644 --- a/src/app/Toolbar.cpp +++ b/src/app/Toolbar.cpp @@ -316,10 +316,10 @@ struct ThreadCountValueItem : ui::MenuItem { void setThreadCount(int threadCount) { this->threadCount = threadCount; text = string::f("%d", threadCount); - if (threadCount == 1) - text += " (default)"; - else if (threadCount == system::getPhysicalCoreCount() / 2) - text += " (recommended)"; + if (threadCount == system::getLogicalCoreCount() / 2) + text += " (best performance)"; + else if (threadCount == 1) + text += " (best efficiency)"; rightText = CHECKMARK(APP->engine->getThreadCount() == threadCount); } void onAction(const event::Action &e) override { @@ -335,7 +335,7 @@ struct ThreadCount : ui::MenuItem { ui::Menu *createChildMenu() override { ui::Menu *menu = new ui::Menu; - int coreCount = system::getPhysicalCoreCount(); + int coreCount = system::getLogicalCoreCount(); for (int i = 1; i <= coreCount; i++) { ThreadCountValueItem *item = new ThreadCountValueItem; item->setThreadCount(i); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 95e72d63..b87a5c5a 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -132,7 +132,6 @@ struct Engine::Internal { bool running = false; float sampleRate; float sampleTime; - float sampleRateRequested; int nextModuleId = 0; int nextCableId = 0; @@ -142,7 +141,7 @@ struct Engine::Internal { int smoothParamId; float smoothValue; - std::mutex mutex; + std::recursive_mutex mutex; std::thread thread; VIPMutex vipMutex; @@ -156,65 +155,39 @@ struct Engine::Internal { Engine::Engine() { internal = new Internal; - float sampleRate = settings.sampleRate; - internal->sampleRate = sampleRate; - internal->sampleTime = 1 / sampleRate; - internal->sampleRateRequested = sampleRate; - - internal->threadCount = settings.threadCount; internal->engineBarrier.total = 1; internal->workerBarrier.total = 1; + + setSampleRate(44100.f); + setThreadCount(settings.threadCount); } Engine::~Engine() { settings.sampleRate = internal->sampleRate; settings.threadCount = internal->threadCount; - // Make sure there are no cables or modules in the rack on destruction. This suggests that a module failed to remove itself before the RackWidget was destroyed. + // Stop worker threads + setThreadCount(1); + + // Make sure there are no cables or modules in the rack on destruction. + // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. assert(internal->cables.empty()); assert(internal->modules.empty()); delete internal; } -static void Engine_setWorkerCount(Engine *engine, int workerCount) { - assert(0 <= workerCount && workerCount <= 32); - Engine::Internal *internal = engine->internal; - - // Stop all workers - for (EngineWorker &worker : internal->workers) { - worker.stop(); - } - internal->engineBarrier.wait(); - - // Destroy all workers - for (EngineWorker &worker : internal->workers) { - worker.join(); - } - internal->workers.resize(0); - - // Set barrier counts - internal->engineBarrier.total = workerCount + 1; - internal->workerBarrier.total = workerCount + 1; - - if (workerCount >= 1) { - // Create workers - internal->workers.resize(workerCount); - for (int i = 0; i < workerCount; i++) { - EngineWorker &worker = internal->workers[i]; - worker.id = i + 1; - worker.engine = engine; - worker.start(); - } - } -} - static void Engine_stepModules(Engine *engine, int threadId) { Engine::Internal *internal = engine->internal; int threadCount = internal->threadCount; int modulesLen = internal->modules.size(); + // TODO + // There's room for optimization here by choosing modules intelligently rather than fixed strides. + // See OpenMP's `guided` scheduling algorithm. + + // Step each module for (int i = threadId; i < modulesLen; i += threadCount) { Module *module = internal->modules[i]; if (!module->bypass) { @@ -235,7 +208,7 @@ static void Engine_stepModules(Engine *engine, int threadId) { } } - // Iterate ports and step plug lights + // Iterate ports to step plug lights for (Input &input : module->inputs) { input.step(); } @@ -248,47 +221,28 @@ static void Engine_stepModules(Engine *engine, int threadId) { static void Engine_step(Engine *engine) { Engine::Internal *internal = engine->internal; - // Sample rate - if (internal->sampleRateRequested != internal->sampleRate) { - internal->sampleRate = internal->sampleRateRequested; - internal->sampleTime = 1 / internal->sampleRate; - for (Module *module : internal->modules) { - module->onSampleRateChange(); - } - } - // Param smoothing - { - Module *smoothModule = internal->smoothModule; - int smoothParamId = internal->smoothParamId; - float smoothValue = internal->smoothValue; - if (smoothModule) { - Param *param = &smoothModule->params[smoothParamId]; - float value = param->value; - // decay rate is 1 graphics frame - const float smoothLambda = 60.f; - float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime; - if (value == newValue || !(param->minValue <= newValue && newValue <= param->maxValue)) { - // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats), or if newValue is out of bounds - param->setValue(smoothValue); - internal->smoothModule = NULL; - } - else { - param->value = newValue; - } + Module *smoothModule = internal->smoothModule; + int smoothParamId = internal->smoothParamId; + float smoothValue = internal->smoothValue; + if (smoothModule) { + Param *param = &smoothModule->params[smoothParamId]; + float value = param->value; + // decay rate is 1 graphics frame + const float smoothLambda = 60.f; + float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime; + if (value == newValue || !(param->minValue <= newValue && newValue <= param->maxValue)) { + // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats), or if newValue is out of bounds + param->setValue(smoothValue); + internal->smoothModule = NULL; + } + else { + param->value = newValue; } - } - - // Lazily create/destroy workers - int workerCount = internal->threadCount - 1; - if ((int) internal->workers.size() != workerCount) { - Engine_setWorkerCount(engine, workerCount); - } - else { - internal->engineBarrier.wait(); } // Step modules along with workers + internal->engineBarrier.wait(); Engine_stepModules(engine, 0); internal->workerBarrier.wait(); @@ -318,7 +272,7 @@ static void Engine_run(Engine *engine) { engine->internal->vipMutex.wait(); if (!engine->internal->paused) { - std::lock_guard lock(engine->internal->mutex); + std::lock_guard lock(engine->internal->mutex); // auto startTime = std::chrono::high_resolution_clock::now(); for (int i = 0; i < mutexSteps; i++) { @@ -345,8 +299,6 @@ static void Engine_run(Engine *engine) { std::this_thread::sleep_for(std::chrono::duration(stepTime)); } } - - Engine_setWorkerCount(engine, 0); } void Engine::start() { @@ -360,10 +312,35 @@ void Engine::stop() { } void Engine::setThreadCount(int threadCount) { - assert(threadCount >= 1); + assert(1 <= threadCount); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); + + // Stop all workers + for (EngineWorker &worker : internal->workers) { + worker.stop(); + } + internal->engineBarrier.wait(); + + // Destroy all workers + for (EngineWorker &worker : internal->workers) { + worker.join(); + } + internal->workers.resize(0); + + // Set barrier counts internal->threadCount = threadCount; + internal->engineBarrier.total = threadCount; + internal->workerBarrier.total = threadCount; + + // Create workers + internal->workers.resize(threadCount - 1); + for (int id = 1; id < threadCount; id++) { + EngineWorker &worker = internal->workers[id - 1]; + worker.id = id; + worker.engine = this; + worker.start(); + } } int Engine::getThreadCount() { @@ -372,7 +349,8 @@ int Engine::getThreadCount() { } void Engine::setPaused(bool paused) { - // No lock + VIPLock vipLock(internal->vipMutex); + std::lock_guard lock(internal->mutex); internal->paused = paused; } @@ -381,10 +359,29 @@ bool Engine::isPaused() { return internal->paused; } +void Engine::setSampleRate(float sampleRate) { + VIPLock vipLock(internal->vipMutex); + std::lock_guard lock(internal->mutex); + + internal->sampleRate = sampleRate; + internal->sampleTime = 1 / sampleRate; + for (Module *module : internal->modules) { + module->onSampleRateChange(); + } +} + +float Engine::getSampleRate() { + return internal->sampleRate; +} + +float Engine::getSampleTime() { + return internal->sampleTime; +} + void Engine::addModule(Module *module) { assert(module); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); // Check that the module is not already added auto it = std::find(internal->modules.begin(), internal->modules.end(), module); assert(it == internal->modules.end()); @@ -410,7 +407,7 @@ void Engine::addModule(Module *module) { void Engine::removeModule(Module *module) { assert(module); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); // If a param is being smoothed on this module, stop smoothing it immediately if (module == internal->smoothModule) { internal->smoothModule = NULL; @@ -429,7 +426,7 @@ void Engine::removeModule(Module *module) { Module *Engine::getModule(int moduleId) { VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); // Find module for (Module *module : internal->modules) { if (module->id == moduleId) @@ -441,7 +438,7 @@ Module *Engine::getModule(int moduleId) { void Engine::resetModule(Module *module) { assert(module); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); module->reset(); } @@ -449,7 +446,7 @@ void Engine::resetModule(Module *module) { void Engine::randomizeModule(Module *module) { assert(module); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); module->randomize(); } @@ -457,7 +454,7 @@ void Engine::randomizeModule(Module *module) { void Engine::bypassModule(Module *module, bool bypass) { assert(module); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); if (bypass) { for (Output &output : module->outputs) { // This also zeros all voltages @@ -494,7 +491,7 @@ static void Engine_updateConnected(Engine *engine) { void Engine::addCable(Cable *cable) { assert(cable); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); // Check cable properties assert(cable->outputModule); assert(cable->inputModule); @@ -526,7 +523,7 @@ void Engine::addCable(Cable *cable) { void Engine::removeCable(Cable *cable) { assert(cable); VIPLock vipLock(internal->vipMutex); - std::lock_guard lock(internal->mutex); + std::lock_guard lock(internal->mutex); // Check that the cable is already added auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); assert(it != internal->cables.end()); @@ -563,27 +560,13 @@ float Engine::getSmoothParam(Module *module, int paramId) { return getParam(module, paramId); } -int Engine::getNextModuleId() { - return internal->nextModuleId++; -} - -void Engine::setSampleRate(float newSampleRate) { - internal->sampleRateRequested = newSampleRate; -} - -float Engine::getSampleRate() { - return internal->sampleRate; -} - -float Engine::getSampleTime() { - return internal->sampleTime; -} - void EngineWorker::step() { + engine->internal->engineBarrier.wait(); + if (!running) + return; Engine_stepModules(engine, id); engine->internal->workerBarrier.wait(); - engine->internal->engineBarrier.wait(); } diff --git a/src/engine/Param.cpp b/src/engine/Param.cpp index 5b8506a3..a4d5dcee 100644 --- a/src/engine/Param.cpp +++ b/src/engine/Param.cpp @@ -7,10 +7,6 @@ namespace rack { namespace engine { -bool Param::isBounded() { - return std::isfinite(minValue) && std::isfinite(maxValue); -} - json_t *Param::toJson() { json_t *rootJ = json_object(); diff --git a/src/engine/ParamMap.cpp b/src/engine/ParamMap.cpp new file mode 100644 index 00000000..734d7886 --- /dev/null +++ b/src/engine/ParamMap.cpp @@ -0,0 +1,27 @@ +#include "engine/ParamMap.hpp" + + +namespace rack { +namespace engine { + + +json_t *ParamMap::toJson() { + json_t *rootJ = json_object(); + json_object_set_new(rootJ, "moduleId", json_integer(moduleId)); + json_object_set_new(rootJ, "paramId", json_integer(paramId)); + return rootJ; +} + +void ParamMap::fromJson(json_t *rootJ) { + json_t *moduleIdJ = json_object_get(rootJ, "moduleId"); + if (moduleIdJ) + moduleId = json_integer_value(moduleIdJ); + + json_t *paramIdJ = json_object_get(rootJ, "paramId"); + if (paramIdJ) + paramId = json_integer_value(paramIdJ); +} + + +} // namespace engine +} // namespace rack diff --git a/src/system.cpp b/src/system.cpp index ac17317e..9b94f215 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -87,7 +87,7 @@ void createDirectory(const std::string &path) { #endif } -int getPhysicalCoreCount() { +int getLogicalCoreCount() { // TODO Return the physical cores, not logical cores. return std::thread::hardware_concurrency(); }