Files
NeuralSynthEd/PluginEditor.h
2025-10-21 17:22:44 +01:00

140 lines
5.5 KiB
C++

#pragma once
#include <JuceHeader.h>
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<float> 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<float>::pi * 0.75f; // 135°
const float sweep = juce::MathConstants<float>::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<float> tip { c.x + faceR*0.9f * std::cos (ang), c.y + faceR*0.9f * std::sin (ang) };
juce::Point<float> 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<CA> aOscA, aOscB;
std::unique_ptr<SA> aMix, aDetune, aCutoff, aReso, aEnv;
std::unique_ptr<SA> aAA, aDA, aSA, aRA, aAF, aDF, aSF, aRF;
std::unique_ptr<SA> aMaster;
// NEW: attachments for LFO
std::unique_ptr<SA> 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<int> presetBarBounds;
// Background group rectangles (drawn in paint, set in resized)
juce::Rectangle<int> groupCutResBounds; // Cutoff + Reso
juce::Rectangle<int> groupMixDetBounds; // Mix + Detune
juce::Rectangle<int> groupAmpBounds; // Amp A/D/S/R
juce::Rectangle<int> groupLfoBounds; // LFO Rate + Depth
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TwoOscAudioProcessorEditor)
};