@@ -30,7 +30,7 @@ struct Engine { | |||
/** Advances the engine by `frames` frames. | |||
Only call this method from the primary module. | |||
*/ | |||
void step(int frames); | |||
void stepBlock(int frames); | |||
void setPrimaryModule(Module* module); | |||
Module* getPrimaryModule(); | |||
@@ -41,29 +41,32 @@ struct Engine { | |||
*/ | |||
float getSampleTime(); | |||
/** Causes worker threads to block on a mutex instead of spinlock. | |||
Call this in your Module::step() method to hint that the operation will take more than ~0.1 ms. | |||
Call this in your Module::stepBlock() method to hint that the operation will take more than ~0.1 ms. | |||
*/ | |||
void yieldWorkers(); | |||
/** Returns the number of audio samples since the Engine's first sample. | |||
/** Returns the number of stepBlock() calls since the Engine was created. | |||
*/ | |||
int64_t getBlock(); | |||
/** Returns the number of audio samples since the Engine was created. | |||
*/ | |||
int64_t getFrame(); | |||
/** Returns the estimated timestamp corresponding to the current frame, based on the timestamp of when step() was last called. | |||
/** Returns the estimated time corresponding to the current frame, based on the time of when stepBlock() was last called. | |||
Calculated by `stepTime + framesSinceStep / sampleRate`. | |||
*/ | |||
double getFrameTime(); | |||
/** Returns the frame when step() was last called. | |||
/** Returns the frame when stepBlock() was last called. | |||
*/ | |||
int64_t getStepFrame(); | |||
/** Returns the timestamp in seconds when step() was last called. | |||
int64_t getBlockFrame(); | |||
/** Returns the time in seconds when stepBlock() was last called. | |||
*/ | |||
double getStepTime(); | |||
/** Returns the total number of frames in the current step() call. | |||
double getBlockTime(); | |||
/** Returns the total number of frames in the current stepBlock() call. | |||
*/ | |||
int getStepFrames(); | |||
/** Returns the total time that step() is advancing, in seconds. | |||
int getBlockFrames(); | |||
/** Returns the total time that stepBlock() is advancing, in seconds. | |||
Calculated by `stepFrames / sampleRate`. | |||
*/ | |||
double getStepDuration(); | |||
double getBlockDuration(); | |||
// Modules | |||
size_t getNumModules(); | |||
@@ -348,7 +348,7 @@ struct Module { | |||
PRIVATE const float* meterBuffer(); | |||
PRIVATE int meterLength(); | |||
PRIVATE int meterIndex(); | |||
PRIVATE void step(const ProcessArgs& args); | |||
PRIVATE void doProcess(const ProcessArgs& args); | |||
}; | |||
@@ -401,40 +401,50 @@ void ModuleWidget::draw(const DrawArgs& args) { | |||
int meterLength = module->meterLength(); | |||
int meterIndex = module->meterIndex(); | |||
float meterMax = 0.f; | |||
float meterAvg = 0.f; | |||
for (int i = 0; i < meterLength; i++) { | |||
meterAvg += meterBuffer[i]; | |||
float m = meterBuffer[i]; | |||
meterAvg += m; | |||
meterMax = std::max(meterMax, m); | |||
} | |||
meterAvg /= meterLength; | |||
float percentMax = meterMax * sampleRate; | |||
float mult = (percentMax <= 0.1f) ? 10.f : 1.f; | |||
// Text background | |||
nvgBeginPath(args.vg); | |||
nvgRect(args.vg, 0, box.size.y - 20, box.size.x, 20); | |||
nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75)); | |||
nvgFill(args.vg); | |||
// Text | |||
float percent = meterAvg * sampleRate * 100.f; | |||
float microseconds = meterAvg * 1e6f; | |||
std::string meterText = string::f("%.1f%% %.2f ÎĽs", percent, microseconds); | |||
bndLabel(args.vg, 0.0, box.size.y - 20.0, INFINITY, INFINITY, -1, meterText.c_str()); | |||
// // Text background | |||
// nvgBeginPath(args.vg); | |||
// nvgRect(args.vg, 0.0, box.size.y - infoHeight, box.size.x, infoHeight); | |||
// nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75)); | |||
// nvgFill(args.vg); | |||
// Draw time plot | |||
nvgBeginPath(args.vg); | |||
nvgMoveTo(args.vg, box.size.x, box.size.y); | |||
for (int i = 0; i < meterLength; i++) { | |||
int index = (meterIndex - i + meterLength) % meterLength; | |||
float percent = math::clamp(meterBuffer[index] * mult * sampleRate, 0.f, 1.f); | |||
math::Vec p; | |||
p.x = (1.f - (float) i / (meterLength - 1)) * box.size.x; | |||
p.y = (1.f - meterBuffer[index] * sampleRate * 1.f) * (box.size.y - 20); | |||
p.y = (1.f - percent) * (box.size.y); | |||
nvgLineTo(args.vg, p.x, p.y); | |||
} | |||
NVGcolor color = nvgRGBAf(0.6, 0, 0, 0.6); | |||
NVGcolor color; | |||
if (mult == 1.f) | |||
color = nvgRGBAf(0.5, 0, 0, 0.85); | |||
else if (mult == 10.f) | |||
color = nvgRGBAf(0.85, 0, 0, 0.85); | |||
nvgLineTo(args.vg, 0.0, box.size.y); | |||
nvgClosePath(args.vg); | |||
nvgFillColor(args.vg, color); | |||
nvgFill(args.vg); | |||
// Text | |||
float percent = meterAvg * sampleRate * 100.f; | |||
float microseconds = meterAvg * 1e6f; | |||
std::string meterText = string::f("%.0fx\n%.2f ÎĽs\n%.1f%%", mult, microseconds, percent); | |||
bndLabel(args.vg, 0.0, box.size.y - 60, INFINITY, INFINITY, -1, meterText.c_str()); | |||
// Draw border | |||
nvgStrokeColor(args.vg, color); | |||
nvgBeginPath(args.vg); | |||
@@ -245,7 +245,7 @@ struct AudioInterface : Module, audio::Port { | |||
bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||
// Step engine | |||
if (isPrimary && requestedEngineFrames > 0) { | |||
APP->engine->step(requestedEngineFrames); | |||
APP->engine->stepBlock(requestedEngineFrames); | |||
} | |||
} | |||
@@ -73,7 +73,7 @@ struct MIDI_CC : Module { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getStepDuration() > APP->engine->getFrameTime()) | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -124,7 +124,7 @@ struct MIDI_CV : Module { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getStepDuration() > APP->engine->getFrameTime()) | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -639,6 +639,10 @@ struct MIDI_CVWidget : ModuleWidget { | |||
panicItem->text = "Panic"; | |||
panicItem->module = module; | |||
menu->addChild(panicItem); | |||
// Example of using appendMidiMenu() | |||
// menu->addChild(new MenuSeparator); | |||
// appendMidiMenu(menu, &module->midiInput); | |||
} | |||
}; | |||
@@ -67,7 +67,7 @@ struct MIDI_Gate : Module { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getStepDuration() > APP->engine->getFrameTime()) | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -85,7 +85,7 @@ struct MIDI_Map : Module { | |||
while (!midiInput.queue.empty()) { | |||
midi::Message& msg = midiInput.queue.front(); | |||
// Don't process MIDI message until its timestamp corresponds with the audio frame time when played back in the next block. | |||
if (msg.timestamp + APP->engine->getStepDuration() > APP->engine->getFrameTime()) | |||
if (msg.timestamp + APP->engine->getBlockDuration() > APP->engine->getFrameTime()) | |||
break; | |||
processMessage(msg); | |||
midiInput.queue.pop(); | |||
@@ -196,10 +196,11 @@ struct Engine::Internal { | |||
float sampleRate = 0.f; | |||
float sampleTime = 0.f; | |||
int64_t block = 0; | |||
int64_t frame = 0; | |||
int64_t stepFrame = 0; | |||
double stepTime = 0.0; | |||
int stepFrames = 0; | |||
int64_t blockFrame = 0; | |||
double blockTime = 0.0; | |||
int blockFrames = 0; | |||
Module* primaryModule = NULL; | |||
// Parameter smoothing | |||
@@ -207,15 +208,15 @@ struct Engine::Internal { | |||
int smoothParamId = 0; | |||
float smoothValue = 0.f; | |||
/** Engine mutex | |||
Writers lock when mutating the engine's Modules, Cables, etc. | |||
Readers lock when using the engine's Modules, Cables, etc. | |||
/** Mutex that guards the Engine state, such as settings, Modules, and Cables. | |||
Writers lock when mutating the engine's state. | |||
Readers lock when using the engine's state. | |||
*/ | |||
SharedMutex mutex; | |||
/** Step mutex | |||
step() locks to guarantee its exclusivity. | |||
/** Mutex that guards the block. | |||
stepBlock() locks to guarantee its exclusivity. | |||
*/ | |||
std::mutex stepMutex; | |||
std::mutex blockMutex; | |||
int threadCount = 0; | |||
std::vector<EngineWorker> workers; | |||
@@ -310,7 +311,7 @@ static void Engine_stepWorker(Engine* that, int threadId) { | |||
break; | |||
Module* module = internal->modules[i]; | |||
module->step(processArgs); | |||
module->doProcess(processArgs); | |||
} | |||
} | |||
@@ -497,16 +498,16 @@ void Engine::clear() { | |||
} | |||
void Engine::step(int frames) { | |||
std::lock_guard<std::mutex> stepLock(internal->stepMutex); | |||
void Engine::stepBlock(int frames) { | |||
std::lock_guard<std::mutex> stepLock(internal->blockMutex); | |||
SharedLock lock(internal->mutex); | |||
// Configure thread | |||
initMXCSR(); | |||
random::init(); | |||
internal->stepFrame = internal->frame; | |||
internal->stepTime = system::getTime(); | |||
internal->stepFrames = frames; | |||
internal->blockFrame = internal->frame; | |||
internal->blockTime = system::getTime(); | |||
internal->blockFrames = frames; | |||
// Set sample rate | |||
if (internal->sampleRate != settings::sampleRate) { | |||
@@ -538,9 +539,11 @@ void Engine::step(int frames) { | |||
yieldWorkers(); | |||
double endTime = system::getTime(); | |||
float duration = endTime - internal->stepTime; | |||
float stepDuration = internal->stepFrames * internal->sampleTime; | |||
// DEBUG("%d %f / %f = %f%%", internal->stepFrames, duration, stepDuration, duration / stepDuration * 100.f); | |||
float duration = endTime - internal->blockTime; | |||
float blockDuration = internal->blockFrames * internal->sampleTime; | |||
// DEBUG("%d %f / %f = %f%%", internal->blockFrames, duration, blockDuration, duration / blockDuration * 100.f); | |||
internal->block++; | |||
} | |||
@@ -577,34 +580,39 @@ void Engine::yieldWorkers() { | |||
} | |||
int64_t Engine::getBlock() { | |||
return internal->block; | |||
} | |||
int64_t Engine::getFrame() { | |||
return internal->frame; | |||
} | |||
double Engine::getFrameTime() { | |||
double timeSinceStep = (internal->frame - internal->stepFrame) * internal->sampleTime; | |||
return internal->stepTime + timeSinceStep; | |||
double timeSinceBlock = (internal->frame - internal->blockFrame) * internal->sampleTime; | |||
return internal->blockTime + timeSinceBlock; | |||
} | |||
int64_t Engine::getStepFrame() { | |||
return internal->stepFrame; | |||
int64_t Engine::getBlockFrame() { | |||
return internal->blockFrame; | |||
} | |||
double Engine::getStepTime() { | |||
return internal->stepTime; | |||
double Engine::getBlockTime() { | |||
return internal->blockTime; | |||
} | |||
int Engine::getStepFrames() { | |||
return internal->stepFrames; | |||
int Engine::getBlockFrames() { | |||
return internal->blockFrames; | |||
} | |||
double Engine::getStepDuration() { | |||
return internal->stepFrames * internal->sampleTime; | |||
double Engine::getBlockDuration() { | |||
return internal->blockFrames * internal->sampleTime; | |||
} | |||
@@ -1031,7 +1039,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::step() before this method exits. | |||
// Also, AudioInterface::fromJson() can open the audio device, which can call Engine::stepBlock() before this method exits. | |||
// ExclusiveSharedLock lock(internal->mutex); | |||
clear(); | |||
// modules | |||
@@ -1,8 +1,10 @@ | |||
#include <engine/Module.hpp> | |||
#include <engine/Engine.hpp> | |||
#include <plugin.hpp> | |||
#include <system.hpp> | |||
#include <settings.hpp> | |||
#include <asset.hpp> | |||
#include <context.hpp> | |||
namespace rack { | |||
@@ -11,19 +13,19 @@ namespace engine { | |||
// Arbitrary prime number so it doesn't over- or under-estimate time of buffered processors. | |||
static const int meterDivider = 23; | |||
static const int samplesCount = 64; | |||
static const int meterDivider = 1; | |||
static const int meterBufferLength = 128; | |||
struct Module::Internal { | |||
bool bypass = false; | |||
float meterTimeTotal = 0.f; | |||
int64_t meterLastBlock = 0; | |||
int meterSamples = 0; | |||
int meterIndex = 0; | |||
float meterTimeTotal = 0.f; | |||
float meterBuffer[meterBufferLength] = {}; | |||
int meterIndex = 0; | |||
}; | |||
@@ -326,7 +328,7 @@ static void Port_step(Port* that, float deltaTime) { | |||
} | |||
void Module::step(const ProcessArgs& args) { | |||
void Module::doProcess(const ProcessArgs& args) { | |||
// This global setting can change while the function is running, so use a local variable. | |||
bool meterEnabled = settings::cpuMeter && (args.frame % meterDivider == 0); | |||
@@ -347,15 +349,21 @@ void Module::step(const ProcessArgs& args) { | |||
double endTime = system::getTime(); | |||
float duration = endTime - startTime; | |||
internal->meterTimeTotal += duration; | |||
if (++internal->meterSamples >= samplesCount) { | |||
int64_t block = APP->engine->getBlock(); | |||
if (block > internal->meterLastBlock) { | |||
// Push time to buffer | |||
internal->meterBuffer[internal->meterIndex++] = internal->meterTimeTotal / samplesCount; | |||
internal->meterIndex %= meterBufferLength; | |||
if (internal->meterSamples > 0) { | |||
internal->meterBuffer[internal->meterIndex++] = internal->meterTimeTotal / internal->meterSamples; | |||
internal->meterIndex %= meterBufferLength; | |||
} | |||
// Reset total | |||
internal->meterSamples = 0; | |||
internal->meterTimeTotal = 0.f; | |||
} | |||
internal->meterLastBlock = block; | |||
internal->meterSamples++; | |||
internal->meterTimeTotal += duration; | |||
} | |||
// Iterate ports to step plug lights | |||