Browse Source

Add Engine::setSuggestedSampleRate(). Make ReadWriteLock support recursive writes.

tags/v2.0.0
Andrew Belt 4 years ago
parent
commit
90ada5c532
4 changed files with 96 additions and 60 deletions
  1. +2
    -1
      include/engine/Engine.hpp
  2. +7
    -2
      src/core/AudioInterface.cpp
  3. +86
    -57
      src/engine/Engine.cpp
  4. +1
    -0
      src/system.cpp

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

@@ -30,7 +30,7 @@ struct Engine {
/** Advances the engine by `frames` frames. /** Advances the engine by `frames` frames.
Only call this method from the primary module. Only call this method from the primary module.
*/ */
void stepBlock(int frames, float suggestedSampleRate = 0.f);
void stepBlock(int frames);
void setPrimaryModule(Module* module); void setPrimaryModule(Module* module);
Module* getPrimaryModule(); Module* getPrimaryModule();


@@ -38,6 +38,7 @@ struct Engine {
*/ */
float getSampleRate(); float getSampleRate();
PRIVATE void setSampleRate(float sampleRate); PRIVATE void setSampleRate(float sampleRate);
void setSuggestedSampleRate(float suggestedSampleRate);
/** Returns the inverse of the current sample rate. /** Returns the inverse of the current sample rate.
*/ */
float getSampleTime(); float getSampleTime();


+ 7
- 2
src/core/AudioInterface.cpp View File

@@ -202,10 +202,15 @@ struct AudioInterface : Module, audio::Port {
} }
bool isPrimary = (APP->engine->getPrimaryModule() == this); bool isPrimary = (APP->engine->getPrimaryModule() == this);


// Set sample rate of engine if engine sample rate is "auto".
float sampleRate = getSampleRate();
if (isPrimary) {
APP->engine->setSuggestedSampleRate(sampleRate);
}

// Initialize sample rate converters // Initialize sample rate converters
int numInputs = getNumInputs(); int numInputs = getNumInputs();
int engineSampleRate = (int) APP->engine->getSampleRate(); int engineSampleRate = (int) APP->engine->getSampleRate();
float sampleRate = getSampleRate();
double sampleRateRatio = (double) engineSampleRate / sampleRate; double sampleRateRatio = (double) engineSampleRate / sampleRate;
outputSrc.setRates(sampleRate, engineSampleRate); outputSrc.setRates(sampleRate, engineSampleRate);
outputSrc.setChannels(numInputs); outputSrc.setChannels(numInputs);
@@ -245,7 +250,7 @@ struct AudioInterface : Module, audio::Port {
bool isPrimary = (APP->engine->getPrimaryModule() == this); bool isPrimary = (APP->engine->getPrimaryModule() == this);
// Step engine // Step engine
if (isPrimary && requestedEngineFrames > 0) { if (isPrimary && requestedEngineFrames > 0) {
APP->engine->stepBlock(requestedEngineFrames, getSampleRate());
APP->engine->stepBlock(requestedEngineFrames);
} }
} }




+ 86
- 57
src/engine/Engine.cpp View File

@@ -32,39 +32,66 @@ 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.
This implementation is just a wrapper for pthreads. This implementation is just a wrapper for pthreads.
This is available in C++14 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.
*/ */
struct SharedMutex {
struct ReadWriteMutex {
pthread_rwlock_t rwlock; pthread_rwlock_t rwlock;
SharedMutex() {
std::atomic<pthread_t> writerThread{0};
int recursion = 0;

ReadWriteMutex() {
if (pthread_rwlock_init(&rwlock, NULL)) if (pthread_rwlock_init(&rwlock, NULL))
throw Exception("pthread_rwlock_init failed"); throw Exception("pthread_rwlock_init failed");
} }
~SharedMutex() {
~ReadWriteMutex() {
pthread_rwlock_destroy(&rwlock); pthread_rwlock_destroy(&rwlock);
} }
void lockReader() {
if (pthread_rwlock_rdlock(&rwlock))
throw Exception("pthread_rwlock_rdlock failed");
}
void unlockReader() {
if (pthread_rwlock_unlock(&rwlock))
throw Exception("pthread_rwlock_unlock failed");
}
void lockWriter() {
pthread_t self = pthread_self();
if (writerThread != self) {
if (pthread_rwlock_wrlock(&rwlock))
throw Exception("pthread_rwlock_wrlock failed");
writerThread = self;
}
// We're safe behind a lock so we can increment the recursion count.
recursion++;
}
void unlockWriter() {
if (--recursion == 0) {
writerThread = 0;
if (pthread_rwlock_unlock(&rwlock))
throw Exception("pthread_rwlock_unlock failed");
}
}
}; };


