More advanced version

This commit is contained in:
Timothy Scully
2025-10-12 16:41:38 +01:00
parent 01d446af2d
commit 3967560318
11 changed files with 1288 additions and 133 deletions

View File

@@ -42,8 +42,8 @@
namespace ProjectInfo
{
const char* const projectName = "NeuralSynth";
const char* const companyName = "";
const char* const versionString = "1.0.0";
const int versionNumber = 0x10000;
const char* const companyName = "Samedi Dimanche";
const char* const versionString = "0.0.1";
const int versionNumber = 0x1;
}
#endif

View File

@@ -47,10 +47,10 @@
#define JucePlugin_Desc "NeuralSynth"
#endif
#ifndef JucePlugin_Manufacturer
#define JucePlugin_Manufacturer "yourcompany"
#define JucePlugin_Manufacturer "Samedi Dimanche"
#endif
#ifndef JucePlugin_ManufacturerWebsite
#define JucePlugin_ManufacturerWebsite "www.yourcompany.com"
#define JucePlugin_ManufacturerWebsite "www.samedidimanche.com"
#endif
#ifndef JucePlugin_ManufacturerEmail
#define JucePlugin_ManufacturerEmail ""
@@ -62,10 +62,10 @@
#define JucePlugin_PluginCode 0x4d73347a
#endif
#ifndef JucePlugin_IsSynth
#define JucePlugin_IsSynth 0
#define JucePlugin_IsSynth 1
#endif
#ifndef JucePlugin_WantsMidiInput
#define JucePlugin_WantsMidiInput 0
#define JucePlugin_WantsMidiInput 1
#endif
#ifndef JucePlugin_ProducesMidiOutput
#define JucePlugin_ProducesMidiOutput 0
@@ -77,25 +77,25 @@
#define JucePlugin_EditorRequiresKeyboardFocus 0
#endif
#ifndef JucePlugin_Version
#define JucePlugin_Version 1.0.0
#define JucePlugin_Version 0.0.1
#endif
#ifndef JucePlugin_VersionCode
#define JucePlugin_VersionCode 0x10000
#define JucePlugin_VersionCode 0x1
#endif
#ifndef JucePlugin_VersionString
#define JucePlugin_VersionString "1.0.0"
#define JucePlugin_VersionString "0.0.1"
#endif
#ifndef JucePlugin_VSTUniqueID
#define JucePlugin_VSTUniqueID JucePlugin_PluginCode
#endif
#ifndef JucePlugin_VSTCategory
#define JucePlugin_VSTCategory kPlugCategEffect
#define JucePlugin_VSTCategory kPlugCategSynth
#endif
#ifndef JucePlugin_Vst3Category
#define JucePlugin_Vst3Category "Fx"
#define JucePlugin_Vst3Category "Instrument|Synth"
#endif
#ifndef JucePlugin_AUMainType
#define JucePlugin_AUMainType 'aufx'
#define JucePlugin_AUMainType 'aumu'
#endif
#ifndef JucePlugin_AUSubType
#define JucePlugin_AUSubType JucePlugin_PluginCode
@@ -110,10 +110,10 @@
#define JucePlugin_AUManufacturerCode JucePlugin_ManufacturerCode
#endif
#ifndef JucePlugin_CFBundleIdentifier
#define JucePlugin_CFBundleIdentifier com.yourcompany.NeuralSynth
#define JucePlugin_CFBundleIdentifier com.samedidimanche.NeuralSynth
#endif
#ifndef JucePlugin_AAXIdentifier
#define JucePlugin_AAXIdentifier com.yourcompany.NeuralSynth
#define JucePlugin_AAXIdentifier com.SamediDimanche.NeuralSynth
#endif
#ifndef JucePlugin_AAXManufacturerCode
#define JucePlugin_AAXManufacturerCode JucePlugin_ManufacturerCode
@@ -122,7 +122,7 @@
#define JucePlugin_AAXProductId JucePlugin_PluginCode
#endif
#ifndef JucePlugin_AAXCategory
#define JucePlugin_AAXCategory 0
#define JucePlugin_AAXCategory 2048
#endif
#ifndef JucePlugin_AAXDisableBypass
#define JucePlugin_AAXDisableBypass 0
@@ -131,16 +131,16 @@
#define JucePlugin_AAXDisableMultiMono 0
#endif
#ifndef JucePlugin_IAAType
#define JucePlugin_IAAType 0x61757278
#define JucePlugin_IAAType 0x61757269
#endif
#ifndef JucePlugin_IAASubType
#define JucePlugin_IAASubType JucePlugin_PluginCode
#endif
#ifndef JucePlugin_IAAName
#define JucePlugin_IAAName "yourcompany: NeuralSynth"
#define JucePlugin_IAAName "Samedi Dimanche: NeuralSynth"
#endif
#ifndef JucePlugin_VSTNumMidiInputs
#define JucePlugin_VSTNumMidiInputs 16
#define JucePlugin_VSTNumMidiInputs 1
#endif
#ifndef JucePlugin_VSTNumMidiOutputs
#define JucePlugin_VSTNumMidiOutputs 16
@@ -152,11 +152,20 @@
#define JucePlugin_ARATransformationFlags 0
#endif
#ifndef JucePlugin_ARAFactoryID
#define JucePlugin_ARAFactoryID "com.yourcompany.NeuralSynth.factory"
#define JucePlugin_ARAFactoryID "com.SamediDimanche.NeuralSynth.factory"
#endif
#ifndef JucePlugin_ARADocumentArchiveID
#define JucePlugin_ARADocumentArchiveID "com.yourcompany.NeuralSynth.aradocumentarchive.1.0.0"
#define JucePlugin_ARADocumentArchiveID "com.SamediDimanche.NeuralSynth.aradocumentarchive.0.0.1"
#endif
#ifndef JucePlugin_ARACompatibleArchiveIDs
#define JucePlugin_ARACompatibleArchiveIDs ""
#endif
#ifndef JucePlugin_MaxNumInputChannels
#define JucePlugin_MaxNumInputChannels 0
#endif
#ifndef JucePlugin_MaxNumOutputChannels
#define JucePlugin_MaxNumOutputChannels 2
#endif
#ifndef JucePlugin_PreferredChannelConfigurations
#define JucePlugin_PreferredChannelConfigurations {0, 2}
#endif

