From 10feb7a7fe0fe0dbf1569edec970a7f4303055cf Mon Sep 17 00:00:00 2001 From: Gelyk Date: Sat, 29 Aug 2026 15:32:16 +0200 Subject: [PATCH] Expand to 7 bands; add labeled rotary Q knobs and gain dB readouts --- CMakeLists.txt | 7 -- Source/PluginEditor.cpp | 132 +++++++++++++++++++++++++++++-------- Source/PluginEditor.h | 10 ++- Source/PluginProcessor.cpp | 24 ++++--- Source/PluginProcessor.h | 2 +- 5 files changed, 126 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee75c97..8886c23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,13 +102,6 @@ target_link_libraries(GelykEQ PRIVATE juce::juce_dsp) target_compile_definitions(GelykEQ PRIVATE JUCE_MODULE_AVAILABLE_juce_dsp=1) -target_compile_definitions(GelykEQ PRIVATE - JucePlugin_Build_VST=1 - JucePlugin_Build_AU=1 - JucePlugin_Build_AAX=0 - JucePlugin_Build_Standalone=0 -) - target_compile_options(GelykEQ PRIVATE $<$:-Wall -Wextra> ) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 83f707a..ef09b78 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -67,6 +67,46 @@ void NeonLookAndFeel::drawLinearSlider(juce::Graphics& g, int x, int y, int widt } } +void NeonLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, + float sliderPos, float startAngle, float endAngle, juce::Slider& slider) +{ + const float cx = x + width * 0.5f; + const float cy = y + height * 0.5f; + const float radius = juce::jmin(width, height) * 0.5f; + const float toAngle = startAngle + sliderPos * (endAngle - startAngle); + const float lineW = juce::jmax(2.0f, radius * 0.10f); + const float arcRadius = radius - lineW * 0.5f; + + // Background disc. + g.setColour(panel); + g.fillEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f); + g.setColour(line); + g.drawEllipse(cx - radius, cy - radius, radius * 2.0f, radius * 2.0f, 1.0f); + + // Filled arc (progress). + juce::Path filledArc; + filledArc.addCentredArc(cx, cy, arcRadius, arcRadius, 0.0f, + startAngle, toAngle, true); + juce::PathStrokeType stroke(lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded); + g.setColour(slider.isMouseOverOrDragging(true) ? magenta : cyan); + g.strokePath(filledArc, stroke); + + // Pointer line. + juce::Path pointer; + const float pointerLength = radius * 0.68f; + pointer.startNewSubPath(cx, cy); + pointer.lineTo(cx + pointerLength * std::cos(toAngle), + cy + pointerLength * std::sin(toAngle)); + g.setColour(slider.isMouseOverOrDragging(true) ? violet : bg); + g.strokePath(pointer, juce::PathStrokeType(juce::jmax(1.5f, radius * 0.07f), + juce::PathStrokeType::curved, + juce::PathStrokeType::rounded)); + + // Centre hub. + g.setColour(slider.isMouseOverOrDragging(true) ? cyan : line); + g.fillEllipse(cx - 2.0f, cy - 2.0f, 4.0f, 4.0f); +} + void NeonLookAndFeel::drawComboBox(juce::Graphics& g, int width, int height, bool, int, int, int, int, juce::ComboBox& box) { @@ -136,39 +176,68 @@ GelykEQAudioProcessorEditor::GelykEQAudioProcessorEditor(GelykEQAudioProcessor& masterAttachment = std::make_unique( processor.getAPVTS(), "master", *master); // Band faders. - const char* const names[numBands] = { "LOW", "LOW-MID", "MID", "HIGH-MID", "HIGH" }; - const char* const freqs[numBands] = { "60", "200", "1K", "4K", "10K" }; + const char* const names[numBands] = { "LOW SHELF", "LOW", "LOW-MID", "MID", "HIGH-MID", "HIGH", "HIGH SHELF" }; + const char* const freqs[numBands] = { "60", "160", "400", "800", "1.6K", "4K", "10K" }; for (int i = 0; i < numBands; ++i) { auto& w = bands[i]; + const juce::Colour accent = (i % 2 == 0) ? lookAndFeel.cyan : lookAndFeel.magenta; w.freqLabel = std::make_unique("freq", freqs[i]); - w.freqLabel->setFont(monoFont(13.0f)); - w.freqLabel->setColour(juce::Label::textColourId, lookAndFeel.textDim); + w.freqLabel->setFont(monoFont(14.0f).boldened()); + w.freqLabel->setColour(juce::Label::textColourId, accent); w.freqLabel->setJustificationType(juce::Justification::centred); - w.gainLabel = std::make_unique("gname", names[i]); - w.gainLabel->setFont(monoFont(11.0f)); - w.gainLabel->setColour(juce::Label::textColourId, - i % 2 == 0 ? lookAndFeel.cyan : lookAndFeel.magenta); - w.gainLabel->setJustificationType(juce::Justification::centred); + w.gainCaption = std::make_unique("gname", names[i]); + w.gainCaption->setFont(monoFont(10.0f)); + w.gainCaption->setColour(juce::Label::textColourId, lookAndFeel.textDim); + w.gainCaption->setJustificationType(juce::Justification::centred); - w.gain = std::make_unique(juce::Slider::LinearVertical, juce::Slider::TextBoxBelow); + w.gainValue = std::make_unique("gval", "0.0"); + w.gainValue->setFont(monoFont(11.0f)); + w.gainValue->setColour(juce::Label::textColourId, accent); + w.gainValue->setJustificationType(juce::Justification::centred); + + w.qCaption = std::make_unique("qcap", "Q"); + w.qCaption->setFont(monoFont(10.0f)); + w.qCaption->setColour(juce::Label::textColourId, lookAndFeel.textDim); + w.qCaption->setJustificationType(juce::Justification::centred); + + w.qValue = std::make_unique("qval", "1.00"); + w.qValue->setFont(monoFont(10.0f)); + w.qValue->setColour(juce::Label::textColourId, lookAndFeel.violet); + w.qValue->setJustificationType(juce::Justification::centred); + + w.gain = std::make_unique(juce::Slider::LinearVertical, juce::Slider::NoTextBox); w.gain->setLookAndFeel(&lookAndFeel); w.gain->setSliderStyle(juce::Slider::LinearVertical); - w.gain->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 40, 14); w.gain->setRange(-24.0, 24.0, 0.01); w.gain->setDoubleClickReturnValue(true, 0.0); + w.gain->setValue(0.0, juce::dontSendNotification); + w.gain->onValueChange = [this, i] + { + bands[i].gainValue->setText(juce::String(bands[i].gain->getValue(), 1), juce::dontSendNotification); + }; - w.q = std::make_unique(juce::Slider::LinearHorizontal, juce::Slider::NoTextBox); + w.q = std::make_unique(juce::Slider::RotaryVerticalDrag, juce::Slider::NoTextBox); w.q->setLookAndFeel(&lookAndFeel); + w.q->setSliderStyle(juce::Slider::RotaryVerticalDrag); + w.q->setRotaryParameters(2.4f, 6.9f, true); // ~270° sweep w.q->setRange(0.1, 6.0, 0.01); w.q->setDoubleClickReturnValue(true, 1.0); + w.q->setValue(1.0, juce::dontSendNotification); + w.q->onValueChange = [this, i] + { + bands[i].qValue->setText(juce::String(bands[i].q->getValue(), 2), juce::dontSendNotification); + }; addAndMakeVisible(*w.freqLabel); - addAndMakeVisible(*w.gainLabel); + addAndMakeVisible(*w.gainCaption); + addAndMakeVisible(*w.gainValue); addAndMakeVisible(*w.gain); + addAndMakeVisible(*w.qCaption); addAndMakeVisible(*w.q); + addAndMakeVisible(*w.qValue); gainAttachments[i] = std::make_unique( processor.getAPVTS(), "gain" + juce::String(i), *w.gain); @@ -217,7 +286,7 @@ void GelykEQAudioProcessorEditor::scaleUI(float percent) { const float s = percent / 100.0f; scaling.store(s); - setSize((int) (920 * s), (int) (540 * s)); + setSize((int) (1180 * s), (int) (640 * s)); resized(); } @@ -226,13 +295,13 @@ void GelykEQAudioProcessorEditor::resized() juce::Rectangle area = getLocalBounds(); const float s = scaling.load(); - const int headerH = (int) (46 * s); + const int headerH = (int) (48 * s); const int footerH = (int) (26 * s); const int pad = (int) (12 * s); // Header. juce::Rectangle header = area.removeFromTop(headerH); - titleLabel->setBounds(header.removeFromLeft((int) (170 * s)).reduced(pad, 0)); + titleLabel->setBounds(header.removeFromLeft((int) (180 * s)).reduced(pad, 0)); scaleBox->setBounds(header.removeFromRight((int) (90 * s)).reduced((int)(6*s), (int)(9*s))); // Footer. @@ -241,7 +310,7 @@ void GelykEQAudioProcessorEditor::resized() statusBar->setBounds(footer.removeFromLeft(footer.getWidth() / 2)); statusGit->setBounds(footer); - // Main grid. + // Master column (right). const int masterW = (int) (64 * s); juce::Rectangle masterArea = area.removeFromRight(masterW); @@ -250,7 +319,7 @@ void GelykEQAudioProcessorEditor::resized() const int totalW = area.getWidth(); const int height = area.getHeight(); - const int gap = (int) (10 * s); + const int gap = (int) (12 * s); const int colW = (totalW - gap * (numBands - 1)) / numBands; for (int i = 0; i < numBands; ++i) @@ -258,22 +327,27 @@ void GelykEQAudioProcessorEditor::resized() auto& w = bands[i]; juce::Rectangle col(x0 + i * (colW + gap), y0, colW, height); - const int topLabelH = (int) (20 * s); - const int freqH = (int) (16 * s); - const int gainSliderH = (int) (58 * s); - const int qH = (int) (18 * s); + const int captionH = (int) (18 * s); + const int valueH = (int) (15 * s); + const int freqH = (int) (20 * s); + const int qValueH = (int) (14 * s); + const int qKnobH = (int) (58 * s); + const int qCapH = (int) (14 * s); - w.gainLabel->setBounds(col.removeFromTop(topLabelH)); + w.gainCaption->setBounds(col.removeFromTop(captionH)); + w.gainValue->setBounds(col.removeFromTop(valueH)); - const int analyserH = col.getHeight() - freqH - gainSliderH - qH - (int)(8*s); - // reserve analyser space by subtracting it (faders sit on top of the spectrum) - col.removeFromTop(juce::jmax(0, analyserH)); + // The gain fader fills the large analyser area (spectrum drawn behind it). + w.gain->setBounds(col.removeFromTop(col.getHeight() + - freqH - qValueH - qKnobH - qCapH - (int)(6*s))); w.freqLabel->setBounds(col.removeFromTop(freqH)); - w.gain->setBounds(col.removeFromTop(gainSliderH)); - juce::Rectangle qArea = col.removeFromBottom(qH); - w.q->setBounds(qArea.reduced((int)(12*s), 0)); + // Q rotary control: value label, knob, caption. + w.qValue->setBounds(col.removeFromTop(qValueH)); + juce::Rectangle knobArea = col.removeFromTop(qKnobH); + w.q->setBounds(knobArea.reduced((int)(colW * 0.22f), 0)); + w.qCaption->setBounds(col.removeFromTop(qCapH)); } // Master column. diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index b70a472..f7b5550 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -13,6 +13,9 @@ public: void drawLinearSlider(juce::Graphics&, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const juce::Slider::SliderStyle, juce::Slider&) override; + void drawRotarySlider(juce::Graphics&, int x, int y, int width, int height, + float sliderPosProportional, float rotaryStartAngle, + float rotaryEndAngle, juce::Slider&) override; void drawComboBox(juce::Graphics&, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, juce::ComboBox&) override; @@ -48,13 +51,16 @@ private: NeonLookAndFeel lookAndFeel; - static constexpr int numBands = 5; + static constexpr int numBands = 7; struct BandWidget { std::unique_ptr gain; std::unique_ptr q; std::unique_ptr freqLabel; - std::unique_ptr gainLabel; + std::unique_ptr gainCaption; + std::unique_ptr gainValue; + std::unique_ptr qCaption; + std::unique_ptr qValue; }; BandWidget bands[numBands]; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index b4cd6d7..69080fa 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -24,16 +24,20 @@ juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout() }; addGain("gain0", "Low Shelf Gain", 0.0f); - addGain("gain1", "Low-Mid Gain", 0.0f); - addGain("gain2", "Mid Gain", 0.0f); - addGain("gain3", "High-Mid Gain", 0.0f); - addGain("gain4", "High Shelf Gain", 0.0f); + addGain("gain1", "Low Gain", 0.0f); + addGain("gain2", "Low-Mid Gain", 0.0f); + addGain("gain3", "Mid Gain", 0.0f); + addGain("gain4", "High-Mid Gain", 0.0f); + addGain("gain5", "High Gain", 0.0f); + addGain("gain6", "High Shelf Gain", 0.0f); addQ("q0", "Low Shelf Q", 0.71f); - addQ("q1", "Low-Mid Q", 1.0f); - addQ("q2", "Mid Q", 1.0f); - addQ("q3", "High-Mid Q", 1.0f); - addQ("q4", "High Shelf Q", 0.71f); + addQ("q1", "Low Q", 1.0f); + addQ("q2", "Low-Mid Q", 1.0f); + addQ("q3", "Mid Q", 1.0f); + addQ("q4", "High-Mid Q", 1.0f); + addQ("q5", "High Q", 1.0f); + addQ("q6", "High Shelf Q", 0.71f); layout.add(std::make_unique( "master", "Master", juce::NormalisableRange(-24.0f, 24.0f, 0.01f), 0.0f)); @@ -44,8 +48,8 @@ juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout() return layout; } -const char* const bandTypes[5] = { "lowShelf", "peak", "peak", "peak", "highShelf" }; -const float bandFreqs[5] = { 60.0f, 200.0f, 1000.0f, 4000.0f, 10000.0f }; +const char* const bandTypes[7] = { "lowShelf", "peak", "peak", "peak", "peak", "peak", "highShelf" }; +const float bandFreqs[7] = { 60.0f, 160.0f, 400.0f, 800.0f, 1600.0f, 4000.0f, 10000.0f }; } // namespace GelykEQAudioProcessor::GelykEQAudioProcessor() diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 7bd9256..e449adf 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -44,7 +44,7 @@ public: private: juce::AudioProcessorValueTreeState apvts; - static constexpr int numBands = 5; + static constexpr int numBands = 7; FilterBand bands[numBands]; double currentSampleRate = 44100.0;