struct SharedLock {
SharedMutex& m;
SharedLock(SharedMutex& m) : m(m) {
if (pthread_rwlock_rdlock(&m.rwlock))
throw Exception("pthread_rwlock_rdlock failed");
struct ReadLock {
ReadWriteMutex& m;
ReadLock(ReadWriteMutex& m) : m(m) {
m.lockReader();
} }
~SharedLock() {
pthread_rwlock_unlock(&m.rwlock);
~ReadLock() {
m.unlockReader();
} }
}; };


struct ExclusiveSharedLock {
SharedMutex& m;
ExclusiveSharedLock(SharedMutex& m) : m(m) {
if (pthread_rwlock_wrlock(&m.rwlock))
throw Exception("pthread_rwlock_wrlock failed");
struct WriteLock {
ReadWriteMutex& m;
WriteLock(ReadWriteMutex& m) : m(m) {
m.lockWriter();
} }
~ExclusiveSharedLock() {
pthread_rwlock_unlock(&m.rwlock);
~WriteLock() {
m.unlockWriter();
} }
}; };


@@ -215,7 +242,7 @@ struct Engine::Internal {
Writers lock when mutating the engine's state. Writers lock when mutating the engine's state.
Readers lock when using the engine's state. Readers lock when using the engine's state.
*/ */
SharedMutex mutex;
ReadWriteMutex mutex;
/** Mutex that guards the block. /** Mutex that guards the block.
stepBlock() locks to guarantee its exclusivity. stepBlock() locks to guarantee its exclusivity.
*/ */
@@ -479,7 +506,7 @@ Engine::~Engine() {




void Engine::clear() { void Engine::clear() {
// TODO This needs a lock that doesn't interfere with removeParamHandle, removeCable, and removeModule.
WriteLock lock(internal->mutex);


// Copy lists because we'll be removing while iterating // Copy lists because we'll be removing while iterating
std::set<ParamHandle*> paramHandles = internal->paramHandles; std::set<ParamHandle*> paramHandles = internal->paramHandles;
@@ -500,9 +527,9 @@ void Engine::clear() {
} }




void Engine::stepBlock(int frames, float suggestedSampleRate) {
void Engine::stepBlock(int frames) {
std::lock_guard<std::mutex> stepLock(internal->blockMutex); std::lock_guard<std::mutex> stepLock(internal->blockMutex);
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
// Configure thread // Configure thread
initMXCSR(); initMXCSR();
random::init(); random::init();
@@ -511,17 +538,6 @@ void Engine::stepBlock(int frames, float suggestedSampleRate) {
internal->blockTime = system::getTime(); internal->blockTime = system::getTime();
internal->blockFrames = frames; internal->blockFrames = frames;


// Set sample rate
if (settings::sampleRate > 0) {
setSampleRate(settings::sampleRate);
}
else if (suggestedSampleRate > 0) {
setSampleRate(suggestedSampleRate);
}
else {
setSampleRate(FALLBACK_SAMPLE_RATE);
}

// Update expander pointers // Update expander pointers
for (Module* module : internal->modules) { for (Module* module : internal->modules) {
Engine_updateExpander(this, module, false); Engine_updateExpander(this, module, false);
@@ -548,7 +564,7 @@ void Engine::stepBlock(int frames, float suggestedSampleRate) {




void Engine::setPrimaryModule(Module* module) { void Engine::setPrimaryModule(Module* module) {
SharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
// Don't allow module to be set if not added to the Engine. // Don't allow module to be set if not added to the Engine.
// NULL will unset the primary module. // NULL will unset the primary module.
if (module) { if (module) {
@@ -573,6 +589,8 @@ float Engine::getSampleRate() {
void Engine::setSampleRate(float sampleRate) { void Engine::setSampleRate(float sampleRate) {
if (sampleRate == internal->sampleRate) if (sampleRate == internal->sampleRate)
return; return;
WriteLock lock(internal->mutex);

internal->sampleRate = sampleRate; internal->sampleRate = sampleRate;
internal->sampleTime = 1.f / sampleRate; internal->sampleTime = 1.f / sampleRate;
// Trigger SampleRateChangeEvent // Trigger SampleRateChangeEvent
@@ -585,6 +603,19 @@ void Engine::setSampleRate(float sampleRate) {
} }




void Engine::setSuggestedSampleRate(float suggestedSampleRate) {
if (settings::sampleRate > 0) {
setSampleRate(settings::sampleRate);
}
else if (suggestedSampleRate > 0) {
setSampleRate(suggestedSampleRate);
}
else {
setSampleRate(FALLBACK_SAMPLE_RATE);
}
}


float Engine::getSampleTime() { float Engine::getSampleTime() {
return internal->sampleTime; return internal->sampleTime;
} }
@@ -637,7 +668,7 @@ size_t Engine::getNumModules() {




size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) { size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
size_t i = 0; size_t i = 0;
for (Module* m : internal->modules) { for (Module* m : internal->modules) {
if (i >= len) if (i >= len)
@@ -650,7 +681,7 @@ size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) {




std::vector<int64_t> Engine::getModuleIds() { std::vector<int64_t> Engine::getModuleIds() {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
std::vector<int64_t> moduleIds; std::vector<int64_t> moduleIds;
moduleIds.reserve(internal->modules.size()); moduleIds.reserve(internal->modules.size());
for (Module* m : internal->modules) { for (Module* m : internal->modules) {
@@ -661,7 +692,7 @@ std::vector<int64_t> Engine::getModuleIds() {




void Engine::addModule(Module* module) { void Engine::addModule(Module* module) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(module); assert(module);
// Check that the module is not already added // Check that the module is not already added
auto it = std::find(internal->modules.begin(), internal->modules.end(), module); auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
@@ -686,7 +717,7 @@ void Engine::addModule(Module* module) {




void Engine::removeModule(Module* module) { void Engine::removeModule(Module* module) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(module); assert(module);
// Check that the module actually exists // Check that the module actually exists
auto it = std::find(internal->modules.begin(), internal->modules.end(), module); auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
@@ -729,7 +760,7 @@ void Engine::removeModule(Module* module) {




Module* Engine::getModule(int64_t moduleId) { Module* Engine::getModule(int64_t moduleId) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
auto it = internal->modulesCache.find(moduleId); auto it = internal->modulesCache.find(moduleId);
if (it == internal->modulesCache.end()) if (it == internal->modulesCache.end())
return NULL; return NULL;
@@ -738,7 +769,7 @@ Module* Engine::getModule(int64_t moduleId) {




void Engine::resetModule(Module* module) { void Engine::resetModule(Module* module) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(module); assert(module);


Module::ResetEvent eReset; Module::ResetEvent eReset;
@@ -747,7 +778,7 @@ void Engine::resetModule(Module* module) {




void Engine::randomizeModule(Module* module) { void Engine::randomizeModule(Module* module) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(module); assert(module);


Module::RandomizeEvent eRandomize; Module::RandomizeEvent eRandomize;
@@ -756,7 +787,7 @@ void Engine::randomizeModule(Module* module) {




void Engine::bypassModule(Module* module, bool bypass) { void Engine::bypassModule(Module* module, bool bypass) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(module); assert(module);


if (module->bypass() == bypass) if (module->bypass() == bypass)
@@ -781,13 +812,13 @@ void Engine::bypassModule(Module* module, bool bypass) {




json_t* Engine::moduleToJson(Module* module) { json_t* Engine::moduleToJson(Module* module) {
ExclusiveSharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
return module->toJson(); return module->toJson();
} }




void Engine::moduleFromJson(Module* module, json_t* rootJ) { void Engine::moduleFromJson(Module* module, json_t* rootJ) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
module->fromJson(rootJ); module->fromJson(rootJ);
} }


@@ -798,7 +829,7 @@ size_t Engine::getNumCables() {




size_t Engine::getCableIds(int64_t* cableIds, size_t len) { size_t Engine::getCableIds(int64_t* cableIds, size_t len) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
size_t i = 0; size_t i = 0;
for (Cable* c : internal->cables) { for (Cable* c : internal->cables) {
if (i >= len) if (i >= len)
@@ -811,7 +842,7 @@ size_t Engine::getCableIds(int64_t* cableIds, size_t len) {




std::vector<int64_t> Engine::getCableIds() { std::vector<int64_t> Engine::getCableIds() {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
std::vector<int64_t> cableIds; std::vector<int64_t> cableIds;
cableIds.reserve(internal->cables.size()); cableIds.reserve(internal->cables.size());
for (Cable* c : internal->cables) { for (Cable* c : internal->cables) {
@@ -822,7 +853,7 @@ std::vector<int64_t> Engine::getCableIds() {




void Engine::addCable(Cable* cable) { void Engine::addCable(Cable* cable) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(cable); assert(cable);
// Check cable properties // Check cable properties
assert(cable->inputModule); assert(cable->inputModule);
@@ -867,7 +898,7 @@ void Engine::addCable(Cable* cable) {




void Engine::removeCable(Cable* cable) { void Engine::removeCable(Cable* cable) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(cable); assert(cable);
// Check that the cable is already added // Check that the cable is already added
auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); auto it = std::find(internal->cables.begin(), internal->cables.end(), cable);
@@ -903,7 +934,7 @@ void Engine::removeCable(Cable* cable) {




Cable* Engine::getCable(int64_t cableId) { Cable* Engine::getCable(int64_t cableId) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
auto it = internal->cablesCache.find(cableId); auto it = internal->cablesCache.find(cableId);
if (it == internal->cablesCache.end()) if (it == internal->cablesCache.end())
return NULL; return NULL;
@@ -946,7 +977,7 @@ float Engine::getSmoothParam(Module* module, int paramId) {




void Engine::addParamHandle(ParamHandle* paramHandle) { void Engine::addParamHandle(ParamHandle* paramHandle) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
// New ParamHandles must be blank. // New ParamHandles must be blank.
// This means we don't have to refresh the cache. // This means we don't have to refresh the cache.
assert(paramHandle->moduleId < 0); assert(paramHandle->moduleId < 0);
@@ -962,7 +993,7 @@ void Engine::addParamHandle(ParamHandle* paramHandle) {




void Engine::removeParamHandle(ParamHandle* paramHandle) { void Engine::removeParamHandle(ParamHandle* paramHandle) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
// Check that the ParamHandle is already added // Check that the ParamHandle is already added
auto it = internal->paramHandles.find(paramHandle); auto it = internal->paramHandles.find(paramHandle);
assert(it != internal->paramHandles.end()); assert(it != internal->paramHandles.end());
@@ -975,7 +1006,7 @@ void Engine::removeParamHandle(ParamHandle* paramHandle) {




ParamHandle* Engine::getParamHandle(int64_t moduleId, int paramId) { ParamHandle* Engine::getParamHandle(int64_t moduleId, int paramId) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
auto it = internal->paramHandlesCache.find(std::make_tuple(moduleId, paramId)); auto it = internal->paramHandlesCache.find(std::make_tuple(moduleId, paramId));
if (it == internal->paramHandlesCache.end()) if (it == internal->paramHandlesCache.end())
return NULL; return NULL;
@@ -989,7 +1020,7 @@ ParamHandle* Engine::getParamHandle(Module* module, int paramId) {




void Engine::updateParamHandle(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) { void Engine::updateParamHandle(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
// Check that it exists // Check that it exists
auto it = internal->paramHandles.find(paramHandle); auto it = internal->paramHandles.find(paramHandle);
assert(it != internal->paramHandles.end()); assert(it != internal->paramHandles.end());
@@ -1027,7 +1058,7 @@ void Engine::updateParamHandle(ParamHandle* paramHandle, int64_t moduleId, int p




json_t* Engine::toJson() { json_t* Engine::toJson() {
ExclusiveSharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
json_t* rootJ = json_object(); json_t* rootJ = json_object();


// modules // modules
@@ -1053,9 +1084,7 @@ json_t* Engine::toJson() {




void Engine::fromJson(json_t* rootJ) { void Engine::fromJson(json_t* rootJ) {
// We can't lock here because addModule() and addCable() are called inside.
// Also, AudioInterface::fromJson() can open the audio device, which can call Engine::stepBlock() before this method exits.
// ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
clear(); clear();
// modules // modules
json_t* modulesJ = json_object_get(rootJ, "modules"); json_t* modulesJ = json_object_get(rootJ, "modules");


+ 1
- 0
src/system.cpp View File

@@ -9,6 +9,7 @@


#if defined ARCH_LIN || defined ARCH_MAC #if defined ARCH_LIN || defined ARCH_MAC
#include <pthread.h> #include <pthread.h>
#include <time.h> // for clock_gettime
#include <sched.h> #include <sched.h>
#include <execinfo.h> // for backtrace and backtrace_symbols #include <execinfo.h> // for backtrace and backtrace_symbols
#include <unistd.h> // for execl #include <unistd.h> // for execl


Loading…
Cancel
Save