@@ -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 step(int frames); | |||||
void stepBlock(int frames); | |||||
void setPrimaryModule(Module* module); | void setPrimaryModule(Module* module); | ||||
Module* getPrimaryModule(); | Module* getPrimaryModule(); | ||||
@@ -41,29 +41,32 @@ struct Engine { | |||||
*/ | */ | ||||
float getSampleTime(); | float getSampleTime(); | ||||
/** Causes worker threads to block on a mutex instead of spinlock. | /** 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(); | 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(); | 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`. | Calculated by `stepTime + framesSinceStep / sampleRate`. | ||||
*/ | */ | ||||
double getFrameTime(); | 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`. | Calculated by `stepFrames / sampleRate`. | ||||
*/ | */ | ||||
double getStepDuration(); | |||||
double getBlockDuration(); | |||||
// Modules | // Modules | ||||
size_t getNumModules(); | size_t getNumModules(); | ||||
@@ -348,7 +348,7 @@ struct Module { | |||||
PRIVATE const float* meterBuffer(); | PRIVATE const float* meterBuffer(); | ||||
PRIVATE int meterLength(); | PRIVATE int meterLength(); | ||||
PRIVATE int meterIndex(); | 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 meterLength = module->meterLength(); | ||||
int meterIndex = module->meterIndex(); | int meterIndex = module->meterIndex(); | ||||
float meterMax = 0.f; | |||||
float meterAvg = 0.f; | float meterAvg = 0.f; | ||||
for (int i = 0; i < meterLength; i++) { | for (int i = 0; i < meterLength; i++) { | ||||
meterAvg += meterBuffer[i]; | |||||
float m = meterBuffer[i]; | |||||
meterAvg += m; | |||||
meterMax = std::max(meterMax, m); | |||||
} | } | ||||
meterAvg /= meterLength; | 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 | // Draw time plot | ||||
nvgBeginPath(args.vg); | nvgBeginPath(args.vg); | ||||
nvgMoveTo(args.vg, box.size.x, box.size.y); | nvgMoveTo(args.vg, box.size.x, box.size.y); | ||||
for (int i = 0; i < meterLength; i++) { | for (int i = 0; i < meterLength; i++) { | ||||
int index = (meterIndex - i + meterLength) % meterLength; | int index = (meterIndex - i + meterLength) % meterLength; | ||||
float percent = math::clamp(meterBuffer[index] * mult * sampleRate, 0.f, 1.f); | |||||
math::Vec p; | math::Vec p; | ||||
p.x = (1.f - (float) i / (meterLength - 1)) * box.size.x; | 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); | 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); | nvgLineTo(args.vg, 0.0, box.size.y); | ||||
nvgClosePath(args.vg); | nvgClosePath(args.vg); | ||||
nvgFillColor(args.vg, color); | nvgFillColor(args.vg, color); | ||||
nvgFill(args.vg); | 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 | // Draw border | ||||
nvgStrokeColor(args.vg, color); | nvgStrokeColor(args.vg, color); | ||||
nvgBeginPath(args.vg); | nvgBeginPath(args.vg); | ||||
@@ -245,7 +245,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->step(requestedEngineFrames); | |||||
APP->engine->stepBlock(requestedEngineFrames); | |||||
} | } | ||||
} | } | ||||
@@ -73,7 +73,7 @@ struct MIDI_CC : Module { | |||||
while (!midiInput.queue.empty()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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. | // 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | midiInput.queue.pop(); | ||||
@@ -124,7 +124,7 @@ struct MIDI_CV : Module { | |||||
while (!midiInput.queue.empty()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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. | // 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | midiInput.queue.pop(); | ||||
@@ -639,6 +639,10 @@ struct MIDI_CVWidget : ModuleWidget { | |||||
panicItem->text = "Panic"; | panicItem->text = "Panic"; | ||||
panicItem->module = module; | panicItem->module = module; | ||||
menu->addChild(panicItem); | 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()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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. | // 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | midiInput.queue.pop(); | ||||
@@ -85,7 +85,7 @@ struct MIDI_Map : Module { | |||||
while (!midiInput.queue.empty()) { | while (!midiInput.queue.empty()) { | ||||
midi::Message& msg = midiInput.queue.front(); | 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. | // 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; | break; | ||||
processMessage(msg); | processMessage(msg); | ||||
midiInput.queue.pop(); | midiInput.queue.pop(); | ||||
@@ -196,10 +196,11 @@ struct Engine::Internal { | |||||
float sampleRate = 0.f; | float sampleRate = 0.f; | ||||
float sampleTime = 0.f; | float sampleTime = 0.f; | ||||
int64_t block = 0; | |||||
int64_t frame = 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; | Module* primaryModule = NULL; | ||||
// Parameter smoothing | // Parameter smoothing | ||||
@@ -207,15 +208,15 @@ struct Engine::Internal { | |||||
int smoothParamId = 0; | int smoothParamId = 0; | ||||
float smoothValue = 0.f; | 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; | 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; | int threadCount = 0; | ||||
std::vector<EngineWorker> workers; | std::vector<EngineWorker> workers; | ||||
@@ -310,7 +311,7 @@ static void Engine_stepWorker(Engine* that, int threadId) { | |||||
break; | break; | ||||
Module* module = internal->modules[i]; | 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); | SharedLock lock(internal->mutex); | ||||
// Configure thread | // Configure thread | ||||
initMXCSR(); | initMXCSR(); | ||||
random::init(); | 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 | // Set sample rate | ||||
if (internal->sampleRate != settings::sampleRate) { | if (internal->sampleRate != settings::sampleRate) { | ||||
@@ -538,9 +539,11 @@ void Engine::step(int frames) { | |||||
yieldWorkers(); | yieldWorkers(); | ||||
double endTime = system::getTime(); | 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() { | int64_t Engine::getFrame() { | ||||
return internal->frame; | return internal->frame; | ||||
} | } | ||||
double Engine::getFrameTime() { | 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) { | void Engine::fromJson(json_t* rootJ) { | ||||
// We can't lock here because addModule() and addCable() are called inside. | // 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); | // ExclusiveSharedLock lock(internal->mutex); | ||||
clear(); | clear(); | ||||
// modules | // modules | ||||
@@ -1,8 +1,10 @@ | |||||
#include <engine/Module.hpp> | #include <engine/Module.hpp> | ||||
#include <engine/Engine.hpp> | |||||
#include <plugin.hpp> | #include <plugin.hpp> | ||||
#include <system.hpp> | #include <system.hpp> | ||||
#include <settings.hpp> | #include <settings.hpp> | ||||
#include <asset.hpp> | #include <asset.hpp> | ||||
#include <context.hpp> | |||||
namespace rack { | namespace rack { | ||||
@@ -11,19 +13,19 @@ namespace engine { | |||||
// Arbitrary prime number so it doesn't over- or under-estimate time of buffered processors. | // 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; | static const int meterBufferLength = 128; | ||||
struct Module::Internal { | struct Module::Internal { | ||||
bool bypass = false; | bool bypass = false; | ||||
float meterTimeTotal = 0.f; | |||||
int64_t meterLastBlock = 0; | |||||
int meterSamples = 0; | int meterSamples = 0; | ||||
int meterIndex = 0; | |||||
float meterTimeTotal = 0.f; | |||||
float meterBuffer[meterBufferLength] = {}; | 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. | // This global setting can change while the function is running, so use a local variable. | ||||
bool meterEnabled = settings::cpuMeter && (args.frame % meterDivider == 0); | bool meterEnabled = settings::cpuMeter && (args.frame % meterDivider == 0); | ||||
@@ -347,15 +349,21 @@ void Module::step(const ProcessArgs& args) { | |||||
double endTime = system::getTime(); | double endTime = system::getTime(); | ||||
float duration = endTime - startTime; | 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 | // 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 | // Reset total | ||||
internal->meterSamples = 0; | internal->meterSamples = 0; | ||||
internal->meterTimeTotal = 0.f; | internal->meterTimeTotal = 0.f; | ||||
} | } | ||||
internal->meterLastBlock = block; | |||||
internal->meterSamples++; | |||||
internal->meterTimeTotal += duration; | |||||
} | } | ||||
// Iterate ports to step plug lights | // Iterate ports to step plug lights | ||||