diff --git a/include/common.hpp b/include/common.hpp index 08b3e776..aadb3577 100644 --- a/include/common.hpp +++ b/include/common.hpp @@ -1,5 +1,6 @@ #pragma once // Include most of the C stdlib for convenience +#include #include #include #include diff --git a/include/engine/Module.hpp b/include/engine/Module.hpp index bb53d75e..510001ea 100644 --- a/include/engine/Module.hpp +++ b/include/engine/Module.hpp @@ -225,6 +225,8 @@ struct Module { Defined by `1 / sampleRate`. */ float sampleTime; + /** Number of audio samples since the Engine's first sample. */ + int64_t frame; }; /** Advances the module by one audio sample. Override this method to read Inputs and Params and to write Outputs and Lights. @@ -342,10 +344,11 @@ struct Module { /** DEPRECATED. Override `onSampleRateChange(e)` instead. */ virtual void onSampleRateChange() {} - /** Unstable API, do not use in plugins. */ - float& cpuTime(); - /** Unstable API, do not use in plugins. */ - bool& bypass(); + PRIVATE bool& bypass(); + PRIVATE const float* meterBuffer(); + PRIVATE int meterLength(); + PRIVATE int meterIndex(); + PRIVATE void step(const ProcessArgs& args); }; diff --git a/src/app/MenuBar.cpp b/src/app/MenuBar.cpp index 7473f0fd..f6e3b93d 100644 --- a/src/app/MenuBar.cpp +++ b/src/app/MenuBar.cpp @@ -553,7 +553,7 @@ struct EngineButton : MenuButton { menu->box.size.x = box.size.x; CpuMeterItem* cpuMeterItem = new CpuMeterItem; - cpuMeterItem->text = "CPU meter"; + cpuMeterItem->text = "Performance meters"; cpuMeterItem->rightText = "F3 "; cpuMeterItem->rightText += CHECKMARK(settings::cpuMeter); menu->addChild(cpuMeterItem); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 26d8aefb..597b6b11 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -394,27 +394,53 @@ void ModuleWidget::draw(const DrawArgs& args) { Widget::draw(args); - // Power meter + // Meter if (module && settings::cpuMeter) { + float sampleRate = APP->engine->getSampleRate(); + const float* meterBuffer = module->meterBuffer(); + int meterLength = module->meterLength(); + int meterIndex = module->meterIndex(); + + float meterAvg = 0.f; + for (int i = 0; i < meterLength; i++) { + meterAvg += meterBuffer[i]; + } + meterAvg /= meterLength; + + // Text background nvgBeginPath(args.vg); - nvgRect(args.vg, - 0, box.size.y - 35, - 65, 35); + nvgRect(args.vg, 0, box.size.y - 20, box.size.x, 20); nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75)); nvgFill(args.vg); - float percent = module->cpuTime() * APP->engine->getSampleRate() * 100; - float microseconds = module->cpuTime() * 1e6f; - std::string cpuText = string::f("%.1f%%\n%.2f μs", percent, microseconds); - bndLabel(args.vg, 2.0, box.size.y - 34.0, INFINITY, INFINITY, -1, cpuText.c_str()); + // 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()); - float p = math::clamp(module->cpuTime() / APP->engine->getSampleTime(), 0.f, 1.f); + // Draw time plot nvgBeginPath(args.vg); - nvgRect(args.vg, - 0, (1.f - p) * box.size.y, - 5, p * box.size.y); - nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 1.0)); + nvgMoveTo(args.vg, box.size.x, box.size.y); + for (int i = 0; i < meterLength; i++) { + int index = (meterIndex - i + meterLength) % meterLength; + 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); + nvgLineTo(args.vg, p.x, p.y); + } + NVGcolor color = nvgRGBAf(0.6, 0, 0, 0.6); + nvgLineTo(args.vg, 0.0, box.size.y); + nvgClosePath(args.vg); + nvgFillColor(args.vg, color); nvgFill(args.vg); + + // Draw border + nvgStrokeColor(args.vg, color); + nvgBeginPath(args.vg); + nvgRect(args.vg, 0, 0, box.size.x, box.size.y); + nvgStrokeWidth(args.vg, 2.0); + nvgStroke(args.vg); } // if (module) { diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 22cf3023..c659e894 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -289,43 +289,17 @@ static void Engine_relaunchWorkers(Engine* that, int threadCount) { } -static void Port_step(Port* that, float deltaTime) { - // Set plug lights - if (that->channels == 0) { - that->plugLights[0].setBrightness(0.f); - that->plugLights[1].setBrightness(0.f); - that->plugLights[2].setBrightness(0.f); - } - else if (that->channels == 1) { - float v = that->getVoltage() / 10.f; - that->plugLights[0].setSmoothBrightness(v, deltaTime); - that->plugLights[1].setSmoothBrightness(-v, deltaTime); - that->plugLights[2].setBrightness(0.f); - } - else { - float v2 = 0.f; - for (int c = 0; c < that->channels; c++) { - v2 += std::pow(that->getVoltage(c), 2); - } - float v = std::sqrt(v2) / 10.f; - that->plugLights[0].setBrightness(0.f); - that->plugLights[1].setBrightness(0.f); - that->plugLights[2].setSmoothBrightness(v, deltaTime); - } -} - - -static void Engine_stepModulesWorker(Engine* that, int threadId) { +static void Engine_stepWorker(Engine* that, int threadId) { Engine::Internal* internal = that->internal; // int threadCount = internal->threadCount; int modulesLen = internal->modules.size(); + // Build ProcessArgs Module::ProcessArgs processArgs; processArgs.sampleRate = internal->sampleRate; processArgs.sampleTime = internal->sampleTime; - - bool cpuMeter = settings::cpuMeter; + processArgs.frame = internal->frame; // Step each module while (true) { @@ -336,40 +310,7 @@ static void Engine_stepModulesWorker(Engine* that, int threadId) { break; Module* module = internal->modules[i]; - - // Start CPU timer - double startTime; - if (cpuMeter) { - startTime = system::getRuntime(); - } - - // Step module - if (!module->bypass()) - module->process(processArgs); - else - module->processBypass(processArgs); - - // Stop CPU timer - if (cpuMeter) { - double endTime = system::getRuntime(); - float duration = endTime - startTime; - - // Smooth CPU time - const float cpuTau = 2.f /* seconds */; - module->cpuTime() += (duration - module->cpuTime()) * processArgs.sampleTime / cpuTau; - } - - // Iterate ports to step plug lights - const int portDivider = 8; - if (internal->frame % portDivider == 0) { - float portTime = processArgs.sampleTime * portDivider; - for (Input& input : module->inputs) { - Port_step(&input, portTime); - } - for (Output& output : module->outputs) { - Port_step(&output, portTime); - } - } + module->step(processArgs); } } @@ -441,7 +382,7 @@ static void Engine_stepFrame(Engine* that) { // Step modules along with workers internal->workerModuleIndex = 0; internal->engineBarrier.wait(); - Engine_stepModulesWorker(that, 0); + Engine_stepWorker(that, 0); internal->workerBarrier.wait(); internal->frame++; @@ -1156,7 +1097,7 @@ void EngineWorker::run() { engine->internal->engineBarrier.wait(); if (!running) return; - Engine_stepModulesWorker(engine, id); + Engine_stepWorker(engine, id); engine->internal->workerBarrier.wait(); } } diff --git a/src/engine/Module.cpp b/src/engine/Module.cpp index 9d266d9c..7b97b71f 100644 --- a/src/engine/Module.cpp +++ b/src/engine/Module.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include @@ -7,12 +9,21 @@ namespace rack { 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 meterBufferLength = 128; + + + struct Module::Internal { - /** Seconds spent in the process() method, with exponential smoothing. - Only written when CPU timing is enabled, since time measurement is expensive. - */ - float cpuTime = 0.f; bool bypass = false; + + float meterTimeTotal = 0.f; + int meterSamples = 0; + int meterIndex = 0; + float meterBuffer[meterBufferLength] = {}; }; @@ -269,13 +280,95 @@ void Module::onRandomize(const RandomizeEvent& e) { } -float& Module::cpuTime() { - return internal->cpuTime; +bool& Module::bypass() { + return internal->bypass; } -bool& Module::bypass() { - return internal->bypass; +const float* Module::meterBuffer() { + return internal->meterBuffer; +} + + +int Module::meterLength() { + return meterBufferLength; +} + + +int Module::meterIndex() { + return internal->meterIndex; +} + + +static void Port_step(Port* that, float deltaTime) { + // Set plug lights + if (that->channels == 0) { + that->plugLights[0].setBrightness(0.f); + that->plugLights[1].setBrightness(0.f); + that->plugLights[2].setBrightness(0.f); + } + else if (that->channels == 1) { + float v = that->getVoltage() / 10.f; + that->plugLights[0].setSmoothBrightness(v, deltaTime); + that->plugLights[1].setSmoothBrightness(-v, deltaTime); + that->plugLights[2].setBrightness(0.f); + } + else { + float v2 = 0.f; + for (int c = 0; c < that->channels; c++) { + v2 += std::pow(that->getVoltage(c), 2); + } + float v = std::sqrt(v2) / 10.f; + that->plugLights[0].setBrightness(0.f); + that->plugLights[1].setBrightness(0.f); + that->plugLights[2].setSmoothBrightness(v, deltaTime); + } +} + + +void Module::step(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); + + // Start CPU timer + double startTime; + if (meterEnabled) { + startTime = system::getTime(); + } + + // Step module + if (!internal->bypass) + process(args); + else + processBypass(args); + + // Stop CPU timer + if (meterEnabled) { + double endTime = system::getTime(); + float duration = endTime - startTime; + + internal->meterTimeTotal += duration; + if (++internal->meterSamples >= samplesCount) { + // Push time to buffer + internal->meterBuffer[internal->meterIndex++] = internal->meterTimeTotal / samplesCount; + internal->meterIndex %= meterBufferLength; + // Reset total + internal->meterSamples = 0; + internal->meterTimeTotal = 0.f; + } + } + + // Iterate ports to step plug lights + const int portDivider = 8; + if (args.frame % portDivider == 0) { + float portTime = args.sampleTime * portDivider; + for (Input& input : inputs) { + Port_step(&input, portTime); + } + for (Output& output : outputs) { + Port_step(&output, portTime); + } + } }