View File

@@ -1,9 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<JUCERPROJECT id="ms4Z8E" name="NeuralSynth" projectType="audioplug" useAppConfig="0"
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1">
addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1" companyWebsite="www.samedidimanche.com"
bundleIdentifier="com.samedidimanche.NeuralSynth" pluginManufacturer="Samedi Dimanche"
companyName="Samedi Dimanche" pluginCharacteristicsValue="pluginIsSynth,pluginWantsMidiIn"
pluginVSTNumMidiInputs="1" pluginChannelConfigs="{0, 2}" version="0.0.1">
<MAINGROUP id="UQstsW" name="NeuralSynth">
<GROUP id="{D5B48DA9-9A47-914A-8C72-EE5E8DD868A3}" name="Source">
<FILE id="nmKMnf" name="GraphComponent.h" compile="0" resource="0"
file="Source/GraphComponent.h"/>
<FILE id="CjJ141" name="NeuralSharedParams.h" compile="0" resource="0"
file="Source/NeuralSharedParams.h"/>
<FILE id="ZrdRl1" name="AudioBufferQueue.h" compile="0" resource="0"

118
Source/GraphComponent.h Normal file
View File

@@ -0,0 +1,118 @@
/*
==============================================================================
GraphComponent.h
Created: 4 Jul 2025 11:43:57pm
Author: timot
==============================================================================
*/
#pragma once
#include "AudioBufferQueue.h"
//==============================================================================
template <typename SampleType>
class GraphComponent : public juce::Component,
private juce::Timer
{
public:
//==============================================================================
GraphComponent(SampleType min, SampleType max, int numPoints): func(func), min(min), max(max), numPoints(numPoints)
{
x.resize(numPoints);
y.resize(numPoints);
setFramesPerSecond(30);
}
//==============================================================================
void setFramesPerSecond(int framesPerSecond)
{
jassert(framesPerSecond > 0 && framesPerSecond < 1000);
startTimerHz(framesPerSecond);
}
//==============================================================================
void setFunction(const std::function<SampleType(SampleType)>& func) {
this->func = func;
}
//==============================================================================
void paint(juce::Graphics& g) override
{
g.fillAll(juce::Colours::black);
g.setColour(juce::Colours::white);
auto area = getLocalBounds();
if (hasData && area.isFinite()) {
auto h = (SampleType)area.getHeight();
auto w = (SampleType)area.getWidth();
for (size_t i = 1; i < numPoints; ++i) {
auto px_prev = ((x[i - 1] - min) / (max - min)) * w;
auto py_prev = h - ((y[i - 1] - minY) / (maxY - minY)) * h;
auto px_next = ((x[i] - min) / (max - min)) * w;
auto py_next = h - ((y[i] - minY) / (maxY - minY)) * h;
juce::Line<SampleType> line(juce::Point<SampleType>(px_prev, py_prev), juce::Point<SampleType>(px_next, py_next));
g.drawLine(line);
}
}
}
//==============================================================================
void resized() override {}
private:
//==============================================================================
std::vector<SampleType> x, y;
SampleType minY, maxY;
SampleType min, max;
int numPoints;
std::function<SampleType(SampleType)> func;
bool hasData = false;
//==============================================================================
void timerCallback() override
{
float step = (max - min) / (SampleType)(numPoints - 1);
for (int i = 0; i < numPoints; i++) {
x[i] = min + step * (SampleType)i;
y[i] = func(x[i]);
}
auto p = minmax_element(y.begin(), y.end());
minY = *p.first; maxY = *p.second;
hasData = true;
repaint();
}
//==============================================================================
/*static void plot(const SampleType* data,
size_t numSamples,
juce::Graphics& g,
juce::Rectangle<SampleType> rect,
SampleType scaler = SampleType(1),
SampleType offset = SampleType(0))
{
auto w = rect.getWidth();
auto h = rect.getHeight();
auto right = rect.getRight();
auto center = rect.getBottom() - offset;
auto gain = h * scaler;
for (size_t i = 1; i < numSamples; ++i)
g.drawLine({ juce::jmap(SampleType(i - 1), SampleType(0), SampleType(numSamples - 1), SampleType(right - w), SampleType(right)),
center - gain * data[i - 1],
juce::jmap(SampleType(i), SampleType(0), SampleType(numSamples - 1), SampleType(right - w), SampleType(right)),
center - gain * data[i] });
}*/
};

View File

