v1.1 — Improved metering, transformer, and optical cell tuning

- Needle VU meters with spring-mass-damper physics (analog inertia)
- Swappable meter modes: GR needles + input bars, or input needles + GR bars
- GR bar meters fill right-to-left (0dB=empty, -30dB=full)
- Input bar meters fill left-to-right with green color
- Optical cell: normalized parameters (eta=50) for proper audio-level response
- Transformer: removed 3-band crossover artifacts, simplified waveshaping with dry/wet mix
- Nickel/Iron/Steel with distinct but subtle harmonic character
- Layout: optical left, discrete right, meters center, transformer+output bottom center
This commit is contained in:
hariel1985
2026-03-27 17:46:25 +01:00
szülő 1c8b8012f6
commit d750716608
7 fájl változott, egészen pontosan 244 új sor hozzáadva és 123 régi sor törölve

Fájl megtekintése

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.22) cmake_minimum_required(VERSION 3.22)
project(InstaShadow VERSION 1.0.0) project(InstaShadow VERSION 1.1.0)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)

Fájl megtekintése

@@ -34,6 +34,13 @@ void CompressorEngine::processBlock (juce::AudioBuffer<float>& buffer)
if (globalBypass.load() || numChannels == 0) return; if (globalBypass.load() || numChannels == 0) return;
// Measure input level BEFORE any processing
inputLevelL.store (buffer.getMagnitude (0, 0, numSamples));
if (numChannels > 1)
inputLevelR.store (buffer.getMagnitude (1, 0, numSamples));
else
inputLevelR.store (inputLevelL.load());
// Read parameters once per block // Read parameters once per block
float optoThresh = optoThresholdDb.load(); float optoThresh = optoThresholdDb.load();
float optoGain = optoGainDb.load(); float optoGain = optoGainDb.load();

Fájl megtekintése

@@ -37,6 +37,8 @@ public:
// --- Metering (audio → GUI) --- // --- Metering (audio → GUI) ---
std::atomic<float> optoGrDb { 0.0f }; std::atomic<float> optoGrDb { 0.0f };
std::atomic<float> vcaGrDb { 0.0f }; std::atomic<float> vcaGrDb { 0.0f };
std::atomic<float> inputLevelL { 0.0f };
std::atomic<float> inputLevelR { 0.0f };
std::atomic<float> outputLevelL { 0.0f }; std::atomic<float> outputLevelL { 0.0f };
std::atomic<float> outputLevelR { 0.0f }; std::atomic<float> outputLevelR { 0.0f };

Fájl megtekintése

