Files
NeuralSynth/Source/PluginProcessor.cpp
2025-10-26 00:49:50 +01:00

927 lines
43 KiB
C++

#include "PluginProcessor.h"
#include "PluginEditor.h"
#include "WavetableOsc.h"
#include <array>
#include <map>
//==============================================================================
NeuralSynthAudioProcessor::NeuralSynthAudioProcessor()
: parameters(*this, nullptr, "PARAMETERS", createParameterLayout())
, AudioProcessor(BusesProperties().withOutput("Output", juce::AudioChannelSet::stereo(), true))
, audioEngine(sp)
, factoryPresets(makeFactoryPresets())
{
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");
sp.delayOn = parameters.getRawParameterValue("delay_on");
sp.reverbOn = parameters.getRawParameterValue("reverb_on");
sp.flangerOn = parameters.getRawParameterValue("flanger_on");
sp.distortionOn = parameters.getRawParameterValue("distortion_on");
sp.filterOn = parameters.getRawParameterValue("filter_on");
sp.eqOn = parameters.getRawParameterValue("eq_on");
// === 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.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");
// === Amp 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");
// === Filter Env ===
parameters.addParameterListener("fenv_attack", this);
parameters.addParameterListener("fenv_decay", this);
parameters.addParameterListener("fenv_sustain", this);
parameters.addParameterListener("fenv_release", this);
parameters.addParameterListener("fenv_amount", this);
sp.fenvAttack = parameters.getRawParameterValue("fenv_attack");
sp.fenvDecay = parameters.getRawParameterValue("fenv_decay");
sp.fenvSustain = parameters.getRawParameterValue("fenv_sustain");
sp.fenvRelease = parameters.getRawParameterValue("fenv_release");
sp.fenvAmount = parameters.getRawParameterValue("fenv_amount");
// === Filter base ===
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");
// === Master / EQ ===
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() = default;
//==============================================================================
const juce::String NeuralSynthAudioProcessor::getName() const { return JucePlugin_Name; }
bool NeuralSynthAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
return true;
#else
return false;
#endif
}
bool NeuralSynthAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
return true;
#else
return false;
#endif
}
bool NeuralSynthAudioProcessor::isMidiEffect() const
{
#if JucePlugin_IsMidiEffect
return true;
#else
return false;
#endif
}
double NeuralSynthAudioProcessor::getTailLengthSeconds() const { return 0.0; }
int NeuralSynthAudioProcessor::getNumPrograms() { return 1; }
int NeuralSynthAudioProcessor::getCurrentProgram() { return 0; }
void NeuralSynthAudioProcessor::setCurrentProgram (int) {}
const juce::String NeuralSynthAudioProcessor::getProgramName (int) { return {}; }
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);
}
void NeuralSynthAudioProcessor::releaseResources() {}
/*bool NeuralSynthAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
return false;
return true;
}*/
void NeuralSynthAudioProcessor::processBlock(juce::AudioSampleBuffer& buffer, juce::MidiBuffer& midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
midiMessageCollector.removeNextBlockOfMessages(midiMessages, buffer.getNumSamples());
for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear(i, 0, buffer.getNumSamples());
audioEngine.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
juce::dsp::AudioBlock<float> outputBlock (buffer);
outputLimiter.process (juce::dsp::ProcessContextReplacing<float> (outputBlock));
scopeDataCollector.process(buffer.getReadPointer(0), (size_t)buffer.getNumSamples());
}
//==============================================================================
bool NeuralSynthAudioProcessor::hasEditor() const { return true; }
juce::AudioProcessorEditor* NeuralSynthAudioProcessor::createEditor()
{
return new NeuralSynthAudioProcessorEditor (*this);
}
//==============================================================================
void NeuralSynthAudioProcessor::getStateInformation (juce::MemoryBlock& destData) { juce::ignoreUnused(destData); }
void NeuralSynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { juce::ignoreUnused(data, sizeInBytes); }
void NeuralSynthAudioProcessor::parameterChanged(const juce::String& id, float newValue)
{
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;
}
}
//==============================================================================
// This creates new instances of the plugin..
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, s] : paramGroupSettings) {
params.push_back(std::make_unique<juce::AudioParameterFloat>(
paramGroup + "_" + name, s.label,
juce::NormalisableRange<float>(s.min, s.max, s.interval),
s.defValue));
}
}
std::vector<NeuralSynthAudioProcessor::PresetDefinition> NeuralSynthAudioProcessor::makeFactoryPresets()
{
std::vector<PresetDefinition> presets;
const auto& wtLibrary = WT::FactoryLibrary::get();
if (wtLibrary.empty())
return presets;
presets.reserve(220);
std::map<juce::String, std::vector<int>> categoryToIndices;
for (int i = 0; i < (int) wtLibrary.size(); ++i)
categoryToIndices[wtLibrary[(size_t) i].category].push_back(i);
const std::array<juce::String, 12> requestedCategories = {
"Electric Piano", "Organ", "Bass", "Drums", "Strings",
"Brass", "Choir", "Pad", "SFX", "Lead", "Pluck", "Misc"
};
static const std::array<std::pair<int, const char*>, 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<int, const char*> gmInfo { 0, "" };
if (isDrumCategory)
gmInfo = gmDrumInfo[(size_t) variant];
std::map<juce::String, float> 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<bool> guard(presetChangeInProgress, true, false);
for (const auto& entry : preset.parameterValues)
setParameterValue(entry.first, entry.second);
currentPresetIndex = index;
}
juce::AudioProcessorValueTreeState::ParameterLayout NeuralSynthAudioProcessor::createParameterLayout()
{
std::vector<std::unique_ptr<juce::RangedAudioParameter>> params;
params.push_back(std::make_unique<juce::AudioParameterBool>(
"wt_on", "Layer A On", true));
params.push_back(std::make_unique<juce::AudioParameterBool>(
"wt2_on", "Layer B On", false));
// Per-panel bypass toggles (default OFF)
params.push_back(std::make_unique<juce::AudioParameterBool>("chorus_on", "Chorus On", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("delay_on", "Delay On", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("reverb_on", "Reverb On", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("flanger_on", "Flanger On", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("distortion_on", "Distortion On", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("filter_on", "Filter On", false));
params.push_back(std::make_unique<juce::AudioParameterBool>("eq_on", "EQ On", false));
buildParams(params, "adsr");
buildParams(params, "fenv");
buildParams(params, "chorus");
buildParams(params, "delay");
buildParams(params, "reverb");
buildParams(params, "flanger");
buildParams(params, "distortion");
buildParams(params, "filter");
buildParams(params, "wt");
buildParams(params, "wt2");
params.push_back(std::make_unique<juce::AudioParameterFloat>("master", "Master",
juce::NormalisableRange<float>(-24.0f, 24.0f, 0.1f), -6.0f));
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() };
}