#pragma once #include #include "PluginProcessor.h" #include "GraphComponent.h" #include "ScopeComponent.h" //============================== ScopeSliderComponent ========================== // A generic panel: optional scope/graph + rotary sliders + labels. // Adds a per-panel "On" toggle (bound to "_on"). class ScopeSliderComponent : public juce::Component { static const int fontSize = 11; public: ScopeSliderComponent(juce::AudioProcessorValueTreeState& tree, const std::string paramGroup, const juce::String& titleText = {}) : paramGroupId(paramGroup), treeRef(tree) { const auto& sliderDetails = PARAM_SETTINGS.at(paramGroup); for (const auto& [name, sliderDetail] : sliderDetails) { sliders.push_back(std::make_unique()); labels.push_back(std::make_unique()); attachments.push_back(std::make_unique( tree, paramGroup + "_" + name, *sliders.back())); labels.back()->setText(sliderDetail.label, juce::dontSendNotification); sliders.back()->setRange(sliderDetail.min, sliderDetail.max); } for (auto& slider : sliders) { slider->setSliderStyle(juce::Slider::Rotary); slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); addAndMakeVisible(*slider); } for (auto& label : labels) { juce::Font f; f.setHeight((float)fontSize); f.setBold(true); label->setFont(f); label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); label->setJustificationType(juce::Justification::centred); addAndMakeVisible(*label); } if (titleText.isNotEmpty()) { titleLabel.setText(titleText, juce::dontSendNotification); juce::Font tf; tf.setHeight(12.0f); tf.setBold(true); titleLabel.setFont(tf); titleLabel.setJustificationType(juce::Justification::centredLeft); titleLabel.setColour(juce::Label::textColourId, juce::Colours::white); addAndMakeVisible(titleLabel); } // Bypass toggle (per panel), id "_on" bypassButton.setButtonText("On"); bypassButton.setClickingTogglesState(true); addAndMakeVisible(bypassButton); bypassAttachment = std::make_unique( treeRef, paramGroupId + "_on", bypassButton); } void enableSampleScope(AudioBufferQueue& audioBufferQueue) { scope.emplace(audioBufferQueue); useGraphScope = false; addAndMakeVisible(*scope); } void enableGraphScope(const std::function& func) { graphScope.emplace(0.0f, 1.0f, 100); graphScope->setFunction(func); useGraphScope = true; addAndMakeVisible(*graphScope); } private: void paint(juce::Graphics& g) override { g.fillAll(juce::Colours::darkgrey); g.setColour(juce::Colours::white); g.drawRect(getLocalBounds()); } void resized() override { // --- Top bar (manual) ---------------------------------------------- auto area = getLocalBounds().reduced(10); auto top = area.removeFromTop(22); auto btnW = 46; bypassButton.setBounds(top.removeFromRight(btnW).reduced(2, 1)); titleLabel.setBounds(top); // --- Rest (grid) ---------------------------------------------------- juce::Grid grid; grid.templateRows = { juce::Grid::TrackInfo(juce::Grid::Fr(55)), // scope/graph juce::Grid::TrackInfo(juce::Grid::Fr(30)), // sliders juce::Grid::TrackInfo(juce::Grid::Fr(15)) // labels }; const int n = (int)sliders.size(); grid.templateColumns.resize(n); for (int i = 0; i < n; ++i) grid.templateColumns.getReference(i) = juce::Grid::TrackInfo(juce::Grid::Fr(1)); grid.items.clear(); // Row 1: scope/graph – only add if constructed if (useGraphScope) { if (graphScope) grid.items.add(juce::GridItem(*graphScope) .withArea(juce::GridItem::Span(1), juce::GridItem::Span(n))); else grid.items.add(juce::GridItem() .withArea(juce::GridItem::Span(1), juce::GridItem::Span(n))); } else { if (scope) grid.items.add(juce::GridItem(*scope) .withArea(juce::GridItem::Span(1), juce::GridItem::Span(n))); else grid.items.add(juce::GridItem() .withArea(juce::GridItem::Span(1), juce::GridItem::Span(n))); } // Row 2: sliders for (int i = 0; i < n; ++i) grid.items.add(juce::GridItem(*sliders[(size_t)i])); // Row 3: labels for (int i = 0; i < n; ++i) grid.items.add(juce::GridItem(*labels[(size_t)i])); grid.performLayout(area); } bool useGraphScope{ false }; std::optional> scope; std::optional> graphScope; std::vector> sliders; std::vector> labels; std::vector> attachments; juce::ToggleButton bypassButton; std::unique_ptr bypassAttachment; juce::Label titleLabel; std::string paramGroupId; juce::AudioProcessorValueTreeState& treeRef; }; //============================== EqualizerComponent ============================ // Adds an On/Off toggle bound to "eq_on". class EqualizerComponent : public juce::Component { static const int fontSize = 11; public: explicit EqualizerComponent(juce::AudioProcessorValueTreeState& tree, const juce::String& titleText = {}) { setupSlider(lowGainSlider); setupSlider(midGainSlider); setupSlider(highGainSlider); setupLabel(lowGainLabel, "L"); setupLabel(midGainLabel, "M"); setupLabel(highGainLabel, "H"); if (titleText.isNotEmpty()) { titleLabel.setText(titleText, juce::dontSendNotification); juce::Font tf; tf.setHeight(13.0f); tf.setBold(true); titleLabel.setFont(tf); titleLabel.setJustificationType(juce::Justification::centredLeft); titleLabel.setColour(juce::Label::textColourId, juce::Colours::white); addAndMakeVisible(titleLabel); } // Attachments lowGainAttachment = std::make_unique(tree, "lowEQ", lowGainSlider); midGainAttachment = std::make_unique(tree, "midEQ", midGainSlider); highGainAttachment = std::make_unique(tree, "highEQ", highGainSlider); // EQ bypass toggle bypassButton.setButtonText("On"); bypassButton.setClickingTogglesState(true); addAndMakeVisible(bypassButton); bypassAttachment = std::make_unique(tree, "eq_on", bypassButton); } private: void setupSlider(juce::Slider& slider) { slider.setRange(-24.0f, 24.0f, 0.1f); slider.setSliderStyle(juce::Slider::LinearBarVertical); slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); addAndMakeVisible(slider); } void setupLabel(juce::Label& lbl, juce::String txt) { juce::Font f; f.setHeight((float)fontSize); f.setBold(true); lbl.setFont(f); lbl.setColour(juce::Label::textColourId, juce::Colours::lightgreen); lbl.setJustificationType(juce::Justification::centred); lbl.setText(txt, juce::dontSendNotification); addAndMakeVisible(lbl); } void paint(juce::Graphics& g) override { g.fillAll(juce::Colours::darkgrey); g.setColour(juce::Colours::white); g.drawRect(getLocalBounds()); } void resized() override { auto area = getLocalBounds().reduced(10); auto top = area.removeFromTop(22); auto btnW = 46; bypassButton.setBounds(top.removeFromRight(btnW).reduced(2, 1)); titleLabel.setBounds(top); juce::Grid grid; grid.templateRows = { juce::Grid::TrackInfo(juce::Grid::Fr(1)), juce::Grid::TrackInfo(juce::Grid::Fr(1)) }; grid.templateColumns = { juce::Grid::TrackInfo(juce::Grid::Fr(1)), juce::Grid::TrackInfo(juce::Grid::Fr(1)), juce::Grid::TrackInfo(juce::Grid::Fr(1)) }; grid.items = { lowGainSlider, midGainSlider, highGainSlider, lowGainLabel, midGainLabel, highGainLabel }; grid.performLayout(area); } juce::Slider lowGainSlider, midGainSlider, highGainSlider; juce::Label lowGainLabel, midGainLabel, highGainLabel; std::unique_ptr lowGainAttachment, midGainAttachment, highGainAttachment; juce::ToggleButton bypassButton; std::unique_ptr bypassAttachment; juce::Label titleLabel; }; //============================== Waveform List Model =========================== struct WaveformSelectorContents final : public juce::ListBoxModel { int getNumRows() override { return 4; } void paintListBoxItem(int rowNumber, juce::Graphics& g, int width, int height, bool rowIsSelected) override { if (rowIsSelected) g.fillAll(juce::Colours::lightblue); g.setColour(juce::LookAndFeel::getDefaultLookAndFeel() .findColour(juce::Label::textColourId)); juce::Font f; f.setHeight((float)height * 0.7f); g.setFont(f); g.drawText(waves[(size_t)rowNumber], 5, 0, width, height, juce::Justification::centredLeft, true); } void selectedRowsChanged (int lastRowSelected) override { if (onSelect) onSelect(lastRowSelected); } std::function onSelect; std::vector waves { "Sine", "Saw", "Square", "Triangle" }; }; //============================== MasterVolumeComponent ========================= class MasterVolumeComponent : public juce::Component { public: MasterVolumeComponent() { slider.setSliderStyle(juce::Slider::LinearBarVertical); slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20); addAndMakeVisible(slider); } void resized() override { slider.setBounds(getLocalBounds().reduced(30)); } juce::Slider slider; }; //============================== Editor ======================================= class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor { public: NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&); ~NeuralSynthAudioProcessorEditor() override; void paint (juce::Graphics&) override; void resized() override; private: NeuralSynthAudioProcessor& audioProcessor; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor) juce::ListBox waveformSelector; WaveformSelectorContents waveformContents; std::optional adsrComponent; // Amp Env std::optional chorusComponent; std::optional delayComponent; std::optional reverbComponent; std::optional flangerComponent; std::optional distortionComponent; std::optional filterComponent; std::optional filterEnvComponent; // Filter Env panel MasterVolumeComponent masterLevelSlider; juce::Label masterLevelLabel; std::optional eqComponent; std::unique_ptr gainAttachment; ScopeComponent mainScopeComponent; juce::Component blankPanel; };