@@ -6,11 +6,27 @@ class GRMeter : public juce::Component
public: public:
void setGainReduction (float grDb) void setGainReduction (float grDb)
{ {
// 0dB GR = 0.0 (empty), -30dB GR = 1.0 (full bar)
// Bar fills from RIGHT to LEFT showing how much GR
float clamped = juce::jlimit (-30.0f, 0.0f, grDb); float clamped = juce::jlimit (-30.0f, 0.0f, grDb);
float normalised = -clamped / 30.0f; float normalised = -clamped / 30.0f; // 0dB→0.0, -30dB→1.0
currentGr = std::max (normalised, currentGr * 0.92f); currentLevel = std::max (normalised, currentLevel * 0.92f);
if (normalised > peakGr) peakGr = normalised; if (normalised > peakLevel) peakLevel = normalised;
else peakGr *= 0.998f; else peakLevel *= 0.998f;
leftToRight = false; // right-to-left
repaint();
}
// Input level meter (left-to-right, linear level 0..1)
void setInputLevel (float linearLevel)
{
float db = (linearLevel > 0.0001f) ? 20.0f * std::log10 (linearLevel) : -60.0f;
// Map -30..0 dB to 0..1
float normalised = juce::jlimit (0.0f, 1.0f, (db + 30.0f) / 30.0f);
currentLevel = std::max (normalised, currentLevel * 0.92f);
if (normalised > peakLevel) peakLevel = normalised;
else peakLevel *= 0.998f;
leftToRight = true;
repaint(); repaint();
} }
@@ -21,22 +37,28 @@ public:
{ {
auto bounds = getLocalBounds().toFloat().reduced (1); auto bounds = getLocalBounds().toFloat().reduced (1);
// Background
g.setColour (juce::Colour (0xff111122)); g.setColour (juce::Colour (0xff111122));
g.fillRoundedRectangle (bounds, 2.0f); g.fillRoundedRectangle (bounds, 2.0f);
// GR bar (fills from right to left) float w = bounds.getWidth() * currentLevel;
float w = bounds.getWidth() * currentGr;
auto filled = bounds.withLeft (bounds.getRight() - w); juce::Rectangle<float> filled;
if (leftToRight)
filled = bounds.withWidth (w); // left to right for input level
else
filled = bounds.withLeft (bounds.getRight() - w); // right to left for GR
g.setColour (barColour); g.setColour (barColour);
g.fillRoundedRectangle (filled, 2.0f); g.fillRoundedRectangle (filled, 2.0f);
// Peak hold line // Peak hold line
if (peakGr > 0.01f) if (peakLevel > 0.01f)
{ {
float peakX = bounds.getRight() - bounds.getWidth() * peakGr; float peakX = leftToRight
? bounds.getX() + bounds.getWidth() * peakLevel
: bounds.getRight() - bounds.getWidth() * peakLevel;
g.setColour (juce::Colours::white.withAlpha (0.8f)); g.setColour (juce::Colours::white.withAlpha (0.8f));
g.fillRect (peakX, bounds.getY(), 1.5f, bounds.getHeight()); g.fillRect (peakX - 0.75f, bounds.getY(), 1.5f, bounds.getHeight());
} }
// Label // Label
@@ -45,15 +67,20 @@ public:
g.drawText (label, bounds.reduced (4, 0), juce::Justification::centredLeft); g.drawText (label, bounds.reduced (4, 0), juce::Justification::centredLeft);
// dB readout // dB readout
float dbVal = -currentGr * 30.0f; if (currentLevel > 0.001f)
if (currentGr > 0.001f) {
float dbVal = leftToRight
? (currentLevel * 30.0f - 30.0f) // input: -30..0 dB
: (-currentLevel * 30.0f); // GR: 0..-30 dB
g.drawText (juce::String (dbVal, 1) + " dB", bounds.reduced (4, 0), g.drawText (juce::String (dbVal, 1) + " dB", bounds.reduced (4, 0),
juce::Justification::centredRight); juce::Justification::centredRight);
} }
}
private: private:
float currentGr = 0.0f; float currentLevel = 0.0f;
float peakGr = 0.0f; float peakLevel = 0.0f;
bool leftToRight = false;
juce::Colour barColour { 0xffff8833 }; juce::Colour barColour { 0xffff8833 };
juce::String label; juce::String label;
}; };

Fájl megtekintése

