#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())); paramNames.push_back(name); 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); } if (tree.getParameter(paramGroupId + "_on") != nullptr) { hasBypass = true; bypassButton.setButtonText("On"); bypassButton.setClickingTogglesState(true); addAndMakeVisible(bypassButton); bypassAttachment = std::make_unique( treeRef, paramGroupId + "_on", bypassButton); } } void setTitleText(const juce::String& text) { if (titleLabel.isVisible()) titleLabel.setText(text, juce::dontSendNotification); } void setTopBarAccessory(juce::Component* component, int preferredWidth = 120) { topBarAccessory = component; accessoryPreferredWidth = preferredWidth; if (topBarAccessory != nullptr) addAndMakeVisible(*topBarAccessory); } void reassignParamGroup(const std::string& newGroup) { if (newGroup == paramGroupId) return; const auto& sliderDetails = PARAM_SETTINGS.at(newGroup); jassert(sliderDetails.size() == sliders.size()); jassert(sliderDetails.size() == labels.size()); jassert(sliderDetails.size() == paramNames.size()); for (size_t i = 0; i < sliderDetails.size(); ++i) { const auto& [name, detail] = sliderDetails[i]; paramNames[i] = name; sliders[i]->setRange(detail.min, detail.max, detail.interval); labels[i]->setText(detail.label, juce::dontSendNotification); } attachments.clear(); attachments.reserve(sliderDetails.size()); for (size_t i = 0; i < sliderDetails.size(); ++i) { const auto paramId = newGroup + "_" + paramNames[i]; attachments.push_back(std::make_unique( treeRef, paramId, *sliders[i])); } if (hasBypass) { bypassAttachment.reset(); if (treeRef.getParameter(newGroup + "_on") != nullptr) { bypassAttachment = std::make_unique( treeRef, newGroup + "_on", bypassButton); bypassButton.setEnabled(true); } else { bypassButton.setEnabled(false); } } paramGroupId = newGroup; } 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); } juce::Slider* getSlider(const std::string& name) { return findSlider(name); } private: juce::Slider* findSlider(const std::string& name) { for (size_t i = 0; i < paramNames.size(); ++i) if (paramNames[i] == name) return sliders[i].get(); return nullptr; } 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); if (hasBypass) { auto btnW = 46; auto buttonArea = top.removeFromRight(btnW); bypassButton.setBounds(buttonArea.reduced(2, 1)); } if (topBarAccessory != nullptr) { const int widthLimit = juce::jmax(60, juce::jmin(accessoryPreferredWidth, top.getWidth())); auto accessoryArea = top.removeFromRight(widthLimit); topBarAccessory->setBounds(accessoryArea.reduced(2, 1)); } if (titleLabel.isVisible()) 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; std::vector paramNames; juce::Component* topBarAccessory{ nullptr }; int accessoryPreferredWidth{ 120 }; juce::ToggleButton bypassButton; std::unique_ptr bypassAttachment; bool hasBypass{ false }; 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; }; //============================== MasterVolumeComponent ========================= class MasterVolumeComponent : public juce::Component { public: MasterVolumeComponent() { slider.setSliderStyle(juce::Slider::LinearBarVertical); slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20); slider.setRange(-24.0f, 24.0f, 0.1f); addAndMakeVisible(slider); } void resized() override { slider.setBounds(getLocalBounds().reduced(30)); } juce::Slider slider; }; //============================== Editor ======================================= class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor, private juce::Timer, private juce::MidiKeyboardStateListener { public: NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&); ~NeuralSynthAudioProcessorEditor() override; void paint (juce::Graphics&) override; void resized() override; void timerCallback() override; void handleNoteOn(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; void handleNoteOff(juce::MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; private: NeuralSynthAudioProcessor& audioProcessor; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor) void updatePresetButtonLabel(); void showPresetMenu(); void handleLayerSelectionChanged(); juce::TextButton presetMenuButton; int lastPresetIndex { -1 }; 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 std::optional wtComponent; // Wavetable panel MasterVolumeComponent masterLevelSlider; juce::Label masterLevelLabel; std::optional eqComponent; std::unique_ptr gainAttachment; ScopeComponent mainScopeComponent; juce::MidiKeyboardState keyboardState; juce::MidiKeyboardComponent keyboardComponent; juce::ComboBox layerSelector; bool controllingLayerB { false }; };