#include "DeepPluckParameters.h"

#include <cmath>

namespace deeppluck
{
namespace
{
constexpr float outputGainMinimum = -60.0f;
constexpr float outputGainMaximum = 6.0f;
constexpr float masterTuneMinimum = 420.0f;
constexpr float masterTuneMaximum = 460.0f;
constexpr float attackMinimum = 0.001f;
constexpr float attackMaximum = 2.0f;
constexpr float decayMinimum = 0.001f;
constexpr float decayMaximum = 5.0f;
constexpr float sustainMinimum = 0.0f;
constexpr float sustainMaximum = 1.0f;
constexpr float releaseMinimum = 0.001f;
constexpr float releaseMaximum = 10.0f;
constexpr float filterCutoffMinimum = 20.0f;
constexpr float filterCutoffMaximum = 20000.0f;
constexpr float filterResonanceMinimum = 0.0f;
constexpr float filterResonanceMaximum = 1.0f;
constexpr float filterDriveMinimum = 0.0f;
constexpr float filterDriveMaximum = 18.0f;
constexpr float filterKeyTrackingMinimum = 0.0f;
constexpr float filterKeyTrackingMaximum = 1.0f;
constexpr float filterEnvAmountMinimum = -5.0f;
constexpr float filterEnvAmountMaximum = 5.0f;
constexpr float layerLevelMinimum = -60.0f;
constexpr float layerLevelMaximum = 0.0f;
constexpr float transientDecayMinimum = 0.001f;
constexpr float transientDecayMaximum = 0.100f;
constexpr float noiseDecayMinimum = 0.005f;
constexpr float noiseDecayMaximum = 1.500f;
constexpr float toneMinimum = 0.0f;
constexpr float toneMaximum = 1.0f;
constexpr float osc2OctaveMinimum = -1.0f;
constexpr float osc2OctaveMaximum = 1.0f;
constexpr float osc2SemitoneMinimum = -12.0f;
constexpr float osc2SemitoneMaximum = 12.0f;
constexpr float osc2FineTuneMinimum = -50.0f;
constexpr float osc2FineTuneMaximum = 50.0f;
constexpr float unisonVoicesMinimum = 1.0f;
constexpr float unisonVoicesMaximum = 4.0f;
constexpr float unisonDetuneMinimum = 0.0f;
constexpr float unisonDetuneMaximum = 30.0f;
constexpr float stereoSpreadMinimum = 0.0f;
constexpr float stereoSpreadMaximum = 1.0f;
constexpr float lfoRateMinimum = 0.020f;
constexpr float lfoRateMaximum = 12.0f;
constexpr float lfoFilterAmountMinimum = -2.0f;
constexpr float lfoFilterAmountMaximum = 2.0f;
constexpr float lfoPitchAmountMinimum = -50.0f;
constexpr float lfoPitchAmountMaximum = 50.0f;
constexpr float lfoSpreadAmountMinimum = -1.0f;
constexpr float lfoSpreadAmountMaximum = 1.0f;
constexpr float pulseWidthMinimum = 0.05f;
constexpr float pulseWidthMaximum = 0.95f;
constexpr float chorusRateMinimum = 0.05f;
constexpr float chorusRateMaximum = 4.0f;
constexpr float delayTimeMinimum = 0.010f;
constexpr float delayTimeMaximum = 2.0f;
constexpr float delayFeedbackMaximum = 0.750f;
constexpr float delayToneMinimum = 800.0f;
constexpr float delayToneMaximum = 18000.0f;
constexpr float reverbSizeMinimum = 0.100f;
constexpr float reverbSizeMaximum = 0.920f;
constexpr float reverbPreDelayMaximum = 0.120f;

juce::NormalisableRange<float> makeLogarithmicRange (float minimum, float maximum)
{
    juce::NormalisableRange<float> range { minimum, maximum };
    range.setSkewForCentre (std::sqrt (minimum * maximum));
    return range;
}

float loadClamped (const std::atomic<float>* value,
                   float fallback,
                   float minimum,
                   float maximum) noexcept
{
    if (value == nullptr)
        return fallback;

    const auto loaded = value->load (std::memory_order_relaxed);
    return std::isfinite (loaded) ? juce::jlimit (minimum, maximum, loaded) : fallback;
}

float loadToggle (const std::atomic<float>* value, float fallback) noexcept
{
    const auto loaded = loadClamped (value, fallback, 0.0f, 1.0f);
    return loaded >= 0.5f ? 1.0f : 0.0f;
}

float loadRoundedClamped (const std::atomic<float>* value,
                          float fallback,
                          float minimum,
                          float maximum) noexcept
{
    return std::round (loadClamped (value, fallback, minimum, maximum));
}

juce::String decimalText (double value, int decimalPlaces, bool keepTrailingZeros = false)
{
    if (! std::isfinite (value))
        value = 0.0;

    const auto zeroThreshold = 0.5 * std::pow (10.0, -decimalPlaces);
    if (std::abs (value) < zeroThreshold)
        value = 0.0;

    auto text = juce::String (value, decimalPlaces);
    if (! keepTrailingZeros && text.containsChar ('.'))
    {
        while (text.endsWithChar ('0'))
            text = text.dropLastCharacters (1);
        if (text.endsWithChar ('.'))
            text = text.dropLastCharacters (1);
    }
    return text;
}

juce::String signedText (double value, int decimalPlaces, bool keepTrailingZeros = false)
{
    const auto text = decimalText (value, decimalPlaces, keepTrailingZeros);
    return value > 0.0 ? "+" + text : text;
}

bool isEnvelopeTimeParameter (const juce::String& id) noexcept
{
    return id == ParameterIDs::ampAttack || id == ParameterIDs::ampDecay
        || id == ParameterIDs::ampRelease || id == ParameterIDs::filterAttack
        || id == ParameterIDs::filterDecay || id == ParameterIDs::filterRelease
        || id == ParameterIDs::transientDecay || id == ParameterIDs::noiseDecay;
}

bool isPercentageParameter (const juce::String& id) noexcept
{
    return id == ParameterIDs::ampSustain || id == ParameterIDs::filterResonance
        || id == ParameterIDs::filterKeyTracking || id == ParameterIDs::filterSustain
        || id == ParameterIDs::transientTone || id == ParameterIDs::noiseTone
        || id == ParameterIDs::stereoSpread || id == ParameterIDs::lfoSpreadAmount
        || id == ParameterIDs::osc1PulseWidth || id == ParameterIDs::osc2PulseWidth
        || id == ParameterIDs::chorusDepth || id == ParameterIDs::chorusWidth
        || id == ParameterIDs::chorusMix || id == ParameterIDs::delayFeedback
        || id == ParameterIDs::delayMix || id == ParameterIDs::reverbSize
        || id == ParameterIDs::reverbDamping || id == ParameterIDs::reverbMix;
}

juce::AudioParameterFloatAttributes floatAttributes (const char* id)
{
    const auto parameterID = juce::String (id);
    return juce::AudioParameterFloatAttributes()
        .withStringFromValueFunction ([parameterID] (float value, int maximumLength)
        {
            return formatParameterValue (parameterID, value, maximumLength);
        })
        .withValueFromStringFunction ([parameterID] (const juce::String& text)
        {
            return parseParameterValue (parameterID, text);
        });
}

juce::AudioParameterIntAttributes intAttributes (const char* id)
{
    const auto parameterID = juce::String (id);
    return juce::AudioParameterIntAttributes()
        .withStringFromValueFunction ([parameterID] (int value, int maximumLength)
        {
            return formatParameterValue (parameterID, static_cast<float> (value), maximumLength);
        })
        .withValueFromStringFunction ([parameterID] (const juce::String& text)
        {
            return static_cast<int> (std::lround (parseParameterValue (parameterID, text)));
        });
}

float canonicalizeRestoredAttack (const juce::String& parameterID,
                                  const juce::var& storedValue) noexcept
{
    const auto fallback = parameterID == ParameterIDs::filterAttack ? 0.002f : 0.003f;
    auto value = 0.0;
    if (storedValue.isDouble() || storedValue.isInt() || storedValue.isInt64())
        value = static_cast<double> (storedValue);
    else if (storedValue.isString())
    {
        const auto text = storedValue.toString().trim();
        if (text.isEmpty() || ! text.containsAnyOf ("0123456789")
            || text.retainCharacters ("+-0123456789.eE") != text)
            return fallback;
        value = text.getDoubleValue();
    }
    else
    {
        return fallback;
    }

    return std::isfinite (value)
        ? juce::jlimit (attackMinimum, attackMaximum, static_cast<float> (value))
        : fallback;
}
}

juce::String formatParameterValue (const juce::String& parameterID,
                                   float rawValue,
                                   int maximumLength)
{
    juce::ignoreUnused (maximumLength);
    const auto value = std::isfinite (rawValue) ? static_cast<double> (rawValue) : 0.0;

    if (isEnvelopeTimeParameter (parameterID))
    {
        if (value < 1.0)
        {
            const auto milliseconds = value * 1000.0;
            const auto decimals = milliseconds < 10.0 ? 2 : milliseconds < 100.0 ? 1 : 0;
            return decimalText (milliseconds, decimals) + " ms";
        }
        return decimalText (value, 2) + " s";
    }

    if (parameterID == ParameterIDs::delayTime)
        return decimalText (value, 3) + " s";

    if (parameterID == ParameterIDs::reverbPreDelay)
        return decimalText (value * 1000.0, 1) + " ms";

    if (parameterID == ParameterIDs::filterCutoff || parameterID == ParameterIDs::delayTone)
        return value < 1000.0 ? decimalText (value, 0) + " Hz"
                              : decimalText (value / 1000.0, 2, true) + " kHz";

    if (parameterID == ParameterIDs::masterTune)
        return decimalText (value, 1, true) + " Hz";

    if (parameterID == ParameterIDs::lfoRate || parameterID == ParameterIDs::chorusRate)
        return decimalText (value, 2, true) + " Hz";

    if (parameterID == ParameterIDs::outputGain || parameterID == ParameterIDs::filterDrive
        || parameterID == ParameterIDs::transientLevel || parameterID == ParameterIDs::noiseLevel
        || parameterID == ParameterIDs::osc2Level)
        return decimalText (value, 1, true) + " dB";

    if (isPercentageParameter (parameterID))
    {
        const auto percentage = value * 100.0;
        const auto text = parameterID == ParameterIDs::lfoSpreadAmount
                        ? signedText (percentage, 1)
                        : decimalText (percentage, 1);
        return text + " %";
    }

    if (parameterID == ParameterIDs::filterEnvAmount
        || parameterID == ParameterIDs::lfoFilterAmount)
        return signedText (value, 2, true) + " oct";

    if (parameterID == ParameterIDs::osc2FineTune
        || parameterID == ParameterIDs::lfoPitchAmount)
        return signedText (value, 1) + " ct";

    if (parameterID == ParameterIDs::unisonDetune)
        return decimalText (value, 1, true) + " ct";

    if (parameterID == ParameterIDs::osc2Octave)
        return signedText (value, 0) + " oct";

    if (parameterID == ParameterIDs::osc2Semitone)
        return signedText (value, 0) + " st";

    if (parameterID == ParameterIDs::unisonVoices)
        return decimalText (value, 0);

    return decimalText (value, 3);
}

float parseParameterValue (const juce::String& parameterID,
                           const juce::String& text) noexcept
{
    const auto normalized = text.trim().toLowerCase().replaceCharacter (',', '.');
    auto value = normalized.getDoubleValue();

    if (isEnvelopeTimeParameter (parameterID) || parameterID == ParameterIDs::delayTime
        || parameterID == ParameterIDs::reverbPreDelay)
    {
        if (normalized.contains ("ms"))
            value *= 0.001;
    }
    else if (parameterID == ParameterIDs::filterCutoff || parameterID == ParameterIDs::delayTone
             || parameterID == ParameterIDs::masterTune || parameterID == ParameterIDs::lfoRate
             || parameterID == ParameterIDs::chorusRate)
    {
        if (normalized.contains ("khz"))
            value *= 1000.0;
    }
    else if (isPercentageParameter (parameterID))
    {
        value *= 0.01;
    }

    return std::isfinite (value) ? static_cast<float> (value) : 0.0f;
}

juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
{
    juce::AudioProcessorValueTreeState::ParameterLayout layout;

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::outputGain, 1 }, "Output",
        juce::NormalisableRange<float> { outputGainMinimum, outputGainMaximum, 0.1f },
        -3.0f, floatAttributes (ParameterIDs::outputGain)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::masterTune, 1 }, "Master Tune",
        juce::NormalisableRange<float> { masterTuneMinimum, masterTuneMaximum, 0.1f },
        440.0f, floatAttributes (ParameterIDs::masterTune)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::ampAttack, 1 }, "Attack",
        makeLogarithmicRange (attackMinimum, attackMaximum), 0.003f,
        floatAttributes (ParameterIDs::ampAttack)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::ampDecay, 1 }, "Decay",
        makeLogarithmicRange (decayMinimum, decayMaximum), 0.320f,
        floatAttributes (ParameterIDs::ampDecay)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::ampSustain, 1 }, "Sustain",
        juce::NormalisableRange<float> { sustainMinimum, sustainMaximum, 0.001f },
        0.0f, floatAttributes (ParameterIDs::ampSustain)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::ampRelease, 1 }, "Release",
        makeLogarithmicRange (releaseMinimum, releaseMaximum), 0.180f,
        floatAttributes (ParameterIDs::ampRelease)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::filterEnabled, 1 }, "Filter", true));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterCutoff, 1 }, "Filter Cutoff",
        makeLogarithmicRange (filterCutoffMinimum, filterCutoffMaximum),
        1800.0f, floatAttributes (ParameterIDs::filterCutoff)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterResonance, 1 }, "Filter Resonance",
        juce::NormalisableRange<float> { filterResonanceMinimum, filterResonanceMaximum, 0.001f },
        0.18f, floatAttributes (ParameterIDs::filterResonance)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterDrive, 1 }, "Filter Drive",
        juce::NormalisableRange<float> { filterDriveMinimum, filterDriveMaximum, 0.1f },
        3.0f, floatAttributes (ParameterIDs::filterDrive)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterKeyTracking, 1 }, "Filter Key Track",
        juce::NormalisableRange<float> { filterKeyTrackingMinimum,
                                         filterKeyTrackingMaximum, 0.001f },
        0.35f, floatAttributes (ParameterIDs::filterKeyTracking)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterEnvAmount, 1 }, "Filter Env Amount",
        juce::NormalisableRange<float> { filterEnvAmountMinimum,
                                         filterEnvAmountMaximum, 0.01f },
        2.50f, floatAttributes (ParameterIDs::filterEnvAmount)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterAttack, 1 }, "Filter Attack",
        makeLogarithmicRange (attackMinimum, attackMaximum), 0.002f,
        floatAttributes (ParameterIDs::filterAttack)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterDecay, 1 }, "Filter Decay",
        makeLogarithmicRange (decayMinimum, decayMaximum), 0.280f,
        floatAttributes (ParameterIDs::filterDecay)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterSustain, 1 }, "Filter Sustain",
        juce::NormalisableRange<float> { sustainMinimum, sustainMaximum, 0.001f },
        0.0f, floatAttributes (ParameterIDs::filterSustain)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::filterRelease, 1 }, "Filter Release",
        makeLogarithmicRange (releaseMinimum, releaseMaximum), 0.180f,
        floatAttributes (ParameterIDs::filterRelease)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::transientEnabled, 1 }, "Transient", true));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::transientLevel, 1 }, "Transient Level",
        juce::NormalisableRange<float> { layerLevelMinimum, layerLevelMaximum, 0.1f },
        -18.0f, floatAttributes (ParameterIDs::transientLevel)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::transientDecay, 1 }, "Transient Decay",
        makeLogarithmicRange (transientDecayMinimum, transientDecayMaximum),
        0.012f, floatAttributes (ParameterIDs::transientDecay)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::transientTone, 1 }, "Transient Tone",
        juce::NormalisableRange<float> { toneMinimum, toneMaximum, 0.001f },
        0.55f, floatAttributes (ParameterIDs::transientTone)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::noiseEnabled, 1 }, "Noise", true));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::noiseLevel, 1 }, "Noise Level",
        juce::NormalisableRange<float> { layerLevelMinimum, layerLevelMaximum, 0.1f },
        -27.0f, floatAttributes (ParameterIDs::noiseLevel)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::noiseDecay, 1 }, "Noise Decay",
        makeLogarithmicRange (noiseDecayMinimum, noiseDecayMaximum),
        0.120f, floatAttributes (ParameterIDs::noiseDecay)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::noiseTone, 1 }, "Noise Tone",
        juce::NormalisableRange<float> { toneMinimum, toneMaximum, 0.001f },
        0.35f, floatAttributes (ParameterIDs::noiseTone)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::osc2Enabled, 1 }, "Oscillator 2", true));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::osc2Level, 1 }, "Osc 2 Level",
        juce::NormalisableRange<float> { layerLevelMinimum, layerLevelMaximum, 0.1f },
        -15.0f, floatAttributes (ParameterIDs::osc2Level)));

    layout.add (std::make_unique<juce::AudioParameterInt> (
        juce::ParameterID { ParameterIDs::osc2Octave, 1 }, "Osc 2 Octave",
        static_cast<int> (osc2OctaveMinimum), static_cast<int> (osc2OctaveMaximum), 0,
        intAttributes (ParameterIDs::osc2Octave)));

    layout.add (std::make_unique<juce::AudioParameterInt> (
        juce::ParameterID { ParameterIDs::osc2Semitone, 1 }, "Osc 2 Semitone",
        static_cast<int> (osc2SemitoneMinimum), static_cast<int> (osc2SemitoneMaximum), 0,
        intAttributes (ParameterIDs::osc2Semitone)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::osc2FineTune, 1 }, "Osc 2 Fine Tune",
        juce::NormalisableRange<float> { osc2FineTuneMinimum, osc2FineTuneMaximum, 0.1f },
        -7.0f, floatAttributes (ParameterIDs::osc2FineTune)));

    layout.add (std::make_unique<juce::AudioParameterInt> (
        juce::ParameterID { ParameterIDs::unisonVoices, 1 }, "Unison Voices",
        static_cast<int> (unisonVoicesMinimum), static_cast<int> (unisonVoicesMaximum), 2,
        intAttributes (ParameterIDs::unisonVoices)));

    auto unisonDetuneRange = juce::NormalisableRange<float> {
        unisonDetuneMinimum, unisonDetuneMaximum, 0.1f
    };
    unisonDetuneRange.setSkewForCentre (5.0f);
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::unisonDetune, 1 }, "Unison Detune",
        unisonDetuneRange, 4.0f, floatAttributes (ParameterIDs::unisonDetune)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::stereoSpread, 1 }, "Stereo Spread",
        juce::NormalisableRange<float> { stereoSpreadMinimum, stereoSpreadMaximum, 0.001f },
        0.35f, floatAttributes (ParameterIDs::stereoSpread)));

    auto lfoRateRange = juce::NormalisableRange<float> {
        lfoRateMinimum, lfoRateMaximum, 0.001f
    };
    lfoRateRange.setSkewForCentre (0.5f);
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::lfoRate, 1 }, "LFO Rate",
        lfoRateRange, 0.25f, floatAttributes (ParameterIDs::lfoRate)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::lfoSync, 1 }, "LFO Sync", false));

    layout.add (std::make_unique<juce::AudioParameterChoice> (
        juce::ParameterID { ParameterIDs::lfoSyncDivision, 1 }, "LFO Sync Division",
        juce::StringArray { "4/1", "2/1", "1/1", "1/2", "1/4", "1/8", "1/16" }, 4));

    layout.add (std::make_unique<juce::AudioParameterChoice> (
        juce::ParameterID { ParameterIDs::lfoWaveform, 1 }, "LFO Waveform",
        juce::StringArray { "Sine", "Triangle", "Saw", "Square" }, 0));

    layout.add (std::make_unique<juce::AudioParameterChoice> (
        juce::ParameterID { ParameterIDs::lfoRestartMode, 1 }, "LFO Restart",
        juce::StringArray { "Free Running", "Retrigger First Note" }, 1));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::lfoFilterAmount, 1 }, "LFO Filter Amount",
        juce::NormalisableRange<float> { lfoFilterAmountMinimum,
                                         lfoFilterAmountMaximum, 0.01f },
        0.0f, floatAttributes (ParameterIDs::lfoFilterAmount)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::lfoPitchAmount, 1 }, "LFO Pitch Amount",
        juce::NormalisableRange<float> { lfoPitchAmountMinimum,
                                         lfoPitchAmountMaximum, 0.1f },
        0.0f, floatAttributes (ParameterIDs::lfoPitchAmount)));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::lfoSpreadAmount, 1 }, "LFO Spread Amount",
        juce::NormalisableRange<float> { lfoSpreadAmountMinimum,
                                         lfoSpreadAmountMaximum, 0.001f },
        0.0f, floatAttributes (ParameterIDs::lfoSpreadAmount)));

    layout.add (std::make_unique<juce::AudioParameterChoice> (
        juce::ParameterID { ParameterIDs::osc1Waveform, 1 }, "Osc 1 Waveform",
        juce::StringArray { "Saw", "Triangle", "Square", "Sine", "Pulse" }, 0));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::osc1PulseWidth, 1 }, "Osc 1 Pulse Width",
        juce::NormalisableRange<float> { pulseWidthMinimum, pulseWidthMaximum, 0.001f },
        0.50f, floatAttributes (ParameterIDs::osc1PulseWidth)));

    layout.add (std::make_unique<juce::AudioParameterChoice> (
        juce::ParameterID { ParameterIDs::osc2Waveform, 1 }, "Osc 2 Waveform",
        juce::StringArray { "Saw", "Triangle", "Square", "Sine", "Pulse" }, 0));

    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::osc2PulseWidth, 1 }, "Osc 2 Pulse Width",
        juce::NormalisableRange<float> { pulseWidthMinimum, pulseWidthMaximum, 0.001f },
        0.50f, floatAttributes (ParameterIDs::osc2PulseWidth)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::chorusBypass, 1 }, "Chorus Bypass", true));
    auto chorusRateRange = juce::NormalisableRange<float> {
        chorusRateMinimum, chorusRateMaximum
    };
    chorusRateRange.setSkewForCentre (1.0f);
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::chorusRate, 1 }, "Chorus Rate",
        chorusRateRange, 0.35f, floatAttributes (ParameterIDs::chorusRate)));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::chorusDepth, 1 }, "Chorus Depth",
        juce::NormalisableRange<float> { 0.0f, 1.0f, 0.001f },
        0.25f, floatAttributes (ParameterIDs::chorusDepth)));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::chorusWidth, 1 }, "Chorus Width",
        juce::NormalisableRange<float> { 0.0f, 1.0f, 0.001f },
        0.65f, floatAttributes (ParameterIDs::chorusWidth)));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::chorusMix, 1 }, "Chorus Mix",
        juce::NormalisableRange<float> { 0.0f, 1.0f, 0.001f },
        0.20f, floatAttributes (ParameterIDs::chorusMix)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::delayBypass, 1 }, "Delay Bypass", true));
    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::delaySync, 1 }, "Delay Sync", true));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::delayTime, 1 }, "Delay Time",
        makeLogarithmicRange (delayTimeMinimum, delayTimeMaximum),
        0.375f, floatAttributes (ParameterIDs::delayTime)));
    layout.add (std::make_unique<juce::AudioParameterChoice> (
        juce::ParameterID { ParameterIDs::delaySyncDivision, 1 }, "Delay Sync Division",
        juce::StringArray { "1/4", "1/8 D", "1/8", "1/8 T", "1/16 D", "1/16", "1/16 T" }, 1));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::delayFeedback, 1 }, "Delay Feedback",
        juce::NormalisableRange<float> { 0.0f, delayFeedbackMaximum, 0.001f },
        0.32f, floatAttributes (ParameterIDs::delayFeedback)));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::delayTone, 1 }, "Delay Tone",
        makeLogarithmicRange (delayToneMinimum, delayToneMaximum),
        6000.0f, floatAttributes (ParameterIDs::delayTone)));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::delayMix, 1 }, "Delay Mix",
        juce::NormalisableRange<float> { 0.0f, 1.0f, 0.001f },
        0.22f, floatAttributes (ParameterIDs::delayMix)));

    layout.add (std::make_unique<juce::AudioParameterBool> (
        juce::ParameterID { ParameterIDs::reverbBypass, 1 }, "Reverb Bypass", true));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::reverbSize, 1 }, "Reverb Size",
        juce::NormalisableRange<float> { reverbSizeMinimum, reverbSizeMaximum, 0.001f },
        0.55f, floatAttributes (ParameterIDs::reverbSize)));
    auto reverbPreDelayRange = juce::NormalisableRange<float> {
        0.0f, reverbPreDelayMaximum, 0.0001f
    };
    reverbPreDelayRange.setSkewForCentre (0.040f);
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::reverbPreDelay, 1 }, "Reverb Pre-Delay",
        reverbPreDelayRange, 0.018f, floatAttributes (ParameterIDs::reverbPreDelay)));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::reverbDamping, 1 }, "Reverb Damping",
        juce::NormalisableRange<float> { 0.0f, 1.0f, 0.001f },
        0.55f, floatAttributes (ParameterIDs::reverbDamping)));
    layout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { ParameterIDs::reverbMix, 1 }, "Reverb Mix",
        juce::NormalisableRange<float> { 0.0f, 1.0f, 0.001f },
        0.18f, floatAttributes (ParameterIDs::reverbMix)));
    layout.add (std::make_unique<juce::AudioParameterChoice> (
        juce::ParameterID { ParameterIDs::fxOrder, 1 }, "FX Order",
        juce::StringArray { "Chorus > Delay > Reverb", "Chorus > Reverb > Delay",
                            "Delay > Chorus > Reverb", "Delay > Reverb > Chorus",
                            "Reverb > Chorus > Delay", "Reverb > Delay > Chorus" }, 0));

    return layout;
}

