@@ -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); | |||
} | |||
} | |||
} | |||