@@ -1,5 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
// Include most of the C stdlib for convenience | // Include most of the C stdlib for convenience | ||||
#include <cstddef> | |||||
#include <cstdlib> | #include <cstdlib> | ||||
#include <cstdio> | #include <cstdio> | ||||
#include <cstdint> | #include <cstdint> | ||||
@@ -225,6 +225,8 @@ struct Module { | |||||
Defined by `1 / sampleRate`. | Defined by `1 / sampleRate`. | ||||
*/ | */ | ||||
float sampleTime; | float sampleTime; | ||||
/** Number of audio samples since the Engine's first sample. */ | |||||
int64_t frame; | |||||
}; | }; | ||||
/** Advances the module by one audio sample. | /** Advances the module by one audio sample. | ||||
Override this method to read Inputs and Params and to write Outputs and Lights. | 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. */ | /** DEPRECATED. Override `onSampleRateChange(e)` instead. */ | ||||
virtual void onSampleRateChange() {} | 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; | menu->box.size.x = box.size.x; | ||||
CpuMeterItem* cpuMeterItem = new CpuMeterItem; | CpuMeterItem* cpuMeterItem = new CpuMeterItem; | ||||
cpuMeterItem->text = "CPU meter"; | |||||
cpuMeterItem->text = "Performance meters"; | |||||
cpuMeterItem->rightText = "F3 "; | cpuMeterItem->rightText = "F3 "; | ||||
cpuMeterItem->rightText += CHECKMARK(settings::cpuMeter); | cpuMeterItem->rightText += CHECKMARK(settings::cpuMeter); | ||||
menu->addChild(cpuMeterItem); | menu->addChild(cpuMeterItem); | ||||
@@ -394,27 +394,53 @@ void ModuleWidget::draw(const DrawArgs& args) { | |||||
Widget::draw(args); | Widget::draw(args); | ||||
// Power meter | |||||
// Meter | |||||
if (module && settings::cpuMeter) { | 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); | 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)); | nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75)); | ||||
nvgFill(args.vg); | 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); | 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); | 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) { | // 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; | Engine::Internal* internal = that->internal; | ||||
// int threadCount = internal->threadCount; | // int threadCount = internal->threadCount; | ||||
int modulesLen = internal->modules.size(); | int modulesLen = internal->modules.size(); | ||||
// Build ProcessArgs | |||||
Module::ProcessArgs processArgs; | Module::ProcessArgs processArgs; | ||||
processArgs.sampleRate = internal->sampleRate; | processArgs.sampleRate = internal->sampleRate; | ||||
processArgs.sampleTime = internal->sampleTime; | processArgs.sampleTime = internal->sampleTime; | ||||
bool cpuMeter = settings::cpuMeter; | |||||
processArgs.frame = internal->frame; | |||||
// Step each module | // Step each module | ||||
while (true) { | while (true) { | ||||
@@ -336,40 +310,7 @@ static void Engine_stepModulesWorker(Engine* that, int threadId) { | |||||
break; | break; | ||||
Module* module = internal->modules[i]; | 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 | // Step modules along with workers | ||||
internal->workerModuleIndex = 0; | internal->workerModuleIndex = 0; | ||||
internal->engineBarrier.wait(); | internal->engineBarrier.wait(); | ||||
Engine_stepModulesWorker(that, 0); | |||||
Engine_stepWorker(that, 0); | |||||
internal->workerBarrier.wait(); | internal->workerBarrier.wait(); | ||||
internal->frame++; | internal->frame++; | ||||
@@ -1156,7 +1097,7 @@ void EngineWorker::run() { | |||||
engine->internal->engineBarrier.wait(); | engine->internal->engineBarrier.wait(); | ||||
if (!running) | if (!running) | ||||
return; | return; | ||||
Engine_stepModulesWorker(engine, id); | |||||
Engine_stepWorker(engine, id); | |||||
engine->internal->workerBarrier.wait(); | engine->internal->workerBarrier.wait(); | ||||
} | } | ||||
} | } | ||||
@@ -1,5 +1,7 @@ | |||||
#include <engine/Module.hpp> | #include <engine/Module.hpp> | ||||
#include <plugin.hpp> | #include <plugin.hpp> | ||||
#include <system.hpp> | |||||
#include <settings.hpp> | |||||
#include <asset.hpp> | #include <asset.hpp> | ||||
@@ -7,12 +9,21 @@ namespace rack { | |||||
namespace engine { | 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 { | 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; | 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); | |||||
} | |||||
} | |||||
} | } | ||||