juce::ValueTree mergeRestoredState (const juce::ValueTree& currentState,
                                    const juce::ValueTree& restoredState)
{
    if (! currentState.isValid() || ! restoredState.isValid()
        || ! restoredState.hasType (currentState.getType()))
        return {};

    auto merged = currentState.createCopy();
    merged.copyPropertiesFrom (restoredState, nullptr);

    auto hasAnyV04Parameter = false;
    auto hasAnyV05Parameter = false;
    auto hasAnyV06Parameter = false;

    for (const auto& restoredChild : restoredState)
    {
        if (! restoredChild.hasProperty ("id"))
            continue;

        const auto restoredID = restoredChild["id"].toString();
        hasAnyV04Parameter = hasAnyV04Parameter
            || restoredID == ParameterIDs::transientEnabled
            || restoredID == ParameterIDs::transientLevel
            || restoredID == ParameterIDs::transientDecay
            || restoredID == ParameterIDs::transientTone
            || restoredID == ParameterIDs::noiseEnabled
            || restoredID == ParameterIDs::noiseLevel
            || restoredID == ParameterIDs::noiseDecay
            || restoredID == ParameterIDs::noiseTone;
        hasAnyV05Parameter = hasAnyV05Parameter
            || restoredID == ParameterIDs::osc2Enabled
            || restoredID == ParameterIDs::osc2Level
            || restoredID == ParameterIDs::osc2Octave
            || restoredID == ParameterIDs::osc2Semitone
            || restoredID == ParameterIDs::osc2FineTune;
        hasAnyV06Parameter = hasAnyV06Parameter
            || restoredID == ParameterIDs::unisonVoices
            || restoredID == ParameterIDs::unisonDetune
            || restoredID == ParameterIDs::stereoSpread;
        auto matchingChild = merged.getChildWithProperty ("id", restoredChild["id"]);

        if (matchingChild.isValid())
        {
            matchingChild.copyPropertiesFrom (restoredChild, nullptr);
            if ((restoredID == ParameterIDs::ampAttack
                 || restoredID == ParameterIDs::filterAttack)
                && restoredChild.hasProperty ("value"))
                matchingChild.setProperty (
                    "value", canonicalizeRestoredAttack (restoredID, restoredChild["value"]),
                    nullptr);
        }
    }

    struct V04Default
    {
        const char* id;
        float value;
        bool enabled;
    };

    constexpr std::array v04Defaults {
        V04Default { ParameterIDs::transientEnabled, 1.0f, true },
        V04Default { ParameterIDs::transientLevel, -18.0f, false },
        V04Default { ParameterIDs::transientDecay, 0.012f, false },
        V04Default { ParameterIDs::transientTone, 0.55f, false },
        V04Default { ParameterIDs::noiseEnabled, 1.0f, true },
        V04Default { ParameterIDs::noiseLevel, -27.0f, false },
        V04Default { ParameterIDs::noiseDecay, 0.120f, false },
        V04Default { ParameterIDs::noiseTone, 0.35f, false }
    };

    for (const auto& item : v04Defaults)
    {
        if (restoredState.getChildWithProperty ("id", item.id).isValid())
            continue;

        if (auto child = merged.getChildWithProperty ("id", item.id); child.isValid())
            child.setProperty ("value", ! hasAnyV04Parameter && item.enabled ? 0.0f : item.value,
                               nullptr);
    }

    struct V05Default
    {
        const char* id;
        float value;
        bool enabled;
    };

    constexpr std::array v05Defaults {
        V05Default { ParameterIDs::osc2Enabled, 1.0f, true },
        V05Default { ParameterIDs::osc2Level, -15.0f, false },
        V05Default { ParameterIDs::osc2Octave, 0.0f, false },
        V05Default { ParameterIDs::osc2Semitone, 0.0f, false },
        V05Default { ParameterIDs::osc2FineTune, -7.0f, false }
    };

    for (const auto& item : v05Defaults)
    {
        if (restoredState.getChildWithProperty ("id", item.id).isValid())
            continue;

        if (auto child = merged.getChildWithProperty ("id", item.id); child.isValid())
            child.setProperty ("value", ! hasAnyV05Parameter && item.enabled ? 0.0f : item.value,
                               nullptr);
    }

    struct V06Default
    {
        const char* id;
        float freshValue;
        float legacyValue;
    };

    constexpr std::array v06Defaults {
        V06Default { ParameterIDs::unisonVoices, 2.0f, 1.0f },
        V06Default { ParameterIDs::unisonDetune, 4.0f, 0.0f },
        V06Default { ParameterIDs::stereoSpread, 0.35f, 0.0f }
    };

    for (const auto& item : v06Defaults)
    {
        if (restoredState.getChildWithProperty ("id", item.id).isValid())
            continue;

        if (auto child = merged.getChildWithProperty ("id", item.id); child.isValid())
            child.setProperty ("value", hasAnyV06Parameter ? item.freshValue : item.legacyValue,
                               nullptr);
    }

    struct V07Default
    {
        const char* id;
        float value;
    };

    constexpr std::array v07Defaults {
        V07Default { ParameterIDs::lfoRate, 0.25f },
        V07Default { ParameterIDs::lfoSync, 0.0f },
        V07Default { ParameterIDs::lfoSyncDivision, 4.0f },
        V07Default { ParameterIDs::lfoWaveform, 0.0f },
        V07Default { ParameterIDs::lfoRestartMode, 1.0f },
        V07Default { ParameterIDs::lfoFilterAmount, 0.0f },
        V07Default { ParameterIDs::lfoPitchAmount, 0.0f },
        V07Default { ParameterIDs::lfoSpreadAmount, 0.0f }
    };

    for (const auto& item : v07Defaults)
    {
        if (restoredState.getChildWithProperty ("id", item.id).isValid())
            continue;

        if (auto child = merged.getChildWithProperty ("id", item.id); child.isValid())
            child.setProperty ("value", item.value, nullptr);
    }

    struct V075Default
    {
        const char* id;
        float value;
    };

    constexpr std::array v075Defaults {
        V075Default { ParameterIDs::osc1Waveform, 0.0f },
        V075Default { ParameterIDs::osc1PulseWidth, 0.50f },
        V075Default { ParameterIDs::osc2Waveform, 0.0f },
        V075Default { ParameterIDs::osc2PulseWidth, 0.50f }
    };

    for (const auto& item : v075Defaults)
    {
        if (restoredState.getChildWithProperty ("id", item.id).isValid())
            continue;

        if (auto child = merged.getChildWithProperty ("id", item.id); child.isValid())
            child.setProperty ("value", item.value, nullptr);
    }

    struct V09Default
    {
        const char* id;
        float value;
    };

    constexpr std::array v09Defaults {
        V09Default { ParameterIDs::chorusBypass, 1.0f },
        V09Default { ParameterIDs::chorusRate, 0.35f },
        V09Default { ParameterIDs::chorusDepth, 0.25f },
        V09Default { ParameterIDs::chorusWidth, 0.65f },
        V09Default { ParameterIDs::chorusMix, 0.20f },
        V09Default { ParameterIDs::delayBypass, 1.0f },
        V09Default { ParameterIDs::delaySync, 1.0f },
        V09Default { ParameterIDs::delayTime, 0.375f },
        V09Default { ParameterIDs::delaySyncDivision, 1.0f },
        V09Default { ParameterIDs::delayFeedback, 0.32f },
        V09Default { ParameterIDs::delayTone, 6000.0f },
        V09Default { ParameterIDs::delayMix, 0.22f },
        V09Default { ParameterIDs::reverbBypass, 1.0f },
        V09Default { ParameterIDs::reverbSize, 0.55f },
        V09Default { ParameterIDs::reverbPreDelay, 0.018f },
        V09Default { ParameterIDs::reverbDamping, 0.55f },
        V09Default { ParameterIDs::reverbMix, 0.18f },
        V09Default { ParameterIDs::fxOrder, 0.0f }
    };

    for (const auto& item : v09Defaults)
    {
        if (restoredState.getChildWithProperty ("id", item.id).isValid())
            continue;

        if (auto child = merged.getChildWithProperty ("id", item.id); child.isValid())
            child.setProperty ("value", item.value, nullptr);
    }

    return merged;
}

