From d9f672cd1005f3db0567d81d449d8b14b5a04a3b Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 25 Oct 2025 17:56:37 +0000 Subject: [PATCH] Upload files to "Source" --- Source/PluginProcessor.cpp | 690 ++++++++++++++++++++++++++++++++++++- Source/PluginProcessor.h | 22 ++ 2 files changed, 695 insertions(+), 17 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index f357fe3..f051d70 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1,13 +1,54 @@ #include "PluginProcessor.h" #include "PluginEditor.h" +#include "WavetableOsc.h" +#include +#include //============================================================================== NeuralSynthAudioProcessor::NeuralSynthAudioProcessor() : parameters(*this, nullptr, "PARAMETERS", createParameterLayout()) , AudioProcessor(BusesProperties().withOutput("Output", juce::AudioChannelSet::stereo(), true)) , audioEngine(sp) + , factoryPresets(makeFactoryPresets()) { - parameters.addParameterListener("waveform", this); + parameters.addParameterListener("wt_phase", this); + parameters.addParameterListener("wt_on", this); + parameters.addParameterListener("wt_morph", this); + parameters.addParameterListener("wt_bank", this); + parameters.addParameterListener("wt_lfoRate", this); + parameters.addParameterListener("wt_lfoDepth", this); + parameters.addParameterListener("wt_lfoShape", this); + parameters.addParameterListener("wt_level", this); + + parameters.addParameterListener("wt2_phase", this); + parameters.addParameterListener("wt2_on", this); + parameters.addParameterListener("wt2_morph", this); + parameters.addParameterListener("wt2_bank", this); + parameters.addParameterListener("wt2_lfoRate", this); + parameters.addParameterListener("wt2_lfoDepth", this); + parameters.addParameterListener("wt2_lfoShape", this); + parameters.addParameterListener("wt2_level", this); + + sp.wtPhase = parameters.getRawParameterValue("wt_phase"); + sp.wtOn = parameters.getRawParameterValue("wt_on"); + sp.wtMorph = parameters.getRawParameterValue("wt_morph"); + sp.wtBank = parameters.getRawParameterValue("wt_bank"); + sp.wtLfoRate = parameters.getRawParameterValue("wt_lfoRate"); + sp.wtLfoDepth= parameters.getRawParameterValue("wt_lfoDepth"); + sp.wtLfoShape= parameters.getRawParameterValue("wt_lfoShape"); + sp.wtLevel = parameters.getRawParameterValue("wt_level"); + + sp.wt2Phase = parameters.getRawParameterValue("wt2_phase"); + sp.wt2On = parameters.getRawParameterValue("wt2_on"); + sp.wt2Morph = parameters.getRawParameterValue("wt2_morph"); + sp.wt2Bank = parameters.getRawParameterValue("wt2_bank"); + sp.wt2LfoRate= parameters.getRawParameterValue("wt2_lfoRate"); + sp.wt2LfoDepth= parameters.getRawParameterValue("wt2_lfoDepth"); + sp.wt2LfoShape= parameters.getRawParameterValue("wt2_lfoShape"); + sp.wt2Level = parameters.getRawParameterValue("wt2_level"); + + if (! factoryPresets.empty()) + applyPreset(0); // === Per-panel bypass (default OFF) === sp.chorusOn = parameters.getRawParameterValue("chorus_on"); @@ -158,6 +199,12 @@ void NeuralSynthAudioProcessor::changeProgramName (int, const juce::String&) {} void NeuralSynthAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { audioEngine.prepare({ sampleRate, (juce::uint32)samplesPerBlock, 2 }); + const auto numChannels = (juce::uint32) juce::jmax (1, getTotalNumOutputChannels()); + juce::dsp::ProcessSpec limiterSpec { sampleRate, (juce::uint32) samplesPerBlock, numChannels }; + outputLimiter.reset(); + outputLimiter.prepare (limiterSpec); + outputLimiter.setThreshold (-0.8f); + outputLimiter.setRelease (0.05f); midiMessageCollector.reset(sampleRate); } @@ -174,15 +221,6 @@ bool NeuralSynthAudioProcessor::isBusesLayoutSupported (const BusesLayout& layou void NeuralSynthAudioProcessor::processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiMessages) { - const int newWaveform = sp.waveform.exchange(-1); - - if (newWaveform != -1) { - audioEngine.applyToVoices([newWaveform](NeuralSynthVoice* v) - { - v->changeWaveform(newWaveform); - }); - } - juce::ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); @@ -193,6 +231,8 @@ void NeuralSynthAudioProcessor::processBlock(juce::AudioSampleBuffer& buffer, ju buffer.clear(i, 0, buffer.getNumSamples()); audioEngine.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples()); + juce::dsp::AudioBlock outputBlock (buffer); + outputLimiter.process (juce::dsp::ProcessContextReplacing (outputBlock)); scopeDataCollector.process(buffer.getReadPointer(0), (size_t)buffer.getNumSamples()); } @@ -210,9 +250,26 @@ void NeuralSynthAudioProcessor::setStateInformation (const void* data, int sizeI void NeuralSynthAudioProcessor::parameterChanged(const juce::String& id, float newValue) { - juce::ignoreUnused(newValue); - if (id == "waveform") - sp.waveform.store((int)newValue, std::memory_order_release); + if (id == "wt_bank" && ! presetChangeInProgress) + { + const int targetBank = juce::jlimit(0, (int)WT::FactoryLibrary::get().size() - 1, + (int) std::lround(newValue)); + int matched = -1; + for (int i = 0; i < (int) factoryPresets.size(); ++i) + { + if (factoryPresets[(size_t)i].wtBankIndex == targetBank) + { + matched = i; + break; + } + } + currentPresetIndex = matched; + } + else if (id == "wt2_bank" && ! presetChangeInProgress) + { + juce::ignoreUnused(newValue); + currentPresetIndex = -1; + } } //============================================================================== @@ -230,13 +287,610 @@ void NeuralSynthAudioProcessor::buildParams(std::vector NeuralSynthAudioProcessor::makeFactoryPresets() +{ + std::vector presets; + const auto& wtLibrary = WT::FactoryLibrary::get(); + if (wtLibrary.empty()) + return presets; + + presets.reserve(220); + + std::map> categoryToIndices; + for (int i = 0; i < (int) wtLibrary.size(); ++i) + categoryToIndices[wtLibrary[(size_t) i].category].push_back(i); + + const std::array requestedCategories = { + "Electric Piano", "Organ", "Bass", "Drums", "Strings", + "Brass", "Choir", "Pad", "SFX", "Lead", "Pluck", "Misc" + }; + + static const std::array, 20> gmDrumInfo = {{ + {35, "Acoustic Bass Drum"}, + {36, "Bass Drum 1"}, + {37, "Side Stick"}, + {38, "Acoustic Snare"}, + {39, "Hand Clap"}, + {40, "Electric Snare"}, + {41, "Low Floor Tom"}, + {42, "Closed Hi-Hat"}, + {43, "High Floor Tom"}, + {44, "Pedal Hi-Hat"}, + {45, "Low Tom"}, + {46, "Open Hi-Hat"}, + {47, "Low-Mid Tom"}, + {48, "Hi-Mid Tom"}, + {49, "Crash Cymbal 1"}, + {50, "High Tom"}, + {51, "Ride Cymbal 1"}, + {52, "Chinese Cymbal"}, + {53, "Ride Bell"}, + {54, "Tambourine"} + }}; + + auto clampMorph = [](float v) { return juce::jlimit(0.0f, 15.0f, v); }; + auto clampUnit = [](float v) { return juce::jlimit(0.0f, 1.0f, v); }; + auto clampLfoDepth = [](float v) { return juce::jlimit(0.0f, 8.0f, v); }; + auto clampLfoRate = [](float v) { return juce::jlimit(0.01f, 10.0f, v); }; + auto clampCutoff = [](float v) { return juce::jlimit(20.0f, 20000.0f, v); }; + auto clampRes = [](float v) { return juce::jlimit(0.1f, 10.0f, v); }; + auto clampDb = [](float v) { return juce::jlimit(-24.0f, 24.0f, v); }; + + for (const auto& category : requestedCategories) + { + auto mapIt = categoryToIndices.find(category); + if (mapIt == categoryToIndices.end()) + continue; + + const auto& indices = mapIt->second; + if (indices.size() < 20) + continue; + + for (int variant = 0; variant < 20; ++variant) + { + const bool layered = variant >= 10; + const int primaryIndex = indices[(size_t) (variant % indices.size())]; + const int secondaryIndex = layered ? indices[(size_t) ((variant + 5) % indices.size())] + : primaryIndex; + + const float t = (float) variant / 19.0f; + const float subT = (float) (variant % 10) / 9.0f; + + const bool isDrumCategory = (category == "Drums"); + std::pair gmInfo { 0, "" }; + if (isDrumCategory) + gmInfo = gmDrumInfo[(size_t) variant]; + + std::map values; + auto set = [&](const juce::String& param, float value) + { + values[param] = value; + }; + + set("wt_on", 1.0f); + set("wt_bank", (float) primaryIndex); + set("wt_morph", clampMorph(2.0f + 8.0f * t)); + set("wt_phase", 0.0f); + set("wt_lfoRate", clampLfoRate(0.25f + 0.8f * t)); + set("wt_lfoDepth", clampLfoDepth(0.6f + 2.0f * t)); + set("wt_lfoShape", (float) (variant % 4)); + set("wt_level", clampUnit(layered ? 0.72f : 0.85f)); + + set("wt2_on", layered ? 1.0f : 0.0f); + set("wt2_bank", (float) secondaryIndex); + set("wt2_morph", layered ? clampMorph(4.0f + 6.0f * (1.0f - t)) : 0.0f); + set("wt2_phase", layered ? 0.25f : 0.0f); + set("wt2_lfoRate", clampLfoRate(layered ? (0.4f + 1.3f * (1.0f - t)) : 0.3f)); + set("wt2_lfoDepth", clampLfoDepth(layered ? (1.0f + 2.0f * subT) : 0.0f)); + set("wt2_lfoShape", layered ? (float) ((variant + 1) % 4) : 0.0f); + set("wt2_level", layered ? clampUnit(0.5f + 0.3f * t) : 0.0f); + + set("chorus_on", 0.0f); + set("chorus_rate", clampUnit(0.3f)); + set("chorus_depth", clampUnit(0.3f)); + set("chorus_centre", clampUnit(0.5f)); + set("chorus_feedback", clampUnit(0.12f)); + set("chorus_mix", clampUnit(0.2f)); + + set("delay_on", 0.0f); + set("delay_delay", clampUnit(0.2f)); + + set("reverb_on", 0.0f); + set("reverb_roomSize", clampUnit(0.3f)); + set("reverb_damping", clampUnit(0.5f)); + set("reverb_wetLevel", clampUnit(0.18f)); + set("reverb_dryLevel", clampUnit(0.85f)); + set("reverb_width", clampUnit(0.9f)); + set("reverb_freezeMode", 0.0f); + + set("flanger_on", 0.0f); + set("flanger_rate", clampUnit(0.35f)); + set("flanger_depth", 2.5f); + set("flanger_feedback", clampUnit(0.15f)); + set("flanger_dryMix", clampUnit(0.25f)); + set("flanger_phase", clampUnit(0.2f)); + set("flanger_delay", 0.2f); + + set("distortion_on", 0.0f); + set("distortion_drive", 10.0f); + set("distortion_mix", clampUnit(0.2f)); + set("distortion_bias", 0.0f); + set("distortion_tone", juce::jlimit(100.0f, 8000.0f, 2400.0f)); + set("distortion_shape", 0.0f); + + set("filter_on", 0.0f); + set("filter_cutoff", clampCutoff(2000.0f)); + set("filter_resonance", clampRes(0.7f)); + set("filter_type", 0.0f); + set("filter_drive", 0.0f); + set("filter_mod", 0.0f); + set("filter_key", 0.0f); + + set("adsr_attack", clampUnit(0.02f)); + set("adsr_decay", clampUnit(0.3f)); + set("adsr_sustain", clampUnit(0.7f)); + set("adsr_release", clampUnit(0.4f)); + + set("fenv_attack", juce::jlimit(0.0f, 2.0f, 0.03f)); + set("fenv_decay", juce::jlimit(0.0f, 2.0f, 0.3f)); + set("fenv_sustain", clampUnit(0.5f)); + set("fenv_release", juce::jlimit(0.0f, 4.0f, 0.4f)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.2f)); + + set("eq_on", 1.0f); + set("lowEQ", clampDb(0.0f)); + set("midEQ", clampDb(0.0f)); + set("highEQ", clampDb(0.0f)); + + set("master", layered ? -8.0f : -6.0f); + + if (category == "Electric Piano") + { + set("chorus_on", 1.0f); + set("chorus_rate", clampUnit(0.18f + 0.25f * t)); + set("chorus_depth", clampUnit(0.35f + 0.2f * t)); + set("chorus_mix", clampUnit(0.2f + 0.15f * t)); + set("chorus_feedback", clampUnit(0.18f + 0.1f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.35f + 0.25f * t)); + set("reverb_wetLevel", clampUnit(0.2f + 0.12f * t)); + set("reverb_damping", clampUnit(0.45f + 0.1f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(1500.0f + 700.0f * t)); + set("filter_resonance", clampRes(0.8f + 0.3f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.35f + 0.15f * t)); + set("adsr_attack", clampUnit(0.02f + 0.03f * t)); + set("adsr_decay", clampUnit(0.25f + 0.1f * t)); + set("adsr_sustain", clampUnit(0.65f + 0.1f * t)); + set("adsr_release", clampUnit(0.45f + 0.18f * t)); + set("wt_level", clampUnit(layered ? 0.7f : 0.83f)); + if (layered) + { + set("wt2_level", clampUnit(0.55f + 0.2f * t)); + set("wt2_lfoRate", clampLfoRate(0.35f + 0.9f * (1.0f - t))); + set("wt2_lfoDepth", clampLfoDepth(1.1f + 1.4f * t)); + } + set("lowEQ", clampDb(1.5f)); + set("midEQ", clampDb(-1.0f + 2.5f * t)); + set("highEQ", clampDb(2.0f + 3.0f * t)); + } + else if (category == "Organ") + { + set("chorus_on", 1.0f); + set("chorus_rate", clampUnit(0.45f + 0.15f * t)); + set("chorus_depth", clampUnit(0.3f + 0.1f * t)); + set("chorus_mix", clampUnit(0.33f + 0.08f * t)); + set("chorus_feedback", clampUnit(0.15f + 0.05f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.25f + 0.2f * t)); + set("reverb_wetLevel", clampUnit(0.16f + 0.1f * t)); + set("reverb_damping", clampUnit(0.4f + 0.15f * t)); + set("filter_on", 0.0f); + set("adsr_attack", clampUnit(0.01f)); + set("adsr_decay", clampUnit(0.3f)); + set("adsr_sustain", clampUnit(1.0f)); + set("adsr_release", clampUnit(0.3f + 0.2f * t)); + set("fenv_amount", 0.0f); + set("wt_morph", clampMorph(3.0f + 5.0f * t)); + set("wt_lfoRate", clampLfoRate(0.4f + 0.45f * t)); + set("wt_lfoDepth", clampLfoDepth(0.25f + 0.35f * t)); + if (layered) + { + set("wt2_level", clampUnit(0.5f + 0.2f * t)); + set("wt2_lfoDepth", clampLfoDepth(0.4f + 0.5f * t)); + set("distortion_on", variant >= 15 ? 1.0f : 0.0f); + if (variant >= 15) + { + set("distortion_drive", 9.0f + 5.0f * t); + set("distortion_mix", clampUnit(0.18f + 0.12f * t)); + set("distortion_shape", 1.0f); + } + } + set("midEQ", clampDb(1.5f + 1.0f * t)); + set("highEQ", clampDb(2.0f + 1.5f * t)); + set("master", layered ? -8.0f : -5.0f); + } + else if (category == "Bass") + { + set("wt_morph", clampMorph(1.5f + 5.5f * t)); + set("wt_lfoRate", clampLfoRate(0.2f + 0.6f * t)); + set("wt_lfoDepth", clampLfoDepth(0.5f + 1.3f * t)); + set("wt_level", clampUnit(0.92f)); + set("adsr_attack", clampUnit(0.005f)); + set("adsr_decay", clampUnit(0.18f + 0.08f * t)); + set("adsr_sustain", clampUnit(0.45f - 0.15f * t)); + set("adsr_release", clampUnit(0.22f + 0.12f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(180.0f + 720.0f * t)); + set("filter_resonance", clampRes(0.85f + 0.35f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.6f + 0.25f * t)); + set("distortion_on", (layered || variant >= 6) ? 1.0f : 0.0f); + if (values["distortion_on"] > 0.5f) + { + set("distortion_drive", 14.0f + 8.0f * t); + set("distortion_mix", clampUnit(0.25f + 0.25f * t)); + set("distortion_shape", 2.0f); + } + set("lowEQ", clampDb(3.5f + 2.0f * t)); + set("midEQ", clampDb(-2.5f + 1.8f * t)); + set("highEQ", clampDb(-5.0f + 2.0f * t)); + if (layered) + { + set("wt2_level", clampUnit(0.6f + 0.2f * t)); + set("wt2_lfoDepth", clampLfoDepth(0.8f + 1.6f * t)); + set("wt2_lfoRate", clampLfoRate(0.3f + 0.7f * t)); + } + set("master", layered ? -7.5f : -5.0f); + } + else if (category == "Drums") + { + set("wt_morph", clampMorph(1.0f + 9.0f * t)); + set("wt_lfoRate", clampLfoRate(1.2f + 2.5f * t)); + set("wt_lfoDepth", clampLfoDepth(0.3f + 2.8f * t)); + set("wt_level", clampUnit(0.88f)); + set("adsr_attack", clampUnit(0.001f)); + set("adsr_decay", clampUnit(0.12f + 0.08f * t)); + set("adsr_sustain", clampUnit(0.05f)); + set("adsr_release", clampUnit(0.18f + 0.12f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, -0.2f + 0.4f * t)); + set("distortion_on", 1.0f); + set("distortion_drive", 16.0f + 10.0f * t); + set("distortion_mix", clampUnit(0.35f + 0.2f * t)); + set("distortion_shape", (float) (variant % 3)); + set("distortion_bias", juce::jlimit(-1.0f, 1.0f, 0.05f * (variant % 5))); + set("reverb_on", layered ? 1.0f : (variant >= 8 ? 1.0f : 0.0f)); + if (values["reverb_on"] > 0.5f) + { + set("reverb_roomSize", clampUnit(layered ? (0.45f + 0.3f * t) : 0.35f + 0.15f * t)); + set("reverb_wetLevel", clampUnit(layered ? (0.3f + 0.18f * t) : 0.18f + 0.1f * t)); + set("reverb_damping", clampUnit(0.45f + 0.25f * t)); + } + set("filter_on", layered ? 1.0f : 0.0f); + if (values["filter_on"] > 0.5f) + { + set("filter_cutoff", clampCutoff(800.0f + 1200.0f * t)); + set("filter_resonance", clampRes(1.0f + 0.6f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.1f + 0.35f * t)); + } + if (layered) + { + set("wt2_level", clampUnit(0.6f + 0.25f * t)); + set("wt2_lfoRate", clampLfoRate(0.9f + 1.6f * (1.0f - t))); + set("wt2_lfoDepth", clampLfoDepth(1.3f + 1.7f * t)); + } + set("lowEQ", clampDb(4.0f + 2.0f * t)); + set("midEQ", clampDb(-4.0f + 3.0f * t)); + set("highEQ", clampDb(3.0f + 4.0f * t)); + set("master", layered ? -8.5f : -6.5f); + } + else if (category == "Strings") + { + set("wt_morph", clampMorph(3.0f + 9.0f * t)); + set("wt_lfoRate", clampLfoRate(0.18f + 0.4f * t)); + set("wt_lfoDepth", clampLfoDepth(0.8f + 1.8f * t)); + set("adsr_attack", clampUnit(0.22f + 0.18f * t)); + set("adsr_decay", clampUnit(0.3f + 0.12f * t)); + set("adsr_sustain", clampUnit(0.85f)); + set("adsr_release", clampUnit(0.6f + 0.25f * t)); + set("chorus_on", 1.0f); + set("chorus_rate", clampUnit(0.25f + 0.1f * t)); + set("chorus_depth", clampUnit(0.45f + 0.15f * t)); + set("chorus_mix", clampUnit(0.3f + 0.1f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.55f + 0.2f * t)); + set("reverb_wetLevel", clampUnit(0.35f + 0.15f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(2600.0f + 1500.0f * t)); + set("filter_resonance", clampRes(0.85f + 0.25f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.25f + 0.1f * t)); + if (layered) + { + set("wt2_level", clampUnit(0.5f + 0.25f * t)); + set("wt2_lfoDepth", clampLfoDepth(1.0f + 1.5f * t)); + } + set("midEQ", clampDb(-1.0f + 2.0f * t)); + set("highEQ", clampDb(2.5f + 2.5f * t)); + set("master", -7.5f); + } + else if (category == "Brass") + { + set("adsr_attack", clampUnit(0.05f + 0.05f * t)); + set("adsr_decay", clampUnit(0.25f + 0.1f * t)); + set("adsr_sustain", clampUnit(0.75f)); + set("adsr_release", clampUnit(0.35f + 0.15f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(2200.0f + 1800.0f * t)); + set("filter_resonance", clampRes(1.0f + 0.3f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.55f + 0.25f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.4f + 0.25f * t)); + set("reverb_wetLevel", clampUnit(0.3f + 0.1f * t)); + set("distortion_on", (layered || variant >= 8) ? 1.0f : 0.0f); + if (values["distortion_on"] > 0.5f) + { + set("distortion_drive", 10.0f + 6.0f * t); + set("distortion_mix", clampUnit(0.18f + 0.18f * t)); + set("distortion_shape", 1.0f); + } + set("flanger_on", layered ? 1.0f : 0.0f); + if (values["flanger_on"] > 0.5f) + { + set("flanger_rate", clampUnit(0.35f + 0.25f * t)); + set("flanger_depth", 4.0f + 3.0f * t); + set("flanger_dryMix", clampUnit(0.5f)); + } + if (layered) + { + set("wt2_level", clampUnit(0.52f + 0.22f * t)); + set("wt2_lfoDepth", clampLfoDepth(0.8f + 1.4f * t)); + } + set("lowEQ", clampDb(2.0f + 1.0f * t)); + set("midEQ", clampDb(1.0f + 1.5f * t)); + set("highEQ", clampDb(1.0f + 2.5f * t)); + } + else if (category == "Choir") + { + set("adsr_attack", clampUnit(0.3f + 0.2f * t)); + set("adsr_decay", clampUnit(0.4f + 0.1f * t)); + set("adsr_sustain", clampUnit(0.9f)); + set("adsr_release", clampUnit(0.55f + 0.3f * t)); + set("chorus_on", 1.0f); + set("chorus_rate", clampUnit(0.2f + 0.15f * t)); + set("chorus_depth", clampUnit(0.5f + 0.2f * t)); + set("chorus_mix", clampUnit(0.35f + 0.15f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.6f + 0.25f * t)); + set("reverb_wetLevel", clampUnit(0.4f + 0.15f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(1800.0f + 800.0f * t)); + set("filter_resonance", clampRes(0.7f + 0.2f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.15f + 0.08f * t)); + set("wt_lfoDepth", clampLfoDepth(1.0f + 2.0f * t)); + if (layered) + { + set("wt2_level", clampUnit(0.48f + 0.2f * t)); + set("wt2_lfoDepth", clampLfoDepth(1.2f + 1.5f * t)); + } + set("midEQ", clampDb(-1.5f + 1.5f * t)); + set("highEQ", clampDb(3.0f + 2.0f * t)); + set("master", -7.5f); + } + else if (category == "Pad") + { + set("adsr_attack", clampUnit(0.35f + 0.25f * t)); + set("adsr_decay", clampUnit(0.35f + 0.15f * t)); + set("adsr_sustain", clampUnit(0.85f)); + set("adsr_release", clampUnit(0.7f + 0.35f * t)); + set("chorus_on", 1.0f); + set("chorus_rate", clampUnit(0.18f + 0.12f * t)); + set("chorus_depth", clampUnit(0.55f + 0.2f * t)); + set("chorus_mix", clampUnit(0.35f + 0.15f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.65f + 0.25f * t)); + set("reverb_wetLevel", clampUnit(0.4f + 0.2f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(1500.0f + 900.0f * t)); + set("filter_resonance", clampRes(0.8f + 0.25f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.2f + 0.12f * t)); + set("flanger_on", (layered || variant % 3 == 0) ? 1.0f : 0.0f); + if (values["flanger_on"] > 0.5f) + { + set("flanger_rate", clampUnit(0.25f + 0.15f * t)); + set("flanger_depth", 5.0f + 3.0f * t); + set("flanger_dryMix", clampUnit(0.4f)); + } + set("wt_lfoRate", clampLfoRate(0.15f + 0.35f * t)); + set("wt_lfoDepth", clampLfoDepth(1.5f + 2.5f * t)); + if (layered) + { + set("wt2_level", clampUnit(0.5f + 0.3f * t)); + set("wt2_lfoDepth", clampLfoDepth(1.3f + 2.0f * t)); + } + set("master", -8.5f); + } + else if (category == "SFX") + { + set("wt_morph", clampMorph(4.0f + 10.0f * t)); + set("wt_lfoRate", clampLfoRate(1.2f + 3.5f * t)); + set("wt_lfoDepth", clampLfoDepth(2.5f + 5.0f * t)); + set("wt_lfoShape", (float) (variant % 4)); + set("wt_phase", (variant % 2 == 0) ? 0.0f : 0.5f); + set("chorus_on", 1.0f); + set("chorus_rate", clampUnit(0.35f + 0.3f * t)); + set("chorus_depth", clampUnit(0.45f + 0.25f * t)); + set("chorus_mix", clampUnit(0.3f + 0.2f * t)); + set("flanger_on", 1.0f); + set("flanger_rate", clampUnit(0.45f + 0.4f * t)); + set("flanger_depth", 6.0f + 4.0f * t); + set("flanger_phase", clampUnit(0.2f + 0.5f * t)); + set("flanger_dryMix", clampUnit(0.5f)); + set("distortion_on", 1.0f); + set("distortion_drive", 18.0f + 7.0f * t); + set("distortion_mix", clampUnit(0.35f + 0.2f * t)); + set("distortion_shape", (float) ((variant + 1) % 3)); + set("distortion_bias", juce::jlimit(-1.0f, 1.0f, -0.2f + 0.4f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.7f + 0.2f * t)); + set("reverb_wetLevel", clampUnit(0.45f + 0.2f * t)); + if (layered) + { + set("wt2_level", clampUnit(0.6f + 0.25f * t)); + set("wt2_morph", clampMorph(6.0f + 7.0f * (1.0f - t))); + set("wt2_lfoRate", clampLfoRate(0.8f + 4.0f * (1.0f - t))); + set("wt2_lfoDepth", clampLfoDepth(2.0f + 4.0f * t)); + set("wt2_lfoShape", (float) ((variant + 2) % 4)); + } + set("lowEQ", clampDb(-4.0f + 4.0f * t)); + set("midEQ", clampDb(3.0f - 4.0f * t)); + set("highEQ", clampDb(6.0f + 4.0f * t)); + set("master", -12.0f); + } + else if (category == "Lead") + { + set("wt_morph", clampMorph(3.0f + 8.0f * t)); + set("wt_lfoRate", clampLfoRate(0.4f + 1.2f * t)); + set("wt_lfoDepth", clampLfoDepth(1.0f + 2.0f * t)); + set("adsr_attack", clampUnit(0.01f + 0.02f * t)); + set("adsr_decay", clampUnit(0.18f + 0.08f * t)); + set("adsr_sustain", clampUnit(0.85f)); + set("adsr_release", clampUnit(0.25f + 0.1f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(2300.0f + 3200.0f * t)); + set("filter_resonance", clampRes(0.9f + 0.25f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.45f + 0.25f * t)); + set("distortion_on", 1.0f); + set("distortion_drive", 12.0f + 10.0f * t); + set("distortion_mix", clampUnit(0.25f + 0.25f * t)); + set("distortion_shape", (float) ((variant % 2) + 1)); + set("chorus_on", (layered || variant % 3 == 0) ? 1.0f : 0.0f); + if (values["chorus_on"] > 0.5f) + { + set("chorus_rate", clampUnit(0.3f + 0.2f * t)); + set("chorus_depth", clampUnit(0.4f + 0.2f * t)); + set("chorus_mix", clampUnit(0.25f + 0.15f * t)); + } + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.35f + 0.25f * t)); + set("reverb_wetLevel", clampUnit(0.22f + 0.15f * t)); + if (layered) + { + set("wt2_level", clampUnit(0.52f + 0.22f * t)); + set("wt2_lfoDepth", clampLfoDepth(1.1f + 1.6f * t)); + } + set("lowEQ", clampDb(-1.5f + 1.5f * t)); + set("midEQ", clampDb(2.0f + 1.5f * t)); + set("highEQ", clampDb(4.0f + 3.0f * t)); + set("master", layered ? -7.5f : -6.0f); + } + else if (category == "Pluck") + { + set("wt_morph", clampMorph(2.5f + 7.0f * t)); + set("wt_lfoRate", clampLfoRate(0.25f + 0.7f * t)); + set("wt_lfoDepth", clampLfoDepth(0.6f + 1.8f * t)); + set("adsr_attack", clampUnit(0.005f)); + set("adsr_decay", clampUnit(0.22f + 0.15f * t)); + set("adsr_sustain", clampUnit(0.2f + 0.1f * t)); + set("adsr_release", clampUnit(0.25f + 0.1f * t)); + set("fenv_amount", juce::jlimit(-1.0f, 1.0f, 0.4f + 0.2f * t)); + set("filter_on", 1.0f); + set("filter_cutoff", clampCutoff(1800.0f + 2000.0f * t)); + set("filter_resonance", clampRes(1.1f + 0.3f * t)); + set("delay_on", 1.0f); + set("delay_delay", clampUnit(0.25f + 0.25f * t)); + set("reverb_on", 1.0f); + set("reverb_roomSize", clampUnit(0.45f + 0.25f * t)); + set("reverb_wetLevel", clampUnit(0.25f + 0.15f * t)); + set("chorus_on", 0.0f); + set("distortion_on", variant >= 12 ? 1.0f : 0.0f); + if (values["distortion_on"] > 0.5f) + { + set("distortion_drive", 10.0f + 8.0f * t); + set("distortion_mix", clampUnit(0.2f + 0.2f * t)); + set("distortion_shape", 2.0f); + } + if (layered) + { + set("wt2_level", clampUnit(0.48f + 0.25f * t)); + set("wt2_lfoDepth", clampLfoDepth(0.9f + 1.5f * t)); + } + set("lowEQ", clampDb(-1.0f + 2.0f * t)); + set("midEQ", clampDb(-2.0f + 3.0f * t)); + set("highEQ", clampDb(3.0f + 2.5f * t)); + } + + juce::String presetName; + if (isDrumCategory) + { + presetName = juce::String::formatted("GM %d %s%s", + gmInfo.first, + gmInfo.second, + layered ? " Stack" : ""); + } + else + { + presetName = category + (layered ? " Duo " : " Solo ") + + juce::String(variant + 1).paddedLeft('0', 2); + } + + auto scaleLevel = [&](const juce::String& paramId, float factor) + { + auto it = values.find(paramId); + if (it != values.end()) + it->second = juce::jlimit(0.0f, 1.0f, it->second * factor); + }; + + scaleLevel("wt_level", 0.75f); + scaleLevel("wt2_level", 0.75f); + + if (auto it = values.find("master"); it != values.end()) + it->second = juce::jlimit(-24.0f, 24.0f, it->second - 4.0f); + + PresetDefinition def; + def.category = category; + def.name = presetName.trim(); + def.wtBankIndex = primaryIndex; + def.wt2BankIndex = layered ? secondaryIndex : -1; + def.parameterValues.reserve(values.size()); + for (const auto& entry : values) + def.parameterValues.emplace_back(entry.first, entry.second); + + presets.push_back(std::move(def)); + } + } + + return presets; +} + +void NeuralSynthAudioProcessor::setParameterValue(const juce::String& paramID, float value) +{ + if (parameters.getParameter(paramID) != nullptr) + parameters.getParameterAsValue(paramID) = value; +} + +void NeuralSynthAudioProcessor::applyPreset(int index) +{ + if (factoryPresets.empty()) + return; + + index = juce::jlimit(0, (int) factoryPresets.size() - 1, index); + const auto& preset = factoryPresets[(size_t) index]; + + juce::ScopedValueSetter guard(presetChangeInProgress, true, false); + + for (const auto& entry : preset.parameterValues) + setParameterValue(entry.first, entry.second); + + currentPresetIndex = index; +} + juce::AudioProcessorValueTreeState::ParameterLayout NeuralSynthAudioProcessor::createParameterLayout() { std::vector> params; - params.push_back(std::make_unique( - "waveform", "Waveform", - juce::StringArray{ "Sine", "Saw", "Square", "Triangle" }, 0)); + params.push_back(std::make_unique( + "wt_on", "Layer A On", true)); + params.push_back(std::make_unique( + "wt2_on", "Layer B On", false)); // Per-panel bypass toggles (default OFF) params.push_back(std::make_unique("chorus_on", "Chorus On", false)); @@ -255,9 +909,11 @@ juce::AudioProcessorValueTreeState::ParameterLayout NeuralSynthAudioProcessor::c buildParams(params, "flanger"); buildParams(params, "distortion"); buildParams(params, "filter"); + buildParams(params, "wt"); + buildParams(params, "wt2"); params.push_back(std::make_unique("master", "Master", - juce::NormalisableRange(-24.0f, 24.0f, 0.1f), 0.1f)); + juce::NormalisableRange(-24.0f, 24.0f, 0.1f), -6.0f)); params.push_back(std::make_unique("lowEQ", "Low Gain", juce::NormalisableRange(-24.0f, 24.0f, 0.1f), 0.5f)); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a599382..1b2c02e 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -12,6 +12,15 @@ class NeuralSynthAudioProcessor : public juce::AudioProcessor, private juce::AudioProcessorValueTreeState::Listener { public: + struct PresetDefinition + { + juce::String category; + juce::String name; + int wtBankIndex; + int wt2BankIndex { -1 }; + std::vector> parameterValues; + }; + NeuralSynthAudioProcessor(); ~NeuralSynthAudioProcessor() override; @@ -53,6 +62,10 @@ public: const std::string& paramGroup); juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); + void applyPreset(int index); + const std::vector& getFactoryPresets() const noexcept { return factoryPresets; } + int getCurrentPresetIndex() const noexcept { return currentPresetIndex; } + // Utilities juce::MidiMessageCollector& getMidiMessageCollector() noexcept { return midiMessageCollector; } AudioBufferQueue& getAudioBufferQueue() noexcept { return audioBufferQueue; } @@ -87,4 +100,13 @@ private: // Scope collector (uses audioBufferQueue, so declare after it) ScopeDataCollector scopeDataCollector { audioBufferQueue }; + + std::vector factoryPresets; + int currentPresetIndex { -1 }; + bool presetChangeInProgress { false }; + + static std::vector makeFactoryPresets(); + void setParameterValue(const juce::String& paramID, float value); + + juce::dsp::Limiter outputLimiter; };