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.
Only call this method from the primary module.
*/
void stepBlock(int frames, float suggestedSampleRate = 0.f);
void stepBlock(int frames);
void setPrimaryModule(Module* module);
Module* getPrimaryModule();

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

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

// Initialize sample rate converters
int numInputs = getNumInputs();
int engineSampleRate = (int) APP->engine->getSampleRate();
float sampleRate = getSampleRate();
double sampleRateRatio = (double) engineSampleRate / sampleRate;
outputSrc.setRates(sampleRate, engineSampleRate);
outputSrc.setChannels(numInputs);
@@ -245,7 +250,7 @@ struct AudioInterface : Module, audio::Port {
bool isPrimary = (APP->engine->getPrimaryModule() == this);
// Step engine
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.
Recursive, so WriteLock can be used recursively.
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;
SharedMutex() {
std::atomic<pthread_t> writerThread{0};
int recursion = 0;

ReadWriteMutex() {
if (pthread_rwlock_init(&rwlock, NULL))
throw Exception("pthread_rwlock_init failed");
}
~SharedMutex() {
~ReadWriteMutex() {
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.
Readers lock when using the engine's state.
*/
SharedMutex mutex;
ReadWriteMutex mutex;
/** Mutex that guards the block.
stepBlock() locks to guarantee its exclusivity.
*/
@@ -479,7 +506,7 @@ Engine::~Engine() {


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
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);
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
// Configure thread
initMXCSR();
random::init();
@@ -511,17 +538,6 @@ void Engine::stepBlock(int frames, float suggestedSampleRate) {
internal->blockTime = system::getTime();
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
for (Module* module : internal->modules) {
Engine_updateExpander(this, module, false);
@@ -548,7 +564,7 @@ void Engine::stepBlock(int frames, float suggestedSampleRate) {


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.
// NULL will unset the primary module.
if (module) {
@@ -573,6 +589,8 @@ float Engine::getSampleRate() {
void Engine::setSampleRate(float sampleRate) {
if (sampleRate == internal->sampleRate)
return;
WriteLock lock(internal->mutex);

internal->sampleRate = sampleRate;
internal->sampleTime = 1.f / sampleRate;
// 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() {
return internal->sampleTime;
}
@@ -637,7 +668,7 @@ size_t Engine::getNumModules() {


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


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


void Engine::addModule(Module* module) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(module);
// Check that the module is not already added
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) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(module);
// Check that the module actually exists
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) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
auto it = internal->modulesCache.find(moduleId);
if (it == internal->modulesCache.end())
return NULL;
@@ -738,7 +769,7 @@ Module* Engine::getModule(int64_t moduleId) {


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

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


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

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


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

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


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


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

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


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


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


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


void Engine::removeCable(Cable* cable) {
ExclusiveSharedLock lock(internal->mutex);
WriteLock lock(internal->mutex);
assert(cable);
// Check that the cable is already added
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) {
SharedLock lock(internal->mutex);
ReadLock lock(internal->mutex);
auto it = internal->cablesCache.find(cableId);
if (it == internal->cablesCache.end())
return NULL;
@@ -946,7 +977,7 @@ float Engine::getSmoothParam(Module* module, int paramId) {


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


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


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


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

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


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();
// 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
#include <pthread.h>
#include <time.h> // for clock_gettime
#include <sched.h>
#include <execinfo.h> // for backtrace and backtrace_symbols
#include <unistd.h> // for execl


Loading…
Cancel
Save