DeepPluckParameterCache::DeepPluckParameterCache (juce::AudioProcessorValueTreeState& state) noexcept
    : outputGain (state.getRawParameterValue (ParameterIDs::outputGain)),
      masterTune (state.getRawParameterValue (ParameterIDs::masterTune)),
      ampAttack (state.getRawParameterValue (ParameterIDs::ampAttack)),
      ampDecay (state.getRawParameterValue (ParameterIDs::ampDecay)),
      ampSustain (state.getRawParameterValue (ParameterIDs::ampSustain)),
      ampRelease (state.getRawParameterValue (ParameterIDs::ampRelease)),
      filterEnabled (state.getRawParameterValue (ParameterIDs::filterEnabled)),
      filterCutoff (state.getRawParameterValue (ParameterIDs::filterCutoff)),
      filterResonance (state.getRawParameterValue (ParameterIDs::filterResonance)),
      filterDrive (state.getRawParameterValue (ParameterIDs::filterDrive)),
      filterKeyTracking (state.getRawParameterValue (ParameterIDs::filterKeyTracking)),
      filterEnvAmount (state.getRawParameterValue (ParameterIDs::filterEnvAmount)),
      filterAttack (state.getRawParameterValue (ParameterIDs::filterAttack)),
      filterDecay (state.getRawParameterValue (ParameterIDs::filterDecay)),
      filterSustain (state.getRawParameterValue (ParameterIDs::filterSustain)),
      filterRelease (state.getRawParameterValue (ParameterIDs::filterRelease)),
      transientEnabled (state.getRawParameterValue (ParameterIDs::transientEnabled)),
      transientLevel (state.getRawParameterValue (ParameterIDs::transientLevel)),
      transientDecay (state.getRawParameterValue (ParameterIDs::transientDecay)),
      transientTone (state.getRawParameterValue (ParameterIDs::transientTone)),
      noiseEnabled (state.getRawParameterValue (ParameterIDs::noiseEnabled)),
      noiseLevel (state.getRawParameterValue (ParameterIDs::noiseLevel)),
      noiseDecay (state.getRawParameterValue (ParameterIDs::noiseDecay)),
      noiseTone (state.getRawParameterValue (ParameterIDs::noiseTone)),
      osc2Enabled (state.getRawParameterValue (ParameterIDs::osc2Enabled)),
      osc2Level (state.getRawParameterValue (ParameterIDs::osc2Level)),
      osc2Octave (state.getRawParameterValue (ParameterIDs::osc2Octave)),
      osc2Semitone (state.getRawParameterValue (ParameterIDs::osc2Semitone)),
      osc2FineTune (state.getRawParameterValue (ParameterIDs::osc2FineTune)),
      unisonVoices (state.getRawParameterValue (ParameterIDs::unisonVoices)),
      unisonDetune (state.getRawParameterValue (ParameterIDs::unisonDetune)),
      stereoSpread (state.getRawParameterValue (ParameterIDs::stereoSpread)),
      lfoRate (state.getRawParameterValue (ParameterIDs::lfoRate)),
      lfoSync (state.getRawParameterValue (ParameterIDs::lfoSync)),
      lfoSyncDivision (state.getRawParameterValue (ParameterIDs::lfoSyncDivision)),
      lfoWaveform (state.getRawParameterValue (ParameterIDs::lfoWaveform)),
      lfoRestartMode (state.getRawParameterValue (ParameterIDs::lfoRestartMode)),
      lfoFilterAmount (state.getRawParameterValue (ParameterIDs::lfoFilterAmount)),
      lfoPitchAmount (state.getRawParameterValue (ParameterIDs::lfoPitchAmount)),
      lfoSpreadAmount (state.getRawParameterValue (ParameterIDs::lfoSpreadAmount)),
      osc1Waveform (state.getRawParameterValue (ParameterIDs::osc1Waveform)),
      osc1PulseWidth (state.getRawParameterValue (ParameterIDs::osc1PulseWidth)),
      osc2Waveform (state.getRawParameterValue (ParameterIDs::osc2Waveform)),
      osc2PulseWidth (state.getRawParameterValue (ParameterIDs::osc2PulseWidth)),
      chorusBypass (state.getRawParameterValue (ParameterIDs::chorusBypass)),
      chorusRate (state.getRawParameterValue (ParameterIDs::chorusRate)),
      chorusDepth (state.getRawParameterValue (ParameterIDs::chorusDepth)),
      chorusWidth (state.getRawParameterValue (ParameterIDs::chorusWidth)),
      chorusMix (state.getRawParameterValue (ParameterIDs::chorusMix)),
      delayBypass (state.getRawParameterValue (ParameterIDs::delayBypass)),
      delaySync (state.getRawParameterValue (ParameterIDs::delaySync)),
      delayTime (state.getRawParameterValue (ParameterIDs::delayTime)),
      delaySyncDivision (state.getRawParameterValue (ParameterIDs::delaySyncDivision)),
      delayFeedback (state.getRawParameterValue (ParameterIDs::delayFeedback)),
      delayTone (state.getRawParameterValue (ParameterIDs::delayTone)),
      delayMix (state.getRawParameterValue (ParameterIDs::delayMix)),
      reverbBypass (state.getRawParameterValue (ParameterIDs::reverbBypass)),
      reverbSize (state.getRawParameterValue (ParameterIDs::reverbSize)),
      reverbPreDelay (state.getRawParameterValue (ParameterIDs::reverbPreDelay)),
      reverbDamping (state.getRawParameterValue (ParameterIDs::reverbDamping)),
      reverbMix (state.getRawParameterValue (ParameterIDs::reverbMix)),
      fxOrder (state.getRawParameterValue (ParameterIDs::fxOrder))
{
    jassert (isValid());
}