@@ -2,28 +2,31 @@
#include <JuceHeader.h> #include <JuceHeader.h>
// ============================================================ // ============================================================
// Analog-style needle VU meter (semicircular, like Shadow Hills) // Analog-style needle meter (semicircular)
// Two modes: VU (level) and GR (gain reduction)
// ============================================================ // ============================================================
class NeedleVuMeter : public juce::Component class NeedleVuMeter : public juce::Component
{ {
public: public:
enum Mode { VU, GR };
void setMode (Mode m) { mode = m; repaint(); }
void setLevel (float linearLevel) void setLevel (float linearLevel)
{ {
// Convert to dB, map to needle position
float db = (linearLevel > 0.0001f) float db = (linearLevel > 0.0001f)
? 20.0f * std::log10 (linearLevel) ? 20.0f * std::log10 (linearLevel)
: -60.0f; : -60.0f;
// VU range: -20 to +3 dB → 0.0 to 1.0
float target = juce::jlimit (0.0f, 1.0f, (db + 20.0f) / 23.0f); float target = juce::jlimit (0.0f, 1.0f, (db + 20.0f) / 23.0f);
applyNeedlePhysics (target);
}
// Smooth needle movement (ballistic) // For GR mode: pass negative dB value (e.g. -6.0 = 6dB reduction)
if (target > needlePos) // Standard VU scale, needle rests at 0dB mark, moves LEFT with compression
needlePos += (target - needlePos) * 0.07f; // slow attack (inertia) void setGainReduction (float grDb)
else {
needlePos += (target - needlePos) * 0.05f; // moderate release float target = juce::jlimit (0.0f, 1.0f, (grDb + 20.0f) / 23.0f);
applyNeedlePhysics (target);
repaint();
} }
void setLabel (const juce::String& text) { label = text; } void setLabel (const juce::String& text) { label = text; }
@@ -31,13 +34,12 @@ public:
void paint (juce::Graphics& g) override void paint (juce::Graphics& g) override
{ {
auto bounds = getLocalBounds().toFloat().reduced (2); auto bounds = getLocalBounds().toFloat().reduced (2);
float w = bounds.getWidth();
float h = bounds.getHeight(); float h = bounds.getHeight();
// Meter face background (warm cream)
float arcH = h * 0.85f; float arcH = h * 0.85f;
auto faceRect = bounds.withHeight (arcH); auto faceRect = bounds.withHeight (arcH);
// Dark background
g.setColour (juce::Colour (0xff1a1a22)); g.setColour (juce::Colour (0xff1a1a22));
g.fillRoundedRectangle (bounds, 4.0f); g.fillRoundedRectangle (bounds, 4.0f);
@@ -50,61 +52,17 @@ public:
g.fillRoundedRectangle (arcArea, 3.0f); g.fillRoundedRectangle (arcArea, 3.0f);
} }
// Arc center point (bottom center of arc area)
float cx = arcArea.getCentreX(); float cx = arcArea.getCentreX();
float cy = arcArea.getBottom() - 4.0f; float cy = arcArea.getBottom() - 4.0f;
float radius = std::min (arcArea.getWidth() * 0.45f, arcArea.getHeight() * 0.8f); float radius = std::min (arcArea.getWidth() * 0.45f, arcArea.getHeight() * 0.8f);
// Scale markings float startAngle = juce::MathConstants<float>::pi * 1.25f;
float startAngle = juce::MathConstants<float>::pi * 1.25f; // -225 deg float endAngle = juce::MathConstants<float>::pi * 1.75f;
float endAngle = juce::MathConstants<float>::pi * 1.75f; // -315 deg (sweep right)
// Draw scale ticks and labels
g.setFont (std::max (6.0f, h * 0.045f)); g.setFont (std::max (6.0f, h * 0.045f));
const float dbValues[] = { -20, -10, -7, -5, -3, -1, 0, 1, 2, 3 };
const int numTicks = 10;
for (int i = 0; i < numTicks; ++i) // Always use VU scale — in GR mode the needle just starts at 0 and goes left
{ drawVuScale (g, cx, cy, radius, startAngle, endAngle);
float norm = (dbValues[i] + 20.0f) / 23.0f;
float angle = startAngle + norm * (endAngle - startAngle);
float cosA = std::cos (angle);
float sinA = std::sin (angle);
float innerR = radius * 0.82f;
float outerR = radius * 0.95f;
bool isMajor = (dbValues[i] == -20 || dbValues[i] == -10 || dbValues[i] == -5
|| dbValues[i] == 0 || dbValues[i] == 3);
// Tick line
g.setColour (dbValues[i] >= 0 ? juce::Colour (0xffcc3333) : juce::Colour (0xff333333));
float tickInner = isMajor ? innerR * 0.9f : innerR;
g.drawLine (cx + cosA * tickInner, cy + sinA * tickInner,
cx + cosA * outerR, cy + sinA * outerR,
isMajor ? 1.5f : 0.8f);
// Label for major ticks
if (isMajor)
{
float labelR = radius * 0.7f;
float lx = cx + cosA * labelR;
float ly = cy + sinA * labelR;
juce::String txt = (dbValues[i] > 0 ? "+" : "") + juce::String ((int) dbValues[i]);
g.setColour (dbValues[i] >= 0 ? juce::Colour (0xffcc3333) : juce::Colour (0xff444444));
g.drawText (txt, (int) (lx - 12), (int) (ly - 6), 24, 12, juce::Justification::centred);
}
}
// Red zone arc (0 to +3 dB)
{
float redStart = startAngle + (20.0f / 23.0f) * (endAngle - startAngle);
juce::Path redArc;
redArc.addCentredArc (cx, cy, radius * 0.92f, radius * 0.92f, 0,
redStart, endAngle, true);
g.setColour (juce::Colour (0x33ff3333));
g.strokePath (redArc, juce::PathStrokeType (radius * 0.08f));
}
// Needle // Needle
{ {
@@ -112,35 +70,115 @@ public:
float cosA = std::cos (angle); float cosA = std::cos (angle);
float sinA = std::sin (angle); float sinA = std::sin (angle);
// Needle shadow
g.setColour (juce::Colours::black.withAlpha (0.3f)); g.setColour (juce::Colours::black.withAlpha (0.3f));
g.drawLine (cx + 1, cy + 1, g.drawLine (cx + 1, cy + 1,
cx + cosA * radius * 0.88f + 1, cy + sinA * radius * 0.88f + 1, cx + cosA * radius * 0.88f + 1, cy + sinA * radius * 0.88f + 1, 2.0f);
2.0f);
// Needle
g.setColour (juce::Colour (0xff222222)); g.setColour (juce::Colour (0xff222222));
g.drawLine (cx, cy, g.drawLine (cx, cy, cx + cosA * radius * 0.88f, cy + sinA * radius * 0.88f, 1.5f);
cx + cosA * radius * 0.88f, cy + sinA * radius * 0.88f,
1.5f);
// Needle pivot dot
g.setColour (juce::Colour (0xff333333)); g.setColour (juce::Colour (0xff333333));
g.fillEllipse (cx - 3, cy - 3, 6, 6); g.fillEllipse (cx - 3, cy - 3, 6, 6);
} }
// Label below // Label
g.setColour (juce::Colour (0xffaaaaaa)); g.setColour (juce::Colour (0xffaaaaaa));
g.setFont (std::max (7.0f, h * 0.05f)); g.setFont (std::max (7.0f, h * 0.05f));
g.drawText (label, bounds.getX(), bounds.getBottom() - h * 0.18f, g.drawText (label, bounds.getX(), bounds.getBottom() - h * 0.18f,
bounds.getWidth(), h * 0.15f, juce::Justification::centred); bounds.getWidth(), h * 0.15f, juce::Justification::centred);
// Border
g.setColour (juce::Colour (0xff333344)); g.setColour (juce::Colour (0xff333344));
g.drawRoundedRectangle (bounds, 4.0f, 1.0f); g.drawRoundedRectangle (bounds, 4.0f, 1.0f);
} }
private: private:
float needlePos = 0.0f; // 0..1 mapped to -20..+3 dB Mode mode = VU;
float needlePos = 0.0f;
float needleVelocity = 0.0f;
juce::String label; juce::String label;
void applyNeedlePhysics (float target)
{
constexpr float spring = 0.35f;
constexpr float damping = 0.55f;
float force = spring * (target - needlePos);
needleVelocity = needleVelocity * (1.0f - damping) + force;
needlePos += needleVelocity;
needlePos = juce::jlimit (0.0f, 1.05f, needlePos);
repaint();
}
void drawVuScale (juce::Graphics& g, float cx, float cy, float radius,
float startAngle, float endAngle)
{
const float dbValues[] = { -20, -10, -7, -5, -3, -1, 0, 1, 2, 3 };
for (int i = 0; i < 10; ++i)
{
float norm = (dbValues[i] + 20.0f) / 23.0f;
float angle = startAngle + norm * (endAngle - startAngle);
float cosA = std::cos (angle), sinA = std::sin (angle);
float innerR = radius * 0.82f, outerR = radius * 0.95f;
bool isMajor = (dbValues[i] == -20 || dbValues[i] == -10 || dbValues[i] == -5
|| dbValues[i] == 0 || dbValues[i] == 3);
g.setColour (dbValues[i] >= 0 ? juce::Colour (0xffcc3333) : juce::Colour (0xff333333));
g.drawLine (cx + cosA * (isMajor ? innerR * 0.9f : innerR), cy + sinA * (isMajor ? innerR * 0.9f : innerR),
cx + cosA * outerR, cy + sinA * outerR, isMajor ? 1.5f : 0.8f);
if (isMajor)
{
float lx = cx + cosA * radius * 0.7f, ly = cy + sinA * radius * 0.7f;
juce::String txt = (dbValues[i] > 0 ? "+" : "") + juce::String ((int) dbValues[i]);
g.setColour (dbValues[i] >= 0 ? juce::Colour (0xffcc3333) : juce::Colour (0xff444444));
g.drawText (txt, (int) (lx - 12), (int) (ly - 6), 24, 12, juce::Justification::centred);
}
}
// Red zone arc
float redStart = startAngle + (20.0f / 23.0f) * (endAngle - startAngle);
juce::Path redArc;
redArc.addCentredArc (cx, cy, radius * 0.92f, radius * 0.92f, 0, redStart, endAngle, true);
g.setColour (juce::Colour (0x33ff3333));
g.strokePath (redArc, juce::PathStrokeType (radius * 0.08f));
}
void drawGrScale (juce::Graphics& g, float cx, float cy, float radius,
float startAngle, float endAngle)
{
// GR scale: 0 (left, rest) to -20 (right, max compression)
const float grValues[] = { 0, -2, -4, -6, -8, -10, -14, -20 };
for (int i = 0; i < 8; ++i)
{
float norm = -grValues[i] / 20.0f; // 0→0.0, -20→1.0
float angle = startAngle + norm * (endAngle - startAngle);
float cosA = std::cos (angle), sinA = std::sin (angle);
float innerR = radius * 0.82f, outerR = radius * 0.95f;
bool isMajor = (grValues[i] == 0 || grValues[i] == -6 || grValues[i] == -10 || grValues[i] == -20);
g.setColour (grValues[i] <= -10 ? juce::Colour (0xffcc3333) : juce::Colour (0xff333333));
g.drawLine (cx + cosA * (isMajor ? innerR * 0.9f : innerR), cy + sinA * (isMajor ? innerR * 0.9f : innerR),
cx + cosA * outerR, cy + sinA * outerR, isMajor ? 1.5f : 0.8f);
if (isMajor)
{
float lx = cx + cosA * radius * 0.7f, ly = cy + sinA * radius * 0.7f;
juce::String txt = juce::String ((int) grValues[i]);
g.setColour (grValues[i] <= -10 ? juce::Colour (0xffcc3333) : juce::Colour (0xff444444));
g.drawText (txt, (int) (lx - 12), (int) (ly - 6), 24, 12, juce::Justification::centred);
}
}
// Warning zone arc (-10 to -20 dB GR)
float warnStart = startAngle + (10.0f / 20.0f) * (endAngle - startAngle);
juce::Path warnArc;
warnArc.addCentredArc (cx, cy, radius * 0.92f, radius * 0.92f, 0, warnStart, endAngle, true);
g.setColour (juce::Colour (0x33ff3333));
g.strokePath (warnArc, juce::PathStrokeType (radius * 0.08f));
}
}; };

