diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index 408d1408..e5d46f9d 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -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(); diff --git a/include/engine/Module.hpp b/include/engine/Module.hpp index 510001ea..ff4d1592 100644 --- a/include/engine/Module.hpp +++ b/include/engine/Module.hpp @@ -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); }; diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 597b6b11..5b9f1afc 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -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); diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index b7da7775..b7d6a9af 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -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); } } diff --git a/src/core/MIDI_CC.cpp b/src/core/MIDI_CC.cpp index 25904977..818c5c7d 100644 --- a/src/core/MIDI_CC.cpp +++ b/src/core/MIDI_CC.cpp @@ -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(); diff --git a/src/core/MIDI_CV.cpp b/src/core/MIDI_CV.cpp index d52d9319..4952c773 100644 --- a/src/core/MIDI_CV.cpp +++ b/src/core/MIDI_CV.cpp @@ -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); } }; diff --git a/src/core/MIDI_Gate.cpp b/src/core/MIDI_Gate.cpp index f7025128..1849a37b 100644 --- a/src/core/MIDI_Gate.cpp +++ b/src/core/MIDI_Gate.cpp @@ -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(); diff --git a/src/core/MIDI_Map.cpp b/src/core/MIDI_Map.cpp index 366c82bd..a47493ac 100644 --- a/src/core/MIDI_Map.cpp +++ b/src/core/MIDI_Map.cpp @@ -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(); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index c659e894..845ad18f 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -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 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 stepLock(internal->stepMutex); +void Engine::stepBlock(int frames) { + std::lock_guard 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 diff --git a/src/engine/Module.cpp b/src/engine/Module.cpp index 7b97b71f..bd977b5c 100644 --- a/src/engine/Module.cpp +++ b/src/engine/Module.cpp @@ -1,8 +1,10 @@ #include +#include #include #include #include #include +#include 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