bool DeepPluckParameterCache::isValid() const noexcept
{
    return outputGain != nullptr && masterTune != nullptr && ampAttack != nullptr
        && ampDecay != nullptr && ampSustain != nullptr && ampRelease != nullptr
        && filterEnabled != nullptr && filterCutoff != nullptr && filterResonance != nullptr
        && filterDrive != nullptr && filterKeyTracking != nullptr && filterEnvAmount != nullptr
        && filterAttack != nullptr && filterDecay != nullptr && filterSustain != nullptr
        && filterRelease != nullptr && transientEnabled != nullptr && transientLevel != nullptr
        && transientDecay != nullptr && transientTone != nullptr && noiseEnabled != nullptr
        && noiseLevel != nullptr && noiseDecay != nullptr && noiseTone != nullptr
        && osc2Enabled != nullptr && osc2Level != nullptr && osc2Octave != nullptr
        && osc2Semitone != nullptr && osc2FineTune != nullptr
        && unisonVoices != nullptr && unisonDetune != nullptr && stereoSpread != nullptr
        && lfoRate != nullptr && lfoSync != nullptr && lfoSyncDivision != nullptr
        && lfoWaveform != nullptr && lfoRestartMode != nullptr
        && lfoFilterAmount != nullptr && lfoPitchAmount != nullptr
        && lfoSpreadAmount != nullptr && osc1Waveform != nullptr
        && osc1PulseWidth != nullptr && osc2Waveform != nullptr
        && osc2PulseWidth != nullptr && chorusBypass != nullptr && chorusRate != nullptr
        && chorusDepth != nullptr && chorusWidth != nullptr && chorusMix != nullptr
        && delayBypass != nullptr && delaySync != nullptr && delayTime != nullptr
        && delaySyncDivision != nullptr && delayFeedback != nullptr && delayTone != nullptr
        && delayMix != nullptr && reverbBypass != nullptr && reverbSize != nullptr
        && reverbPreDelay != nullptr && reverbDamping != nullptr && reverbMix != nullptr
        && fxOrder != nullptr;
}

