From 3967560318c8b1ba8eff42b8ec506fc55638068a Mon Sep 17 00:00:00 2001 From: Timothy Scully Date: Sun, 12 Oct 2025 16:41:38 +0100 Subject: [PATCH] More advanced version --- JuceLibraryCode/JuceHeader.h | 6 +- JuceLibraryCode/JucePluginDefines.h | 45 ++-- NeuralSynth.jucer | 7 +- Source/GraphComponent.h | 118 +++++++++ Source/NeuralSharedParams.h | 116 ++++++++- Source/PluginEditor.cpp | 385 +++++++++++++++++++++++++--- Source/PluginEditor.h | 332 +++++++++++++++++++++++- Source/PluginProcessor.cpp | 141 ++++++++-- Source/PluginProcessor.h | 23 +- Source/SynthVoice.cpp | 220 +++++++++++++--- Source/SynthVoice.h | 28 +- 11 files changed, 1288 insertions(+), 133 deletions(-) create mode 100644 Source/GraphComponent.h diff --git a/JuceLibraryCode/JuceHeader.h b/JuceLibraryCode/JuceHeader.h index bfa7f03..9eccfa7 100644 --- a/JuceLibraryCode/JuceHeader.h +++ b/JuceLibraryCode/JuceHeader.h @@ -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 diff --git a/JuceLibraryCode/JucePluginDefines.h b/JuceLibraryCode/JucePluginDefines.h index f47135b..6c0fa21 100644 --- a/JuceLibraryCode/JucePluginDefines.h +++ b/JuceLibraryCode/JucePluginDefines.h @@ -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 diff --git a/NeuralSynth.jucer b/NeuralSynth.jucer index 3c21fa2..f43271b 100644 --- a/NeuralSynth.jucer +++ b/NeuralSynth.jucer @@ -1,9 +1,14 @@ + 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"> + +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& 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 line(juce::Point(px_prev, py_prev), juce::Point(px_next, py_next)); + + g.drawLine(line); + } + } + } + + //============================================================================== + void resized() override {} + +private: + //============================================================================== + std::vector x, y; + SampleType minY, maxY; + SampleType min, max; + int numPoints; + std::function 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 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] }); + }*/ +}; \ No newline at end of file diff --git a/Source/NeuralSharedParams.h b/Source/NeuralSharedParams.h index 709d70e..3527cb9 100644 --- a/Source/NeuralSharedParams.h +++ b/Source/NeuralSharedParams.h @@ -12,12 +12,120 @@ #include +struct SliderDetail { + std::string label; + + float min, max, interval, defValue; +}; + +typedef std::unordered_map ParamMap; + +// Each SliderDetail: { label, min, max, step, defaultValue } +const std::unordered_map 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 waveform{ -1 }; - std::atomic* attack; - std::atomic* decay; - std::atomic* sustain; - std::atomic* release; + std::atomic* adsrAttack; + std::atomic* adsrDecay; + std::atomic* adsrSustain; + std::atomic* adsrRelease; + + std::atomic* delayTime; + + std::atomic* chorusRate; + std::atomic* chorusDepth; + std::atomic* chorusCentre; + std::atomic* chorusFeedback; + std::atomic* chorusMix; + + std::atomic* reverbRoomSize; + std::atomic* reverbDamping; + std::atomic* reverbWetLevel; + std::atomic* reverbDryLevel; + std::atomic* reverbWidth; + std::atomic* reverbFreezeMode; + + std::atomic* flangerRate; + std::atomic* flangerDepth; + std::atomic* flangerFeedback; + std::atomic* flangerDryMix; + std::atomic* flangerPhase; + std::atomic* flangerDelay; + + std::atomic* filterCutoff; + std::atomic* filterResonance; + std::atomic* filterType; + std::atomic* filterDrive; + std::atomic* filterMod; + std::atomic* filterKey; + + std::atomic* distortionDrive; + std::atomic* distortionMix; + std::atomic* distortionBias; + std::atomic* distortionTone; + std::atomic* distortionShape; + + std::atomic* lowGainDbls; + std::atomic* midGainDbls; + std::atomic* highGainDbls; + + std::atomic* masterDbls; }; \ No newline at end of file diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index fa44511..99d8acb 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -12,65 +12,335 @@ //============================================================================== 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( + // audioProcessor.parameters, "waveform", waveformSelector); + + //attachments.push_back(std::make_unique( + // tree, sliderDetail.name, *sliders.back())); + + gainAttachment = std::make_unique( + audioProcessor.parameters, "master", masterLevelSlider.slider + ); + + + flangerComponent.emplace(tree, "flanger"); + flangerComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue()); + addAndMakeVisible(*flangerComponent); + + distortionComponent.emplace(tree, "distortion"); + distortionComponent->enableSampleScope(audioProcessor.getFlangerAudioBufferQueue()); + addAndMakeVisible(*distortionComponent); + + filterComponent.emplace(tree, "filter"); + filterComponent->enableSampleScope(audioProcessor.getFilterAudioBufferQueue()); + addAndMakeVisible(*filterComponent); + + //addAndMakeVisible(midiKeyboardComponent); + + + //scopeComponent.setSize(area.getWidth(), area.getHeight()); + + //midiKeyboardComponent.setMidiChannel(2); + //midiKeyboardState.addListener(&audioProcessor.getMidiMessageCollector()); + + //midiKeyboardComponent.setBounds(area.removeFromTop(80).reduced(8)); + //midiKeyboardComponent.setTopLeftPosition(8, 420); + + // Make sure that before the constructor has finished, you've set the + // editor's size to whatever you need it to be. + setSize(1400, 700); + +} + +//============================================================================== +NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor() +{ +} + +/*void NeuralSynthAudioProcessorEditor::updateEQFromSliders() +{ + using Coefficients = juce::dsp::IIR::Coefficients; + + auto& low = audioProcessor.getProcess.get<0>(); + auto& mid = audioProcessor.eqChain.get<1>(); + auto& high = audioProcessor.eqChain.get<2>(); + + low.coefficients = Coefficients::makeLowShelf(audioProcessor.sampleRate, 100.0f, 0.707f, + juce::Decibels::decibelsToGain(lowGainSlider.getValue())); + mid.coefficients = Coefficients::makePeakFilter(audioProcessor.sampleRate, 1000.0f, 0.707f, + juce::Decibels::decibelsToGain(midGainSlider.getValue())); + high.coefficients = Coefficients::makeHighShelf(audioProcessor.sampleRate, 8000.0f, 0.707f, + juce::Decibels::decibelsToGain(highGainSlider.getValue())); +}*/ + +//============================================================================== +/*void NeuralSynthAudioProcessorEditor::createADSR(int xCoord, int yCoord) { + adsrGraph.setFunction([this](float x) { + auto& tree = this->audioProcessor.parameters; + + float attackValue = tree.getParameter("attack")->getValue(); + float decayValue = tree.getParameter("decay")->getValue(); + float sustainValue = tree.getParameter("sustain")->getValue(); + float releaseValue = tree.getParameter("release")->getValue(); + + float sustainLength = 1.0f; + float totalTime = attackValue + decayValue + sustainLength + releaseValue; + + attackValue /= totalTime; + decayValue /= totalTime; + sustainLength /= totalTime; + releaseValue /= totalTime; + + float m, c; + if (x < attackValue) + { + m = (1.0f / attackValue); + c = 0; + } else if (x < (attackValue + decayValue)) { + m = (sustainValue - 1.0f) / decayValue; + c = 1.0f - m * attackValue; + } else if (x < (attackValue + decayValue + sustainLength)) { + m = 0.0f; + c = sustainValue; + } else { + m = (sustainValue / -releaseValue); + c = -m; + } + + return m * x + c; + }); + + + int fontSize = 11; + int leftPosition = xCoord; + const int sliderWidth = 60; + const int sliderWidthWithPadding = sliderWidth + 20; + + adsrGraph.setBounds(xCoord, yCoord, 240, 150); + + addAndMakeVisible(adsrGraph); + + + for (auto* slider : { &attackSlider, &decaySlider, &sustainSlider, &releaseSlider }) + { + slider->setSliderStyle(juce::Slider::Rotary); + slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, sliderWidth + 20, 20); + //slider->setTopLeftPosition(0, 0); + slider->setBounds(leftPosition, yCoord + 150, sliderWidth, sliderWidthWithPadding); + addAndMakeVisible(*slider); + leftPosition += sliderWidth; + } + + leftPosition = xCoord + 3; // (sliderWidth / 2); + for (auto* label : { &attackLabel, &decayLabel, &sustainLabel, &releaseLabel }) + { + label->setFont(juce::Font((float)fontSize, juce::Font::bold)); + //label->setTopLeftPosition(leftPosition, 300); + label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); + label->setJustificationType(juce::Justification::centred); + label->setBounds(leftPosition, yCoord + 240, 50, 20); + //label->setText(""); + addAndMakeVisible(*label); + leftPosition += sliderWidth; + } + + attackLabel.setText("Attack", juce::dontSendNotification); decayLabel.setText("Decay", juce::dontSendNotification); + sustainLabel.setText("Sustain", juce::dontSendNotification); releaseLabel.setText("Release", juce::dontSendNotification); + + auto& tree = this->audioProcessor.parameters; attackAttachment = std::make_unique(tree, "attack", attackSlider); decayAttachment = std::make_unique(tree, "decay", decaySlider); sustainAttachment = std::make_unique(tree, "sustain", sustainSlider); releaseAttachment = std::make_unique(tree, "release", releaseSlider); - 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); + 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); - // Attach to parameter - waveformAttachment = std::make_unique( - audioProcessor.parameters, "waveform", waveformSelector); + auto& tree = this->audioProcessor.parameters; + delayDelayAttachment = std::make_unique(tree, "delayDelay", reverbRoomSizeSlider); - 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); + delayDelaySlider.setRange(0.0, 1.0); } +*/ -NeuralSynthAudioProcessorEditor::~NeuralSynthAudioProcessorEditor() -{ -} +/*void NeuralSynthAudioProcessorEditor::createReverb(int xCoord, int yCoord) { + + int fontSize = 11; + int leftPosition = xCoord; + const int sliderWidth = 60; + const int sliderWidthWithPadding = sliderWidth + 20; + + reverbScopeComponent.setBounds(xCoord, yCoord, 360, 150); + + addAndMakeVisible(reverbScopeComponent); + + + for (auto* slider : { &reverbRoomSizeSlider, &reverbDampingSlider, &reverbWetLevelSlider, &reverbDryLevelSlider, &reverbWidthSlider, &reverbFreezeModeSlider }) + { + slider->setSliderStyle(juce::Slider::Rotary); + slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, sliderWidth + 20, 20); + //slider->setTopLeftPosition(0, 0); + slider->setBounds(leftPosition, yCoord + 150, sliderWidth, sliderWidthWithPadding); + addAndMakeVisible(*slider); + leftPosition += sliderWidth; + } + + leftPosition = xCoord + 3; // (sliderWidth / 2); + for (auto* label : { &reverbRoomSizeLabel, &reverbDampingLabel, &reverbWetLevelLabel, &reverbDryLevelLabel, &reverbWidthLabel, &reverbFreezeModeLabel }) + { + label->setFont(juce::Font((float)fontSize, juce::Font::bold)); + //label->setTopLeftPosition(leftPosition, 300); + label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); + label->setJustificationType(juce::Justification::centred); + label->setBounds(leftPosition, yCoord + 240, 50, 20); + //label->setText(""); + addAndMakeVisible(*label); + leftPosition += sliderWidth; + } + + reverbRoomSizeLabel.setText("Room Size", juce::dontSendNotification); reverbDampingLabel.setText("Damping", juce::dontSendNotification); + reverbWetLevelLabel.setText("Wet Level", juce::dontSendNotification); reverbDryLevelLabel.setText("Dry Level", juce::dontSendNotification); + reverbWidthLabel.setText("Width", juce::dontSendNotification); reverbFreezeModeLabel.setText("Freeze Mode", juce::dontSendNotification); + + auto& tree = this->audioProcessor.parameters; + + reverbRoomSizeAttachment = std::make_unique(tree, "reverbRoomSize", reverbRoomSizeSlider); + reverbDampingAttachment = std::make_unique(tree, "reverbDamping", reverbDampingSlider); + reverbWetLevelAttachment = std::make_unique(tree, "reverbWetLevel", reverbWetLevelSlider); + reverbDryLevelAttachment = std::make_unique(tree, "reverbDryLevel", reverbDryLevelSlider); + reverbWidthAttachment = std::make_unique(tree, "reverbWidth", reverbWidthSlider); + reverbFreezeModeAttachment = std::make_unique(tree, "reverbFreezeMode", reverbFreezeModeSlider); + + + reverbRoomSizeSlider.setRange(0.0, 1.0); + reverbDampingSlider.setRange(0.0, 1.0); + reverbWetLevelSlider.setRange(0.0, 1.0); + reverbDryLevelSlider.setRange(0.0, 1.0); + reverbWidthSlider.setRange(0.0, 1.0); + reverbFreezeModeSlider.setRange(0.0, 1.0); +}*/ //============================================================================== void NeuralSynthAudioProcessorEditor::paint (juce::Graphics& g) @@ -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); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index b0a1353..7a78e19 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -10,8 +10,227 @@ #include #include "PluginProcessor.h" +#include "GraphComponent.h" #include "ScopeComponent.h" + +class ScopeSliderComponent : public juce::Component { + static const int fontSize = 11; + +public: + ScopeSliderComponent(juce::AudioProcessorValueTreeState& tree, const std::string paramGroup) { + const auto& sliderDetails = PARAM_SETTINGS.at(paramGroup); + + for (const auto& [name, sliderDetail] : sliderDetails) { + sliders.push_back(std::make_unique()); + labels.push_back(std::make_unique()); + attachments.push_back(std::make_unique( + tree, paramGroup + "_" + name, *sliders.back())); + + labels.back()->setText(sliderDetail.label, juce::NotificationType::dontSendNotification); + sliders.back()->setRange(sliderDetail.min, sliderDetail.max); + } + + for (auto& slider : sliders) + { + slider->setSliderStyle(juce::Slider::Rotary); + slider->setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); + addAndMakeVisible(*slider); + } + + for (auto& label : labels) + { + label->setFont(juce::Font((float)fontSize, juce::Font::bold)); + label->setColour(juce::Label::textColourId, juce::Colours::lightgreen); + label->setJustificationType(juce::Justification::centred); + //label->setBoundsToFit() + addAndMakeVisible(*label); + } + } + + void enableSampleScope(AudioBufferQueue& audioBufferQueue) { + scope.emplace(audioBufferQueue); + useGraphScope = false; + addAndMakeVisible(*scope); + + } + + void enableGraphScope(const std::function& func) { + graphScope.emplace(0.0f, 1.0f, 100); + graphScope->setFunction(func); + useGraphScope = true; + addAndMakeVisible(*graphScope); + } + +private: + void paint(juce::Graphics& g) override + { + //juce::Random rng; + //g.fillAll(juce::Colour::fromFloatRGBA(rng.nextFloat(), rng.nextFloat(), rng.nextFloat(), 1.0f)); + + g.fillAll(juce::Colours::darkgrey); + + g.setColour(juce::Colours::white); + g.drawRect(getLocalBounds()); + } + + void resized() override { + juce::Grid grid; + grid.templateRows = { juce::Grid::TrackInfo(juce::Grid::Fr(50)), + juce::Grid::TrackInfo(juce::Grid::Fr(30)), + juce::Grid::TrackInfo(juce::Grid::Fr(10)), + juce::Grid::TrackInfo(juce::Grid::Fr(10)) + }; + + grid.templateColumns.resize(sliders.size()); + for (int i = 0; i < sliders.size(); i++) { + grid.templateColumns.getReference(i) = juce::Grid::TrackInfo(juce::Grid::Fr(1)); + } + + std::optional scopeGridItem; + scopeGridItem.emplace(useGraphScope ? juce::GridItem(*graphScope).withArea({}, juce::GridItem::Span(sliders.size())) + : juce::GridItem(*scope).withArea({}, juce::GridItem::Span(sliders.size()))); + + grid.items.resize(1 + 2 * sliders.size()); + + grid.items.getReference(0) = *scopeGridItem; + + for (int i = 0; i < sliders.size(); i++) { + grid.items.getReference(i + 1) = juce::GridItem(*sliders[i]); + } + + for (int i = 0; i < sliders.size(); i++) { + grid.items.getReference(i + sliders.size() + 1) = juce::GridItem(*labels[i]); + }; + + auto bounds = getLocalBounds().reduced(10); + grid.performLayout(bounds); + } + + bool useGraphScope{ false }; + std::optional > scope; + std::optional > graphScope; + + std::vector > sliders; + std::vector > labels; + std::vector > attachments; +}; + + +class EqualizerComponent : public juce::Component { + static const int fontSize = 11; + +public: + EqualizerComponent(juce::AudioProcessorValueTreeState& tree) { + setupSlider(lowGainSlider); + setupSlider(midGainSlider); + setupSlider(highGainSlider); + + setupLabel(lowGainLabel, "L"); + setupLabel(midGainLabel, "M"); + setupLabel(highGainLabel, "H"); + + lowGainAttachment = std::make_unique(tree, "lowEQ", lowGainSlider); + midGainAttachment = std::make_unique(tree, "midEQ", midGainSlider); + highGainAttachment = std::make_unique(tree, "highEQ", highGainSlider); + } + + void setupSlider(juce::Slider& slider) { + slider.setRange(-24.0f, 24.0f, 0.1f); + + slider.setSliderStyle(juce::Slider::LinearBarVertical); + slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 20); + + addAndMakeVisible(slider); + } + + void setupLabel(juce::Label &lbl, juce::String txt) { + lbl.setFont(juce::Font((float)fontSize, juce::Font::bold)); + lbl.setColour(juce::Label::textColourId, juce::Colours::lightgreen); + lbl.setJustificationType(juce::Justification::centred); + lbl.setText(txt, juce::NotificationType::dontSendNotification); + + addAndMakeVisible(lbl); + } + +private: + void paint(juce::Graphics& g) override + { + g.fillAll(juce::Colours::darkgrey); + g.setColour(juce::Colours::white); + g.drawRect(getLocalBounds()); + } + + void resized() override { + juce::Grid grid; + grid.templateRows = { + juce::Grid::TrackInfo(juce::Grid::Fr(1)), + juce::Grid::TrackInfo(juce::Grid::Fr(1)) + }; + + grid.templateColumns = { + juce::Grid::TrackInfo(juce::Grid::Fr(1)), + juce::Grid::TrackInfo(juce::Grid::Fr(1)), + juce::Grid::TrackInfo(juce::Grid::Fr(1)) + }; + + grid.items = { + lowGainSlider, midGainSlider, highGainSlider, + lowGainLabel, midGainLabel, highGainLabel + }; + + auto bounds = getLocalBounds().reduced(10); + grid.performLayout(bounds); + } + + juce::Slider lowGainSlider, midGainSlider, highGainSlider; + juce::Label lowGainLabel, midGainLabel, highGainLabel; + std::unique_ptr lowGainAttachment, midGainAttachment, highGainAttachment; +}; + +struct WaveformSelectorContents final : public juce::ListBoxModel +{ + // The following methods implement the necessary virtual functions from ListBoxModel, + // telling the listbox how many rows there are, painting them, etc. + int getNumRows() override + { + return 4; + } + + void paintListBoxItem(int rowNumber, juce::Graphics& g, + int width, int height, bool rowIsSelected) override + { + if (rowIsSelected) + g.fillAll(juce::Colours::lightblue); + + g.setColour(juce::LookAndFeel::getDefaultLookAndFeel().findColour(juce::Label::textColourId)); + g.setFont((float)height * 0.7f); + + g.drawText(waves[rowNumber], 5, 0, width, height, juce::Justification::centredLeft, true); + } + + std::vector waves = { "Sine", "Saw", "Square", "Triangle" }; +}; + +class MasterVolumeComponent : public juce::Component +{ +public: + MasterVolumeComponent() + { + slider.setSliderStyle(juce::Slider::LinearBarVertical); + slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 20, 20); // Optional + addAndMakeVisible(slider); + } + + void resized() override + { + auto padded = getLocalBounds().reduced(30); // Adjust padding here + slider.setBounds(padded); + } + + juce::Slider slider; +}; + //============================================================================== /** */ @@ -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 waveformAttachment; + juce::ListBox waveformSelector; + WaveformSelectorContents waveformContents; + //std::unique_ptr waveformAttachment; + + + //============================================================================== + // ADSR + + /*void createADSR(int xCoord, int yCoord); juce::Slider attackSlider, decaySlider, sustainSlider, releaseSlider; + juce::Label attackLabel, decayLabel, sustainLabel, releaseLabel; std::unique_ptr attackAttachment; std::unique_ptr decayAttachment; std::unique_ptr sustainAttachment; - std::unique_ptr releaseAttachment; + std::unique_ptr releaseAttachment;*/ + + + //============================================================================== + + std::optional adsrComponent; + std::optional chorusComponent; + std::optional delayComponent; + std::optional reverbComponent; + + + std::optional flangerComponent; + std::optional distortionComponent; + std::optional filterComponent; + + + + + /*//============================================================================== + // Chorus + + void createChorus(int xCoord, int yCoord); + juce::Slider chorusRateSlider, chorusDepthSlider, chorusCentreSlider, chorusFeedbackSlider, chorusMixSlider; + juce::Label chorusRateLabel, chorusDepthLabel, chorusCentreLabel, chorusFeedbackLabel, chorusMixLabel; + + std::unique_ptr chorusRateAttachment; + std::unique_ptr chorusDepthAttachment; + std::unique_ptr chorusCentreAttachment; + std::unique_ptr chorusFeedbackAttachment; + std::unique_ptr chorusMixAttachment; + + //============================================================================== + + //============================================================================== + // Delay + + void createDelay(int xCoord, int yCoord); + juce::Slider delayDelaySlider; + juce::Label delayDelayLabel; + + std::unique_ptr delayDelayAttachment; + + //============================================================================== + + + //============================================================================== + // Reverb + + void createReverb(int xCoord, int yCoord); + juce::Slider reverbRoomSizeSlider, reverbDampingSlider, reverbWetLevelSlider, reverbDryLevelSlider, reverbWidthSlider, + reverbFreezeModeSlider; + juce::Label reverbRoomSizeLabel, reverbDampingLabel, reverbWetLevelLabel, reverbDryLevelLabel, reverbWidthLabel, + reverbFreezeModeLabel; + + std::unique_ptr reverbRoomSizeAttachment; + std::unique_ptr reverbDampingAttachment; + std::unique_ptr reverbWetLevelAttachment; + std::unique_ptr reverbDryLevelAttachment; + std::unique_ptr reverbWidthAttachment; + std::unique_ptr reverbFreezeModeAttachment;*/ + + //============================================================================== + // Master + + MasterVolumeComponent masterLevelSlider; + juce::Label masterLevelLabel; + + //============================================================================== + + //============================================================================== + // EQ + + //void updateEQFromSliders(); + std::optional eqComponent; + + + //============================================================================== + + //============================================================================== + // Master + + juce::Slider gainSlider; + juce::Label gainLabel; + + std::unique_ptr gainAttachment; + + //============================================================================== + + //juce::MidiKeyboardState midiKeyboardState; + //juce::MidiKeyboardComponent midiKeyboardComponent{ midiKeyboardState, juce::MidiKeyboardComponent::horizontalKeyboard }; + ScopeComponent mainScopeComponent; + /*ScopeComponent chorusScopeComponent; + ScopeComponent delayScopeComponent; + ScopeComponent reverbScopeComponent; + + GraphComponent adsrGraph;*/ - juce::MidiKeyboardState midiKeyboardState; - juce::MidiKeyboardComponent midiKeyboardComponent{ midiKeyboardState, juce::MidiKeyboardComponent::horizontalKeyboard }; - ScopeComponent scopeComponent; }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index b0d4558..0d548b1 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -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> ¶ms, const std::string& paramGroup) { + const auto& paramGroupSettings = PARAM_SETTINGS.at(paramGroup); + + for (const auto& [name, paramSettings] : paramGroupSettings) { + params.push_back(std::make_unique(paramGroup + "_" + name, paramSettings.label, + juce::NormalisableRange(paramSettings.min, paramSettings.max, paramSettings.interval), + paramSettings.defValue)); + } +} + juce::AudioProcessorValueTreeState::ParameterLayout NeuralSynthAudioProcessor::createParameterLayout() { std::vector> 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("attack", "Attack", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.1f)); - params.push_back(std::make_unique("decay", "Decay", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.5f)); - params.push_back(std::make_unique("sustain", "Sustain", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.8f)); - params.push_back(std::make_unique("release", "Release", - juce::NormalisableRange(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("master", "Master", + juce::NormalisableRange(-24.0f, 24.0f, 0.1f), 0.1f)); + + params.push_back(std::make_unique("lowEQ", "Low Gain", + juce::NormalisableRange(-24.0f, 24.0f, 0.1f), 0.5f)); + params.push_back(std::make_unique("midEQ", "Mid EQ", + juce::NormalisableRange(-24.0f, 24.0f, 0.1f), 0.8f)); + params.push_back(std::make_unique("highEQ", "High EQ", + juce::NormalisableRange(-24.0f, 24.0f, 0.1f), 1.0f)); return { params.begin(), params.end() }; } \ No newline at end of file diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index af14600..3e95a2a 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -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>& params, const std::string& paramGroup); + juce::MidiMessageCollector& getMidiMessageCollector() noexcept { return midiMessageCollector; } juce::MidiMessageCollector midiMessageCollector; @@ -68,12 +71,30 @@ public: juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); AudioBufferQueue& getAudioBufferQueue() noexcept { return audioBufferQueue; } + + AudioBufferQueue& getChorusAudioBufferQueue() noexcept { return chorusBufferQueue; } + AudioBufferQueue& getDelayAudioBufferQueue() noexcept { return delayBufferQueue; } + AudioBufferQueue& getReverbAudioBufferQueue() noexcept { return reverbBufferQueue; } + + AudioBufferQueue& getFlangerAudioBufferQueue() noexcept { return flangerBufferQueue; } + AudioBufferQueue& getDistortionAudioBufferQueue() noexcept { return distortionBufferQueue; } + AudioBufferQueue& getFilterAudioBufferQueue() noexcept { return filterBufferQueue; } + private: //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NeuralSynthAudioProcessor) - NeuralAudioEngine audioEngine; + NeuralAudioEngine audioEngine; AudioBufferQueue audioBufferQueue; + + AudioBufferQueue chorusBufferQueue; + AudioBufferQueue delayBufferQueue; + AudioBufferQueue reverbBufferQueue; + + AudioBufferQueue flangerBufferQueue; + AudioBufferQueue distortionBufferQueue; + AudioBufferQueue filterBufferQueue; + ScopeDataCollector scopeDataCollector{ audioBufferQueue }; NeuralSharedParams sp; diff --git a/Source/SynthVoice.cpp b/Source/SynthVoice.cpp index bc7be6b..7475dcb 100644 --- a/Source/SynthVoice.cpp +++ b/Source/SynthVoice.cpp @@ -1,5 +1,7 @@ #include "SynthVoice.h" +#include + //============================================================================== NeuralSynthVoice::NeuralSynthVoice(NeuralSharedParams& sp) : shared(sp) {} @@ -10,60 +12,142 @@ void NeuralSynthVoice::prepare(const juce::dsp::ProcessSpec& spec) tempBlock = juce::dsp::AudioBlock(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().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().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& outputBuffer, int startSample, int numSamples) { + if (numSamples <= 0) return; + if (!adsr.isActive()) clearCurrentNote(); - if (waveform != -1) { + if (waveform != -1) { setWaveform(waveform); waveform = -1; } + const int numChannels = outputBuffer.getNumChannels(); + auto block = tempBlock.getSubBlock(0, (size_t)numSamples); block.clear(); - juce::dsp::ProcessContextReplacing context(block); - processorChain.process(context); + // ===================================================================== + // Oscillator + // ===================================================================== + auto& osc = processorChain.get(); + juce::dsp::ProcessContextReplacing 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(); + + auto& distWaveshaper = processorChain.template get(); + + 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::pi)); + }; + } + auto& distPreGain = processorChain.template get(); // [5] + distPreGain.setGainDecibels(driveDb); // [6] + + auto& distPostLPF = processorChain.template get(); + distPostLPF.coefficients = *juce::dsp::IIR::Coefficients::makePeakFilter( + spec.sampleRate, + toneHz, // cutoff + 0.707f, // Q + juce::Decibels::decibelsToGain(shared.highGainDbls->load()) + ); + + // ===================================================================== + // Flanger + // ===================================================================== + // Get pointer to writable data + auto flanger = processorChain.get(); + 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::twoPi * rate / spec.sampleRate; + if (lfoPhase > juce::MathConstants::twoPi) + lfoPhase -= juce::MathConstants::twoPi; + } + + // Step 3: Run through ProcessorChain (filter + distortion) + juce::dsp::ProcessContextReplacing fxContext(block); + processorChain.process(fxContext); + + auto& master = processorChain.get(); + const auto ex = shared.masterDbls->load(); + master.setGainDecibels(shared.masterDbls->load()); + + auto& lowEQ = processorChain.get(); + lowEQ.coefficients = juce::dsp::IIR::Coefficients::makeLowShelf( + spec.sampleRate, + 100.0f, // cutoff + 0.707f, // Q, not used by all filters + juce::Decibels::decibelsToGain(shared.lowGainDbls->load()) + ); + + auto& midEQ = processorChain.get(); + midEQ.coefficients = *juce::dsp::IIR::Coefficients::makePeakFilter( + spec.sampleRate, + 1000.0f, // center frequency + 1.0f, // Q + juce::Decibels::decibelsToGain(shared.midGainDbls->load()) + ); + + // HIGH SHELF + auto& highEQ = processorChain.get(); + highEQ.coefficients = *juce::dsp::IIR::Coefficients::makePeakFilter( + spec.sampleRate, + 10000.0f, // cutoff + 0.707f, // Q + juce::Decibels::decibelsToGain(shared.highGainDbls->load()) + ); + // 3. Apply ADSR envelope to tempBlock std::vector channelPtrs; for (size_t ch = 0; ch < tempBlock.getNumChannels(); ++ch) @@ -80,9 +164,65 @@ void NeuralSynthVoice::renderNextBlock(juce::AudioBuffer& outputBuffer, i .add(tempBlock); } +//============================================================================== +void NeuralSynthVoice::noteStarted() +{ + auto velocity = getCurrentlyPlayingNote().noteOnVelocity.asUnsignedFloat(); + auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz(); + + processorChain.get().setFrequency(freqHz, true); + + auto& chorus = processorChain.get(); + 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().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().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().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(); + auto& osc = processorChain.template get(); switch (waveformType) { diff --git a/Source/SynthVoice.h b/Source/SynthVoice.h index fa8f2fb..d30bd60 100644 --- a/Source/SynthVoice.h +++ b/Source/SynthVoice.h @@ -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 + juce::dsp::Oscillator, + juce::dsp::Gain, + juce::dsp::WaveShaper>, + juce::dsp::IIR::Filter, + juce::dsp::DelayLine, + juce::dsp::Chorus, + juce::dsp::DelayLine, + juce::dsp::Reverb, + juce::dsp::IIR::Filter, // Low shelf + juce::dsp::IIR::Filter, // Mid peak + juce::dsp::IIR::Filter, // High shelf + juce::dsp::Gain > processorChain; + juce::dsp::ProcessSpec spec; + juce::ADSR adsr; NeuralSharedParams& shared;