| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| // Include most of the C stdlib for convenience | |||
| #include <cstddef> | |||
| #include <cstdlib> | |||
| #include <cstdio> | |||
| #include <cstdint> | |||
| @@ -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); | |||
| }; | |||
| @@ -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); | |||
| @@ -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) { | |||
| @@ -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(); | |||
| } | |||
| } | |||
| @@ -1,5 +1,7 @@ | |||
| #include <engine/Module.hpp> | |||
| #include <plugin.hpp> | |||
| #include <system.hpp> | |||
| #include <settings.hpp> | |||
| #include <asset.hpp> | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||