DeepPluckParameterSnapshot DeepPluckParameterCache::getSnapshot() const noexcept
{
    return {
        loadClamped (outputGain, -3.0f, outputGainMinimum, outputGainMaximum),
        loadClamped (masterTune, 440.0f, masterTuneMinimum, masterTuneMaximum),
        loadClamped (ampAttack, 0.003f, attackMinimum, attackMaximum),
        loadClamped (ampDecay, 0.320f, decayMinimum, decayMaximum),
        loadClamped (ampSustain, 0.0f, sustainMinimum, sustainMaximum),
        loadClamped (ampRelease, 0.180f, releaseMinimum, releaseMaximum),
        loadClamped (filterEnabled, 1.0f, 0.0f, 1.0f),
        loadClamped (filterCutoff, 1800.0f, filterCutoffMinimum, filterCutoffMaximum),
        loadClamped (filterResonance, 0.18f, filterResonanceMinimum, filterResonanceMaximum),
        loadClamped (filterDrive, 3.0f, filterDriveMinimum, filterDriveMaximum),
        loadClamped (filterKeyTracking, 0.35f,
                     filterKeyTrackingMinimum, filterKeyTrackingMaximum),
        loadClamped (filterEnvAmount, 2.50f,
                     filterEnvAmountMinimum, filterEnvAmountMaximum),
        loadClamped (filterAttack, 0.002f, attackMinimum, attackMaximum),
        loadClamped (filterDecay, 0.280f, decayMinimum, decayMaximum),
        loadClamped (filterSustain, 0.0f, sustainMinimum, sustainMaximum),
        loadClamped (filterRelease, 0.180f, releaseMinimum, releaseMaximum),
        loadClamped (transientEnabled, 1.0f, 0.0f, 1.0f),
        loadClamped (transientLevel, -18.0f, layerLevelMinimum, layerLevelMaximum),
        loadClamped (transientDecay, 0.012f, transientDecayMinimum, transientDecayMaximum),
        loadClamped (transientTone, 0.55f, toneMinimum, toneMaximum),
        loadClamped (noiseEnabled, 1.0f, 0.0f, 1.0f),
        loadClamped (noiseLevel, -27.0f, layerLevelMinimum, layerLevelMaximum),
        loadClamped (noiseDecay, 0.120f, noiseDecayMinimum, noiseDecayMaximum),
        loadClamped (noiseTone, 0.35f, toneMinimum, toneMaximum),
        loadToggle (osc2Enabled, 1.0f),
        loadClamped (osc2Level, -15.0f, layerLevelMinimum, layerLevelMaximum),
        loadRoundedClamped (osc2Octave, 0.0f, osc2OctaveMinimum, osc2OctaveMaximum),
        loadRoundedClamped (osc2Semitone, 0.0f,
                            osc2SemitoneMinimum, osc2SemitoneMaximum),
        loadClamped (osc2FineTune, -7.0f, osc2FineTuneMinimum, osc2FineTuneMaximum),
        loadRoundedClamped (unisonVoices, 2.0f,
                            unisonVoicesMinimum, unisonVoicesMaximum),
        loadClamped (unisonDetune, 4.0f,
                     unisonDetuneMinimum, unisonDetuneMaximum),
        loadClamped (stereoSpread, 0.35f,
                     stereoSpreadMinimum, stereoSpreadMaximum),
        loadClamped (lfoRate, 0.25f, lfoRateMinimum, lfoRateMaximum),
        loadToggle (lfoSync, 0.0f),
        loadRoundedClamped (lfoSyncDivision, 4.0f, 0.0f, 6.0f),
        loadRoundedClamped (lfoWaveform, 0.0f, 0.0f, 3.0f),
        loadRoundedClamped (lfoRestartMode, 1.0f, 0.0f, 1.0f),
        loadClamped (lfoFilterAmount, 0.0f,
                     lfoFilterAmountMinimum, lfoFilterAmountMaximum),
        loadClamped (lfoPitchAmount, 0.0f,
                     lfoPitchAmountMinimum, lfoPitchAmountMaximum),
        loadClamped (lfoSpreadAmount, 0.0f,
                     lfoSpreadAmountMinimum, lfoSpreadAmountMaximum),
        loadRoundedClamped (osc1Waveform, 0.0f, 0.0f, 4.0f),
        loadClamped (osc1PulseWidth, 0.50f, pulseWidthMinimum, pulseWidthMaximum),
        loadRoundedClamped (osc2Waveform, 0.0f, 0.0f, 4.0f),
        loadClamped (osc2PulseWidth, 0.50f, pulseWidthMinimum, pulseWidthMaximum),
        loadToggle (chorusBypass, 1.0f),
        loadClamped (chorusRate, 0.35f, chorusRateMinimum, chorusRateMaximum),
        loadClamped (chorusDepth, 0.25f, 0.0f, 1.0f),
        loadClamped (chorusWidth, 0.65f, 0.0f, 1.0f),
        loadClamped (chorusMix, 0.20f, 0.0f, 1.0f),
        loadToggle (delayBypass, 1.0f),
        loadToggle (delaySync, 1.0f),
        loadClamped (delayTime, 0.375f, delayTimeMinimum, delayTimeMaximum),
        loadRoundedClamped (delaySyncDivision, 1.0f, 0.0f, 6.0f),
        loadClamped (delayFeedback, 0.32f, 0.0f, delayFeedbackMaximum),
        loadClamped (delayTone, 6000.0f, delayToneMinimum, delayToneMaximum),
        loadClamped (delayMix, 0.22f, 0.0f, 1.0f),
        loadToggle (reverbBypass, 1.0f),
        loadClamped (reverbSize, 0.55f, reverbSizeMinimum, reverbSizeMaximum),
        loadClamped (reverbPreDelay, 0.018f, 0.0f, reverbPreDelayMaximum),
        loadClamped (reverbDamping, 0.55f, 0.0f, 1.0f),
        loadClamped (reverbMix, 0.18f, 0.0f, 1.0f),
        loadRoundedClamped (fxOrder, 0.0f, 0.0f, 5.0f)
    };
}
}
