@@ -31,6 +31,7 @@ struct Engine { | |||||
Only call this method from the primary module. | Only call this method from the primary module. | ||||
*/ | */ | ||||
void stepBlock(int frames); | void stepBlock(int frames); | ||||
/** */ | |||||
void setPrimaryModule(Module* module); | void setPrimaryModule(Module* module); | ||||
Module* getPrimaryModule(); | Module* getPrimaryModule(); | ||||
@@ -85,6 +86,7 @@ struct Engine { | |||||
*/ | */ | ||||
void addModule(Module* module); | void addModule(Module* module); | ||||
void removeModule(Module* module); | void removeModule(Module* module); | ||||
bool hasModule(Module* module); | |||||
Module* getModule(int64_t moduleId); | Module* getModule(int64_t moduleId); | ||||
void resetModule(Module* module); | void resetModule(Module* module); | ||||
void randomizeModule(Module* module); | void randomizeModule(Module* module); | ||||
@@ -184,6 +184,10 @@ struct AudioInterface : Module, audio::Port { | |||||
json_t* dataToJson() override { | json_t* dataToJson() override { | ||||
json_t* rootJ = json_object(); | json_t* rootJ = json_object(); | ||||
json_object_set_new(rootJ, "audio", audio::Port::toJson()); | json_object_set_new(rootJ, "audio", audio::Port::toJson()); | ||||
if (isPrimary()) | |||||
json_object_set_new(rootJ, "primary", json_boolean(true)); | |||||
return rootJ; | return rootJ; | ||||
} | } | ||||
@@ -191,20 +195,38 @@ struct AudioInterface : Module, audio::Port { | |||||
json_t* audioJ = json_object_get(rootJ, "audio"); | json_t* audioJ = json_object_get(rootJ, "audio"); | ||||
if (audioJ) | if (audioJ) | ||||
audio::Port::fromJson(audioJ); | audio::Port::fromJson(audioJ); | ||||
// TODO | |||||
// json_t* primaryJ = json_object_get(rootJ, "primary"); | |||||
// if (primaryJ) | |||||
// setPrimary(); | |||||
} | |||||
/** Must be called when the Engine mutex is unlocked. | |||||
*/ | |||||
void setPrimary() { | |||||
APP->engine->setPrimaryModule(this); | |||||
} | |||||
bool isPrimary() { | |||||
return APP->engine->getPrimaryModule() == this; | |||||
} | } | ||||
// audio::Port | // audio::Port | ||||
void processInput(const float* input, int inputStride, int frames) override { | void processInput(const float* input, int inputStride, int frames) override { | ||||
if (!APP->engine->hasModule(this)) | |||||
return; | |||||
// Claim primary module if there is none | // Claim primary module if there is none | ||||
if (!APP->engine->getPrimaryModule()) { | if (!APP->engine->getPrimaryModule()) { | ||||
APP->engine->setPrimaryModule(this); | |||||
setPrimary(); | |||||
} | } | ||||
bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||||
bool isPrimaryCached = isPrimary(); | |||||
// Set sample rate of engine if engine sample rate is "auto". | // Set sample rate of engine if engine sample rate is "auto". | ||||
float sampleRate = getSampleRate(); | float sampleRate = getSampleRate(); | ||||
if (isPrimary) { | |||||
if (isPrimaryCached) { | |||||
APP->engine->setSuggestedSampleRate(sampleRate); | APP->engine->setSuggestedSampleRate(sampleRate); | ||||
} | } | ||||
@@ -218,7 +240,7 @@ struct AudioInterface : Module, audio::Port { | |||||
// Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate. | // Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate. | ||||
maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5); | maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5); | ||||
// If this is a secondary audio module and the engine output buffer is too full, flush it. | // If this is a secondary audio module and the engine output buffer is too full, flush it. | ||||
if (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) { | |||||
if (!isPrimaryCached && (int) outputBuffer.size() > maxEngineFrames) { | |||||
outputBuffer.clear(); | outputBuffer.clear(); | ||||
// DEBUG("%p: flushing engine output", this); | // DEBUG("%p: flushing engine output", this); | ||||
} | } | ||||
@@ -247,15 +269,14 @@ struct AudioInterface : Module, audio::Port { | |||||
} | } | ||||
void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) override { | void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) override { | ||||
bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||||
// Step engine | // Step engine | ||||
if (isPrimary && requestedEngineFrames > 0) { | |||||
if (isPrimary() && requestedEngineFrames > 0) { | |||||
APP->engine->stepBlock(requestedEngineFrames); | APP->engine->stepBlock(requestedEngineFrames); | ||||
} | } | ||||
} | } | ||||
void processOutput(float* output, int outputStride, int frames) override { | void processOutput(float* output, int outputStride, int frames) override { | ||||
bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||||
bool isPrimaryCached = isPrimary(); | |||||
int numOutputs = getNumOutputs(); | int numOutputs = getNumOutputs(); | ||||
int engineSampleRate = (int) APP->engine->getSampleRate(); | int engineSampleRate = (int) APP->engine->getSampleRate(); | ||||
float sampleRate = getSampleRate(); | float sampleRate = getSampleRate(); | ||||
@@ -279,12 +300,12 @@ struct AudioInterface : Module, audio::Port { | |||||
} | } | ||||
// If this is a secondary audio module and the engine input buffer is too full, flush it. | // If this is a secondary audio module and the engine input buffer is too full, flush it. | ||||
if (!isPrimary && (int) inputBuffer.size() > maxEngineFrames) { | |||||
if (!isPrimaryCached && (int) inputBuffer.size() > maxEngineFrames) { | |||||
inputBuffer.clear(); | inputBuffer.clear(); | ||||
// DEBUG("%p: flushing engine input", this); | // DEBUG("%p: flushing engine input", this); | ||||
} | } | ||||
// DEBUG("%p %s:\tframes %d requestedEngineFrames %d\toutputBuffer %d inputBuffer %d\t", this, isPrimary ? "primary" : "secondary", frames, requestedEngineFrames, outputBuffer.size(), inputBuffer.size()); | |||||
// DEBUG("%p %s:\tframes %d requestedEngineFrames %d\toutputBuffer %d inputBuffer %d\t", this, isPrimaryCached ? "primary" : "secondary", frames, requestedEngineFrames, outputBuffer.size(), inputBuffer.size()); | |||||
} | } | ||||
void onOpenStream() override { | void onOpenStream() override { | ||||
@@ -299,16 +320,6 @@ struct AudioInterface : Module, audio::Port { | |||||
}; | }; | ||||
template <typename TAudioInterface> | |||||
struct PrimaryModuleItem : MenuItem { | |||||
TAudioInterface* module; | |||||
void onAction(const event::Action& e) override { | |||||
APP->engine->setPrimaryModule(module); | |||||
} | |||||
}; | |||||
template <int NUM_AUDIO_INPUTS, int NUM_AUDIO_OUTPUTS> | template <int NUM_AUDIO_INPUTS, int NUM_AUDIO_OUTPUTS> | ||||
struct AudioInterfaceWidget : ModuleWidget { | struct AudioInterfaceWidget : ModuleWidget { | ||||
typedef AudioInterface<NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS> TAudioInterface; | typedef AudioInterface<NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS> TAudioInterface; | ||||
@@ -463,9 +474,16 @@ struct AudioInterfaceWidget : ModuleWidget { | |||||
menu->addChild(new MenuSeparator); | menu->addChild(new MenuSeparator); | ||||
PrimaryModuleItem<TAudioInterface>* primaryModuleItem = new PrimaryModuleItem<TAudioInterface>; | |||||
struct PrimaryModuleItem : MenuItem { | |||||
TAudioInterface* module; | |||||
void onAction(const event::Action& e) override { | |||||
module->setPrimary(); | |||||
} | |||||
}; | |||||
PrimaryModuleItem* primaryModuleItem = new PrimaryModuleItem; | |||||
primaryModuleItem->text = "Primary audio module"; | primaryModuleItem->text = "Primary audio module"; | ||||
primaryModuleItem->rightText = CHECKMARK(APP->engine->getPrimaryModule() == module); | |||||
primaryModuleItem->rightText = CHECKMARK(module->isPrimary()); | |||||
primaryModuleItem->module = module; | primaryModuleItem->module = module; | ||||
menu->addChild(primaryModuleItem); | menu->addChild(primaryModuleItem); | ||||
} | } | ||||
@@ -7,6 +7,7 @@ | |||||
#include <tuple> | #include <tuple> | ||||
#include <pmmintrin.h> | #include <pmmintrin.h> | ||||
#include <pthread.h> | #include <pthread.h> | ||||
#include <unistd.h> | |||||
#include <engine/Engine.hpp> | #include <engine/Engine.hpp> | ||||
#include <settings.hpp> | #include <settings.hpp> | ||||
@@ -32,7 +33,8 @@ static void initMXCSR() { | |||||
/** Allows multiple "reader" threads to obtain a lock simultaneously, but only one "writer" thread. | /** Allows multiple "reader" threads to obtain a lock simultaneously, but only one "writer" thread. | ||||
Recursive, so WriteLock can be used recursively. | |||||
WriteLock can be used recursively. | |||||
Uses reader priority, which implies that ReadLock can also be used recursively. | |||||
This implementation is just a wrapper for pthreads. | This implementation is just a wrapper for pthreads. | ||||
This is available in C++17 as std::shared_mutex, but unfortunately we're using C++11. | This is available in C++17 as std::shared_mutex, but unfortunately we're using C++11. | ||||
*/ | */ | ||||
@@ -569,7 +571,7 @@ void Engine::setPrimaryModule(Module* module) { | |||||
if (module) { | if (module) { | ||||
auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | ||||
if (it == internal->modules.end()) | if (it == internal->modules.end()) | ||||
return; | |||||
throw Exception("Module being set as primary does not belong to Engine"); | |||||
} | } | ||||
internal->primaryModule = module; | internal->primaryModule = module; | ||||
} | } | ||||
@@ -758,6 +760,14 @@ void Engine::removeModule(Module* module) { | |||||
} | } | ||||
bool Engine::hasModule(Module* module) { | |||||
ReadLock lock(internal->mutex); | |||||
// TODO Performance could be improved by searching modulesCache, but more testing would be needed to make sure it's always valid. | |||||
auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||||
return it != internal->modules.end(); | |||||
} | |||||
Module* Engine::getModule(int64_t moduleId) { | Module* Engine::getModule(int64_t moduleId) { | ||||
ReadLock lock(internal->mutex); | ReadLock lock(internal->mutex); | ||||
auto it = internal->modulesCache.find(moduleId); | auto it = internal->modulesCache.find(moduleId); | ||||
@@ -1105,10 +1115,13 @@ void Engine::fromJson(json_t* rootJ) { | |||||
module->id = moduleIndex; | module->id = moduleIndex; | ||||
} | } | ||||
sleep(1); | |||||
// This write-locks | // This write-locks | ||||
addModule(module); | addModule(module); | ||||
} | } | ||||
catch (Exception& e) { | catch (Exception& e) { | ||||
WARN("Cannot deserialize module: %s", e.what()); | |||||
APP->patch->log(e.what()); | APP->patch->log(e.what()); | ||||
} | } | ||||
} | } | ||||
@@ -1131,6 +1144,7 @@ void Engine::fromJson(json_t* rootJ) { | |||||
addCable(cable); | addCable(cable); | ||||
} | } | ||||
catch (Exception& e) { | catch (Exception& e) { | ||||
WARN("Cannot deserialize cable: %s", e.what()); | |||||
delete cable; | delete cable; | ||||
// Don't log exceptions because missing modules create unnecessary complaining when cables try to connect to them. | // Don't log exceptions because missing modules create unnecessary complaining when cables try to connect to them. | ||||
} | } | ||||
@@ -32,7 +32,7 @@ Model* Plugin::getModel(const std::string& slug) { | |||||
} | } | ||||
void Plugin::fromJson(json_t* rootJ) { | void Plugin::fromJson(json_t* rootJ) { | ||||
// Slug | |||||
// slug | |||||
json_t* slugJ = json_object_get(rootJ, "slug"); | json_t* slugJ = json_object_get(rootJ, "slug"); | ||||
if (slugJ) | if (slugJ) | ||||
slug = json_string_value(slugJ); | slug = json_string_value(slugJ); | ||||
@@ -41,7 +41,7 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
if (!isSlugValid(slug)) | if (!isSlugValid(slug)) | ||||
throw Exception(string::f("Plugin slug \"%s\" is invalid", slug.c_str())); | throw Exception(string::f("Plugin slug \"%s\" is invalid", slug.c_str())); | ||||
// Version | |||||
// version | |||||
json_t* versionJ = json_object_get(rootJ, "version"); | json_t* versionJ = json_object_get(rootJ, "version"); | ||||
if (versionJ) | if (versionJ) | ||||
version = json_string_value(versionJ); | version = json_string_value(versionJ); | ||||
@@ -50,14 +50,14 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
if (version == "") | if (version == "") | ||||
throw Exception("No plugin version"); | throw Exception("No plugin version"); | ||||
// Name | |||||
// name | |||||
json_t* nameJ = json_object_get(rootJ, "name"); | json_t* nameJ = json_object_get(rootJ, "name"); | ||||
if (nameJ) | if (nameJ) | ||||
name = json_string_value(nameJ); | name = json_string_value(nameJ); | ||||
if (name == "") | if (name == "") | ||||
throw Exception("No plugin name"); | throw Exception("No plugin name"); | ||||
// Brand | |||||
// brand | |||||
json_t* brandJ = json_object_get(rootJ, "brand"); | json_t* brandJ = json_object_get(rootJ, "brand"); | ||||
if (brandJ) | if (brandJ) | ||||
brand = json_string_value(brandJ); | brand = json_string_value(brandJ); | ||||
@@ -106,7 +106,7 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
changelogUrl = json_string_value(changelogUrlJ); | changelogUrl = json_string_value(changelogUrlJ); | ||||
json_t* modulesJ = json_object_get(rootJ, "modules"); | json_t* modulesJ = json_object_get(rootJ, "modules"); | ||||
if (modulesJ) { | |||||
if (modulesJ && json_array_size(modulesJ) > 0) { | |||||
size_t moduleId; | size_t moduleId; | ||||
json_t* moduleJ; | json_t* moduleJ; | ||||
json_array_foreach(modulesJ, moduleId, moduleJ) { | json_array_foreach(modulesJ, moduleId, moduleJ) { | ||||
@@ -138,6 +138,9 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
model->fromJson(moduleJ); | model->fromJson(moduleJ); | ||||
} | } | ||||
} | } | ||||
else { | |||||
WARN("No modules in plugin %s", slug.c_str()); | |||||
} | |||||
// Remove models without names | // Remove models without names | ||||
// This is a hacky way of matching JSON models with C++ models. | // This is a hacky way of matching JSON models with C++ models. | ||||