@@ -12,12 +12,120 @@
#include <atomic>
struct SliderDetail {
std::string label;
float min, max, interval, defValue;
};
typedef std::unordered_map <std::string, SliderDetail> ParamMap;
// Each SliderDetail: { label, min, max, step, defaultValue }
const std::unordered_map<std::string, ParamMap> PARAM_SETTINGS = {
{ "chorus", {
{ "rate", { "Rate", 0.0f, 1.0f, 0.1f, 0.1f } }, // Modulation speed
{ "depth", { "Depth", 0.0f, 1.0f, 0.1f, 0.1f } }, // Modulation amount
{ "centre", { "Centre", 0.0f, 1.0f, 0.1f, 0.1f } }, // Center delay
{ "feedback", { "Feedback", 0.0f, 1.0f, 0.1f, 0.1f } }, // Feedback amount
{ "mix", { "Mix", 0.0f, 1.0f, 0.1f, 0.1f } } // Dry/wet blend
}},
{ "delay", {
{ "delay", { "Delay", 0.0f, 1.0f, 0.1f, 0.1f } } // Delay time
}},
{ "reverb", {
{ "roomSize", { "Room Size", 0.0f, 1.0f, 0.1f, 0.1f } }, // Size of reverb space
{ "damping", { "Damping", 0.0f, 1.0f, 0.1f, 0.1f } }, // High-frequency attenuation
{ "wetLevel", { "Wet Level", 0.0f, 1.0f, 0.1f, 0.1f } }, // Reverb amount
{ "dryLevel", { "Dry Level", 0.0f, 1.0f, 0.1f, 0.1f } }, // Dry signal amount
{ "width", { "Width", 0.0f, 1.0f, 0.1f, 0.1f } }, // Stereo width
{ "freezeMode", { "Freeze Mode", 0.0f, 1.0f, 0.1f, 0.1f } } // Infinite decay toggle
}},
{ "adsr", {
{ "attack", { "Attack", 0.0f, 1.0f, 0.1f, 0.1f } }, // Attack time
{ "decay", { "Decay", 0.0f, 1.0f, 0.1f, 0.1f } }, // Decay time
{ "sustain", { "Sustain", 0.0f, 1.0f, 0.1f, 0.1f } }, // Sustain level
{ "release", { "Release", 0.0f, 1.0f, 0.1f, 0.1f } } // Release time
}},
{ "flanger", {
{ "rate", { "Rate", 0.1f, 5.0f, 0.1f, 0.1f } }, // LFO speed
{ "depth", { "Depth", 0.1f, 10.0f, 0.1f, 0.1f } }, // Mod depth in ms
{ "feedback", { "Feedback", 0.0f, 1.0f, 0.1f, 0.1f } }, // Feedback amount
{ "dryMix", { "Dry/Wet", 0.0f, 1.0f, 0.1f, 0.1f } }, // Mix control
{ "phase", { "Phase", 0.0f, 1.0f, 0.1f, 0.1f } }, // LFO phase offset (for stereo)
{ "delay", { "Delay", 0.0f, 3.0f, 0.1f, 0.1f } } // Base delay offset
}},
{ "filter", {
{ "cutoff", { "Cutoff", 0.2f, 20000.0f, 1.0f, 1000.0f } }, // Frequency cutoff
{ "resonance", { "Resonance", 0.1f, 10.0f, 0.1f, 0.2f } }, // Resonance/Q factor
{ "type", { "L/H/B", 0.0f, 2.0f, 1.0f, 0.0f } }, // 0 = LPF, 1 = HPF, 2 = BPF
{ "drive", { "Drive", 0.0f, 1.0f, 0.01f, 0.0f } }, // Pre-gain into filter
{ "mod", { "Mod", -1.0f, 1.0f, 0.1f, 0.0f } }, // Modulation amount
{ "key", { "Key", 0.0f, 1.0f, 0.1f, 0.0f } } // Key tracking
}},
{ "distortion", {
{ "drive", { "Drive", 0.0f, 30.0f, 0.1f, 10.0f } }, // Input gain before shaping
{ "mix", { "Mix", 0.0f, 1.0f, 0.01f, 0.5f } }, // Wet/dry blend
{ "bias", { "Bias", -1.0f, 1.0f, 0.01f, 0.0f } }, // DC offset
{ "tone", { "Tone", 100.0f, 8000.0f, 10.0f, 3000.0f } }, // LPF after distortion
{ "shape", { "Shape", 0.0f, 2.0f, 1.0f, 0.0f } } // 0=tanh, 1=hard clip, 2=atan
}}
};
struct NeuralSharedParams
{
std::atomic<int> waveform{ -1 };
std::atomic<float>* attack;
std::atomic<float>* decay;
std::atomic<float>* sustain;
std::atomic<float>* release;
std::atomic<float>* adsrAttack;
std::atomic<float>* adsrDecay;
std::atomic<float>* adsrSustain;
std::atomic<float>* adsrRelease;
std::atomic<float>* delayTime;
std::atomic<float>* chorusRate;
std::atomic<float>* chorusDepth;
std::atomic<float>* chorusCentre;
std::atomic<float>* chorusFeedback;
std::atomic<float>* chorusMix;
std::atomic<float>* reverbRoomSize;
std::atomic<float>* reverbDamping;
std::atomic<float>* reverbWetLevel;
std::atomic<float>* reverbDryLevel;
std::atomic<float>* reverbWidth;
std::atomic<float>* reverbFreezeMode;
std::atomic<float>* flangerRate;
std::atomic<float>* flangerDepth;
std::atomic<float>* flangerFeedback;
std::atomic<float>* flangerDryMix;
std::atomic<float>* flangerPhase;
std::atomic<float>* flangerDelay;
std::atomic<float>* filterCutoff;
std::atomic<float>* filterResonance;
std::atomic<float>* filterType;
std::atomic<float>* filterDrive;
std::atomic<float>* filterMod;
std::atomic<float>* filterKey;
std::atomic<float>* distortionDrive;
std::atomic<float>* distortionMix;
std::atomic<float>* distortionBias;
std::atomic<float>* distortionTone;
std::atomic<float>* distortionShape;
std::atomic<float>* lowGainDbls;
std::atomic<float>* midGainDbls;
std::atomic<float>* highGainDbls;
std::atomic<float>* masterDbls;
};