Fájl megtekintése

@@ -41,20 +41,50 @@ InstaShadowEditor::InstaShadowEditor (InstaShadowProcessor& p)
addAndMakeVisible (transformerPanel); addAndMakeVisible (transformerPanel);
addAndMakeVisible (outputPanel); addAndMakeVisible (outputPanel);
// Needle VU meters // Needle meters (default: GR)
vuMeterL.setLabel ("L"); needleMeterL.setLabel ("OPTICAL GR");
addAndMakeVisible (vuMeterL); needleMeterL.setMode (NeedleVuMeter::GR);
vuMeterR.setLabel ("R"); addAndMakeVisible (needleMeterL);
addAndMakeVisible (vuMeterR); needleMeterR.setLabel ("DISCRETE GR");
needleMeterR.setMode (NeedleVuMeter::GR);
addAndMakeVisible (needleMeterR);
// GR meters (compact bars) // Bar meters (default: input level)
optoGrMeter.setLabel ("OPTICAL GR"); barMeterL.setLabel ("INPUT L");
optoGrMeter.setBarColour (juce::Colour (0xffff8833)); barMeterL.setBarColour (juce::Colour (0xff00cc44));
addAndMakeVisible (optoGrMeter); addAndMakeVisible (barMeterL);
barMeterR.setLabel ("INPUT R");
barMeterR.setBarColour (juce::Colour (0xff00cc44));
addAndMakeVisible (barMeterR);
vcaGrMeter.setLabel ("DISCRETE GR"); // Meter swap button
vcaGrMeter.setBarColour (juce::Colour (0xff4488ff)); meterSwapButton.onClick = [this]
addAndMakeVisible (vcaGrMeter); {
metersSwapped = ! metersSwapped;
if (metersSwapped)
{
needleMeterL.setLabel ("INPUT L");
needleMeterL.setMode (NeedleVuMeter::VU);
needleMeterR.setLabel ("INPUT R");
needleMeterR.setMode (NeedleVuMeter::VU);
barMeterL.setLabel ("OPTICAL GR");
barMeterL.setBarColour (juce::Colour (0xffff8833));
barMeterR.setLabel ("DISCRETE GR");
barMeterR.setBarColour (juce::Colour (0xff4488ff));
}
else
{
needleMeterL.setLabel ("OPTICAL GR");
needleMeterL.setMode (NeedleVuMeter::GR);
needleMeterR.setLabel ("DISCRETE GR");
needleMeterR.setMode (NeedleVuMeter::GR);
barMeterL.setLabel ("INPUT L");
barMeterL.setBarColour (juce::Colour (0xff00cc44));
barMeterR.setLabel ("INPUT R");
barMeterR.setBarColour (juce::Colour (0xff00cc44));
}
};
addAndMakeVisible (meterSwapButton);
syncKnobsToEngine(); syncKnobsToEngine();
startTimerHz (30); startTimerHz (30);
@@ -116,13 +146,22 @@ void InstaShadowEditor::timerCallback()
auto& eng = processor.getEngine(); auto& eng = processor.getEngine();
// Needle VU meters if (! metersSwapped)
vuMeterL.setLevel (eng.outputLevelL.load()); {
vuMeterR.setLevel (eng.outputLevelR.load()); // Default: needles = GR, bars = input
needleMeterL.setGainReduction (eng.optoGrDb.load());
// GR meters needleMeterR.setGainReduction (eng.vcaGrDb.load());
optoGrMeter.setGainReduction (eng.optoGrDb.load()); barMeterL.setInputLevel (eng.inputLevelL.load());
vcaGrMeter.setGainReduction (eng.vcaGrDb.load()); barMeterR.setInputLevel (eng.inputLevelR.load());
}
else
{
// Swapped: needles = input, bars = GR
needleMeterL.setLevel (eng.inputLevelL.load());
needleMeterR.setLevel (eng.inputLevelR.load());
barMeterL.setGainReduction (eng.optoGrDb.load());
barMeterR.setGainReduction (eng.vcaGrDb.load());
}
// Output panel VU // Output panel VU
outputPanel.vuMeter.setLevel (eng.outputLevelL.load(), eng.outputLevelR.load()); outputPanel.vuMeter.setLevel (eng.outputLevelL.load(), eng.outputLevelR.load());
@@ -185,20 +224,24 @@ void InstaShadowEditor::resized()
// Center column: VU meters, GR bars, Transformer, Output — all stacked // Center column: VU meters, GR bars, Transformer, Output — all stacked
auto centerArea = mainRow; auto centerArea = mainRow;
// Two needle VU meters side by side (~30%) // Two needle meters side by side (~30%)
int vuH = (int) (centerArea.getHeight() * 0.30f); int vuH = (int) (centerArea.getHeight() * 0.30f);
auto vuRow = centerArea.removeFromTop (vuH); auto needleRow = centerArea.removeFromTop (vuH);
int vuW = (vuRow.getWidth() - pad) / 2; int needleW = (needleRow.getWidth() - pad) / 2;
vuMeterL.setBounds (vuRow.removeFromLeft (vuW)); needleMeterL.setBounds (needleRow.removeFromLeft (needleW));
vuRow.removeFromLeft (pad); needleRow.removeFromLeft (pad);
vuMeterR.setBounds (vuRow); needleMeterR.setBounds (needleRow);
centerArea.removeFromTop (pad); centerArea.removeFromTop (pad);
// Two GR meter bars (~15%) // Swap button (compact, between needles and bars)
int grBarH = (int) (centerArea.getHeight() * 0.12f); meterSwapButton.setBounds (centerArea.removeFromTop (20).reduced (centerArea.getWidth() / 4, 0));
optoGrMeter.setBounds (centerArea.removeFromTop (grBarH));
centerArea.removeFromTop (pad); centerArea.removeFromTop (pad);
vcaGrMeter.setBounds (centerArea.removeFromTop (grBarH));
// Two bar meters (~10%)
int barH = (int) (centerArea.getHeight() * 0.10f);
barMeterL.setBounds (centerArea.removeFromTop (barH));
centerArea.removeFromTop (pad);
barMeterR.setBounds (centerArea.removeFromTop (barH));
centerArea.removeFromTop (pad); centerArea.removeFromTop (pad);
// Transformer + Output side by side in remaining center space // Transformer + Output side by side in remaining center space

