From 756271f1d280db4e1b56f041612609014d14b478 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 29 Jan 2022 02:49:01 +0000 Subject: [PATCH] Add a little level meter to audio2 module Signed-off-by: falkTX --- plugins/Cardinal/src/HostAudio.cpp | 215 +++++++++++++++++++++++++---- 1 file changed, 191 insertions(+), 24 deletions(-) diff --git a/plugins/Cardinal/src/HostAudio.cpp b/plugins/Cardinal/src/HostAudio.cpp index 42cb628..0f46885 100644 --- a/plugins/Cardinal/src/HostAudio.cpp +++ b/plugins/Cardinal/src/HostAudio.cpp @@ -44,7 +44,7 @@ struct NanoKnob : Knob { const int knobSize = std::min(w, h - BND_WIDGET_HEIGHT * 2) - ringSize; const int knobStartX = w / 2 - knobSize / 2; - const int knobStartY = BND_WIDGET_HEIGHT + ringSize; + const int knobStartY = ringSize; const int knobCenterX = knobStartX + knobSize / 2; const int knobCenterY = knobStartY + knobSize / 2; @@ -78,6 +78,18 @@ struct NanoKnob : Knob { nvgStrokeColor(args.vg, nvgRGBAf(testing.r, testing.g, testing.b, 0.1f)); nvgStroke(args.vg); + // line indicator + nvgStrokeWidth(args.vg, 2); + nvgSave(args.vg); + nvgTranslate(args.vg, knobCenterX, knobCenterY); + nvgRotate(args.vg, nvgDegToRad(45.0f) + normalizedValue * nvgDegToRad(270.0f)); + nvgBeginPath(args.vg); + nvgRoundedRect(args.vg, -2, knobSize / 2 - 9, 2, 6, 1); + nvgClosePath(args.vg); + nvgFillColor(args.vg, nvgRGBf(1.0f, 1.0f, 1.0f)); + nvgFill(args.vg); + nvgRestore(args.vg); + // adjusted from VCVRack's LightWidget.cpp if (const float halo = settings::haloBrightness) { @@ -95,7 +107,7 @@ struct NanoKnob : Knob { } // bottom label (value) - bndIconLabelValue(args.vg, 0, BND_WIDGET_HEIGHT + knobSize + ringSize, w, BND_WIDGET_HEIGHT, -1, + bndIconLabelValue(args.vg, 0, knobSize + ringSize, w, BND_WIDGET_HEIGHT, -1, testing, BND_CENTER, BND_LABEL_FONT_SIZE, displayString.c_str(), nullptr); @@ -113,15 +125,10 @@ struct NanoKnob : Knob { const int knobSize = std::min(w, h - BND_WIDGET_HEIGHT * 2) - ringSize; const int knobStartX = w / 2 - knobSize / 2; - const int knobStartY = BND_WIDGET_HEIGHT + ringSize; + const int knobStartY = ringSize; const int knobCenterX = knobStartX + knobSize / 2; const int knobCenterY = knobStartY + knobSize / 2; - // top label (name) - bndIconLabelValue(args.vg, 0, 0, w, BND_WIDGET_HEIGHT, -1, - SCHEME_WHITE, BND_CENTER, - BND_LABEL_FONT_SIZE, displayLabel.c_str(), nullptr); - // knob NVGcolor shade_top; NVGcolor shade_down; @@ -164,18 +171,6 @@ struct NanoKnob : Knob { nvgLineCap(args.vg, NVG_ROUND); - // line indicator - nvgStrokeWidth(args.vg, 2); - nvgSave(args.vg); - nvgTranslate(args.vg, knobCenterX, knobCenterY); - nvgRotate(args.vg, nvgDegToRad(45.0f) + normalizedValue * nvgDegToRad(270.0f)); - nvgBeginPath(args.vg); - nvgRoundedRect(args.vg, -2, knobSize / 2 - 9, 2, 6, 1); - nvgClosePath(args.vg); - nvgFillColor(args.vg, nvgRGBf(1.0f, 1.0f, 1.0f)); - nvgFill(args.vg); - nvgRestore(args.vg); - // outer ring background nvgBeginPath(args.vg); nvgArc(args.vg, @@ -189,6 +184,11 @@ struct NanoKnob : Knob { nvgStrokeColor(args.vg, nvgRGBf(0.5f, 0.5f, 0.5f)); nvgStroke(args.vg); + // bottom label (name) + bndIconLabelValue(args.vg, 0, knobStartY + knobSize + BND_WIDGET_HEIGHT * 0.75f, w, BND_WIDGET_HEIGHT, -1, + SCHEME_WHITE, BND_CENTER, + BND_LABEL_FONT_SIZE, displayLabel.c_str(), nullptr); + Knob::draw(args); } @@ -202,6 +202,36 @@ struct NanoKnob : Knob { } }; +/* + * Find the highest absolute and normalized value within a float array. + */ +static inline +float d_findMaxNormalizedFloat(const float floats[], const std::size_t count) +{ + DISTRHO_SAFE_ASSERT_RETURN(floats != nullptr, 0.0f); + DISTRHO_SAFE_ASSERT_RETURN(count > 0, 0.0f); + + static const float kEmptyFloats[8192] = {}; + + if (count <= 8192 && std::memcmp(floats, kEmptyFloats, count) == 0) + return 0.0f; + + float tmp, maxf2 = std::abs(floats[0]); + + for (std::size_t i=1; i maxf2) + maxf2 = tmp; + } + + if (maxf2 > 1.0f) + maxf2 = 1.0f; + + return maxf2; +} + template struct HostAudio : Module { CardinalPluginContext* const pcontext; @@ -214,6 +244,8 @@ struct HostAudio : Module { // for rack core audio module compatibility dsp::RCFilter dcFilters[numIO]; bool dcFilterEnabled = (numIO == 2); + float gainMeterL = 0.0f; + float gainMeterR = 0.0f; HostAudio() : pcontext(static_cast(APP)), @@ -250,6 +282,7 @@ struct HostAudio : Module { const float* const* const dataIns = pcontext->dataIns; float** const dataOuts = pcontext->dataOuts; + const int blockFrames = pcontext->engine->getBlockFrames(); const int64_t blockFrame = pcontext->engine->getBlockFrame(); if (lastBlockFrame != blockFrame) @@ -259,7 +292,7 @@ struct HostAudio : Module { } const int k = dataFrame++; - DISTRHO_SAFE_ASSERT_RETURN(k < pcontext->engine->getBlockFrames(),); + DISTRHO_SAFE_ASSERT_INT2_RETURN(k < blockFrames, k, blockFrames,); const float gain = numParams != 0 ? std::pow(params[0].getValue(), 2.f) : 1.0f; @@ -285,8 +318,23 @@ struct HostAudio : Module { dataOuts[i][k] += clamp(v * gain, -1.0f, 1.0f); } - if (numInputs == 2 && ! inputs[1].isConnected()) - dataOuts[1][k] += dataOuts[0][k]; + if (numInputs == 2) + { + const bool connected = inputs[1].isConnected(); + + if (! connected) + dataOuts[1][k] += dataOuts[0][k]; + + if (dataFrame == blockFrames) + { + gainMeterL = std::max(gainMeterL, d_findMaxNormalizedFloat(dataOuts[0], blockFrames)); + + if (connected) + gainMeterR = std::max(gainMeterR, d_findMaxNormalizedFloat(dataOuts[1], blockFrames)); + else + gainMeterR = gainMeterL; + } + } } json_t* dataToJson() override @@ -307,6 +355,120 @@ struct HostAudio : Module { } }; +template +struct NanoMeter : Widget { + HostAudio* const module; + + NanoMeter(HostAudio* const m) + : module(m) + { + box.size = Vec(100, 100); + } + + void drawLayer(const DrawArgs& args, int layer) override + { + if (layer != 1) + return; + + const float usableHeight = box.size.y - 10.0f; + + // draw background + nvgBeginPath(args.vg); + nvgRect(args.vg, + 0, + 0, + box.size.x, + usableHeight); + nvgFillColor(args.vg, nvgRGB(26, 26, 26)); + nvgFill(args.vg); + + nvgFillColor(args.vg, nvgRGBAf(0.76f, 0.11f, 0.22f, 0.5f)); + nvgStrokeColor(args.vg, nvgRGBf(0.76f, 0.11f, 0.22f)); + + if (module != nullptr) + { + float gainMeterL = 0.0f; + float gainMeterR = 0.0f; + std::swap(gainMeterL, module->gainMeterL); + std::swap(gainMeterR, module->gainMeterR); + + const float heightL = sqrtf(gainMeterL) * usableHeight; + nvgBeginPath(args.vg); + nvgRect(args.vg, 0.0f, usableHeight - heightL, box.size.x * 0.5f - 1.0f, heightL); + nvgFill(args.vg); + nvgStroke(args.vg); + + const float heightR = sqrtf(gainMeterR) * usableHeight; + nvgBeginPath(args.vg); + nvgRect(args.vg, box.size.x * 0.5f + 1.0f, usableHeight - heightR, box.size.x * 0.5f - 2.0f, heightR); + nvgFill(args.vg); + nvgStroke(args.vg); + } + + nvgLineCap(args.vg, NVG_ROUND); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, 0.0f, usableHeight + 2.0f); + nvgLineTo(args.vg, box.size.x * 0.5f - 11.0f, usableHeight + 2.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, 0.0f, usableHeight + 4.0f); + nvgLineTo(args.vg, box.size.x * 0.5f - 16.0f, usableHeight + 4.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, 0.0f, usableHeight + 6.0f); + nvgLineTo(args.vg, box.size.x * 0.5f - 19.0f, usableHeight + 6.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, 0.0f, usableHeight + 8.0f); + nvgLineTo(args.vg, box.size.x * 0.5f - 22.0f, usableHeight + 8.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, 0.0f, usableHeight + 10.0f); + nvgLineTo(args.vg, box.size.x * 0.5f - 24.0f, usableHeight + 10.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, 0.0f, usableHeight + 12.0f); + nvgLineTo(args.vg, box.size.x * 0.5f - 26.0f, usableHeight + 12.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, box.size.x * 0.5f + 10.0f, usableHeight + 2.0f); + nvgLineTo(args.vg, box.size.x - 1.0f, usableHeight + 2.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, box.size.x * 0.5f + 15.0f, usableHeight + 4.0f); + nvgLineTo(args.vg, box.size.x - 1.0f, usableHeight + 4.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, box.size.x * 0.5f + 18.0f, usableHeight + 6.0f); + nvgLineTo(args.vg, box.size.x - 1.0f, usableHeight + 6.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, box.size.x * 0.5f + 20.0f, usableHeight + 8.0f); + nvgLineTo(args.vg, box.size.x - 1.0f, usableHeight + 8.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, box.size.x * 0.5f + 22.0f, usableHeight + 10.0f); + nvgLineTo(args.vg, box.size.x - 1.0f, usableHeight + 10.0f); + nvgStroke(args.vg); + + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, box.size.x * 0.5f + 24.0f, usableHeight + 12.0f); + nvgLineTo(args.vg, box.size.x - 1.0f, usableHeight + 12.0f); + nvgStroke(args.vg); + } +}; + template struct HostAudioWidget : ModuleWidget { static constexpr const float startX_In = 14.0f; @@ -336,7 +498,12 @@ struct HostAudioWidget : ModuleWidget { if (numIO == 2) { - addParam(createParamCentered(Vec(middleX, 290.0f), m, 0)); + addParam(createParamCentered(Vec(middleX, 310.0f), m, 0)); + + NanoMeter* const meter = new NanoMeter(m); + meter->box.pos = Vec(startX_In + padding - 4.0f, startY + padding * 2); + meter->box.size = Vec(padding * 2.0f - 4.0f, 136.0f); + addChild(meter); } }