View File

@@ -12,66 +12,336 @@
//==============================================================================
NeuralSynthAudioProcessorEditor::NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p), scopeComponent(audioProcessor.getAudioBufferQueue())
: AudioProcessorEditor (&p), audioProcessor (p), mainScopeComponent(audioProcessor.getAudioBufferQueue())
{
// Make sure that before the constructor has finished, you've set the
// editor's size to whatever you need it to be.
setSize(400, 500);
auto& tree = audioProcessor.parameters;
auto area = getLocalBounds();
scopeComponent.setTopLeftPosition(0, 0);
scopeComponent.setSize(400, 200);
//auto area = getLocalBounds();
//mainScopeComponent.setBounds(5, 5, 800, 200);
// scopeComponent.setSize(800, 200);
addAndMakeVisible(scopeComponent);
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<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(
// audioProcessor.parameters, "waveform", waveformSelector);
//attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
// tree, sliderDetail.name, *sliders.back()));
gainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
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<float>;
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<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);
addAndMakeVisible(waveformSelector);
attackSlider.setRange(0.0, 1.0);
decaySlider.setRange(0.0, 1.0);
sustainSlider.setRange(0.0, 1.0);
releaseSlider.setRange(0.0, 1.0);
}*/
waveformSelector.setTopLeftPosition(15, 225);
int leftPosition = 15;
/*void NeuralSynthAudioProcessorEditor::createDelay(int xCoord, int yCoord) {
int fontSize = 11;
int leftPosition = xCoord;
const int sliderWidth = 60;
for (auto* slider : { &attackSlider, &decaySlider, &sustainSlider, &releaseSlider })
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);
slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, sliderWidth + 20, 20);
//slider->setTopLeftPosition(0, 0);
slider->setBounds(leftPosition, yCoord + 150, sliderWidth, sliderWidthWithPadding);
addAndMakeVisible(*slider);
slider->setTopLeftPosition(leftPosition, 250);
leftPosition += (sliderWidth + 40);
leftPosition += sliderWidth;
}
waveformSelector.addItem("Sine", 1);
waveformSelector.addItem("Saw", 2);
waveformSelector.addItem("Square", 3);
waveformSelector.addItem("Triangle", 4);
// Attach to parameter
waveformAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(
audioProcessor.parameters, "waveform", waveformSelector);
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);
}
NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor()
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)
{
@@ -83,19 +353,52 @@ void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g)
//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);
//auto row = bounds.removeFromTop(150);
int knobWidth = row.getWidth() / 4;
//int knobWidth = row.getWidth() / 4;
attackSlider.setBounds(row.removeFromLeft(knobWidth).reduced(10));
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));
releaseSlider.setBounds(row.removeFromLeft(knobWidth).reduced(10));*/
//waveformSelector.setBounds(20, 20, 120, 30);
waveformSelector.setBounds(20, 20, 120, 30);
}

View File

@@ -10,8 +10,227 @@
#include <JuceHeader.h>
#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<juce::Slider>());
labels.push_back(std::make_unique<juce::Label>());
attachments.push_back(std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
tree, paramGroup + "_" + name, *sliders.back()));
labels.back()->setText(sliderDetail.label, juce::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<float>& audioBufferQueue) {
scope.emplace(audioBufferQueue);
useGraphScope = false;
addAndMakeVisible(*scope);
}
void enableGraphScope(const std::function<float(float)>& func) {
graphScope.emplace(0.0f, 1.0f, 100);
graphScope->setFunction(func);
useGraphScope = true;
addAndMakeVisible(*graphScope);
}
private:
void paint(juce::Graphics& g) override
{
//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<juce::GridItem> 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<ScopeComponent<float> > scope;
std::optional<GraphComponent<float> > graphScope;
std::vector<std::unique_ptr<juce::Slider> > sliders;
std::vector<std::unique_ptr<juce::Label> > labels;
std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> > attachments;
};
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<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "lowEQ", lowGainSlider);
midGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "midEQ", midGainSlider);
highGainAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(tree, "highEQ", highGainSlider);
}
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<juce::AudioProcessorValueTreeState::SliderAttachment> 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<juce::String> 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;
};
//==============================================================================
/**
*/
@@ -21,6 +240,7 @@ public:
NeuralSynthAudioProcessorEditor (NeuralSynthAudioProcessor&);
~NeuralSynthAudioProcessorEditor() override;
//==============================================================================
void paint (juce::Graphics&) override;
void resized() override;
@@ -32,17 +252,117 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NeuralSynthAudioProcessorEditor)
juce::ComboBox waveformSelector;
std::unique_ptr<juce::AudioProcessorValueTreeState::ComboBoxAttachment> waveformAttachment;
juce::ListBox waveformSelector;
WaveformSelectorContents waveformContents;
//std::unique_ptr<juce::AudioProcessorValueTreeState::ListBoxAttachment> waveformAttachment;
//==============================================================================
// ADSR
/*void createADSR(int xCoord, int yCoord);
juce::Slider attackSlider, decaySlider, sustainSlider, releaseSlider;
juce::Label attackLabel, decayLabel, sustainLabel, releaseLabel;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> attackAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> decayAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> sustainAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> releaseAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> releaseAttachment;*/
//==============================================================================
std::optional<ScopeSliderComponent> adsrComponent;
std::optional<ScopeSliderComponent> chorusComponent;
std::optional<ScopeSliderComponent> delayComponent;
std::optional<ScopeSliderComponent> reverbComponent;
std::optional<ScopeSliderComponent> flangerComponent;
std::optional<ScopeSliderComponent> distortionComponent;
std::optional<ScopeSliderComponent> 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<juce::AudioProcessorValueTreeState::SliderAttachment> chorusRateAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusDepthAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusCentreAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusFeedbackAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> chorusMixAttachment;
//==============================================================================
//==============================================================================
// Delay
void createDelay(int xCoord, int yCoord);
juce::Slider delayDelaySlider;
juce::Label delayDelayLabel;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> 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<juce::AudioProcessorValueTreeState::SliderAttachment> reverbRoomSizeAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbDampingAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbWetLevelAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbDryLevelAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbWidthAttachment;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> reverbFreezeModeAttachment;*/
//==============================================================================
// Master
MasterVolumeComponent masterLevelSlider;
juce::Label masterLevelLabel;
//==============================================================================
//==============================================================================
// 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;*/
juce::MidiKeyboardState midiKeyboardState;
juce::MidiKeyboardComponent midiKeyboardComponent{ midiKeyboardState, juce::MidiKeyboardComponent::horizontalKeyboard };
ScopeComponent<float> scopeComponent;
};

