diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 99d8acb..a51d3f5 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1,404 +1,170 @@ -/* - ============================================================================== - - This file contains the basic framework code for a JUCE plugin editor. - - ============================================================================== -*/ - -#include "PluginProcessor.h" -#include "PluginEditor.h" -#include "ScopeComponent.h" - -//============================================================================== -NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p) - : AudioProcessorEditor (&p), audioProcessor (p), mainScopeComponent(audioProcessor.getAudioBufferQueue()) -{ - auto& tree = audioProcessor.parameters; - - //auto area = getLocalBounds(); - //mainScopeComponent.setBounds(5, 5, 800, 200); -// scopeComponent.setSize(800, 200); - - addAndMakeVisible(mainScopeComponent); - - waveformSelector.setModel(&waveformContents); - - addAndMakeVisible(waveformSelector); - - - chorusComponent.emplace(tree, "chorus"); - - chorusComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue()); - addAndMakeVisible(*chorusComponent); - - delayComponent.emplace(tree, "delay"); - delayComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue()); - addAndMakeVisible(*delayComponent); - - reverbComponent.emplace(tree, "reverb"); - - reverbComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue()); - addAndMakeVisible(*reverbComponent); - - - adsrComponent.emplace(tree, "adsr"); - - adsrComponent->enableGraphScope([this](float x) { - auto& tree = this->audioProcessor.parameters; - - float attackValue = tree.getParameter("adsr_attack")->getValue(); - float decayValue = tree.getParameter("adsr_decay")->getValue(); - float sustainValue = tree.getParameter("adsr_sustain")->getValue(); - float releaseValue = tree.getParameter("adsr_release")->getValue(); - - float sustainLength = 1.0f; - float totalTime = attackValue + decayValue + sustainLength + releaseValue; - - attackValue /= totalTime; - decayValue /= totalTime; - sustainLength /= totalTime; - releaseValue /= totalTime; - - float m, c; - if (x < attackValue) - { - m = (1.0f / attackValue); - c = 0; - } - else if (x < (attackValue + decayValue)) { - m = (sustainValue - 1.0f) / decayValue; - c = 1.0f - m * attackValue; - } - else if (x < (attackValue + decayValue + sustainLength)) { - m = 0.0f; - c = sustainValue; - } - else { - m = (sustainValue / -releaseValue); - c = -m; - } - - return m * x + c; - }); - addAndMakeVisible(*adsrComponent); - - //createADSR(5, 250); - //createEQ(); - - addAndMakeVisible(masterLevelSlider); - - eqComponent.emplace(tree); - addAndMakeVisible(*eqComponent); - - // Attach to parameter - //waveformAttachment = std::make_unique( - // audioProcessor.parameters, "waveform", waveformSelector); - - //attachments.push_back(std::make_unique( - // tree, sliderDetail.name, *sliders.back())); - - gainAttachment = std::make_unique( - audioProcessor.parameters, "master", masterLevelSlider.slider - ); - - - flangerComponent.emplace(tree, "flanger"); - flangerComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue()); - addAndMakeVisible(*flangerComponent); - - distortionComponent.emplace(tree, "distortion"); - distortionComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue()); - addAndMakeVisible(*distortionComponent); - - filterComponent.emplace(tree, "filter"); - filterComponent->enableSampleScope(audioProcessor.getFilterAudioBufferQueue()); - addAndMakeVisible(*filterComponent); - - //addAndMakeVisible(midiKeyboardComponent); - - - //scopeComponent.setSize(area.getWidth(), area.getHeight()); - - //midiKeyboardComponent.setMidiChannel(2); - //midiKeyboardState.addListener(&audioProcessor.getMidiMessageCollector()); - - //midiKeyboardComponent.setBounds(area.removeFromTop(80).reduced(8)); - //midiKeyboardComponent.setTopLeftPosition(8, 420); - - // Make sure that before the constructor has finished, you've set the - // editor's size to whatever you need it to be. - setSize(1400, 700); - -} - -//============================================================================== -NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor() -{ -} - -/*void NeuralSynthAudioProcessorEditor::updateEQFromSliders() -{ - using Coefficients = juce::dsp::IIR::Coefficients; - - auto& low = audioProcessor.getProcess.get<0>(); - auto& mid = audioProcessor.eqChain.get<1>(); - auto& high = audioProcessor.eqChain.get<2>(); - - low.coefficients = Coefficients::makeLowShelf(audioProcessor.sampleRate, 100.0f, 0.707f, - juce::Decibels::decibelsToGain(lowGainSlider.getValue())); - mid.coefficients = Coefficients::makePeakFilter(audioProcessor.sampleRate, 1000.0f, 0.707f, - juce::Decibels::decibelsToGain(midGainSlider.getValue())); - high.coefficients = Coefficients::makeHighShelf(audioProcessor.sampleRate, 8000.0f, 0.707f, - juce::Decibels::decibelsToGain(highGainSlider.getValue())); -}*/ - -//============================================================================== -/*void NeuralSynthAudioProcessorEditor::createADSR(int xCoord, int yCoord) { - adsrGraph.setFunction([this](float x) { - auto& tree = this->audioProcessor.parameters; - - float attackValue = tree.getParameter("attack")->getValue(); - float decayValue = tree.getParameter("decay")->getValue(); - float sustainValue = tree.getParameter("sustain")->getValue(); - float releaseValue = tree.getParameter("release")->getValue(); - - float sustainLength = 1.0f; - float totalTime = attackValue + decayValue + sustainLength + releaseValue; - - attackValue /= totalTime; - decayValue /= totalTime; - sustainLength /= totalTime; - releaseValue /= totalTime; - - float m, c; - if (x < attackValue) - { - m = (1.0f / attackValue); - c = 0; - } else if (x < (attackValue + decayValue)) { - m = (sustainValue - 1.0f) / decayValue; - c = 1.0f - m * attackValue; - } else if (x < (attackValue + decayValue + sustainLength)) { - m = 0.0f; - c = sustainValue; - } else { - m = (sustainValue / -releaseValue); - c = -m; - } - - return m * x + c; - }); - - - int fontSize = 11; - int leftPosition = xCoord; - const int sliderWidth = 60; - const int sliderWidthWithPadding = sliderWidth + 20; - - adsrGraph.setBounds(xCoord, yCoord, 240, 150); - - addAndMakeVisible(adsrGraph); - - - for (auto* slider : { &attackSlider, &decaySlider, &sustainSlider, &releaseSlider }) - { - slider->setSliderStyle(juce::Slider::Rotary); - slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, sliderWidth + 20, 20); - //slider->setTopLeftPosition(0, 0); - slider->setBounds(leftPosition, yCoord + 150, sliderWidth, sliderWidthWithPadding); - addAndMakeVisible(*slider); - leftPosition += sliderWidth; - } - - leftPosition = xCoord + 3; // (sliderWidth / 2); - for (auto* label : { &attackLabel, &decayLabel, &sustainLabel, &releaseLabel }) - { - label->setFont(juce::Font((float)fontSize, juce::Font::bold)); - //label->setTopLeftPosition(leftPosition, 300); - label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); - label->setJustificationType(juce::Justification::centred); - label->setBounds(leftPosition, yCoord + 240, 50, 20); - //label->setText(""); - addAndMakeVisible(*label); - leftPosition += sliderWidth; - } - - attackLabel.setText("Attack", juce::dontSendNotification); decayLabel.setText("Decay", juce::dontSendNotification); - sustainLabel.setText("Sustain", juce::dontSendNotification); releaseLabel.setText("Release", juce::dontSendNotification); - - auto& tree = this->audioProcessor.parameters; - - attackAttachment = std::make_unique(tree, "attack", attackSlider); - decayAttachment = std::make_unique(tree, "decay", decaySlider); - sustainAttachment = std::make_unique(tree, "sustain", sustainSlider); - releaseAttachment = std::make_unique(tree, "release", releaseSlider); - - attackSlider.setRange(0.0, 1.0); - decaySlider.setRange(0.0, 1.0); - sustainSlider.setRange(0.0, 1.0); - releaseSlider.setRange(0.0, 1.0); -}*/ - - - -/*void NeuralSynthAudioProcessorEditor::createDelay(int xCoord, int yCoord) { - int fontSize = 11; - int leftPosition = xCoord; - const int sliderWidth = 60; - const int sliderWidthWithPadding = sliderWidth + 20; - - delayScopeComponent.setBounds(xCoord, yCoord, 300, 150); - - addAndMakeVisible(delayScopeComponent); - - - for (auto* slider : { &delayDelaySlider }) - { - slider->setSliderStyle(juce::Slider::Rotary); - slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, sliderWidth + 20, 20); - //slider->setTopLeftPosition(0, 0); - slider->setBounds(leftPosition, yCoord + 150, sliderWidth, sliderWidthWithPadding); - addAndMakeVisible(*slider); - leftPosition += sliderWidth; - } - - leftPosition = xCoord + 3; // (sliderWidth / 2); - for (auto* label : { &delayDelayLabel }) - { - label->setFont(juce::Font((float)fontSize, juce::Font::bold)); - //label->setTopLeftPosition(leftPosition, 300); - label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); - label->setJustificationType(juce::Justification::centred); - label->setBounds(leftPosition, yCoord + 240, 50, 20); - //label->setText(""); - addAndMakeVisible(*label); - leftPosition += sliderWidth; - } - - delayDelayLabel.setText("Delay", juce::dontSendNotification); - - auto& tree = this->audioProcessor.parameters; - delayDelayAttachment = std::make_unique(tree, "delayDelay", reverbRoomSizeSlider); - - delayDelaySlider.setRange(0.0, 1.0); -} -*/ - -/*void NeuralSynthAudioProcessorEditor::createReverb(int xCoord, int yCoord) { - - int fontSize = 11; - int leftPosition = xCoord; - const int sliderWidth = 60; - const int sliderWidthWithPadding = sliderWidth + 20; - - reverbScopeComponent.setBounds(xCoord, yCoord, 360, 150); - - addAndMakeVisible(reverbScopeComponent); - - - for (auto* slider : { &reverbRoomSizeSlider, &reverbDampingSlider, &reverbWetLevelSlider, &reverbDryLevelSlider, &reverbWidthSlider, &reverbFreezeModeSlider }) - { - slider->setSliderStyle(juce::Slider::Rotary); - slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, sliderWidth + 20, 20); - //slider->setTopLeftPosition(0, 0); - slider->setBounds(leftPosition, yCoord + 150, sliderWidth, sliderWidthWithPadding); - addAndMakeVisible(*slider); - leftPosition += sliderWidth; - } - - leftPosition = xCoord + 3; // (sliderWidth / 2); - for (auto* label : { &reverbRoomSizeLabel, &reverbDampingLabel, &reverbWetLevelLabel, &reverbDryLevelLabel, &reverbWidthLabel, &reverbFreezeModeLabel }) - { - label->setFont(juce::Font((float)fontSize, juce::Font::bold)); - //label->setTopLeftPosition(leftPosition, 300); - label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); - label->setJustificationType(juce::Justification::centred); - label->setBounds(leftPosition, yCoord + 240, 50, 20); - //label->setText(""); - addAndMakeVisible(*label); - leftPosition += sliderWidth; - } - - reverbRoomSizeLabel.setText("Room Size", juce::dontSendNotification); reverbDampingLabel.setText("Damping", juce::dontSendNotification); - reverbWetLevelLabel.setText("Wet Level", juce::dontSendNotification); reverbDryLevelLabel.setText("Dry Level", juce::dontSendNotification); - reverbWidthLabel.setText("Width", juce::dontSendNotification); reverbFreezeModeLabel.setText("Freeze Mode", juce::dontSendNotification); - - auto& tree = this->audioProcessor.parameters; - - reverbRoomSizeAttachment = std::make_unique(tree, "reverbRoomSize", reverbRoomSizeSlider); - reverbDampingAttachment = std::make_unique(tree, "reverbDamping", reverbDampingSlider); - reverbWetLevelAttachment = std::make_unique(tree, "reverbWetLevel", reverbWetLevelSlider); - reverbDryLevelAttachment = std::make_unique(tree, "reverbDryLevel", reverbDryLevelSlider); - reverbWidthAttachment = std::make_unique(tree, "reverbWidth", reverbWidthSlider); - reverbFreezeModeAttachment = std::make_unique(tree, "reverbFreezeMode", reverbFreezeModeSlider); - - - reverbRoomSizeSlider.setRange(0.0, 1.0); - reverbDampingSlider.setRange(0.0, 1.0); - reverbWetLevelSlider.setRange(0.0, 1.0); - reverbDryLevelSlider.setRange(0.0, 1.0); - reverbWidthSlider.setRange(0.0, 1.0); - reverbFreezeModeSlider.setRange(0.0, 1.0); -}*/ - -//============================================================================== -void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g) -{ - // (Our component is opaque, so we must completely fill the background with a solid colour) - g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); - - //g.setColour (juce::Colours::white); - //g.setFont (juce::FontOptions (15.0f)); - //g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); -} - -//============================================================================== -void NeuralSynthAudioProcessorEditor::resized() -{ - // This is generally where you'll want to lay out the positions of any - // subcomponents in your editor.. - auto bounds = getLocalBounds().reduced(20); - //auto row = bounds.removeFromTop(150); - - //int knobWidth = row.getWidth() / 4; - - juce::Grid grid; - grid.templateRows = { juce::Grid::TrackInfo(juce::Grid::Fr(20)), - juce::Grid::TrackInfo(juce::Grid::Fr(40)), - juce::Grid::TrackInfo(juce::Grid::Fr(40)) }; - - grid.templateColumns = { - juce::Grid::TrackInfo(juce::Grid::Fr(22)), - juce::Grid::TrackInfo(juce::Grid::Fr(22)), - juce::Grid::TrackInfo(juce::Grid::Fr(22)), - juce::Grid::TrackInfo(juce::Grid::Fr(22)), - juce::Grid::TrackInfo(juce::Grid::Fr(8)) - }; - - grid.items = { - juce::GridItem(mainScopeComponent).withArea({}, juce::GridItem::Span(4)), - juce::GridItem(waveformSelector), - juce::GridItem(*adsrComponent), - juce::GridItem(*chorusComponent), - juce::GridItem(*delayComponent), - juce::GridItem(*reverbComponent), - juce::GridItem(masterLevelSlider).withArea(juce::GridItem::Span(2), {}), - juce::GridItem(*eqComponent), - juce::GridItem(*flangerComponent), - juce::GridItem(*distortionComponent), - juce::GridItem(*filterComponent), - }; - - - grid.performLayout(bounds); - - /*attackSlider.setBounds(row.removeFromLeft(knobWidth).reduced(10)); - decaySlider.setBounds(row.removeFromLeft(knobWidth).reduced(10)); - sustainSlider.setBounds(row.removeFromLeft(knobWidth).reduced(10)); - releaseSlider.setBounds(row.removeFromLeft(knobWidth).reduced(10));*/ - - //waveformSelector.setBounds(20, 20, 120, 30); - - -} +#include "PluginProcessor.h" +#include "PluginEditor.h" +#include "ScopeComponent.h" + +//============================================================================== +NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p) + : AudioProcessorEditor (&p), + audioProcessor (p), + mainScopeComponent(audioProcessor.getAudioBufferQueue()) +{ + auto& tree = audioProcessor.parameters; + + addAndMakeVisible(mainScopeComponent); + + waveformSelector.setModel(&waveformContents); + waveformContents.onSelect = [this](int row) + { + // write to the parameter so voices update safely + audioProcessor.parameters.getParameterAsValue("waveform") = (float)juce::jlimit(0, 3, row); + }; + addAndMakeVisible(waveformSelector); + + // --- Panels --- + adsrComponent.emplace(tree, "adsr", "Amp Env"); + adsrComponent->enableGraphScope([this](float x) { + auto& tree = this->audioProcessor.parameters; + + float A = tree.getParameter("adsr_attack")->getValue(); + float D = tree.getParameter("adsr_decay")->getValue(); + float S = tree.getParameter("adsr_sustain")->getValue(); + float R = tree.getParameter("adsr_release")->getValue(); + + const float sustainLen = 1.0f; + const float total = A + D + sustainLen + R; + A /= total; D /= total; R /= total; + + float m = 0.0f, c = 0.0f; + if (x < A) { m = 1.0f / A; c = 0.0f; } + else if (x < A + D) { m = (S - 1.0f) / D; c = 1.0f - m * A; } + else if (x < A + D + (sustainLen / total)) { m = 0.0f; c = S; } + else { m = (S / -R); c = -m; } + return m * x + c; + }); + addAndMakeVisible(*adsrComponent); + + chorusComponent.emplace(tree, "chorus", "Chorus"); + chorusComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue()); + addAndMakeVisible(*chorusComponent); + + delayComponent.emplace(tree, "delay", "Delay"); + delayComponent->enableSampleScope(audioProcessor.getDelayAudioBufferQueue()); + addAndMakeVisible(*delayComponent); + + reverbComponent.emplace(tree, "reverb", "Reverb"); + reverbComponent->enableSampleScope(audioProcessor.getReverbAudioBufferQueue()); + addAndMakeVisible(*reverbComponent); + + eqComponent.emplace(tree, "EQ"); + addAndMakeVisible(*eqComponent); + + flangerComponent.emplace(tree, "flanger", "Flanger"); + flangerComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue()); + addAndMakeVisible(*flangerComponent); + + distortionComponent.emplace(tree, "distortion", "Distortion"); + distortionComponent->enableSampleScope(audioProcessor.getDistortionAudioBufferQueue()); + addAndMakeVisible(*distortionComponent); + + filterComponent.emplace(tree, "filter", "Filter"); + filterComponent->enableSampleScope(audioProcessor.getFilterAudioBufferQueue()); + addAndMakeVisible(*filterComponent); + + filterEnvComponent.emplace(tree, "fenv", "Filter Env"); + filterEnvComponent->enableGraphScope([this](float x) { + auto& tree = this->audioProcessor.parameters; + + float A = tree.getParameter("fenv_attack")->getValue(); + float D = tree.getParameter("fenv_decay")->getValue(); + float S = tree.getParameter("fenv_sustain")->getValue(); + float R = tree.getParameter("fenv_release")->getValue(); + + const float sustainLen = 1.0f; + const float total = A + D + sustainLen + R; + A /= total; D /= total; R /= total; + + float m = 0.0f, c = 0.0f; + if (x < A) { m = 1.0f / A; c = 0.0f; } + else if (x < A + D) { m = (S - 1.0f) / D; c = 1.0f - m * A; } + else if (x < A + D + (sustainLen / total)) { m = 0.0f; c = S; } + else { m = (S / -R); c = -m; } + return m * x + c; + }); + addAndMakeVisible(*filterEnvComponent); + + // Master fader + label + addAndMakeVisible(masterLevelSlider); + masterLevelLabel.setText("Master", juce::dontSendNotification); + { + juce::Font f; f.setHeight(12.0f); f.setBold(true); + masterLevelLabel.setFont(f); + } + masterLevelLabel.setJustificationType(juce::Justification::centred); + addAndMakeVisible(masterLevelLabel); + + // Blank placeholder + addAndMakeVisible(blankPanel); + + // Attach master parameter + gainAttachment = std::make_unique( + audioProcessor.parameters, "master", masterLevelSlider.slider); + + setSize(1400, 720); +} + +//============================================================================== +NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor() = default; + +//============================================================================== +void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g) +{ + g.fillAll(getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); +} + +//============================================================================== +void NeuralSynthAudioProcessorEditor::resized() +{ + auto bounds = getLocalBounds().reduced(16); + + juce::Grid grid; + + grid.templateRows = { + juce::Grid::TrackInfo(juce::Grid::Fr(20)), // scope row + juce::Grid::TrackInfo(juce::Grid::Fr(40)), // row 1 + juce::Grid::TrackInfo(juce::Grid::Fr(40)) // row 2 + }; + + // 6 columns: 5 content + 1 sidebar (waveform+master) + grid.templateColumns = { + juce::Grid::TrackInfo(juce::Grid::Fr(18)), + juce::Grid::TrackInfo(juce::Grid::Fr(18)), + juce::Grid::TrackInfo(juce::Grid::Fr(18)), + juce::Grid::TrackInfo(juce::Grid::Fr(18)), + juce::Grid::TrackInfo(juce::Grid::Fr(18)), + juce::Grid::TrackInfo(juce::Grid::Fr(10)) + }; + + // Row 0 + grid.items.add(juce::GridItem(mainScopeComponent) + .withArea(juce::GridItem::Span(1), juce::GridItem::Span(5))); + grid.items.add(juce::GridItem(waveformSelector) + .withArea(juce::GridItem::Span(1), juce::GridItem::Span(1))); + + // Row 1 + grid.items.add(juce::GridItem(*adsrComponent)); + grid.items.add(juce::GridItem(*chorusComponent)); + grid.items.add(juce::GridItem(*delayComponent)); + grid.items.add(juce::GridItem(*reverbComponent)); + grid.items.add(juce::GridItem(*eqComponent)); + grid.items.add(juce::GridItem(masterLevelLabel)); + + // Row 2 + grid.items.add(juce::GridItem(*flangerComponent)); + grid.items.add(juce::GridItem(*distortionComponent)); + grid.items.add(juce::GridItem(*filterComponent)); + grid.items.add(juce::GridItem(*filterEnvComponent)); + grid.items.add(juce::GridItem(blankPanel)); + grid.items.add(juce::GridItem(masterLevelSlider)); + + grid.performLayout(bounds); +} diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 7a78e19..5a839ba 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -1,368 +1,341 @@ -/* - ============================================================================== - - This file contains the basic framework code for a JUCE plugin editor. - - ============================================================================== -*/ - -#pragma once - -#include -#include "PluginProcessor.h" -#include "GraphComponent.h" -#include "ScopeComponent.h" - - -class ScopeSliderComponent : public juce::Component { - static const int fontSize = 11; - -public: - ScopeSliderComponent(juce::AudioProcessorValueTreeState& tree, const std::string paramGroup) { - 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::NotificationType::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) - { - label->setFont(juce::Font((float)fontSize, juce::Font::bold)); - label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); - label->setJustificationType(juce::Justification::centred); - //label->setBoundsToFit() - addAndMakeVisible(*label); - } - } - - 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 - { - //juce::Random rng; - //g.fillAll(juce::Colour::fromFloatRGBA(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1.0f)); - - g.fillAll(juce::Colours::darkgrey); - - g.setColour(juce::Colours::white); - g.drawRect(getLocalBounds()); - } - - void resized() override { - juce::Grid grid; - grid.templateRows = { juce::Grid::TrackInfo(juce::Grid::Fr(50)), - juce::Grid::TrackInfo(juce::Grid::Fr(30)), - juce::Grid::TrackInfo(juce::Grid::Fr(10)), - juce::Grid::TrackInfo(juce::Grid::Fr(10)) - }; - - grid.templateColumns.resize(sliders.size()); - for (int i = 0; i < sliders.size(); i++) { - grid.templateColumns.getReference(i) = juce::Grid::TrackInfo(juce::Grid::Fr(1)); - } - - std::optional scopeGridItem; - scopeGridItem.emplace(useGraphScope ? juce::GridItem(*graphScope).withArea({}, juce::GridItem::Span(sliders.size())) - : juce::GridItem(*scope).withArea({}, juce::GridItem::Span(sliders.size()))); - - grid.items.resize(1 + 2 * sliders.size()); - - grid.items.getReference(0) = *scopeGridItem; - - for (int i = 0; i < sliders.size(); i++) { - grid.items.getReference(i + 1) = juce::GridItem(*sliders[i]); - } - - for (int i = 0; i < sliders.size(); i++) { - grid.items.getReference(i + sliders.size() + 1) = juce::GridItem(*labels[i]); - }; - - auto bounds = getLocalBounds().reduced(10); - grid.performLayout(bounds); - } - - bool useGraphScope{ false }; - std::optional > scope; - std::optional > graphScope; - - std::vector > sliders; - std::vector > labels; - std::vector > attachments; -}; - - -class EqualizerComponent : public juce::Component { - static const int fontSize = 11; - -public: - EqualizerComponent(juce::AudioProcessorValueTreeState& tree) { - setupSlider(lowGainSlider); - setupSlider(midGainSlider); - setupSlider(highGainSlider); - - setupLabel(lowGainLabel, "L"); - setupLabel(midGainLabel, "M"); - setupLabel(highGainLabel, "H"); - - lowGainAttachment = std::make_unique(tree, "lowEQ", lowGainSlider); - midGainAttachment = std::make_unique(tree, "midEQ", midGainSlider); - highGainAttachment = std::make_unique(tree, "highEQ", highGainSlider); - } - - 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) { - lbl.setFont(juce::Font((float)fontSize, juce::Font::bold)); - lbl.setColour(juce::Label::textColourId, juce::Colours::lightgreen); - lbl.setJustificationType(juce::Justification::centred); - lbl.setText(txt, juce::NotificationType::dontSendNotification); - - addAndMakeVisible(lbl); - } - -private: - void paint(juce::Graphics& g) override - { - g.fillAll(juce::Colours::darkgrey); - g.setColour(juce::Colours::white); - g.drawRect(getLocalBounds()); - } - - void resized() override { - 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 - }; - - auto bounds = getLocalBounds().reduced(10); - grid.performLayout(bounds); - } - - juce::Slider lowGainSlider, midGainSlider, highGainSlider; - juce::Label lowGainLabel, midGainLabel, highGainLabel; - std::unique_ptr lowGainAttachment, midGainAttachment, highGainAttachment; -}; - -struct WaveformSelectorContents final : public juce::ListBoxModel -{ - // The following methods implement the necessary virtual functions from ListBoxModel, - // telling the listbox how many rows there are, painting them, etc. - 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)); - g.setFont((float)height * 0.7f); - - g.drawText(waves[rowNumber], 5, 0, width, height, juce::Justification::centredLeft, true); - } - - std::vector waves = { "Sine", "Saw", "Square", "Triangle" }; -}; - -class MasterVolumeComponent : public juce::Component -{ -public: - MasterVolumeComponent() - { - slider.setSliderStyle(juce::Slider::LinearBarVertical); - slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20); // Optional - addAndMakeVisible(slider); - } - - void resized() override - { - auto padded = getLocalBounds().reduced(30); // Adjust padding here - slider.setBounds(padded); - } - - juce::Slider slider; -}; - -//============================================================================== -/** -*/ -class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor -{ -public: - NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&); - ~NeuralSynthAudioProcessorEditor() override; - - - //============================================================================== - void paint (juce::Graphics&) override; - void resized() override; - -private: - // This reference is provided as a quick way for your editor to - // access the processor object that created it. - NeuralSynthAudioProcessor& audioProcessor; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor) - - juce::ListBox waveformSelector; - WaveformSelectorContents waveformContents; - - //std::unique_ptr waveformAttachment; - - - //============================================================================== - // ADSR - - /*void createADSR(int xCoord, int yCoord); - juce::Slider attackSlider, decaySlider, sustainSlider, releaseSlider; - juce::Label attackLabel, decayLabel, sustainLabel, releaseLabel; - - std::unique_ptr attackAttachment; - std::unique_ptr decayAttachment; - std::unique_ptr sustainAttachment; - std::unique_ptr releaseAttachment;*/ - - - //============================================================================== - - std::optional adsrComponent; - std::optional chorusComponent; - std::optional delayComponent; - std::optional reverbComponent; - - - std::optional flangerComponent; - std::optional distortionComponent; - std::optional filterComponent; - - - - - /*//============================================================================== - // Chorus - - void createChorus(int xCoord, int yCoord); - juce::Slider chorusRateSlider, chorusDepthSlider, chorusCentreSlider, chorusFeedbackSlider, chorusMixSlider; - juce::Label chorusRateLabel, chorusDepthLabel, chorusCentreLabel, chorusFeedbackLabel, chorusMixLabel; - - std::unique_ptr chorusRateAttachment; - std::unique_ptr chorusDepthAttachment; - std::unique_ptr chorusCentreAttachment; - std::unique_ptr chorusFeedbackAttachment; - std::unique_ptr chorusMixAttachment; - - //============================================================================== - - //============================================================================== - // Delay - - void createDelay(int xCoord, int yCoord); - juce::Slider delayDelaySlider; - juce::Label delayDelayLabel; - - std::unique_ptr delayDelayAttachment; - - //============================================================================== - - - //============================================================================== - // Reverb - - void createReverb(int xCoord, int yCoord); - juce::Slider reverbRoomSizeSlider, reverbDampingSlider, reverbWetLevelSlider, reverbDryLevelSlider, reverbWidthSlider, - reverbFreezeModeSlider; - juce::Label reverbRoomSizeLabel, reverbDampingLabel, reverbWetLevelLabel, reverbDryLevelLabel, reverbWidthLabel, - reverbFreezeModeLabel; - - std::unique_ptr reverbRoomSizeAttachment; - std::unique_ptr reverbDampingAttachment; - std::unique_ptr reverbWetLevelAttachment; - std::unique_ptr reverbDryLevelAttachment; - std::unique_ptr reverbWidthAttachment; - std::unique_ptr reverbFreezeModeAttachment;*/ - - //============================================================================== - // Master - - MasterVolumeComponent masterLevelSlider; - juce::Label masterLevelLabel; - - //============================================================================== - - //============================================================================== - // EQ - - //void updateEQFromSliders(); - std::optional eqComponent; - - - //============================================================================== - - //============================================================================== - // Master - - juce::Slider gainSlider; - juce::Label gainLabel; - - std::unique_ptr gainAttachment; - - //============================================================================== - - //juce::MidiKeyboardState midiKeyboardState; - //juce::MidiKeyboardComponent midiKeyboardComponent{ midiKeyboardState, juce::MidiKeyboardComponent::horizontalKeyboard }; - ScopeComponent mainScopeComponent; - /*ScopeComponent chorusScopeComponent; - ScopeComponent delayScopeComponent; - ScopeComponent reverbScopeComponent; - - GraphComponent adsrGraph;*/ - -}; +#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; +};