@@ -20,11 +20,8 @@ struct Engine { | |||||
void start(); | void start(); | ||||
/** Stops engine thread. */ | /** Stops engine thread. */ | ||||
void stop(); | void stop(); | ||||
void setThreadCount(int threadCount); | |||||
int getThreadCount(); | |||||
void setPaused(bool paused); | void setPaused(bool paused); | ||||
bool isPaused(); | bool isPaused(); | ||||
void setSampleRate(float sampleRate); | |||||
float getSampleRate(); | float getSampleRate(); | ||||
/** Returns the inverse of the current sample rate. */ | /** Returns the inverse of the current sample rate. */ | ||||
float getSampleTime(); | float getSampleTime(); | ||||
@@ -25,6 +25,7 @@ extern bool invertZoom; | |||||
extern float cableOpacity; | extern float cableOpacity; | ||||
extern float cableTension; | extern float cableTension; | ||||
extern bool allowCursorLock; | extern bool allowCursorLock; | ||||
extern bool realTime; | |||||
extern float sampleRate; | extern float sampleRate; | ||||
extern int threadCount; | extern int threadCount; | ||||
extern bool paramTooltip; | extern bool paramTooltip; | ||||
@@ -277,7 +277,7 @@ struct EnginePauseItem : ui::MenuItem { | |||||
struct SampleRateValueItem : ui::MenuItem { | struct SampleRateValueItem : ui::MenuItem { | ||||
float sampleRate; | float sampleRate; | ||||
void onAction(const event::Action &e) override { | void onAction(const event::Action &e) override { | ||||
APP->engine->setSampleRate(sampleRate); | |||||
settings::sampleRate = sampleRate; | |||||
APP->engine->setPaused(false); | APP->engine->setPaused(false); | ||||
} | } | ||||
}; | }; | ||||
@@ -288,7 +288,7 @@ struct SampleRateItem : ui::MenuItem { | |||||
ui::Menu *menu = new ui::Menu; | ui::Menu *menu = new ui::Menu; | ||||
EnginePauseItem *enginePauseItem = new EnginePauseItem; | EnginePauseItem *enginePauseItem = new EnginePauseItem; | ||||
enginePauseItem->text = "Pause engine"; | |||||
enginePauseItem->text = "Pause"; | |||||
enginePauseItem->rightText = CHECKMARK(APP->engine->isPaused()); | enginePauseItem->rightText = CHECKMARK(APP->engine->isPaused()); | ||||
menu->addChild(enginePauseItem); | menu->addChild(enginePauseItem); | ||||
@@ -304,7 +304,7 @@ struct SampleRateItem : ui::MenuItem { | |||||
if (oversample > 1) | if (oversample > 1) | ||||
item->rightText += string::f("(%dx)", oversample); | item->rightText += string::f("(%dx)", oversample); | ||||
item->rightText += " "; | item->rightText += " "; | ||||
item->rightText += CHECKMARK(APP->engine->getSampleRate() == sampleRate); | |||||
item->rightText += CHECKMARK(settings::sampleRate == sampleRate); | |||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
} | } | ||||
@@ -313,6 +313,13 @@ struct SampleRateItem : ui::MenuItem { | |||||
}; | }; | ||||
struct RealTimeItem : ui::MenuItem { | |||||
void onAction(const event::Action &e) override { | |||||
settings::realTime ^= true; | |||||
} | |||||
}; | |||||
struct ThreadCountValueItem : ui::MenuItem { | struct ThreadCountValueItem : ui::MenuItem { | ||||
int threadCount; | int threadCount; | ||||
void setThreadCount(int threadCount) { | void setThreadCount(int threadCount) { | ||||
@@ -322,18 +329,23 @@ struct ThreadCountValueItem : ui::MenuItem { | |||||
text += " (most modules)"; | text += " (most modules)"; | ||||
else if (threadCount == 1) | else if (threadCount == 1) | ||||
text += " (lowest CPU usage)"; | text += " (lowest CPU usage)"; | ||||
rightText = CHECKMARK(APP->engine->getThreadCount() == threadCount); | |||||
rightText = CHECKMARK(settings::threadCount == threadCount); | |||||
} | } | ||||
void onAction(const event::Action &e) override { | void onAction(const event::Action &e) override { | ||||
APP->engine->setThreadCount(threadCount); | |||||
settings::threadCount = threadCount; | |||||
} | } | ||||
}; | }; | ||||
struct ThreadCount : ui::MenuItem { | |||||
struct ThreadCountItem : ui::MenuItem { | |||||
ui::Menu *createChildMenu() override { | ui::Menu *createChildMenu() override { | ||||
ui::Menu *menu = new ui::Menu; | ui::Menu *menu = new ui::Menu; | ||||
RealTimeItem *realTimeItem = new RealTimeItem; | |||||
realTimeItem->text = "Real-time priority"; | |||||
realTimeItem->rightText = CHECKMARK(settings::realTime); | |||||
menu->addChild(realTimeItem); | |||||
int coreCount = system::getLogicalCoreCount(); | int coreCount = system::getLogicalCoreCount(); | ||||
for (int i = 1; i <= coreCount; i++) { | for (int i = 1; i <= coreCount; i++) { | ||||
ThreadCountValueItem *item = new ThreadCountValueItem; | ThreadCountValueItem *item = new ThreadCountValueItem; | ||||
@@ -378,8 +390,8 @@ struct SettingsButton : MenuButton { | |||||
sampleRateItem->rightText = RIGHT_ARROW; | sampleRateItem->rightText = RIGHT_ARROW; | ||||
menu->addChild(sampleRateItem); | menu->addChild(sampleRateItem); | ||||
ThreadCount *threadCount = new ThreadCount; | |||||
threadCount->text = "Thread count"; | |||||
ThreadCountItem *threadCount = new ThreadCountItem; | |||||
threadCount->text = "Engine threads"; | |||||
threadCount->rightText = RIGHT_ARROW; | threadCount->rightText = RIGHT_ARROW; | ||||
menu->addChild(threadCount); | menu->addChild(threadCount); | ||||
@@ -151,6 +151,7 @@ struct Engine::Internal { | |||||
std::thread thread; | std::thread thread; | ||||
VIPMutex vipMutex; | VIPMutex vipMutex; | ||||
bool realTime = false; | |||||
int threadCount = 1; | int threadCount = 1; | ||||
std::vector<EngineWorker> workers; | std::vector<EngineWorker> workers; | ||||
SpinBarrier engineBarrier; | SpinBarrier engineBarrier; | ||||
@@ -165,17 +166,11 @@ Engine::Engine() { | |||||
internal->engineBarrier.total = 1; | internal->engineBarrier.total = 1; | ||||
internal->workerBarrier.total = 1; | internal->workerBarrier.total = 1; | ||||
setSampleRate(44100.f); | |||||
setThreadCount(settings::threadCount); | |||||
internal->sampleRate = 44100.f; | |||||
internal->sampleTime = 1 / internal->sampleRate; | |||||
} | } | ||||
Engine::~Engine() { | Engine::~Engine() { | ||||
settings::sampleRate = internal->sampleRate; | |||||
settings::threadCount = internal->threadCount; | |||||
// Stop worker threads | |||||
setThreadCount(1); | |||||
// Make sure there are no cables or modules in the rack on destruction. | // 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. | // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. | ||||
assert(internal->cables.empty()); | assert(internal->cables.empty()); | ||||
@@ -300,7 +295,38 @@ static void Engine_updateAdjacent(Engine *that, Module *m) { | |||||
} | } | ||||
} | } | ||||
static void Engine_relaunchWorkers(Engine *that) { | |||||
Engine::Internal *internal = that->internal; | |||||
assert(1 <= internal->threadCount); | |||||
// 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 = internal->threadCount; | |||||
internal->workerBarrier.total = internal->threadCount; | |||||
// Create workers | |||||
internal->workers.resize(internal->threadCount - 1); | |||||
for (int id = 1; id < internal->threadCount; id++) { | |||||
EngineWorker &worker = internal->workers[id - 1]; | |||||
worker.id = id; | |||||
worker.engine = that; | |||||
worker.start(); | |||||
} | |||||
} | |||||
static void Engine_run(Engine *that) { | static void Engine_run(Engine *that) { | ||||
Engine::Internal *internal = that->internal; | |||||
// Set up thread | // Set up thread | ||||
system::setThreadName("Engine"); | system::setThreadName("Engine"); | ||||
// system::setThreadRealTime(); | // system::setThreadRealTime(); | ||||
@@ -312,13 +338,28 @@ static void Engine_run(Engine *that) { | |||||
double ahead = 0.0; | double ahead = 0.0; | ||||
auto lastTime = std::chrono::high_resolution_clock::now(); | auto lastTime = std::chrono::high_resolution_clock::now(); | ||||
while (that->internal->running) { | |||||
that->internal->vipMutex.wait(); | |||||
while (internal->running) { | |||||
internal->vipMutex.wait(); | |||||
// Set sample rate | |||||
if (internal->sampleRate != settings::sampleRate) { | |||||
internal->sampleRate = settings::sampleRate; | |||||
internal->sampleTime = 1 / internal->sampleRate; | |||||
for (Module *module : internal->modules) { | |||||
module->onSampleRateChange(); | |||||
} | |||||
} | |||||
// Launch workers | |||||
if (internal->threadCount != settings::threadCount) { | |||||
internal->threadCount = settings::threadCount; | |||||
Engine_relaunchWorkers(that); | |||||
} | |||||
if (!that->internal->paused) { | |||||
std::lock_guard<std::recursive_mutex> lock(that->internal->mutex); | |||||
if (!internal->paused) { | |||||
std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||||
for (Module *module : that->internal->modules) { | |||||
for (Module *module : internal->modules) { | |||||
Engine_updateAdjacent(that, module); | Engine_updateAdjacent(that, module); | ||||
} | } | ||||
@@ -328,7 +369,7 @@ static void Engine_run(Engine *that) { | |||||
} | } | ||||
} | } | ||||
double stepTime = mutexSteps * that->internal->sampleTime; | |||||
double stepTime = mutexSteps * internal->sampleTime; | |||||
ahead += stepTime; | ahead += stepTime; | ||||
auto currTime = std::chrono::high_resolution_clock::now(); | auto currTime = std::chrono::high_resolution_clock::now(); | ||||
const double aheadFactor = 2.0; | const double aheadFactor = 2.0; | ||||
@@ -343,6 +384,10 @@ static void Engine_run(Engine *that) { | |||||
std::this_thread::sleep_for(std::chrono::duration<double>(stepTime)); | std::this_thread::sleep_for(std::chrono::duration<double>(stepTime)); | ||||
} | } | ||||
} | } | ||||
// Stop workers | |||||
internal->threadCount = 1; | |||||
Engine_relaunchWorkers(that); | |||||
} | } | ||||
void Engine::start() { | void Engine::start() { | ||||
@@ -355,43 +400,6 @@ void Engine::stop() { | |||||
internal->thread.join(); | internal->thread.join(); | ||||
} | } | ||||
void Engine::setThreadCount(int threadCount) { | |||||
assert(1 <= threadCount); | |||||
VIPLock vipLock(internal->vipMutex); | |||||
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() { | |||||
// No lock | |||||
return internal->threadCount; | |||||
} | |||||
void Engine::setPaused(bool paused) { | void Engine::setPaused(bool paused) { | ||||
VIPLock vipLock(internal->vipMutex); | VIPLock vipLock(internal->vipMutex); | ||||
std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
@@ -403,17 +411,6 @@ bool Engine::isPaused() { | |||||
return internal->paused; | 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() { | float Engine::getSampleRate() { | ||||
return internal->sampleRate; | return internal->sampleRate; | ||||
} | } | ||||
@@ -23,6 +23,7 @@ bool invertZoom = false; | |||||
float cableOpacity = 0.5; | float cableOpacity = 0.5; | ||||
float cableTension = 0.5; | float cableTension = 0.5; | ||||
bool allowCursorLock = true; | bool allowCursorLock = true; | ||||
bool realTime = false; | |||||
float sampleRate = 44100.0; | float sampleRate = 44100.0; | ||||
int threadCount = 1; | int threadCount = 1; | ||||
bool paramTooltip = false; | bool paramTooltip = false; | ||||
@@ -57,6 +58,8 @@ json_t *toJson() { | |||||
json_object_set_new(rootJ, "allowCursorLock", json_boolean(allowCursorLock)); | json_object_set_new(rootJ, "allowCursorLock", json_boolean(allowCursorLock)); | ||||
json_object_set_new(rootJ, "realTime", json_boolean(realTime)); | |||||
json_object_set_new(rootJ, "sampleRate", json_real(sampleRate)); | json_object_set_new(rootJ, "sampleRate", json_real(sampleRate)); | ||||
json_object_set_new(rootJ, "threadCount", json_integer(threadCount)); | json_object_set_new(rootJ, "threadCount", json_integer(threadCount)); | ||||
@@ -130,6 +133,10 @@ void fromJson(json_t *rootJ) { | |||||
if (allowCursorLockJ) | if (allowCursorLockJ) | ||||
allowCursorLock = json_is_true(allowCursorLockJ); | allowCursorLock = json_is_true(allowCursorLockJ); | ||||
json_t *realTimeJ = json_object_get(rootJ, "realTime"); | |||||
if (realTimeJ) | |||||
realTime = json_boolean_value(realTimeJ); | |||||
json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | ||||
if (sampleRateJ) | if (sampleRateJ) | ||||
sampleRate = json_number_value(sampleRateJ); | sampleRate = json_number_value(sampleRateJ); | ||||