Browse Source

AudioInterface: Set primary module after instead of before adding to the Engine (WIP).

tags/v2.0.0
Andrew Belt 4 years ago
parent
commit
9f04e56106
4 changed files with 65 additions and 28 deletions
  1. +2
    -0
      include/engine/Engine.hpp
  2. +39
    -21
      src/core/AudioInterface.cpp
  3. +16
    -2
      src/engine/Engine.cpp
  4. +8
    -5
      src/plugin/Plugin.cpp

+ 2
- 0
include/engine/Engine.hpp View File

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


+ 39
- 21
src/core/AudioInterface.cpp View File

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


+ 16
- 2
src/engine/Engine.cpp View File

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


+ 8
- 5
src/plugin/Plugin.cpp View File

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


Loading…
Cancel
Save