140 lines
5.5 KiB
C++
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)
|
|
};
|
|
|