diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 6b56093..59aee2b 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -9,7 +9,7 @@ static const char* trackNames[] = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "R static const char* noteNames[] = {"C4","C#4","D4","D#4","E4","F4","F#4","G4","G#4","A4"}; static constexpr int ROW_H = 52; -static constexpr int TOP = 80; +static constexpr int TOP = 82; static constexpr int COL_LBL = 8; static constexpr int COL_KNOB = 44; static constexpr int KNOB_W = 44; @@ -22,7 +22,7 @@ static constexpr int COL_REVERB = COL_DELAY + KNOB_W + KNOB_GAP; static constexpr int COL_RINGMOD = COL_REVERB + KNOB_W + KNOB_GAP; static constexpr int COL_DESTR = COL_RINGMOD + KNOB_W + KNOB_GAP; static constexpr int COL_SHUFFLE = COL_DESTR + KNOB_W + KNOB_GAP; -static constexpr int TOTAL_W = COL_SHUFFLE + KNOB_W + 8; +static constexpr int TOTAL_W = COL_SHUFFLE + KNOB_W; // Header knob x positions (compact layout) static constexpr int HDR_KNOB = 36; @@ -60,30 +60,13 @@ void TrommelkisteLookAndFeel::drawRotarySlider(Graphics& g, int x, int y, int w, // Main body — vertical gradient (light top, dark bottom) for 3D cylinder feel { - ColourGradient grad(Colour(0xFF444444), cx, cy - radius, - Colour(0xFF2C2C2C), cx, cy + radius, true); - grad.addColour(0.35, Colour(0xFF3D3D3D)); + ColourGradient grad(Colour(0xFF323232), cx, cy - radius, + Colour(0xFF1C1C1C), cx, cy + radius, true); + grad.addColour(0.10, Colour(0x883D3D3D)); g.setGradientFill(grad); g.fillEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); } - // Top highlight ring (specular edge) - { - Path ring; - ring.addCentredArc(cx, cy, radius - 0.5f, radius - 0.5f, 0.0f, 0.0f, MathConstants::pi, true); - g.setColour(Colour(0x1EFFFFFF)); - g.strokePath(ring, PathStrokeType(1.5f, PathStrokeType::curved, PathStrokeType::rounded)); - } - - // Bottom dark ring - { - Path ring; - ring.addCentredArc(cx, cy, radius - 0.5f, radius - 0.5f, 0.0f, - MathConstants::pi, MathConstants::pi * 2.0f, true); - g.setColour(Colour(0x1E000000)); - g.strokePath(ring, PathStrokeType(1.5f, PathStrokeType::curved, PathStrokeType::rounded)); - } - // Subtle specular highlight blob (top-left) { ColourGradient spec(Colour(0x18FFFFFF), cx - radius * 0.35f, cy - radius * 0.5f, @@ -207,7 +190,7 @@ TrommelkisteEditor::TrommelkisteEditor(TrommelkisteProcessor& p) setLookAndFeel(&lnf); lnf.fontScale = 1.5f; uiScale = 1.5f; - setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H + 12)); + setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H)); setResizable(false, false); setWantsKeyboardFocus(false); @@ -447,7 +430,7 @@ void TrommelkisteEditor::scaleChanged() { uiScale = scales[selId - 1]; lnf.fontScale = uiScale; - setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H + 12)); + setSize(scaled(TOTAL_W), scaled(TOP + NUM_TRACKS * ROW_H)); } } @@ -479,8 +462,27 @@ void TrommelkisteEditor::paint(Graphics& g) g.setFont(fontBold(16.0f, uiScale)); g.drawText("TROMMELKISTE", scaled(10), scaled(4), scaled(400), scaled(20), Justification::centredLeft); + // VU meter + { + const float level = proc.outputLevel.load(std::memory_order_relaxed); + const float meterX = 10.0f; + const float meterY = 30.0f; + const float meterW = 134.0f; + const float meterH = 6.0f; + + // Background + g.setColour(Colour(0xFF111111)); + g.fillRect(scaled((int) meterX), scaled((int) meterY), scaled((int) meterW), scaled((int) meterH)); + + // Level bar + float norm = jlimit(0.0f, 1.0f, level * 4.0f); + float barW = meterW * norm; + g.setColour(lnf.accent); + g.fillRect(scaled((int) meterX), scaled((int) meterY), scaled((int) barW), scaled((int) meterH)); + } + g.setColour(lnf.accent.withAlpha(0.3f)); - g.fillRect(scaled(10), scaled(56), scaled(getWidth() - 20), scaled(1)); + g.fillRect(scaled(10), scaled(60), getWidth() - scaled(15), scaled(1)); g.setColour(Colours::white.withAlpha(0.9f)); g.setFont(font(8.0f, uiScale)); @@ -504,19 +506,19 @@ void TrommelkisteEditor::paint(Graphics& g) g.setColour(Colours::white.withAlpha(0.9f)); g.setFont(font(9.0f, uiScale)); - g.drawText("LVL", scaled(COL_KNOB), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("LEN", scaled(COL_KNOB + 1*(KNOB_W + KNOB_GAP)), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("VEL", scaled(COL_KNOB + 2*(KNOB_W + KNOB_GAP)), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("PIT", scaled(COL_KNOB + 3*(KNOB_W + KNOB_GAP)), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("TONE", scaled(COL_KNOB + 4*(KNOB_W + KNOB_GAP)), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("PAN", scaled(COL_KNOB + 5*(KNOB_W + KNOB_GAP)), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("DECAY", scaled(COL_DECAY), scaled(60), scaled(68), scaled(12), Justification::centred); - g.drawText("SAMPLE", scaled(COL_FILE), scaled(60), scaled(SAMPLE_W), scaled(12), Justification::centred); - g.drawText("DELAY", scaled(COL_DELAY), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("REVERB", scaled(COL_REVERB), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("RING", scaled(COL_RINGMOD), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("DESTR", scaled(COL_DESTR), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); - g.drawText("SHFL", scaled(COL_SHUFFLE), scaled(60), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("LVL", scaled(COL_KNOB), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("LEN", scaled(COL_KNOB + 1*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("VEL", scaled(COL_KNOB + 2*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("PIT", scaled(COL_KNOB + 3*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("TONE", scaled(COL_KNOB + 4*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("PAN", scaled(COL_KNOB + 5*(KNOB_W + KNOB_GAP)), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("DECAY", scaled(COL_DECAY), scaled(65), scaled(68), scaled(12), Justification::centred); + g.drawText("SAMPLE", scaled(COL_FILE), scaled(65), scaled(SAMPLE_W), scaled(12), Justification::centred); + g.drawText("DELAY", scaled(COL_DELAY), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("REVERB", scaled(COL_REVERB), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("RING", scaled(COL_RINGMOD), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("DESTR", scaled(COL_DESTR), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); + g.drawText("SHFL", scaled(COL_SHUFFLE), scaled(65), scaled(KNOB_W), scaled(12), Justification::centred); const double now = juce::Time::getMillisecondCounterHiRes() / 1000.0; for (int i = 0; i < NUM_TRACKS; ++i) @@ -551,11 +553,11 @@ void TrommelkisteEditor::paint(Graphics& g) g.setColour(Colours::white); g.setFont(fontBold(12.0f, uiScale)); - g.drawText(trackNames[i], scaled(COL_LBL), scaled(ry + 6), scaled(32), scaled(16), Justification::centredLeft); + g.drawText(trackNames[i], scaled(COL_LBL), scaled(ry + 14), scaled(32), scaled(16), Justification::centredLeft); g.setColour(Colour(0xFF666666)); g.setFont(font(9.0f, uiScale)); - g.drawText(noteNames[i], scaled(COL_LBL), scaled(ry + 24), scaled(32), scaled(14), Justification::centredLeft); + g.drawText(noteNames[i], scaled(COL_LBL), scaled(ry + 28), scaled(32), scaled(14), Justification::centredLeft); const double elapsed = now - trackActivatedTime[i]; if (elapsed < 0.4) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 2347c98..4ab5bcc 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -341,6 +341,17 @@ void TrommelkisteProcessor::processBlock(juce::AudioBuffer& buffer, juce: if (outR) outR[s] = tmpR; } + + float peak = 0.0f; + for (int s = 0; s < numSamples; ++s) + { + float a = std::abs(outL[s]); + if (outR) + a = jmax(a, std::abs(outR[s])); + if (a > peak) + peak = a; + } + outputLevel.store(peak, std::memory_order_relaxed); } int TrommelkisteProcessor::findFreeVoice(int trackIndex) diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 6abc340..f39c845 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -52,6 +52,7 @@ public: Track tracks[NUM_TRACKS]; std::atomic trackActive[NUM_TRACKS] = {}; + std::atomic outputLevel { 0.0f }; juce::AudioProcessorValueTreeState apvts; // Sample library diff --git a/trommelkisteshot.png b/trommelkisteshot.png index ca0e12f..bd4b769 100644 Binary files a/trommelkisteshot.png and b/trommelkisteshot.png differ