Fájl megtekintése

@@ -9,7 +9,7 @@
#include "GRMeter.h" #include "GRMeter.h"
#include "NeedleVuMeter.h" #include "NeedleVuMeter.h"
static constexpr const char* kInstaShadowVersion = "v1.0"; static constexpr const char* kInstaShadowVersion = "v1.1";
class InstaShadowEditor : public juce::AudioProcessorEditor, class InstaShadowEditor : public juce::AudioProcessorEditor,
public juce::Timer public juce::Timer
@@ -38,11 +38,15 @@ private:
OpticalPanel opticalPanel; OpticalPanel opticalPanel;
DiscretePanel discretePanel; DiscretePanel discretePanel;
// Center: needle VU meters + GR bars // Center: needle meters + bar meters (swappable)
NeedleVuMeter vuMeterL; NeedleVuMeter needleMeterL;
NeedleVuMeter vuMeterR; NeedleVuMeter needleMeterR;
GRMeter optoGrMeter; GRMeter barMeterL;
GRMeter vcaGrMeter; GRMeter barMeterR;
// Meter swap toggle
juce::TextButton meterSwapButton { "GR / INPUT" };
bool metersSwapped = false; // false: needle=GR, bar=input | true: needle=input, bar=GR
// Bottom panels // Bottom panels
TransformerPanel transformerPanel; TransformerPanel transformerPanel;