Files
NeuralSynth/Source/PluginEditor.h
2025-10-23 04:00:21 +01:00

342 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <JuceHeader.h>
#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 "<group>_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<juce::Slider>());
labels.push_back(std::make_unique<juce::Label>());
attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
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 "<group>_on"
bypassButton.setButtonText("On");
bypassButton.setClickingTogglesState(true);
addAndMakeVisible(bypassButton);
bypassAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(
treeRef, paramGroupId + "_on", bypassButton);
}
void enableSampleScope(AudioBufferQueue<float>& audioBufferQueue) {
scope.emplace(audioBufferQueue);
useGraphScope = false;
addAndMakeVisible(*scope);
}
void enableGraphScope(const std::function<float(float)>& 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<ScopeComponent<float>> scope;
std::optional<GraphComponent<float>> graphScope;
std::vector<std::unique_ptr<juce::Slider>> sliders;
std::vector<std::unique_ptr<juce::Label>> labels;
std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment>> attachments;
juce::ToggleButton bypassButton;
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> 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<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "lowEQ", lowGainSlider);
midGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "midEQ", midGainSlider);
highGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "highEQ", highGainSlider);
// EQ bypass toggle
bypassButton.setButtonText("On");
bypassButton.setClickingTogglesState(true);
addAndMakeVisible(bypassButton);
bypassAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(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<juce::AudioProcessorValueTreeState::SliderAttachment> lowGainAttachment, midGainAttachment, highGainAttachment;
juce::ToggleButton bypassButton;
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> 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<void (int)> onSelect;
std::vector<juce::String> 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<ScopeSliderComponent> adsrComponent; // Amp Env
std::optional<ScopeSliderComponent> chorusComponent;
std::optional<ScopeSliderComponent> delayComponent;
std::optional<ScopeSliderComponent> reverbComponent;
std::optional<ScopeSliderComponent> flangerComponent;
std::optional<ScopeSliderComponent> distortionComponent;
std::optional<ScopeSliderComponent> filterComponent;
std::optional<ScopeSliderComponent> filterEnvComponent; // Filter Env panel
MasterVolumeComponent masterLevelSlider;
juce::Label masterLevelLabel;
std::optional<EqualizerComponent> eqComponent;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gainAttachment;
ScopeComponent<float> mainScopeComponent;
juce::Component blankPanel;
};