Browse Source

Rewrite thread API. Add ParamMap::to/fromJson().

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
d7b72c5b0f
11 changed files with 187 additions and 130 deletions
  1. +10
    -6
      include/engine/Engine.hpp
  2. +4
    -1
      include/engine/Param.hpp
  3. +6
    -2
      include/engine/ParamMap.hpp
  4. +1
    -0
      include/rack.hpp
  5. +1
    -2
      include/system.hpp
  6. +40
    -0
      src/Core/MIDI_Map.cpp
  7. +5
    -5
      src/app/Toolbar.cpp
  8. +92
    -109
      src/engine/Engine.cpp
  9. +0
    -4
      src/engine/Param.cpp
  10. +27
    -0
      src/engine/ParamMap.cpp
  11. +1
    -1
      src/system.cpp

+ 10
- 6
include/engine/Engine.hpp View File

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




+ 4
- 1
include/engine/Param.hpp View File

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


+ 6
- 2
include/engine/ParamMap.hpp View File

@@ -1,5 +1,6 @@
#pragma once
#include "common.hpp"
#include <jansson.h>


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




+ 1
- 0
include/rack.hpp View File

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


+ 1
- 2
include/system.hpp View File

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


+ 40
- 0
src/Core/MIDI_Map.cpp View File

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


+ 5
- 5
src/app/Toolbar.cpp View File

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


+ 92
- 109
src/engine/Engine.cpp View File

@@ -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<std::mutex> lock(engine->internal->mutex);
std::lock_guard<std::recursive_mutex> 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<double>(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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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<std::mutex> lock(internal->mutex);
std::lock_guard<std::recursive_mutex> 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();
}




+ 0
- 4
src/engine/Param.cpp View File

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



+ 27
- 0
src/engine/ParamMap.cpp View File

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

+ 1
- 1
src/system.cpp View File

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


Loading…
Cancel
Save