View File

@@ -15,16 +15,105 @@ NeuralSynthAudioProcessor::NeuralSynthAudioProcessor() : parameters(*this, nullp
, audioEngine(sp)
{
parameters.addParameterListener("waveform", this);
//sp.waveform = parameters.getRawParameterValue("waveform");
parameters.addParameterListener("attack", this);
parameters.addParameterListener("decay", this);
parameters.addParameterListener("sustain", this);
parameters.addParameterListener("release", this);
// === Chorus ===
parameters.addParameterListener("chorus_rate", this);
parameters.addParameterListener("chorus_depth", this);
parameters.addParameterListener("chorus_centre", this);
parameters.addParameterListener("chorus_feedback", this);
parameters.addParameterListener("chorus_mix", this);
sp.attack = parameters.getRawParameterValue("attack");
sp.decay = parameters.getRawParameterValue("decay");
sp.sustain = parameters.getRawParameterValue("sustain");
sp.release = parameters.getRawParameterValue("release");
sp.chorusRate = parameters.getRawParameterValue("chorus_rate");
sp.chorusDepth = parameters.getRawParameterValue("chorus_depth");
sp.chorusCentre = parameters.getRawParameterValue("chorus_centre");
sp.chorusFeedback = parameters.getRawParameterValue("chorus_feedback");
sp.chorusMix = parameters.getRawParameterValue("chorus_mix");
// === Delay ===
parameters.addParameterListener("delay_delay", this);
sp.delayTime = parameters.getRawParameterValue("delay_delay");
// === Reverb ===
parameters.addParameterListener("reverb_roomSize", this);
parameters.addParameterListener("reverb_damping", this);
parameters.addParameterListener("reverb_wetLevel", this);
parameters.addParameterListener("reverb_dryLevel", this);
parameters.addParameterListener("reverb_width", this);
parameters.addParameterListener("reverb_freezeMode", this);
sp.reverbRoomSize = parameters.getRawParameterValue("reverb_roomSize");
sp.reverbDamping = parameters.getRawParameterValue("reverb_damping");
sp.reverbWetLevel = parameters.getRawParameterValue("reverb_wetLevel");
sp.reverbDryLevel = parameters.getRawParameterValue("reverb_dryLevel");
sp.reverbWidth = parameters.getRawParameterValue("reverb_width");
sp.reverbFreezeMode = parameters.getRawParameterValue("reverb_freezeMode");
// === ADSR ===
parameters.addParameterListener("adsr_attack", this);
parameters.addParameterListener("adsr_decay", this);
parameters.addParameterListener("adsr_sustain", this);
parameters.addParameterListener("adsr_release", this);
sp.adsrAttack = parameters.getRawParameterValue("adsr_attack");
sp.adsrDecay = parameters.getRawParameterValue("adsr_decay");
sp.adsrSustain = parameters.getRawParameterValue("adsr_sustain");
sp.adsrRelease = parameters.getRawParameterValue("adsr_release");
// === Flanger ===
parameters.addParameterListener("flanger_rate", this);
parameters.addParameterListener("flanger_depth", this);
parameters.addParameterListener("flanger_feedback", this);
parameters.addParameterListener("flanger_dryMix", this);
parameters.addParameterListener("flanger_phase", this);
parameters.addParameterListener("flanger_delay", this);
sp.flangerRate = parameters.getRawParameterValue("flanger_rate");
sp.flangerDepth = parameters.getRawParameterValue("flanger_depth");
sp.flangerFeedback = parameters.getRawParameterValue("flanger_feedback");
sp.flangerDryMix = parameters.getRawParameterValue("flanger_dryMix");
sp.flangerPhase = parameters.getRawParameterValue("flanger_phase");
sp.flangerDelay = parameters.getRawParameterValue("flanger_delay");
// === Filter ===
parameters.addParameterListener("filter_cutoff", this);
parameters.addParameterListener("filter_resonance", this);
parameters.addParameterListener("filter_type", this);
parameters.addParameterListener("filter_drive", this);
parameters.addParameterListener("filter_mod", this);
parameters.addParameterListener("filter_key", this);
sp.filterCutoff = parameters.getRawParameterValue("filter_cutoff");
sp.filterResonance = parameters.getRawParameterValue("filter_resonance");
sp.filterType = parameters.getRawParameterValue("filter_type");
sp.filterDrive = parameters.getRawParameterValue("filter_drive");
sp.filterMod = parameters.getRawParameterValue("filter_mod");
sp.filterKey = parameters.getRawParameterValue("filter_key");
// === Distortion ===
parameters.addParameterListener("distortion_drive", this);
parameters.addParameterListener("distortion_mix", this);
parameters.addParameterListener("distortion_bias", this);
parameters.addParameterListener("distortion_tone", this);
parameters.addParameterListener("distortion_shape", this);
sp.distortionDrive = parameters.getRawParameterValue("distortion_drive");
sp.distortionMix = parameters.getRawParameterValue("distortion_mix");
sp.distortionBias = parameters.getRawParameterValue("distortion_bias");
sp.distortionTone = parameters.getRawParameterValue("distortion_tone");
sp.distortionShape = parameters.getRawParameterValue("distortion_shape");
parameters.addParameterListener("master", this);
parameters.addParameterListener("lowEQ", this);
parameters.addParameterListener("midEQ", this);
parameters.addParameterListener("highEQ", this);
sp.masterDbls = parameters.getRawParameterValue("master");
sp.lowGainDbls = parameters.getRawParameterValue("lowEQ");
sp.midGainDbls = parameters.getRawParameterValue("midEQ");
sp.highGainDbls = parameters.getRawParameterValue("highEQ");
}
NeuralSynthAudioProcessor::~NeuralSynthAudioProcessor()
@@ -179,6 +268,16 @@ juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
return new NeuralSynthAudioProcessor();
}
void NeuralSynthAudioProcessor::buildParams(std::vector<std::unique_ptr<juce::RangedAudioParameter>> &params, const std::string& paramGroup) {
const auto& paramGroupSettings = PARAM_SETTINGS.at(paramGroup);
for (const auto& [name, paramSettings] : paramGroupSettings) {
params.push_back(std::make_unique<juce::AudioParameterFloat>(paramGroup + "_" + name, paramSettings.label,
juce::NormalisableRange<float>(paramSettings.min, paramSettings.max, paramSettings.interval),
paramSettings.defValue));
}
}
juce::AudioProcessorValueTreeState::ParameterLayout NeuralSynthAudioProcessor::createParameterLayout()
{
std::vector<std::unique_ptr<juce::RangedAudioParameter>> params;
@@ -187,15 +286,23 @@ juce::AudioProcessorValueTreeState::ParameterLayout NeuralSynthAudioProcessor::c
"waveform", "Waveform",
juce::StringArray{ "Sine", "Saw", "Square", "Triangle" }, 0));
// Start/end/interval
params.push_back(std::make_unique<juce::AudioParameterFloat>("attack", "Attack",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.1f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("decay", "Decay",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.5f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("sustain", "Sustain",
juce::NormalisableRange<float>(0.0f, 1.0f, 0.01f), 0.8f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("release", "Release",
juce::NormalisableRange<float>(0.01f, 1.0f, 0.01f), 1.0f));
buildParams(params, "adsr");
buildParams(params, "chorus");
buildParams(params, "delay");
buildParams(params, "reverb");
buildParams(params, "flanger");
buildParams(params, "distortion");
buildParams(params, "filter");
params.push_back(std::make_unique<juce::AudioParameterFloat>("master", "Master",
juce::NormalisableRange<float>(-24.0f, 24.0f, 0.1f), 0.1f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("lowEQ", "Low Gain",
juce::NormalisableRange<float>(-24.0f, 24.0f, 0.1f), 0.5f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("midEQ", "Mid EQ",
juce::NormalisableRange<float>(-24.0f, 24.0f, 0.1f), 0.8f));
params.push_back(std::make_unique<juce::AudioParameterFloat>("highEQ", "High EQ",
juce::NormalisableRange<float>(-24.0f, 24.0f, 0.1f), 1.0f));
return { params.begin(), params.end() };
}

View File

@@ -28,6 +28,7 @@ public:
//==============================================================================
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
bool isBusesLayoutSupported(const BusesLayout& layouts) const;
#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported(const BusesLayout& layouts) const override;
@@ -61,6 +62,8 @@ public:
//==============================================================================
void parameterChanged(const juce::String& id, float newValue) override;
void buildParams(std::vector<std::unique_ptr<juce::RangedAudioParameter>>& params, const std::string& paramGroup);
juce::MidiMessageCollector& getMidiMessageCollector() noexcept { return midiMessageCollector; }
juce::MidiMessageCollector midiMessageCollector;
@@ -68,12 +71,30 @@ public:
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
AudioBufferQueue<float>& getAudioBufferQueue() noexcept { return audioBufferQueue; }
AudioBufferQueue<float>& getChorusAudioBufferQueue() noexcept { return chorusBufferQueue; }
AudioBufferQueue<float>& getDelayAudioBufferQueue() noexcept { return delayBufferQueue; }
AudioBufferQueue<float>& getReverbAudioBufferQueue() noexcept { return reverbBufferQueue; }
AudioBufferQueue<float>& getFlangerAudioBufferQueue() noexcept { return flangerBufferQueue; }
AudioBufferQueue<float>& getDistortionAudioBufferQueue() noexcept { return distortionBufferQueue; }
AudioBufferQueue<float>& getFilterAudioBufferQueue() noexcept { return filterBufferQueue; }
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NeuralSynthAudioProcessor)
NeuralAudioEngine audioEngine;
AudioBufferQueue<float> audioBufferQueue;
AudioBufferQueue<float> chorusBufferQueue;
AudioBufferQueue<float> delayBufferQueue;
AudioBufferQueue<float> reverbBufferQueue;
AudioBufferQueue<float> flangerBufferQueue;
AudioBufferQueue<float> distortionBufferQueue;
AudioBufferQueue<float> filterBufferQueue;
ScopeDataCollector<float> scopeDataCollector{ audioBufferQueue };
NeuralSharedParams sp;

View File

@@ -1,5 +1,7 @@
#include "SynthVoice.h"
#include <cmath>
//==============================================================================
NeuralSynthVoice::NeuralSynthVoice(NeuralSharedParams& sp) : shared(sp) {}
@@ -10,47 +12,15 @@ void NeuralSynthVoice::prepare(const juce::dsp::ProcessSpec& spec)
tempBlock = juce::dsp::AudioBlock<float>(heapBlock, spec.numChannels, spec.maximumBlockSize);
processorChain.prepare(spec);
adsr.setSampleRate(spec.sampleRate);
this->spec = spec;
}
//==============================================================================
void NeuralSynthVoice::noteStarted()
{
auto velocity = getCurrentlyPlayingNote().noteOnVelocity.asUnsignedFloat();
auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<synthIndex>().setFrequency(freqHz, true);
juce::ADSR::Parameters p;
p.attack = shared.attack->load();
p.decay = shared.decay->load();
p.sustain = shared.sustain->load();
p.release = shared.release->load();
adsr.setParameters(p);
adsr.noteOn();
}
//==============================================================================
void NeuralSynthVoice::notePitchbendChanged()
{
auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<synthIndex>().setFrequency(freqHz, true);
}
//==============================================================================
void NeuralSynthVoice::noteStopped(bool allowTailOff)
{
adsr.noteOff(); //Triggers release phase
}
//==============================================================================
void NeuralSynthVoice::notePressureChanged() {}
void NeuralSynthVoice::noteTimbreChanged() {}
void NeuralSynthVoice::noteKeyStateChanged() {}
//==============================================================================
void NeuralSynthVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
{
if (numSamples <= 0) return;
if (!adsr.isActive())
clearCurrentNote();
@@ -59,10 +29,124 @@ void NeuralSynthVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer, i
waveform = -1;
}
const int numChannels = outputBuffer.getNumChannels();
auto block = tempBlock.getSubBlock(0, (size_t)numSamples);
block.clear();
juce::dsp::ProcessContextReplacing<float> context(block);
processorChain.process(context);
// =====================================================================
// Oscillator
// =====================================================================
auto& osc = processorChain.get<oscIndex>();
juce::dsp::ProcessContextReplacing<float> oscContext(block);
osc.process(oscContext);
// =====================================================================
// Distortion
// =====================================================================
const float driveDb = shared.distortionDrive->load(); // 0..30
//const float distMix = juce::jlimit(0.0f, 1.0f, shared.distortionMix->load());
const float bias = juce::jlimit(-1.0f, 1.0f, shared.distortionBias->load());
const float toneHz = juce::jlimit(100.0f, 8000.0f, shared.distortionTone->load());
const int shape = (int)std::lround(juce::jlimit(0.0f, 2.0f, shared.distortionShape->load()));
auto& distDry = processorChain.get<distortionPreGain>();
auto& distWaveshaper = processorChain.template get<distortionIndex>();
if (shape == 0) {
distWaveshaper.functionToUse = [bias](float x) noexcept {
return std::tanh(x + bias);
};
}
else if (shape == 1) {
distWaveshaper.functionToUse = [bias](float x) noexcept {
const float v = x + bias;
return juce::jlimit(-1.0f, 1.0f, v);
};
}
else if (shape == 2) {
distWaveshaper.functionToUse = [bias](float x) noexcept {
const float v = x + bias;
return (float)(std::atan(v) * (2.0 / juce::MathConstants<double>::pi));
};
}
auto& distPreGain = processorChain.template get<distortionPreGain>(); // [5]
distPreGain.setGainDecibels(driveDb); // [6]
auto& distPostLPF = processorChain.template get<distortionPostLPF>();
distPostLPF.coefficients = *juce::dsp::IIR::Coefficients<float>::makePeakFilter(
spec.sampleRate,
toneHz, // cutoff
0.707f, // Q
juce::Decibels::decibelsToGain(shared.highGainDbls->load())
);
// =====================================================================
// Flanger
// =====================================================================
// Get pointer to writable data
auto flanger = processorChain.get<flangerIndex>();
auto rate = shared.flangerPhase->load();
auto lfoPhase = shared.flangerPhase->load();
auto flangerDepth = shared.flangerDepth->load();
auto mix = shared.flangerDryMix->load();
auto feedback = shared.flangerFeedback->load();
// Step 2: Apply flanger sample-by-sample to the block
auto* raw = block.getChannelPointer(0);
for (int i = 0; i < numSamples; ++i)
{
float in = raw[i];
float lfo = std::sin(lfoPhase);
float delayTime = (1.0f + lfo) * 0.5f * flangerDepth * spec.sampleRate;
flanger.setDelay(delayTime);
float delayed = flanger.popSample(0);
flanger.pushSample(0, in + delayed * feedback);
raw[i] = in * (1.0f - mix) + delayed * mix;
lfoPhase += juce::MathConstants<float>::twoPi * rate / spec.sampleRate;
if (lfoPhase > juce::MathConstants<float>::twoPi)
lfoPhase -= juce::MathConstants<float>::twoPi;
}
// Step 3: Run through ProcessorChain (filter + distortion)
juce::dsp::ProcessContextReplacing<float> fxContext(block);
processorChain.process(fxContext);
auto& master = processorChain.get<masterIndex>();
const auto ex = shared.masterDbls->load();
master.setGainDecibels(shared.masterDbls->load());
auto& lowEQ = processorChain.get<eqLowIndex>();
lowEQ.coefficients = juce::dsp::IIR::Coefficients<float>::makeLowShelf(
spec.sampleRate,
100.0f, // cutoff
0.707f, // Q, not used by all filters
juce::Decibels::decibelsToGain(shared.lowGainDbls->load())
);
auto& midEQ = processorChain.get<eqMidIndex>();
midEQ.coefficients = *juce::dsp::IIR::Coefficients<float>::makePeakFilter(
spec.sampleRate,
1000.0f, // center frequency
1.0f, // Q
juce::Decibels::decibelsToGain(shared.midGainDbls->load())
);
// HIGH SHELF
auto& highEQ = processorChain.get<eqHighIndex>();
highEQ.coefficients = *juce::dsp::IIR::Coefficients<float>::makePeakFilter(
spec.sampleRate,
10000.0f, // cutoff
0.707f, // Q
juce::Decibels::decibelsToGain(shared.highGainDbls->load())
);
// 3. Apply ADSR envelope to tempBlock
std::vector<float*> channelPtrs;
@@ -80,9 +164,65 @@ void NeuralSynthVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer, i
.add(tempBlock);
}
//==============================================================================
void NeuralSynthVoice::noteStarted()
{
auto velocity = getCurrentlyPlayingNote().noteOnVelocity.asUnsignedFloat();
auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<oscIndex>().setFrequency(freqHz, true);
auto& chorus = processorChain.get<chorusIndex>();
chorus.setCentreDelay(shared.chorusCentre->load());
chorus.setDepth(shared.chorusDepth->load());
chorus.setFeedback(shared.chorusFeedback->load());
chorus.setMix(shared.chorusMix->load());
chorus.setRate(shared.chorusRate->load());
processorChain.get<delayIndex>().setDelay(shared.delayTime->load());
juce::Reverb::Parameters rp;
rp.damping = shared.reverbDamping->load();
rp.dryLevel = shared.reverbDryLevel->load();
rp.freezeMode = shared.reverbFreezeMode->load();
rp.roomSize = shared.reverbRoomSize->load();
rp.wetLevel = shared.reverbWetLevel->load();
rp.width = shared.reverbWidth->load();
processorChain.get<reverbIndex>().setParameters(rp);
juce::ADSR::Parameters p;
p.attack = shared.adsrAttack->load();
p.decay = shared.adsrDecay->load();
p.sustain = shared.adsrSustain->load();
p.release = shared.adsrRelease->load();
adsr.setParameters(p);
adsr.noteOn();
}
//==============================================================================
void NeuralSynthVoice::notePitchbendChanged()
{
auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();
processorChain.get<oscIndex>().setFrequency(freqHz, true);
}
//==============================================================================
void NeuralSynthVoice::noteStopped(bool allowTailOff)
{
adsr.noteOff(); //Triggers release phase
}
//==============================================================================
void NeuralSynthVoice::notePressureChanged() {}
void NeuralSynthVoice::noteTimbreChanged() {}
void NeuralSynthVoice::noteKeyStateChanged() {}
void NeuralSynthVoice::setWaveform(int waveformType)
{
auto& osc = processorChain.template get<synthIndex>();
auto& osc = processorChain.template get<oscIndex>();
switch (waveformType)
{

View File

@@ -82,13 +82,37 @@ private:
enum
{
synthIndex
oscIndex,
distortionPreGain,
distortionIndex,
distortionPostLPF,
flangerIndex,
chorusIndex,
delayIndex,
reverbIndex,
eqLowIndex,
eqMidIndex,
eqHighIndex,
masterIndex
};
juce::dsp::ProcessorChain<
juce::dsp::Oscillator<float>
juce::dsp::Oscillator<float>,
juce::dsp::Gain<float>,
juce::dsp::WaveShaper<float, std::function<float(float)>>,
juce::dsp::IIR::Filter<float>,
juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::Linear>,
juce::dsp::Chorus<float>,
juce::dsp::DelayLine<float>,
juce::dsp::Reverb,
juce::dsp::IIR::Filter<float>, // Low shelf
juce::dsp::IIR::Filter<float>, // Mid peak
juce::dsp::IIR::Filter<float>, // High shelf
juce::dsp::Gain<float>
> processorChain;
juce::dsp::ProcessSpec spec;
juce::ADSR adsr;
NeuralSharedParams& shared;