Upload files to "Source"
This commit is contained in:
@@ -3,13 +3,14 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct SliderDetail {
|
struct SliderDetail {
|
||||||
std::string label;
|
std::string label;
|
||||||
float min, max, interval, defValue;
|
float min, max, interval, defValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ParamMap = std::unordered_map<std::string, SliderDetail>;
|
using ParamMap = std::vector<std::pair<std::string, SliderDetail>>;
|
||||||
|
|
||||||
// Each SliderDetail: { label, min, max, step, defaultValue }
|
// Each SliderDetail: { label, min, max, step, defaultValue }
|
||||||
const std::unordered_map<std::string, ParamMap> PARAM_SETTINGS = {
|
const std::unordered_map<std::string, ParamMap> PARAM_SETTINGS = {
|
||||||
@@ -67,12 +68,30 @@ const std::unordered_map<std::string, ParamMap> PARAM_SETTINGS = {
|
|||||||
{ "bias", { "Bias", -1.0f, 1.0f, 0.01f, 0.0f } },
|
{ "bias", { "Bias", -1.0f, 1.0f, 0.01f, 0.0f } },
|
||||||
{ "tone", { "Tone", 100.0f, 8000.0f, 10.0f, 3000.0f } },
|
{ "tone", { "Tone", 100.0f, 8000.0f, 10.0f, 3000.0f } },
|
||||||
{ "shape", { "Shape", 0.0f, 2.0f, 1.0f, 0.0f } }
|
{ "shape", { "Shape", 0.0f, 2.0f, 1.0f, 0.0f } }
|
||||||
|
}},
|
||||||
|
{ "wt", {
|
||||||
|
{ "morph", { "Morph", 0.0f, 15.0f, 0.001f, 0.0f } },
|
||||||
|
{ "phase", { "Phase", 0.0f, 1.0f, 0.001f, 0.0f } },
|
||||||
|
{ "bank", { "Bank", 0.0f,255.0f, 1.0f, 0.0f } },
|
||||||
|
{ "lfoRate", { "LFO Rate", 0.01f, 10.0f, 0.001f, 1.0f } },
|
||||||
|
{ "lfoDepth",{ "LFO Depth", 0.0f, 8.0f, 0.001f, 0.0f } },
|
||||||
|
{ "lfoShape",{ "LFO Shape", 0.0f, 3.0f, 1.0f, 0.0f } },
|
||||||
|
{ "level", { "Level", 0.0f, 1.0f, 0.001f, 0.8f } }
|
||||||
|
}},
|
||||||
|
{ "wt2", {
|
||||||
|
{ "morph", { "Morph", 0.0f, 15.0f, 0.001f, 0.0f } },
|
||||||
|
{ "phase", { "Phase", 0.0f, 1.0f, 0.001f, 0.0f } },
|
||||||
|
{ "bank", { "Bank", 0.0f,255.0f, 1.0f, 0.0f } },
|
||||||
|
{ "lfoRate", { "LFO Rate", 0.01f, 10.0f, 0.001f, 1.0f } },
|
||||||
|
{ "lfoDepth",{ "LFO Depth", 0.0f, 8.0f, 0.001f, 0.0f } },
|
||||||
|
{ "lfoShape",{ "LFO Shape", 0.0f, 3.0f, 1.0f, 0.0f } },
|
||||||
|
{ "level", { "Level", 0.0f, 1.0f, 0.001f, 0.0f } }
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NeuralSharedParams
|
struct NeuralSharedParams
|
||||||
{
|
{
|
||||||
std::atomic<int> waveform{ -1 };
|
std::atomic<float>* wtPhase{};
|
||||||
|
|
||||||
// Amp ADSR
|
// Amp ADSR
|
||||||
std::atomic<float>* adsrAttack{};
|
std::atomic<float>* adsrAttack{};
|
||||||
@@ -137,9 +156,28 @@ struct NeuralSharedParams
|
|||||||
std::atomic<float>* filterOn{};
|
std::atomic<float>* filterOn{};
|
||||||
std::atomic<float>* eqOn{};
|
std::atomic<float>* eqOn{};
|
||||||
|
|
||||||
|
// Wavetable
|
||||||
|
std::atomic<float>* wtOn{};
|
||||||
|
std::atomic<float>* wtMorph{};
|
||||||
|
std::atomic<float>* wtBank{};
|
||||||
|
std::atomic<float>* wtLfoRate{};
|
||||||
|
std::atomic<float>* wtLfoDepth{};
|
||||||
|
std::atomic<float>* wtLfoShape{};
|
||||||
|
std::atomic<float>* wtLevel{};
|
||||||
|
|
||||||
// EQ + Master
|
// EQ + Master
|
||||||
std::atomic<float>* lowGainDbls{};
|
std::atomic<float>* lowGainDbls{};
|
||||||
std::atomic<float>* midGainDbls{};
|
std::atomic<float>* midGainDbls{};
|
||||||
std::atomic<float>* highGainDbls{};
|
std::atomic<float>* highGainDbls{};
|
||||||
std::atomic<float>* masterDbls{};
|
std::atomic<float>* masterDbls{};
|
||||||
|
|
||||||
|
// Wavetable Layer B
|
||||||
|
std::atomic<float>* wt2Phase{};
|
||||||
|
std::atomic<float>* wt2On{};
|
||||||
|
std::atomic<float>* wt2Morph{};
|
||||||
|
std::atomic<float>* wt2Bank{};
|
||||||
|
std::atomic<float>* wt2LfoRate{};
|
||||||
|
std::atomic<float>* wt2LfoDepth{};
|
||||||
|
std::atomic<float>* wt2LfoShape{};
|
||||||
|
std::atomic<float>* wt2Level{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "PluginProcessor.h"
|
#include "PluginProcessor.h"
|
||||||
#include "PluginEditor.h"
|
#include "PluginEditor.h"
|
||||||
#include "ScopeComponent.h"
|
#include "ScopeComponent.h"
|
||||||
|
#include "WavetableOsc.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p)
|
NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p)
|
||||||
@@ -12,13 +14,9 @@ NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAud
|
|||||||
|
|
||||||
addAndMakeVisible(mainScopeComponent);
|
addAndMakeVisible(mainScopeComponent);
|
||||||
|
|
||||||
waveformSelector.setModel(&waveformContents);
|
presetMenuButton.setButtonText("Preset");
|
||||||
waveformContents.onSelect = [this](int row)
|
presetMenuButton.onClick = [this] { showPresetMenu(); };
|
||||||
{
|
addAndMakeVisible(presetMenuButton);
|
||||||
// write to the parameter so voices update safely
|
|
||||||
audioProcessor.parameters.getParameterAsValue("waveform") = (float)juce::jlimit(0, 3, row);
|
|
||||||
};
|
|
||||||
addAndMakeVisible(waveformSelector);
|
|
||||||
|
|
||||||
// --- Panels ---
|
// --- Panels ---
|
||||||
adsrComponent.emplace(tree, "adsr", "Amp Env");
|
adsrComponent.emplace(tree, "adsr", "Amp Env");
|
||||||
@@ -92,6 +90,76 @@ NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAud
|
|||||||
});
|
});
|
||||||
addAndMakeVisible(*filterEnvComponent);
|
addAndMakeVisible(*filterEnvComponent);
|
||||||
|
|
||||||
|
auto configureBankSlider = [](juce::Slider* slider)
|
||||||
|
{
|
||||||
|
if (slider == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& presets = WT::FactoryLibrary::get();
|
||||||
|
std::vector<juce::String> names;
|
||||||
|
names.reserve(presets.size());
|
||||||
|
for (const auto& preset : presets)
|
||||||
|
names.push_back(preset.name);
|
||||||
|
|
||||||
|
const double maxIndex = names.empty() ? 0.0 : (double) (names.size() - 1);
|
||||||
|
slider->setRange (0.0, maxIndex, 1.0);
|
||||||
|
slider->setNumDecimalPlacesToDisplay(0);
|
||||||
|
|
||||||
|
slider->textFromValueFunction = [names](double value)
|
||||||
|
{
|
||||||
|
if (names.empty())
|
||||||
|
return juce::String ((int) std::lround (value));
|
||||||
|
|
||||||
|
const int idx = juce::jlimit (0, (int) names.size() - 1, (int) std::lround (value));
|
||||||
|
return names[(size_t) idx];
|
||||||
|
};
|
||||||
|
|
||||||
|
slider->valueFromTextFunction = [names](const juce::String& text)
|
||||||
|
{
|
||||||
|
if (! names.empty())
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < names.size(); ++i)
|
||||||
|
if (text.equalsIgnoreCase (names[i]))
|
||||||
|
return (double) i;
|
||||||
|
}
|
||||||
|
return text.getDoubleValue();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
auto configureShapeSlider = [](juce::Slider* slider)
|
||||||
|
{
|
||||||
|
if (slider == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static const std::array<juce::String, 4> shapeNames { "Sine", "Triangle", "Ramp Up", "Ramp Down" };
|
||||||
|
slider->setNumDecimalPlacesToDisplay(0);
|
||||||
|
slider->textFromValueFunction = [](double value)
|
||||||
|
{
|
||||||
|
const int idx = juce::jlimit (0, (int) shapeNames.size() - 1, (int) std::lround (value));
|
||||||
|
return shapeNames[(size_t) idx];
|
||||||
|
};
|
||||||
|
slider->valueFromTextFunction = [](const juce::String& text)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < shapeNames.size(); ++i)
|
||||||
|
if (text.equalsIgnoreCase (shapeNames[i]))
|
||||||
|
return (double) i;
|
||||||
|
return text.getDoubleValue();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
wtComponent.emplace(tree, "wt", "Layer A");
|
||||||
|
configureBankSlider (wtComponent->getSlider("bank"));
|
||||||
|
configureShapeSlider (wtComponent->getSlider("lfoShape"));
|
||||||
|
addAndMakeVisible(*wtComponent);
|
||||||
|
wtComponent->setTitleText("Layer A");
|
||||||
|
|
||||||
|
layerSelector.addItem("Layer A", 1);
|
||||||
|
layerSelector.addItem("Layer B", 2);
|
||||||
|
layerSelector.setSelectedId(1, juce::dontSendNotification);
|
||||||
|
layerSelector.onChange = [this] { handleLayerSelectionChanged(); };
|
||||||
|
wtComponent->setTopBarAccessory(&layerSelector, 118);
|
||||||
|
handleLayerSelectionChanged();
|
||||||
|
|
||||||
// Master fader + label
|
// Master fader + label
|
||||||
addAndMakeVisible(masterLevelSlider);
|
addAndMakeVisible(masterLevelSlider);
|
||||||
masterLevelLabel.setText("Master", juce::dontSendNotification);
|
masterLevelLabel.setText("Master", juce::dontSendNotification);
|
||||||
@@ -102,18 +170,22 @@ NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAud
|
|||||||
masterLevelLabel.setJustificationType(juce::Justification::centred);
|
masterLevelLabel.setJustificationType(juce::Justification::centred);
|
||||||
addAndMakeVisible(masterLevelLabel);
|
addAndMakeVisible(masterLevelLabel);
|
||||||
|
|
||||||
// Blank placeholder
|
|
||||||
addAndMakeVisible(blankPanel);
|
|
||||||
|
|
||||||
// Attach master parameter
|
// Attach master parameter
|
||||||
gainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
gainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
audioProcessor.parameters, "master", masterLevelSlider.slider);
|
audioProcessor.parameters, "master", masterLevelSlider.slider);
|
||||||
|
|
||||||
|
lastPresetIndex = audioProcessor.getCurrentPresetIndex();
|
||||||
|
updatePresetButtonLabel();
|
||||||
|
startTimerHz(10);
|
||||||
|
|
||||||
setSize(1400, 720);
|
setSize(1400, 720);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor() = default;
|
NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor()
|
||||||
|
{
|
||||||
|
stopTimer();
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g)
|
void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g)
|
||||||
@@ -124,47 +196,147 @@ void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g)
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
void NeuralSynthAudioProcessorEditor::resized()
|
void NeuralSynthAudioProcessorEditor::resized()
|
||||||
{
|
{
|
||||||
auto bounds = getLocalBounds().reduced(16);
|
auto outer = getLocalBounds().reduced(16);
|
||||||
|
|
||||||
|
// --- carve out sidebar for MASTER (right side) --------------------------
|
||||||
|
const int sidebarWidth = 160; // tweak if you want it wider/narrower
|
||||||
|
auto gridArea = outer;
|
||||||
|
auto sidebar = gridArea.removeFromRight(sidebarWidth).reduced(8);
|
||||||
|
|
||||||
|
// Master label + fader in the sidebar (stacked)
|
||||||
|
{
|
||||||
|
auto top = sidebar.removeFromTop(24);
|
||||||
|
masterLevelLabel.setBounds(top.withTrimmedLeft(4));
|
||||||
|
|
||||||
|
// leave a little top margin before the fader
|
||||||
|
sidebar.removeFromTop(8);
|
||||||
|
masterLevelSlider.setBounds(sidebar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Grid: Scope + two rows of five boxes (no gaps) ---------------------
|
||||||
juce::Grid grid;
|
juce::Grid grid;
|
||||||
|
|
||||||
grid.templateRows = {
|
grid.templateRows = {
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(20)), // scope row
|
juce::Grid::TrackInfo(juce::Grid::Fr(22)), // scope band
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(40)), // row 1
|
juce::Grid::TrackInfo(juce::Grid::Fr(39)), // row 1
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(40)) // row 2
|
juce::Grid::TrackInfo(juce::Grid::Fr(78)) // row 2 (wider to absorb layer panel)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 6 columns: 5 content + 1 sidebar (waveform+master)
|
|
||||||
grid.templateColumns = {
|
grid.templateColumns = {
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(18)),
|
juce::Grid::TrackInfo(juce::Grid::Fr(1)),
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(18)),
|
juce::Grid::TrackInfo(juce::Grid::Fr(1)),
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(18)),
|
juce::Grid::TrackInfo(juce::Grid::Fr(1)),
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(18)),
|
juce::Grid::TrackInfo(juce::Grid::Fr(1)),
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(18)),
|
juce::Grid::TrackInfo(juce::Grid::Fr(1))
|
||||||
juce::Grid::TrackInfo(juce::Grid::Fr(10))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Row 0
|
grid.rowGap = juce::Grid::Px(0);
|
||||||
grid.items.add(juce::GridItem(mainScopeComponent)
|
grid.columnGap = juce::Grid::Px(0);
|
||||||
.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.clear();
|
||||||
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
|
// Row 1 (scope row)
|
||||||
grid.items.add(juce::GridItem(*flangerComponent));
|
grid.items.add(juce::GridItem(mainScopeComponent).withArea(1, 1, 1, 5));
|
||||||
grid.items.add(juce::GridItem(*distortionComponent));
|
// Put preset button at the top-right cell of the scope row
|
||||||
grid.items.add(juce::GridItem(*filterComponent));
|
grid.items.add(juce::GridItem(presetMenuButton)
|
||||||
grid.items.add(juce::GridItem(*filterEnvComponent));
|
.withArea(1, 5)
|
||||||
grid.items.add(juce::GridItem(blankPanel));
|
.withJustifySelf(juce::GridItem::JustifySelf::end)
|
||||||
grid.items.add(juce::GridItem(masterLevelSlider));
|
.withAlignSelf(juce::GridItem::AlignSelf::start));
|
||||||
|
|
||||||
grid.performLayout(bounds);
|
// Row 2 (top row of panels): Amp Env, Chorus, Delay, Reverb, EQ
|
||||||
|
grid.items.add(juce::GridItem(*adsrComponent ).withArea(2, 1));
|
||||||
|
grid.items.add(juce::GridItem(*chorusComponent ).withArea(2, 2));
|
||||||
|
grid.items.add(juce::GridItem(*delayComponent ).withArea(2, 3));
|
||||||
|
grid.items.add(juce::GridItem(*reverbComponent ).withArea(2, 4));
|
||||||
|
grid.items.add(juce::GridItem(*eqComponent ).withArea(2, 5));
|
||||||
|
|
||||||
|
// Row 3 (bottom row of panels): Flanger, Distortion, Filter, Filter Env, Wavetable
|
||||||
|
grid.items.add(juce::GridItem(*flangerComponent ).withArea(3, 1));
|
||||||
|
grid.items.add(juce::GridItem(*distortionComponent).withArea(3, 2));
|
||||||
|
grid.items.add(juce::GridItem(*filterComponent ).withArea(3, 3));
|
||||||
|
grid.items.add(juce::GridItem(*filterEnvComponent ).withArea(3, 4));
|
||||||
|
grid.items.add(juce::GridItem(*wtComponent ).withArea(3, 5));
|
||||||
|
|
||||||
|
grid.performLayout(gridArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuralSynthAudioProcessorEditor::timerCallback()
|
||||||
|
{
|
||||||
|
const int current = audioProcessor.getCurrentPresetIndex();
|
||||||
|
if (current != lastPresetIndex)
|
||||||
|
{
|
||||||
|
lastPresetIndex = current;
|
||||||
|
updatePresetButtonLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuralSynthAudioProcessorEditor::showPresetMenu()
|
||||||
|
{
|
||||||
|
const auto& presets = audioProcessor.getFactoryPresets();
|
||||||
|
if (presets.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
juce::PopupMenu menu;
|
||||||
|
juce::StringArray categories;
|
||||||
|
for (const auto& preset : presets)
|
||||||
|
if (! categories.contains(preset.category))
|
||||||
|
categories.add(preset.category);
|
||||||
|
|
||||||
|
const int baseId = 1000;
|
||||||
|
for (const auto& category : categories)
|
||||||
|
{
|
||||||
|
juce::PopupMenu sub;
|
||||||
|
for (int i = 0; i < (int) presets.size(); ++i)
|
||||||
|
{
|
||||||
|
if (presets[(size_t) i].category == category)
|
||||||
|
{
|
||||||
|
sub.addItem(baseId + i, presets[(size_t) i].name,
|
||||||
|
true, audioProcessor.getCurrentPresetIndex() == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menu.addSubMenu(category, sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.showMenuAsync(juce::PopupMenu::Options().withParentComponent(this),
|
||||||
|
[this, baseId](int result)
|
||||||
|
{
|
||||||
|
if (result >= baseId)
|
||||||
|
{
|
||||||
|
const int index = result - baseId;
|
||||||
|
audioProcessor.applyPreset(index);
|
||||||
|
lastPresetIndex = index;
|
||||||
|
updatePresetButtonLabel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuralSynthAudioProcessorEditor::updatePresetButtonLabel()
|
||||||
|
{
|
||||||
|
const auto& presets = audioProcessor.getFactoryPresets();
|
||||||
|
const int current = audioProcessor.getCurrentPresetIndex();
|
||||||
|
|
||||||
|
juce::String label = "Preset: ";
|
||||||
|
if (current >= 0 && current < (int) presets.size())
|
||||||
|
{
|
||||||
|
const auto& preset = presets[(size_t) current];
|
||||||
|
label += preset.category + " / " + preset.name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
label += "Custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
presetMenuButton.setButtonText(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeuralSynthAudioProcessorEditor::handleLayerSelectionChanged()
|
||||||
|
{
|
||||||
|
const bool useLayerB = (layerSelector.getSelectedId() == 2);
|
||||||
|
if (! wtComponent || controllingLayerB == useLayerB)
|
||||||
|
return;
|
||||||
|
|
||||||
|
controllingLayerB = useLayerB;
|
||||||
|
const std::string group = useLayerB ? "wt2" : "wt";
|
||||||
|
wtComponent->reassignParamGroup(group);
|
||||||
|
wtComponent->setTitleText(useLayerB ? "Layer B" : "Layer A");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ public:
|
|||||||
attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
tree, paramGroup + "_" + name, *sliders.back()));
|
tree, paramGroup + "_" + name, *sliders.back()));
|
||||||
|
|
||||||
|
paramNames.push_back(name);
|
||||||
|
|
||||||
labels.back()->setText(sliderDetail.label, juce::dontSendNotification);
|
labels.back()->setText(sliderDetail.label, juce::dontSendNotification);
|
||||||
sliders.back()->setRange(sliderDetail.min, sliderDetail.max);
|
sliders.back()->setRange(sliderDetail.min, sliderDetail.max);
|
||||||
}
|
}
|
||||||
@@ -55,13 +57,75 @@ public:
|
|||||||
addAndMakeVisible(titleLabel);
|
addAndMakeVisible(titleLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bypass toggle (per panel), id "<group>_on"
|
if (tree.getParameter(paramGroupId + "_on") != nullptr)
|
||||||
|
{
|
||||||
|
hasBypass = true;
|
||||||
bypassButton.setButtonText("On");
|
bypassButton.setButtonText("On");
|
||||||
bypassButton.setClickingTogglesState(true);
|
bypassButton.setClickingTogglesState(true);
|
||||||
addAndMakeVisible(bypassButton);
|
addAndMakeVisible(bypassButton);
|
||||||
bypassAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(
|
bypassAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(
|
||||||
treeRef, paramGroupId + "_on", bypassButton);
|
treeRef, paramGroupId + "_on", bypassButton);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTitleText(const juce::String& text)
|
||||||
|
{
|
||||||
|
if (titleLabel.isVisible())
|
||||||
|
titleLabel.setText(text, juce::dontSendNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTopBarAccessory(juce::Component* component, int preferredWidth = 120)
|
||||||
|
{
|
||||||
|
topBarAccessory = component;
|
||||||
|
accessoryPreferredWidth = preferredWidth;
|
||||||
|
if (topBarAccessory != nullptr)
|
||||||
|
addAndMakeVisible(*topBarAccessory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reassignParamGroup(const std::string& newGroup)
|
||||||
|
{
|
||||||
|
if (newGroup == paramGroupId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& sliderDetails = PARAM_SETTINGS.at(newGroup);
|
||||||
|
jassert(sliderDetails.size() == sliders.size());
|
||||||
|
jassert(sliderDetails.size() == labels.size());
|
||||||
|
jassert(sliderDetails.size() == paramNames.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sliderDetails.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto& [name, detail] = sliderDetails[i];
|
||||||
|
paramNames[i] = name;
|
||||||
|
sliders[i]->setRange(detail.min, detail.max, detail.interval);
|
||||||
|
labels[i]->setText(detail.label, juce::dontSendNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments.clear();
|
||||||
|
attachments.reserve(sliderDetails.size());
|
||||||
|
for (size_t i = 0; i < sliderDetails.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto paramId = newGroup + "_" + paramNames[i];
|
||||||
|
attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
|
treeRef, paramId, *sliders[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBypass)
|
||||||
|
{
|
||||||
|
bypassAttachment.reset();
|
||||||
|
if (treeRef.getParameter(newGroup + "_on") != nullptr)
|
||||||
|
{
|
||||||
|
bypassAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ButtonAttachment>(
|
||||||
|
treeRef, newGroup + "_on", bypassButton);
|
||||||
|
bypassButton.setEnabled(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bypassButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paramGroupId = newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
void enableSampleScope(AudioBufferQueue<float>& audioBufferQueue) {
|
void enableSampleScope(AudioBufferQueue<float>& audioBufferQueue) {
|
||||||
scope.emplace(audioBufferQueue);
|
scope.emplace(audioBufferQueue);
|
||||||
@@ -76,7 +140,17 @@ public:
|
|||||||
addAndMakeVisible(*graphScope);
|
addAndMakeVisible(*graphScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
juce::Slider* getSlider(const std::string& name) { return findSlider(name); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
juce::Slider* findSlider(const std::string& name)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < paramNames.size(); ++i)
|
||||||
|
if (paramNames[i] == name)
|
||||||
|
return sliders[i].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void paint(juce::Graphics& g) override
|
void paint(juce::Graphics& g) override
|
||||||
{
|
{
|
||||||
g.fillAll(juce::Colours::darkgrey);
|
g.fillAll(juce::Colours::darkgrey);
|
||||||
@@ -89,8 +163,21 @@ private:
|
|||||||
// --- Top bar (manual) ----------------------------------------------
|
// --- Top bar (manual) ----------------------------------------------
|
||||||
auto area = getLocalBounds().reduced(10);
|
auto area = getLocalBounds().reduced(10);
|
||||||
auto top = area.removeFromTop(22);
|
auto top = area.removeFromTop(22);
|
||||||
|
if (hasBypass)
|
||||||
|
{
|
||||||
auto btnW = 46;
|
auto btnW = 46;
|
||||||
bypassButton.setBounds(top.removeFromRight(btnW).reduced(2, 1));
|
auto buttonArea = top.removeFromRight(btnW);
|
||||||
|
bypassButton.setBounds(buttonArea.reduced(2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topBarAccessory != nullptr)
|
||||||
|
{
|
||||||
|
const int widthLimit = juce::jmax(60, juce::jmin(accessoryPreferredWidth, top.getWidth()));
|
||||||
|
auto accessoryArea = top.removeFromRight(widthLimit);
|
||||||
|
topBarAccessory->setBounds(accessoryArea.reduced(2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (titleLabel.isVisible())
|
||||||
titleLabel.setBounds(top);
|
titleLabel.setBounds(top);
|
||||||
|
|
||||||
// --- Rest (grid) ----------------------------------------------------
|
// --- Rest (grid) ----------------------------------------------------
|
||||||
@@ -146,9 +233,14 @@ private:
|
|||||||
std::vector<std::unique_ptr<juce::Slider>> sliders;
|
std::vector<std::unique_ptr<juce::Slider>> sliders;
|
||||||
std::vector<std::unique_ptr<juce::Label>> labels;
|
std::vector<std::unique_ptr<juce::Label>> labels;
|
||||||
std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment>> attachments;
|
std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment>> attachments;
|
||||||
|
std::vector<std::string> paramNames;
|
||||||
|
|
||||||
|
juce::Component* topBarAccessory{ nullptr };
|
||||||
|
int accessoryPreferredWidth{ 120 };
|
||||||
|
|
||||||
juce::ToggleButton bypassButton;
|
juce::ToggleButton bypassButton;
|
||||||
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> bypassAttachment;
|
std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> bypassAttachment;
|
||||||
|
bool hasBypass{ false };
|
||||||
|
|
||||||
juce::Label titleLabel;
|
juce::Label titleLabel;
|
||||||
|
|
||||||
@@ -254,34 +346,6 @@ private:
|
|||||||
juce::Label titleLabel;
|
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 =========================
|
//============================== MasterVolumeComponent =========================
|
||||||
class MasterVolumeComponent : public juce::Component
|
class MasterVolumeComponent : public juce::Component
|
||||||
{
|
{
|
||||||
@@ -290,6 +354,7 @@ public:
|
|||||||
{
|
{
|
||||||
slider.setSliderStyle(juce::Slider::LinearBarVertical);
|
slider.setSliderStyle(juce::Slider::LinearBarVertical);
|
||||||
slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20);
|
slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20);
|
||||||
|
slider.setRange(-24.0f, 24.0f, 0.1f);
|
||||||
addAndMakeVisible(slider);
|
addAndMakeVisible(slider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +367,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
//============================== Editor =======================================
|
//============================== Editor =======================================
|
||||||
class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor
|
class NeuralSynthAudioProcessorEditor : public juce::AudioProcessorEditor,
|
||||||
|
private juce::Timer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&);
|
NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&);
|
||||||
@@ -310,13 +376,18 @@ public:
|
|||||||
|
|
||||||
void paint (juce::Graphics&) override;
|
void paint (juce::Graphics&) override;
|
||||||
void resized() override;
|
void resized() override;
|
||||||
|
void timerCallback() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NeuralSynthAudioProcessor& audioProcessor;
|
NeuralSynthAudioProcessor& audioProcessor;
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor)
|
||||||
|
|
||||||
juce::ListBox waveformSelector;
|
void updatePresetButtonLabel();
|
||||||
WaveformSelectorContents waveformContents;
|
void showPresetMenu();
|
||||||
|
void handleLayerSelectionChanged();
|
||||||
|
|
||||||
|
juce::TextButton presetMenuButton;
|
||||||
|
int lastPresetIndex { -1 };
|
||||||
|
|
||||||
std::optional<ScopeSliderComponent> adsrComponent; // Amp Env
|
std::optional<ScopeSliderComponent> adsrComponent; // Amp Env
|
||||||
std::optional<ScopeSliderComponent> chorusComponent;
|
std::optional<ScopeSliderComponent> chorusComponent;
|
||||||
@@ -327,6 +398,7 @@ private:
|
|||||||
std::optional<ScopeSliderComponent> distortionComponent;
|
std::optional<ScopeSliderComponent> distortionComponent;
|
||||||
std::optional<ScopeSliderComponent> filterComponent;
|
std::optional<ScopeSliderComponent> filterComponent;
|
||||||
std::optional<ScopeSliderComponent> filterEnvComponent; // Filter Env panel
|
std::optional<ScopeSliderComponent> filterEnvComponent; // Filter Env panel
|
||||||
|
std::optional<ScopeSliderComponent> wtComponent; // Wavetable panel
|
||||||
|
|
||||||
MasterVolumeComponent masterLevelSlider;
|
MasterVolumeComponent masterLevelSlider;
|
||||||
juce::Label masterLevelLabel;
|
juce::Label masterLevelLabel;
|
||||||
@@ -336,6 +408,6 @@ private:
|
|||||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gainAttachment;
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gainAttachment;
|
||||||
|
|
||||||
ScopeComponent<float> mainScopeComponent;
|
ScopeComponent<float> mainScopeComponent;
|
||||||
|
juce::ComboBox layerSelector;
|
||||||
juce::Component blankPanel;
|
bool controllingLayerB { false };
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user