Upload files to "Source"

This commit is contained in:
ed
2025-10-22 16:58:01 +00:00
parent 9d232cb2dd
commit 8ecb5e774d
2 changed files with 511 additions and 772 deletions

View File

@@ -1,404 +1,170 @@
/* #include "PluginProcessor.h"
============================================================================== #include "PluginEditor.h"
#include "ScopeComponent.h"
This file contains the basic framework code for a JUCE plugin editor.
//==============================================================================
============================================================================== NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p)
*/ : AudioProcessorEditor (&p),
audioProcessor (p),
#include "PluginProcessor.h" mainScopeComponent(audioProcessor.getAudioBufferQueue())
#include "PluginEditor.h" {
#include "ScopeComponent.h" auto& tree = audioProcessor.parameters;
//============================================================================== addAndMakeVisible(mainScopeComponent);
NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p), mainScopeComponent(audioProcessor.getAudioBufferQueue()) waveformSelector.setModel(&waveformContents);
{ waveformContents.onSelect = [this](int row)
auto& tree = audioProcessor.parameters; {
// write to the parameter so voices update safely
//auto area = getLocalBounds(); audioProcessor.parameters.getParameterAsValue("waveform") = (float)juce::jlimit(0, 3, row);
//mainScopeComponent.setBounds(5, 5, 800, 200); };
// scopeComponent.setSize(800, 200); addAndMakeVisible(waveformSelector);
addAndMakeVisible(mainScopeComponent); // --- Panels ---
adsrComponent.emplace(tree, "adsr", "Amp Env");
waveformSelector.setModel(&waveformContents); adsrComponent->enableGraphScope([this](float x) {
auto& tree = this->audioProcessor.parameters;
addAndMakeVisible(waveformSelector);
float A = tree.getParameter("adsr_attack")->getValue();
float D = tree.getParameter("adsr_decay")->getValue();
chorusComponent.emplace(tree, "chorus"); float S = tree.getParameter("adsr_sustain")->getValue();
float R = tree.getParameter("adsr_release")->getValue();
chorusComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue());
addAndMakeVisible(*chorusComponent); const float sustainLen = 1.0f;
const float total = A + D + sustainLen + R;
delayComponent.emplace(tree, "delay"); A /= total; D /= total; R /= total;
delayComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue());
addAndMakeVisible(*delayComponent); float m = 0.0f, c = 0.0f;
if (x < A) { m = 1.0f / A; c = 0.0f; }
reverbComponent.emplace(tree, "reverb"); 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; }
reverbComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue()); else { m = (S / -R); c = -m; }
addAndMakeVisible(*reverbComponent); return m * x + c;
});
addAndMakeVisible(*adsrComponent);
adsrComponent.emplace(tree, "adsr");
chorusComponent.emplace(tree, "chorus", "Chorus");
adsrComponent->enableGraphScope([this](float x) { chorusComponent->enableSampleScope(audioProcessor.getChorusAudioBufferQueue());
auto& tree = this->audioProcessor.parameters; addAndMakeVisible(*chorusComponent);
float attackValue = tree.getParameter("adsr_attack")->getValue(); delayComponent.emplace(tree, "delay", "Delay");
float decayValue = tree.getParameter("adsr_decay")->getValue(); delayComponent->enableSampleScope(audioProcessor.getDelayAudioBufferQueue());
float sustainValue = tree.getParameter("adsr_sustain")->getValue(); addAndMakeVisible(*delayComponent);
float releaseValue = tree.getParameter("adsr_release")->getValue();
reverbComponent.emplace(tree, "reverb", "Reverb");
float sustainLength = 1.0f; reverbComponent->enableSampleScope(audioProcessor.getReverbAudioBufferQueue());
float totalTime = attackValue + decayValue + sustainLength + releaseValue; addAndMakeVisible(*reverbComponent);
attackValue /= totalTime; eqComponent.emplace(tree, "EQ");
decayValue /= totalTime; addAndMakeVisible(*eqComponent);
sustainLength /= totalTime;
releaseValue /= totalTime; flangerComponent.emplace(tree, "flanger", "Flanger");
flangerComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue());
float m, c; addAndMakeVisible(*flangerComponent);
if (x < attackValue)
{ distortionComponent.emplace(tree, "distortion", "Distortion");
m = (1.0f / attackValue); distortionComponent->enableSampleScope(audioProcessor.getDistortionAudioBufferQueue());
c = 0; addAndMakeVisible(*distortionComponent);
}
else if (x < (attackValue + decayValue)) { filterComponent.emplace(tree, "filter", "Filter");
m = (sustainValue - 1.0f) / decayValue; filterComponent->enableSampleScope(audioProcessor.getFilterAudioBufferQueue());
c = 1.0f - m * attackValue; addAndMakeVisible(*filterComponent);
}
else if (x < (attackValue + decayValue + sustainLength)) { filterEnvComponent.emplace(tree, "fenv", "Filter Env");
m = 0.0f; filterEnvComponent->enableGraphScope([this](float x) {
c = sustainValue; auto& tree = this->audioProcessor.parameters;
}
else { float A = tree.getParameter("fenv_attack")->getValue();
m = (sustainValue / -releaseValue); float D = tree.getParameter("fenv_decay")->getValue();
c = -m; float S = tree.getParameter("fenv_sustain")->getValue();
} float R = tree.getParameter("fenv_release")->getValue();
return m * x + c; const float sustainLen = 1.0f;
}); const float total = A + D + sustainLen + R;
addAndMakeVisible(*adsrComponent); A /= total; D /= total; R /= total;
//createADSR(5, 250); float m = 0.0f, c = 0.0f;
//createEQ(); 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; }
addAndMakeVisible(masterLevelSlider); else if (x < A + D + (sustainLen / total)) { m = 0.0f; c = S; }
else { m = (S / -R); c = -m; }
eqComponent.emplace(tree); return m * x + c;
addAndMakeVisible(*eqComponent); });
addAndMakeVisible(*filterEnvComponent);
// Attach to parameter
//waveformAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>( // Master fader + label
// audioProcessor.parameters, "waveform", waveformSelector); addAndMakeVisible(masterLevelSlider);
masterLevelLabel.setText("Master", juce::dontSendNotification);
//attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>( {
// tree, sliderDetail.name, *sliders.back())); juce::Font f; f.setHeight(12.0f); f.setBold(true);
masterLevelLabel.setFont(f);
gainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>( }
audioProcessor.parameters, "master", masterLevelSlider.slider masterLevelLabel.setJustificationType(juce::Justification::centred);
); addAndMakeVisible(masterLevelLabel);
// Blank placeholder
flangerComponent.emplace(tree, "flanger"); addAndMakeVisible(blankPanel);
flangerComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue());
addAndMakeVisible(*flangerComponent); // Attach master parameter
gainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
distortionComponent.emplace(tree, "distortion"); audioProcessor.parameters, "master", masterLevelSlider.slider);
distortionComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue());
addAndMakeVisible(*distortionComponent); setSize(1400, 720);
}
filterComponent.emplace(tree, "filter");
filterComponent->enableSampleScope(audioProcessor.getFilterAudioBufferQueue()); //==============================================================================
addAndMakeVisible(*filterComponent); NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor() = default;
//addAndMakeVisible(midiKeyboardComponent); //==============================================================================
void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g)
{
//scopeComponent.setSize(area.getWidth(), area.getHeight()); g.fillAll(getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
}
//midiKeyboardComponent.setMidiChannel(2);
//midiKeyboardState.addListener(&audioProcessor.getMidiMessageCollector()); //==============================================================================
void NeuralSynthAudioProcessorEditor::resized()
//midiKeyboardComponent.setBounds(area.removeFromTop(80).reduced(8)); {
//midiKeyboardComponent.setTopLeftPosition(8, 420); auto bounds = getLocalBounds().reduced(16);
// Make sure that before the constructor has finished, you've set the juce::Grid grid;
// editor's size to whatever you need it to be.
setSize(1400, 700); 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
//============================================================================== };
NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor()
{ // 6 columns: 5 content + 1 sidebar (waveform+master)
} grid.templateColumns = {
juce::Grid::TrackInfo(juce::Grid::Fr(18)),
/*void NeuralSynthAudioProcessorEditor::updateEQFromSliders() juce::Grid::TrackInfo(juce::Grid::Fr(18)),
{ juce::Grid::TrackInfo(juce::Grid::Fr(18)),
using Coefficients = juce::dsp::IIR::Coefficients<float>; juce::Grid::TrackInfo(juce::Grid::Fr(18)),
juce::Grid::TrackInfo(juce::Grid::Fr(18)),
auto& low = audioProcessor.getProcess.get<0>(); juce::Grid::TrackInfo(juce::Grid::Fr(10))
auto& mid = audioProcessor.eqChain.get<1>(); };
auto& high = audioProcessor.eqChain.get<2>();
// Row 0
low.coefficients = Coefficients::makeLowShelf(audioProcessor.sampleRate, 100.0f, 0.707f, grid.items.add(juce::GridItem(mainScopeComponent)
juce::Decibels::decibelsToGain(lowGainSlider.getValue())); .withArea(juce::GridItem::Span(1), juce::GridItem::Span(5)));
mid.coefficients = Coefficients::makePeakFilter(audioProcessor.sampleRate, 1000.0f, 0.707f, grid.items.add(juce::GridItem(waveformSelector)
juce::Decibels::decibelsToGain(midGainSlider.getValue())); .withArea(juce::GridItem::Span(1), juce::GridItem::Span(1)));
high.coefficients = Coefficients::makeHighShelf(audioProcessor.sampleRate, 8000.0f, 0.707f,
juce::Decibels::decibelsToGain(highGainSlider.getValue())); // Row 1
}*/ grid.items.add(juce::GridItem(*adsrComponent));
grid.items.add(juce::GridItem(*chorusComponent));
//============================================================================== grid.items.add(juce::GridItem(*delayComponent));
/*void NeuralSynthAudioProcessorEditor::createADSR(int xCoord, int yCoord) { grid.items.add(juce::GridItem(*reverbComponent));
adsrGraph.setFunction([this](float x) { grid.items.add(juce::GridItem(*eqComponent));
auto& tree = this->audioProcessor.parameters; grid.items.add(juce::GridItem(masterLevelLabel));
float attackValue = tree.getParameter("attack")->getValue(); // Row 2
float decayValue = tree.getParameter("decay")->getValue(); grid.items.add(juce::GridItem(*flangerComponent));
float sustainValue = tree.getParameter("sustain")->getValue(); grid.items.add(juce::GridItem(*distortionComponent));
float releaseValue = tree.getParameter("release")->getValue(); grid.items.add(juce::GridItem(*filterComponent));
grid.items.add(juce::GridItem(*filterEnvComponent));
float sustainLength = 1.0f; grid.items.add(juce::GridItem(blankPanel));
float totalTime = attackValue + decayValue + sustainLength + releaseValue; grid.items.add(juce::GridItem(masterLevelSlider));
attackValue /= totalTime; grid.performLayout(bounds);
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<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "attack", attackSlider);
decayAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "decay", decaySlider);
sustainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "sustain", sustainSlider);
releaseAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(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<juce::AudioProcessorValueTreeState::SliderAttachment>(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<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "reverbRoomSize", reverbRoomSizeSlider);
reverbDampingAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "reverbDamping", reverbDampingSlider);
reverbWetLevelAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "reverbWetLevel", reverbWetLevelSlider);
reverbDryLevelAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "reverbDryLevel", reverbDryLevelSlider);
reverbWidthAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "reverbWidth", reverbWidthSlider);
reverbFreezeModeAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(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);
}

View File

@@ -1,368 +1,341 @@
/* #pragma once
==============================================================================
#include <JuceHeader.h>
This file contains the basic framework code for a JUCE plugin editor. #include "PluginProcessor.h"
#include "GraphComponent.h"
============================================================================== #include "ScopeComponent.h"
*/
//============================== ScopeSliderComponent ==========================
#pragma once // A generic panel: optional scope/graph + rotary sliders + labels.
// Adds a per-panel "On" toggle (bound to "<group>_on").
#include <JuceHeader.h> class ScopeSliderComponent : public juce::Component {
#include "PluginProcessor.h" static const int fontSize = 11;
#include "GraphComponent.h"
#include "ScopeComponent.h" public:
ScopeSliderComponent(juce::AudioProcessorValueTreeState& tree,
const std::string paramGroup,
class ScopeSliderComponent : public juce::Component { const juce::String& titleText = {})
static const int fontSize = 11; : paramGroupId(paramGroup), treeRef(tree)
{
public: const auto& sliderDetails = PARAM_SETTINGS.at(paramGroup);
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<juce::Slider>());
for (const auto& [name, sliderDetail] : sliderDetails) { labels.push_back(std::make_unique<juce::Label>());
sliders.push_back(std::make_unique<juce::Slider>()); attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
labels.push_back(std::make_unique<juce::Label>()); tree, paramGroup + "_" + name, *sliders.back()));
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);
labels.back()->setText(sliderDetail.label, juce::NotificationType::dontSendNotification); }
sliders.back()->setRange(sliderDetail.min, sliderDetail.max);
} for (auto& slider : sliders)
{
for (auto& slider : sliders) slider->setSliderStyle(juce::Slider::Rotary);
{ slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20);
slider->setSliderStyle(juce::Slider::Rotary); addAndMakeVisible(*slider);
slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); }
addAndMakeVisible(*slider);
} for (auto& label : labels)
{
for (auto& label : labels) juce::Font f; f.setHeight((float)fontSize); f.setBold(true);
{ label->setFont(f);
label->setFont(juce::Font((float)fontSize, juce::Font::bold)); label->setColour(juce::Label::textColourId, juce::Colours::lightgreen);
label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); label->setJustificationType(juce::Justification::centred);
label->setJustificationType(juce::Justification::centred); addAndMakeVisible(*label);
//label->setBoundsToFit() }
addAndMakeVisible(*label);
} if (titleText.isNotEmpty())
} {
titleLabel.setText(titleText, juce::dontSendNotification);
void enableSampleScope(AudioBufferQueue<float>& audioBufferQueue) { juce::Font tf; tf.setHeight(12.0f); tf.setBold(true);
scope.emplace(audioBufferQueue); titleLabel.setFont(tf);
useGraphScope = false; titleLabel.setJustificationType(juce::Justification::centredLeft);
addAndMakeVisible(*scope); titleLabel.setColour(juce::Label::textColourId, juce::Colours::white);
addAndMakeVisible(titleLabel);
} }
void enableGraphScope(const std::function<float(float)>& func) { // Bypass toggle (per panel), id "<group>_on"
graphScope.emplace(0.0f, 1.0f, 100); bypassButton.setButtonText("On");
graphScope->setFunction(func); bypassButton.setClickingTogglesState(true);
useGraphScope = true; addAndMakeVisible(bypassButton);
addAndMakeVisible(*graphScope); bypassAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(
} treeRef, paramGroupId + "_on", bypassButton);
}
private:
void paint(juce::Graphics& g) override void enableSampleScope(AudioBufferQueue<float>& audioBufferQueue) {
{ scope.emplace(audioBufferQueue);
//juce::Random rng; useGraphScope = false;
//g.fillAll(juce::Colour::fromFloatRGBA(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1.0f)); addAndMakeVisible(*scope);
}
g.fillAll(juce::Colours::darkgrey);
void enableGraphScope(const std::function<float(float)>& func) {
g.setColour(juce::Colours::white); graphScope.emplace(0.0f, 1.0f, 100);
g.drawRect(getLocalBounds()); graphScope->setFunction(func);
} useGraphScope = true;
addAndMakeVisible(*graphScope);
void resized() override { }
juce::Grid grid;
grid.templateRows = { juce::Grid::TrackInfo(juce::Grid::Fr(50)), private:
juce::Grid::TrackInfo(juce::Grid::Fr(30)), void paint(juce::Graphics& g) override
juce::Grid::TrackInfo(juce::Grid::Fr(10)), {
juce::Grid::TrackInfo(juce::Grid::Fr(10)) g.fillAll(juce::Colours::darkgrey);
}; g.setColour(juce::Colours::white);
g.drawRect(getLocalBounds());
grid.templateColumns.resize(sliders.size()); }
for (int i = 0; i < sliders.size(); i++) {
grid.templateColumns.getReference(i) = juce::Grid::TrackInfo(juce::Grid::Fr(1)); void resized() override
} {
// --- Top bar (manual) ----------------------------------------------
std::optional<juce::GridItem> scopeGridItem; auto area = getLocalBounds().reduced(10);
scopeGridItem.emplace(useGraphScope ? juce::GridItem(*graphScope).withArea({}, juce::GridItem::Span(sliders.size())) auto top = area.removeFromTop(22);
: juce::GridItem(*scope).withArea({}, juce::GridItem::Span(sliders.size()))); auto btnW = 46;
bypassButton.setBounds(top.removeFromRight(btnW).reduced(2, 1));
grid.items.resize(1 + 2 * sliders.size()); titleLabel.setBounds(top);
grid.items.getReference(0) = *scopeGridItem; // --- Rest (grid) ----------------------------------------------------
juce::Grid grid;
for (int i = 0; i < sliders.size(); i++) { grid.templateRows = {
grid.items.getReference(i + 1) = juce::GridItem(*sliders[i]); 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
for (int i = 0; i < sliders.size(); i++) { };
grid.items.getReference(i + sliders.size() + 1) = juce::GridItem(*labels[i]);
}; const int n = (int)sliders.size();
grid.templateColumns.resize(n);
auto bounds = getLocalBounds().reduced(10); for (int i = 0; i < n; ++i)
grid.performLayout(bounds); grid.templateColumns.getReference(i) = juce::Grid::TrackInfo(juce::Grid::Fr(1));
}
grid.items.clear();
bool useGraphScope{ false };
std::optional<ScopeComponent<float> > scope; // Row 1: scope/graph only add if constructed
std::optional<GraphComponent<float> > graphScope; if (useGraphScope)
{
std::vector<std::unique_ptr<juce::Slider> > sliders; if (graphScope)
std::vector<std::unique_ptr<juce::Label> > labels; grid.items.add(juce::GridItem(*graphScope)
std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> > attachments; .withArea(juce::GridItem::Span(1), juce::GridItem::Span(n)));
}; else
grid.items.add(juce::GridItem()
.withArea(juce::GridItem::Span(1), juce::GridItem::Span(n)));
class EqualizerComponent : public juce::Component { }
static const int fontSize = 11; else
{
public: if (scope)
EqualizerComponent(juce::AudioProcessorValueTreeState& tree) { grid.items.add(juce::GridItem(*scope)
setupSlider(lowGainSlider); .withArea(juce::GridItem::Span(1), juce::GridItem::Span(n)));
setupSlider(midGainSlider); else
setupSlider(highGainSlider); grid.items.add(juce::GridItem()
.withArea(juce::GridItem::Span(1), juce::GridItem::Span(n)));
setupLabel(lowGainLabel, "L"); }
setupLabel(midGainLabel, "M");
setupLabel(highGainLabel, "H"); // Row 2: sliders
for (int i = 0; i < n; ++i)
lowGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "lowEQ", lowGainSlider); grid.items.add(juce::GridItem(*sliders[(size_t)i]));
midGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "midEQ", midGainSlider);
highGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "highEQ", highGainSlider); // Row 3: labels
} for (int i = 0; i < n; ++i)
grid.items.add(juce::GridItem(*labels[(size_t)i]));
void setupSlider(juce::Slider& slider) {
slider.setRange(-24.0f, 24.0f, 0.1f); grid.performLayout(area);
}
slider.setSliderStyle(juce::Slider::LinearBarVertical);
slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); bool useGraphScope{ false };
std::optional<ScopeComponent<float>> scope;
addAndMakeVisible(slider); std::optional<GraphComponent<float>> graphScope;
}
std::vector<std::unique_ptr<juce::Slider>> sliders;
void setupLabel(juce::Label &lbl, juce::String txt) { std::vector<std::unique_ptr<juce::Label>> labels;
lbl.setFont(juce::Font((float)fontSize, juce::Font::bold)); std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment>> attachments;
lbl.setColour(juce::Label::textColourId, juce::Colours::lightgreen);
lbl.setJustificationType(juce::Justification::centred); juce::ToggleButton bypassButton;
lbl.setText(txt, juce::NotificationType::dontSendNotification); std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> bypassAttachment;
addAndMakeVisible(lbl); juce::Label titleLabel;
}
std::string paramGroupId;
private: juce::AudioProcessorValueTreeState& treeRef;
void paint(juce::Graphics& g) override };
{
g.fillAll(juce::Colours::darkgrey); //============================== EqualizerComponent ============================
g.setColour(juce::Colours::white); // Adds an On/Off toggle bound to "eq_on".
g.drawRect(getLocalBounds()); class EqualizerComponent : public juce::Component {
} static const int fontSize = 11;
void resized() override { public:
juce::Grid grid; explicit EqualizerComponent(juce::AudioProcessorValueTreeState& tree,
grid.templateRows = { const juce::String& titleText = {})
juce::Grid::TrackInfo(juce::Grid::Fr(1)), {
juce::Grid::TrackInfo(juce::Grid::Fr(1)) setupSlider(lowGainSlider);
}; setupSlider(midGainSlider);
setupSlider(highGainSlider);
grid.templateColumns = {
juce::Grid::TrackInfo(juce::Grid::Fr(1)), setupLabel(lowGainLabel, "L");
juce::Grid::TrackInfo(juce::Grid::Fr(1)), setupLabel(midGainLabel, "M");
juce::Grid::TrackInfo(juce::Grid::Fr(1)) setupLabel(highGainLabel, "H");
};
if (titleText.isNotEmpty())
grid.items = { {
lowGainSlider, midGainSlider, highGainSlider, titleLabel.setText(titleText, juce::dontSendNotification);
lowGainLabel, midGainLabel, highGainLabel juce::Font tf; tf.setHeight(13.0f); tf.setBold(true);
}; titleLabel.setFont(tf);
titleLabel.setJustificationType(juce::Justification::centredLeft);
auto bounds = getLocalBounds().reduced(10); titleLabel.setColour(juce::Label::textColourId, juce::Colours::white);
grid.performLayout(bounds); addAndMakeVisible(titleLabel);
} }
juce::Slider lowGainSlider, midGainSlider, highGainSlider; // Attachments
juce::Label lowGainLabel, midGainLabel, highGainLabel; lowGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "lowEQ", lowGainSlider);
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> lowGainAttachment, midGainAttachment, highGainAttachment; midGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "midEQ", midGainSlider);
}; highGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "highEQ", highGainSlider);
struct WaveformSelectorContents final : public juce::ListBoxModel // EQ bypass toggle
{ bypassButton.setButtonText("On");
// The following methods implement the necessary virtual functions from ListBoxModel, bypassButton.setClickingTogglesState(true);
// telling the listbox how many rows there are, painting them, etc. addAndMakeVisible(bypassButton);
int getNumRows() override bypassAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(tree, "eq_on", bypassButton);
{ }
return 4;
} private:
void setupSlider(juce::Slider& slider) {
void paintListBoxItem(int rowNumber, juce::Graphics& g, slider.setRange(-24.0f, 24.0f, 0.1f);
int width, int height, bool rowIsSelected) override slider.setSliderStyle(juce::Slider::LinearBarVertical);
{ slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20);
if (rowIsSelected) addAndMakeVisible(slider);
g.fillAll(juce::Colours::lightblue); }
g.setColour(juce::LookAndFeel::getDefaultLookAndFeel().findColour(juce::Label::textColourId)); void setupLabel(juce::Label& lbl, juce::String txt) {
g.setFont((float)height * 0.7f); juce::Font f; f.setHeight((float)fontSize); f.setBold(true);
lbl.setFont(f);
g.drawText(waves[rowNumber], 5, 0, width, height, juce::Justification::centredLeft, true); lbl.setColour(juce::Label::textColourId, juce::Colours::lightgreen);
} lbl.setJustificationType(juce::Justification::centred);
lbl.setText(txt, juce::dontSendNotification);
std::vector<juce::String> waves = { "Sine", "Saw", "Square", "Triangle" }; addAndMakeVisible(lbl);
}; }
class MasterVolumeComponent : public juce::Component void paint(juce::Graphics& g) override {
{ g.fillAll(juce::Colours::darkgrey);
public: g.setColour(juce::Colours::white);
MasterVolumeComponent() g.drawRect(getLocalBounds());
{ }
slider.setSliderStyle(juce::Slider::LinearBarVertical);
slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20); // Optional void resized() override {
addAndMakeVisible(slider); auto area = getLocalBounds().reduced(10);
} auto top = area.removeFromTop(22);
auto btnW = 46;
void resized() override bypassButton.setBounds(top.removeFromRight(btnW).reduced(2, 1));
{ titleLabel.setBounds(top);
auto padded = getLocalBounds().reduced(30); // Adjust padding here
slider.setBounds(padded); juce::Grid grid;
} grid.templateRows = {
juce::Grid::TrackInfo(juce::Grid::Fr(1)),
juce::Slider slider; 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))
class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor };
{
public: grid.items = {
NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&); lowGainSlider, midGainSlider, highGainSlider,
~NeuralSynthAudioProcessorEditor() override; lowGainLabel, midGainLabel, highGainLabel
};
//============================================================================== grid.performLayout(area);
void paint (juce::Graphics&) override; }
void resized() override;
juce::Slider lowGainSlider, midGainSlider, highGainSlider;
private: juce::Label lowGainLabel, midGainLabel, highGainLabel;
// This reference is provided as a quick way for your editor to std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> lowGainAttachment, midGainAttachment, highGainAttachment;
// access the processor object that created it.
NeuralSynthAudioProcessor& audioProcessor; juce::ToggleButton bypassButton;
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> bypassAttachment;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor)
juce::Label titleLabel;
juce::ListBox waveformSelector; };
WaveformSelectorContents waveformContents;
//============================== Waveform List Model ===========================
//std::unique_ptr<juce::AudioProcessorValueTreeState::ListBoxAttachment> waveformAttachment; struct WaveformSelectorContents final : public juce::ListBoxModel
{
int getNumRows() override { return 4; }
//==============================================================================
// ADSR void paintListBoxItem(int rowNumber, juce::Graphics& g,
int width, int height, bool rowIsSelected) override
/*void createADSR(int xCoord, int yCoord); {
juce::Slider attackSlider, decaySlider, sustainSlider, releaseSlider; if (rowIsSelected) g.fillAll(juce::Colours::lightblue);
juce::Label attackLabel, decayLabel, sustainLabel, releaseLabel; g.setColour(juce::LookAndFeel::getDefaultLookAndFeel()
.findColour(juce::Label::textColourId));
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> attackAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> decayAttachment; juce::Font f; f.setHeight((float)height * 0.7f);
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> sustainAttachment; g.setFont(f);
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> releaseAttachment;*/ g.drawText(waves[(size_t)rowNumber], 5, 0, width, height,
juce::Justification::centredLeft, true);
}
//==============================================================================
void selectedRowsChanged (int lastRowSelected) override
std::optional<ScopeSliderComponent> adsrComponent; {
std::optional<ScopeSliderComponent> chorusComponent; if (onSelect) onSelect(lastRowSelected);
std::optional<ScopeSliderComponent> delayComponent; }
std::optional<ScopeSliderComponent> reverbComponent;
std::function<void (int)> onSelect;
std::optional<ScopeSliderComponent> flangerComponent; std::vector<juce::String> waves { "Sine", "Saw", "Square", "Triangle" };
std::optional<ScopeSliderComponent> distortionComponent; };
std::optional<ScopeSliderComponent> filterComponent;
//============================== MasterVolumeComponent =========================
class MasterVolumeComponent : public juce::Component
{
public:
/*//============================================================================== MasterVolumeComponent()
// Chorus {
slider.setSliderStyle(juce::Slider::LinearBarVertical);
void createChorus(int xCoord, int yCoord); slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20);
juce::Slider chorusRateSlider, chorusDepthSlider, chorusCentreSlider, chorusFeedbackSlider, chorusMixSlider; addAndMakeVisible(slider);
juce::Label chorusRateLabel, chorusDepthLabel, chorusCentreLabel, chorusFeedbackLabel, chorusMixLabel; }
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusRateAttachment; void resized() override
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusDepthAttachment; {
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusCentreAttachment; slider.setBounds(getLocalBounds().reduced(30));
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusFeedbackAttachment; }
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusMixAttachment;
juce::Slider slider;
//============================================================================== };
//============================================================================== //============================== Editor =======================================
// Delay class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor
{
void createDelay(int xCoord, int yCoord); public:
juce::Slider delayDelaySlider; NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&);
juce::Label delayDelayLabel; ~NeuralSynthAudioProcessorEditor() override;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> delayDelayAttachment; void paint (juce::Graphics&) override;
void resized() override;
//==============================================================================
private:
NeuralSynthAudioProcessor& audioProcessor;
//============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor)
// Reverb
juce::ListBox waveformSelector;
void createReverb(int xCoord, int yCoord); WaveformSelectorContents waveformContents;
juce::Slider reverbRoomSizeSlider, reverbDampingSlider, reverbWetLevelSlider, reverbDryLevelSlider, reverbWidthSlider,
reverbFreezeModeSlider; std::optional<ScopeSliderComponent> adsrComponent; // Amp Env
juce::Label reverbRoomSizeLabel, reverbDampingLabel, reverbWetLevelLabel, reverbDryLevelLabel, reverbWidthLabel, std::optional<ScopeSliderComponent> chorusComponent;
reverbFreezeModeLabel; std::optional<ScopeSliderComponent> delayComponent;
std::optional<ScopeSliderComponent> reverbComponent;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbRoomSizeAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbDampingAttachment; std::optional<ScopeSliderComponent> flangerComponent;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbWetLevelAttachment; std::optional<ScopeSliderComponent> distortionComponent;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbDryLevelAttachment; std::optional<ScopeSliderComponent> filterComponent;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbWidthAttachment; std::optional<ScopeSliderComponent> filterEnvComponent; // Filter Env panel
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbFreezeModeAttachment;*/
MasterVolumeComponent masterLevelSlider;
//============================================================================== juce::Label masterLevelLabel;
// Master
std::optional<EqualizerComponent> eqComponent;
MasterVolumeComponent masterLevelSlider;
juce::Label masterLevelLabel; std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gainAttachment;
//============================================================================== ScopeComponent<float> mainScopeComponent;
//============================================================================== juce::Component blankPanel;
// EQ };
//void updateEQFromSliders();
std::optional<EqualizerComponent> eqComponent;
//==============================================================================
//==============================================================================
// Master
juce::Slider gainSlider;
juce::Label gainLabel;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gainAttachment;
//==============================================================================
//juce::MidiKeyboardState midiKeyboardState;
//juce::MidiKeyboardComponent midiKeyboardComponent{ midiKeyboardState, juce::MidiKeyboardComponent::horizontalKeyboard };
ScopeComponent<float> mainScopeComponent;
/*ScopeComponent<float> chorusScopeComponent;
ScopeComponent<float> delayScopeComponent;
ScopeComponent<float> reverbScopeComponent;
GraphComponent<float> adsrGraph;*/
};