#pragma once #include class TwoOscAudioProcessor; // ===== Retro 70s LookAndFeel for rotary knobs ===== class RetroLookAndFeel : public juce::LookAndFeel_V4 { public: RetroLookAndFeel() { setColour (juce::Slider::textBoxTextColourId, juce::Colours::white.withAlpha (0.9f)); setColour (juce::Slider::textBoxOutlineColourId, juce::Colours::transparentBlack); } void drawRotarySlider (juce::Graphics& g, int x, int y, int w, int h, float sliderPosProportional, float /*rotaryStartAngle*/, float /*rotaryEndAngle*/, juce::Slider& /*slider*/) override { const float size = (float) juce::jmin (w, h); const float r = size * 0.5f; const juce::Point c { x + w * 0.5f, y + h * 0.5f }; const float bezelR = r * 0.98f; const float faceR = r * 0.78f; const float capR = r * 0.46f; // SW -> SE 270° clockwise sweep (min at bottom-left) const float start = juce::MathConstants::pi * 0.75f; // 135° const float sweep = juce::MathConstants::pi * 1.5f; // 270° const float ang = start + sliderPosProportional * sweep; g.setColour (juce::Colour::fromRGB (12,12,12)); g.fillEllipse (c.x - bezelR, c.y - bezelR, bezelR*2, bezelR*2); g.setColour (juce::Colours::white.withAlpha (0.06f)); g.drawEllipse (c.x - bezelR, c.y - bezelR, bezelR*2, bezelR*2, 1.2f); juce::ColourGradient faceGrad (juce::Colour::fromRGB (34,34,34), c.x, c.y - faceR, juce::Colour::fromRGB (22,22,22), c.x, c.y + faceR, false); g.setGradientFill (faceGrad); g.fillEllipse (c.x - faceR, c.y - faceR, faceR*2, faceR*2); g.setColour (juce::Colours::white.withAlpha (0.25f)); const int ticks = 9; for (int i = 0; i < ticks; ++i) { const float t = (float) i / (ticks - 1); const float aa = start + t * sweep; const float r1 = faceR * 0.92f, r2 = faceR * 1.04f; g.drawLine (c.x + r1*std::cos(aa), c.y + r1*std::sin(aa), c.x + r2*std::cos(aa), c.y + r2*std::sin(aa), (i % 2 == 0) ? 1.5f : 1.0f); } juce::ColourGradient capGrad (juce::Colour::fromRGB (210,210,210), c.x - capR*0.4f, c.y - capR*0.6f, juce::Colour::fromRGB (160,160,160), c.x + capR*0.6f, c.y + capR*0.6f, true); g.setGradientFill (capGrad); g.fillEllipse (c.x - capR, c.y - capR, capR*2, capR*2); g.setColour (juce::Colours::white.withAlpha (0.95f)); juce::Point tip { c.x + faceR*0.9f * std::cos (ang), c.y + faceR*0.9f * std::sin (ang) }; juce::Point inner { c.x + (capR+2.0f) * std::cos (ang), c.y + (capR+2.0f) * std::sin (ang) }; g.drawLine (inner.x, inner.y, tip.x, tip.y, juce::jlimit (1.2f, 2.0f, r * 0.06f)); } }; //============================================================================== class TwoOscAudioProcessorEditor : public juce::AudioProcessorEditor { public: explicit TwoOscAudioProcessorEditor (TwoOscAudioProcessor&); ~TwoOscAudioProcessorEditor() override; void paint (juce::Graphics&) override; void resized() override; private: TwoOscAudioProcessor& processor; // Header juce::Label title; // Preset header: display + button that opens flyout menu juce::Label presetDisplay; // shows "Category - Name" juce::TextButton browseBtn { "Browse…" }; // Controls juce::ComboBox oscA, oscB; juce::Slider mix, detune, cutoff, reso, envAmt; juce::Slider aA, dA, sA, rA, aF, dF, sF, rF; juce::Slider masterGain; // NEW: LFO controls juce::Slider lfoRate, lfoDepth; // lfoDepth mapped to lfoToCut (octaves) // Captions above knobs juce::Label lOscA, lOscB; juce::Label lMix, lDetune, lCutoff, lReso, lEnv; juce::Label lAA, lDA, lSA, lRA, lAF, lDF, lSF, lRF, lMaster; juce::Label lLfoRate, lLfoDepth; using CA = juce::AudioProcessorValueTreeState::ComboBoxAttachment; using SA = juce::AudioProcessorValueTreeState::SliderAttachment; std::unique_ptr aOscA, aOscB; std::unique_ptr aMix, aDetune, aCutoff, aReso, aEnv; std::unique_ptr aAA, aDA, aSA, aRA, aAF, aDF, aSF, aRF; std::unique_ptr aMaster; // NEW: attachments for LFO std::unique_ptr aLfoRate, aLfoDepth; RetroLookAndFeel retroLNF; // Helpers void styleKnob (juce::Slider& s); void styleLabel (juce::Label& l); void addLabeled (juce::Component& c, juce::Label& label, const juce::String& text); void showPresetPopup(); void updatePresetDisplay(); // Uniform sizing / spacing (smaller UI) static constexpr int knobSize = 60; // was 72 static constexpr int headerH = 48; // was 56 static constexpr int captionH = 16; static constexpr int gap = 10; // was 12 // Bounds of the framed "preset information screen" (drawn in paint) juce::Rectangle presetBarBounds; // Background group rectangles (drawn in paint, set in resized) juce::Rectangle groupCutResBounds; // Cutoff + Reso juce::Rectangle groupMixDetBounds; // Mix + Detune juce::Rectangle groupAmpBounds; // Amp A/D/S/R juce::Rectangle groupLfoBounds; // LFO Rate + Depth JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TwoOscAudioProcessorEditor) };