Browse Source

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

tags/v2.0.0
Andrew Belt 3 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.
*/
void stepBlock(int frames);
/** */
void setPrimaryModule(Module* module);
Module* getPrimaryModule();

@@ -85,6 +86,7 @@ struct Engine {
*/
void addModule(Module* module);
void removeModule(Module* module);
bool hasModule(Module* module);
Module* getModule(int64_t moduleId);
void resetModule(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* rootJ = json_object();
json_object_set_new(rootJ, "audio", audio::Port::toJson());

if (isPrimary())
json_object_set_new(rootJ, "primary", json_boolean(true));

return rootJ;
}

@@ -191,20 +195,38 @@ struct AudioInterface : Module, audio::Port {
json_t* audioJ = json_object_get(rootJ, "audio");
if (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

void processInput(const float* input, int inputStride, int frames) override {
if (!APP->engine->hasModule(this))
return;

// Claim primary module if there is none
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".
float sampleRate = getSampleRate();
if (isPrimary) {
if (isPrimaryCached) {
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.
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 (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) {
if (!isPrimaryCached && (int) outputBuffer.size() > maxEngineFrames) {
outputBuffer.clear();
// 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 {
bool isPrimary = (APP->engine->getPrimaryModule() == this);
// Step engine
if (isPrimary && requestedEngineFrames > 0) {
if (isPrimary() && requestedEngineFrames > 0) {
APP->engine->stepBlock(requestedEngineFrames);
}
}

void processOutput(float* output, int outputStride, int frames) override {
bool isPrimary = (APP->engine->getPrimaryModule() == this);
bool isPrimaryCached = isPrimary();
int numOutputs = getNumOutputs();
int engineSampleRate = (int) APP->engine->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 (!isPrimary && (int) inputBuffer.size() > maxEngineFrames) {
if (!isPrimaryCached && (int) inputBuffer.size() > maxEngineFrames) {
inputBuffer.clear();
// 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 {
@@ -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>
struct AudioInterfaceWidget : ModuleWidget {
typedef AudioInterface<NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS> TAudioInterface;
@@ -463,9 +474,16 @@ struct AudioInterfaceWidget : ModuleWidget {

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->rightText = CHECKMARK(APP->engine->getPrimaryModule() == module);
primaryModuleItem->rightText = CHECKMARK(module->isPrimary());
primaryModuleItem->module = module;
menu->addChild(primaryModuleItem);
}


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

@@ -7,6 +7,7 @@
#include <tuple>
#include <pmmintrin.h>
#include <pthread.h>
#include <unistd.h>

#include <engine/Engine.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.
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 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) {
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
if (it == internal->modules.end())
return;
throw Exception("Module being set as primary does not belong to Engine");
}
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) {
ReadLock lock(internal->mutex);
auto it = internal->modulesCache.find(moduleId);
@@ -1105,10 +1115,13 @@ void Engine::fromJson(json_t* rootJ) {
module->id = moduleIndex;
}

sleep(1);

// This write-locks
addModule(module);
}
catch (Exception& e) {
WARN("Cannot deserialize module: %s", e.what());
APP->patch->log(e.what());
}
}
@@ -1131,6 +1144,7 @@ void Engine::fromJson(json_t* rootJ) {
addCable(cable);
}
catch (Exception& e) {
WARN("Cannot deserialize cable: %s", e.what());
delete cable;
// 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) {
// Slug
// slug
json_t* slugJ = json_object_get(rootJ, "slug");
if (slugJ)
slug = json_string_value(slugJ);
@@ -41,7 +41,7 @@ void Plugin::fromJson(json_t* rootJ) {
if (!isSlugValid(slug))
throw Exception(string::f("Plugin slug \"%s\" is invalid", slug.c_str()));

// Version
// version
json_t* versionJ = json_object_get(rootJ, "version");
if (versionJ)
version = json_string_value(versionJ);
@@ -50,14 +50,14 @@ void Plugin::fromJson(json_t* rootJ) {
if (version == "")
throw Exception("No plugin version");

// Name
// name
json_t* nameJ = json_object_get(rootJ, "name");
if (nameJ)
name = json_string_value(nameJ);
if (name == "")
throw Exception("No plugin name");

// Brand
// brand
json_t* brandJ = json_object_get(rootJ, "brand");
if (brandJ)
brand = json_string_value(brandJ);
@@ -106,7 +106,7 @@ void Plugin::fromJson(json_t* rootJ) {
changelogUrl = json_string_value(changelogUrlJ);

json_t* modulesJ = json_object_get(rootJ, "modules");
if (modulesJ) {
if (modulesJ && json_array_size(modulesJ) > 0) {
size_t moduleId;
json_t* moduleJ;
json_array_foreach(modulesJ, moduleId, moduleJ) {
@@ -138,6 +138,9 @@ void Plugin::fromJson(json_t* rootJ) {
model->fromJson(moduleJ);
}
}
else {
WARN("No modules in plugin %s", slug.c_str());
}

// Remove models without names
// This is a hacky way of matching JSON models with C++ models.


Loading…
Cancel
Save