#include "DeepPluckRandomization.h"

#include <array>
#include <chrono>
#include <cmath>
#include <limits>

namespace deeppluck
{
namespace
{
constexpr std::array oscillator1Indices { 40, 41 };
constexpr std::array oscillator2Indices { 24, 25, 26, 27, 28, 42, 43 };
constexpr std::array ampEnvelopeIndices { 2, 3, 4, 5 };
constexpr std::array filterIndices { 6, 7, 8, 9, 10 };
constexpr std::array filterEnvelopeIndices { 11, 12, 13, 14, 15 };
constexpr std::array transientIndices { 16, 17, 18, 19 };
constexpr std::array noiseIndices { 20, 21, 22, 23 };
constexpr std::array unisonStereoIndices { 29, 30, 31 };
constexpr std::array modulationIndices { 32, 33, 34, 35, 36, 37, 38, 39 };
constexpr std::array effectsRackIndices {
    44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61
};

constexpr std::array moduleNames {
    "Oscillator 1", "Oscillator 2", "Amp Envelope", "Filter",
    "Filter Envelope", "Transient", "Noise", "Unison / Stereo", "Modulation",
    "Effects Rack"
};

constexpr std::array moduleIds {
    "oscillator1", "oscillator2", "ampEnvelope", "filter",
    "filterEnvelope", "transient", "noise", "unisonStereo", "modulation",
    "effectsRack"
};

constexpr std::size_t moduleIndex (RandomizationModule module) noexcept
{
    return static_cast<std::size_t> (module);
}

bool isValidModule (RandomizationModule module) noexcept
{
    return moduleIndex (module) < randomizationModuleCount;
}
}

const char* getRandomizationModuleName (RandomizationModule module) noexcept
{
    return isValidModule (module) ? moduleNames[moduleIndex (module)] : "Unknown";
}

const char* getRandomizationModuleId (RandomizationModule module) noexcept
{
    return isValidModule (module) ? moduleIds[moduleIndex (module)] : "unknown";
}

std::span<const int> getRandomizationParameterIndices (RandomizationModule module) noexcept
{
    switch (module)
    {
        case RandomizationModule::oscillator1: return oscillator1Indices;
        case RandomizationModule::oscillator2: return oscillator2Indices;
        case RandomizationModule::ampEnvelope: return ampEnvelopeIndices;
        case RandomizationModule::filter: return filterIndices;
        case RandomizationModule::filterEnvelope: return filterEnvelopeIndices;
        case RandomizationModule::transient: return transientIndices;
        case RandomizationModule::noise: return noiseIndices;
        case RandomizationModule::unisonStereo: return unisonStereoIndices;
        case RandomizationModule::modulation: return modulationIndices;
        case RandomizationModule::effectsRack: return effectsRackIndices;
        case RandomizationModule::count: break;
    }
    return {};
}

DeepPluckRandomization::DeepPluckRandomization (
    juce::AudioProcessorValueTreeState& parameterState,
    std::optional<std::uint64_t> deterministicSeed)
    : state (parameterState),
      generator (deterministicSeed.value_or (makeProductionSeed()))
{
}

void DeepPluckRandomization::setAmount (float newAmount) noexcept
{
    amount = std::isfinite (newAmount) ? juce::jlimit (0.0f, 1.0f, newAmount) : 1.0f;
}

float DeepPluckRandomization::getAmount() const noexcept { return amount; }

void DeepPluckRandomization::setModuleLocked (RandomizationModule module,
                                              bool shouldBeLocked) noexcept
{
    if (isValidModule (module))
        locks[moduleIndex (module)] = shouldBeLocked;
}

void DeepPluckRandomization::toggleModuleLocked (RandomizationModule module) noexcept
{
    if (isValidModule (module))
        locks[moduleIndex (module)] = ! locks[moduleIndex (module)];
}

bool DeepPluckRandomization::isModuleLocked (RandomizationModule module) const noexcept
{
    return isValidModule (module) && locks[moduleIndex (module)];
}

bool DeepPluckRandomization::randomizeAll() { return randomize (std::nullopt); }

bool DeepPluckRandomization::randomizeModule (RandomizationModule module)
{
    if (! isValidModule (module) || isModuleLocked (module))
        return false;
    return randomize (module);
}

bool DeepPluckRandomization::randomize (std::optional<RandomizationModule> onlyModule)
{
#if JUCE_DEBUG
    const auto* messageManager = juce::MessageManager::getInstanceWithoutCreating();
    jassert (messageManager == nullptr || messageManager->isThisTheMessageThread());
#endif

    if (amount <= 0.0f)
        return false;

    if (! onlyModule.has_value())
    {
        const auto anyUnlocked = std::any_of (locks.begin(), locks.end(), [] (bool locked)
        {
            return ! locked;
        });
        if (! anyUnlocked)
            return false;
    }

    const auto before = captureParameterValues (state);
    auto after = before;
    std::array<bool, allParameterIds.size()> changed {};
    auto changeCount = 0;

    const auto processModule = [&] (RandomizationModule module)
    {
        if (isModuleLocked (module))
            return;

        for (const auto parameterIndex : getRandomizationParameterIndices (module))
        {
            auto* parameter = state.getParameter (
                allParameterIds[static_cast<std::size_t> (parameterIndex)]);
            if (parameter == nullptr)
                continue;

            const auto targetRaw = drawTarget (parameterIndex);
            const auto currentRaw = before[static_cast<std::size_t> (parameterIndex)];
            const auto currentNormalized = juce::jlimit (
                0.0f, 1.0f, parameter->convertTo0to1 (currentRaw));
            const auto targetNormalized = juce::jlimit (
                0.0f, 1.0f, parameter->convertTo0to1 (targetRaw));

            float resultNormalized = currentNormalized;
            const auto categorical = dynamic_cast<juce::AudioParameterBool*> (parameter) != nullptr
                                  || dynamic_cast<juce::AudioParameterChoice*> (parameter) != nullptr;
            if (categorical)
            {
                if (amount >= 1.0f || drawUnit() < static_cast<double> (amount))
                    resultNormalized = targetNormalized;
            }
            else
            {
                resultNormalized = currentNormalized
                                 + amount * (targetNormalized - currentNormalized);
            }

            resultNormalized = juce::jlimit (0.0f, 1.0f, resultNormalized);
            const auto resultRaw = parameter->convertFrom0to1 (resultNormalized);
            const auto canonicalNormalized = parameter->convertTo0to1 (resultRaw);
            if (std::isfinite (resultRaw) && std::isfinite (canonicalNormalized)
                && std::abs (canonicalNormalized - currentNormalized) > 1.0e-7f)
            {
                after[static_cast<std::size_t> (parameterIndex)] = resultRaw;
                changed[static_cast<std::size_t> (parameterIndex)] = true;
                ++changeCount;
            }
        }
    };

    if (onlyModule.has_value())
        processModule (*onlyModule);
    else
        for (auto index = 0; index < static_cast<int> (RandomizationModule::count); ++index)
            processModule (static_cast<RandomizationModule> (index));

    if (changeCount == 0)
        return false;

    undoValues = before;
    undoAvailable = true;
    for (auto index = 0; index < static_cast<int> (after.size()); ++index)
        if (changed[static_cast<std::size_t> (index)])
        {
            const auto applied = setParameterValueNotifyingHost (
                state, index, after[static_cast<std::size_t> (index)]);
            jassert (applied);
            juce::ignoreUnused (applied);
        }
    return true;
}

bool DeepPluckRandomization::canUndo() const noexcept { return undoAvailable; }

bool DeepPluckRandomization::undo()
{
    if (! undoAvailable)
        return false;

    for (auto moduleIndexValue = 0;
         moduleIndexValue < static_cast<int> (RandomizationModule::count);
         ++moduleIndexValue)
        for (const auto parameterIndex : getRandomizationParameterIndices (
                 static_cast<RandomizationModule> (moduleIndexValue)))
        {
            const auto applied = setParameterValueNotifyingHost (
                state, parameterIndex, undoValues[static_cast<std::size_t> (parameterIndex)]);
            jassert (applied);
            juce::ignoreUnused (applied);
        }

    undoAvailable = false;
    return true;
}

void DeepPluckRandomization::invalidateUndo() noexcept { undoAvailable = false; }

double DeepPluckRandomization::drawUnit()
{
    return std::generate_canonical<double, std::numeric_limits<double>::digits> (generator);
}

float DeepPluckRandomization::drawUniform (float minimum, float maximum)
{
    return minimum + static_cast<float> (drawUnit()) * (maximum - minimum);
}

float DeepPluckRandomization::drawLogUniform (float minimum, float maximum)
{
    const auto logMinimum = std::log (minimum);
    return std::exp (logMinimum + static_cast<float> (drawUnit())
                                  * (std::log (maximum) - logMinimum));
}

int DeepPluckRandomization::drawWeightedIndex (std::span<const int> weights)
{
    auto total = 0;
    for (const auto weight : weights)
        total += juce::jmax (0, weight);
    if (total <= 0)
        return 0;

    auto draw = static_cast<int> (drawUnit() * static_cast<double> (total));
    draw = juce::jlimit (0, total - 1, draw);
    for (auto index = 0; index < static_cast<int> (weights.size()); ++index)
    {
        draw -= juce::jmax (0, weights[static_cast<std::size_t> (index)]);
        if (draw < 0)
        {
            lastDistributionBranch = index;
            return index;
        }
    }
    lastDistributionBranch = static_cast<int> (weights.size()) - 1;
    return lastDistributionBranch;
}

float DeepPluckRandomization::drawTargetForTests (int parameterIndex)
{
    lastDistributionBranch = -1;
    return drawTarget (parameterIndex);
}

int DeepPluckRandomization::getLastDistributionBranchForTests() const noexcept
{
    return lastDistributionBranch;
}

float DeepPluckRandomization::drawTarget (int parameterIndex)
{
    const auto uniformMixture = [&] (std::span<const int> weights,
                                     std::span<const std::array<float, 2>> ranges)
    {
        const auto branch = drawWeightedIndex (weights);
        return drawUniform (ranges[static_cast<std::size_t> (branch)][0],
                            ranges[static_cast<std::size_t> (branch)][1]);
    };
    const auto logMixture = [&] (std::span<const int> weights,
                                 std::span<const std::array<float, 2>> ranges)
    {
        const auto branch = drawWeightedIndex (weights);
        return drawLogUniform (ranges[static_cast<std::size_t> (branch)][0],
                               ranges[static_cast<std::size_t> (branch)][1]);
    };

    switch (parameterIndex)
    {
        case 40: { constexpr std::array w { 35, 25, 12, 20, 8 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 41:
        case 43: { constexpr std::array w { 75, 20, 5 }; constexpr std::array r { std::array { 0.30f, 0.70f }, std::array { 0.15f, 0.85f }, std::array { 0.05f, 0.95f } }; return uniformMixture (w, r); }
        case 24: { constexpr std::array w { 15, 85 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 25: { constexpr std::array w { 60, 25, 10, 5 }; constexpr std::array r { std::array { -24.0f, -12.0f }, std::array { -36.0f, -24.0f }, std::array { -12.0f, -6.0f }, std::array { -6.0f, 0.0f } }; return uniformMixture (w, r); }
        case 26: { constexpr std::array w { 30, 60, 10 }; constexpr std::array v { -1.0f, 0.0f, 1.0f }; return v[static_cast<std::size_t> (drawWeightedIndex (w))]; }
        case 27: { constexpr std::array w { 38, 12, 10, 10, 10, 6, 6, 2, 2, 2, 2 }; constexpr std::array v { 0.0f, -12.0f, 12.0f, 7.0f, -5.0f, 5.0f, -7.0f, 3.0f, 4.0f, -3.0f, -4.0f }; return v[static_cast<std::size_t> (drawWeightedIndex (w))]; }
        case 28: { constexpr std::array w { 65, 25, 10 }; constexpr std::array r { std::array { -12.0f, 12.0f }, std::array { -25.0f, 25.0f }, std::array { -50.0f, 50.0f } }; return uniformMixture (w, r); }
        case 42: { constexpr std::array w { 25, 20, 10, 35, 10 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 2: { constexpr std::array w { 70, 23, 6, 1 }; constexpr std::array r { std::array { 0.001f, 0.015f }, std::array { 0.015f, 0.080f }, std::array { 0.080f, 0.500f }, std::array { 0.500f, 2.000f } }; return logMixture (w, r); }
        case 3: { constexpr std::array w { 55, 25, 15, 5 }; constexpr std::array r { std::array { 0.080f, 0.550f }, std::array { 0.020f, 0.080f }, std::array { 0.550f, 1.800f }, std::array { 1.800f, 4.000f } }; return logMixture (w, r); }
        case 4: { constexpr std::array w { 65, 25, 10 }; constexpr std::array r { std::array { 0.00f, 0.12f }, std::array { 0.12f, 0.40f }, std::array { 0.40f, 0.75f } }; return uniformMixture (w, r); }
        case 5: { constexpr std::array w { 50, 30, 15, 5 }; constexpr std::array r { std::array { 0.080f, 0.500f }, std::array { 0.500f, 1.500f }, std::array { 0.020f, 0.080f }, std::array { 1.500f, 5.000f } }; return logMixture (w, r); }
        case 6: { constexpr std::array w { 10, 90 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 7: { constexpr std::array w { 10, 55, 25, 10 }; constexpr std::array r { std::array { 250.0f, 700.0f }, std::array { 700.0f, 4500.0f }, std::array { 4500.0f, 9000.0f }, std::array { 9000.0f, 16000.0f } }; return logMixture (w, r); }
        case 8: { constexpr std::array w { 65, 25, 8, 2 }; constexpr std::array r { std::array { 0.05f, 0.40f }, std::array { 0.40f, 0.65f }, std::array { 0.65f, 0.82f }, std::array { 0.82f, 0.95f } }; return uniformMixture (w, r); }
        case 9: { constexpr std::array w { 55, 30, 12, 3 }; constexpr std::array r { std::array { 0.0f, 6.0f }, std::array { 6.0f, 10.0f }, std::array { 10.0f, 14.0f }, std::array { 14.0f, 18.0f } }; return uniformMixture (w, r); }
        case 10: { constexpr std::array w { 60, 25, 15 }; constexpr std::array r { std::array { 0.20f, 0.60f }, std::array { 0.00f, 0.20f }, std::array { 0.60f, 0.90f } }; return uniformMixture (w, r); }
        case 11: { constexpr std::array w { 70, 15, 10, 5 }; constexpr std::array r { std::array { 0.50f, 3.50f }, std::array { 0.00f, 0.50f }, std::array { -2.00f, 0.00f }, std::array { 3.50f, 5.00f } }; return uniformMixture (w, r); }
        case 12: { constexpr std::array w { 75, 20, 4, 1 }; constexpr std::array r { std::array { 0.001f, 0.020f }, std::array { 0.020f, 0.100f }, std::array { 0.100f, 0.800f }, std::array { 0.800f, 2.000f } }; return logMixture (w, r); }
        case 13: { constexpr std::array w { 60, 25, 10, 5 }; constexpr std::array r { std::array { 0.060f, 0.500f }, std::array { 0.500f, 1.500f }, std::array { 0.015f, 0.060f }, std::array { 1.500f, 3.000f } }; return logMixture (w, r); }
        case 14: { constexpr std::array w { 70, 20, 10 }; constexpr std::array r { std::array { 0.00f, 0.15f }, std::array { 0.15f, 0.40f }, std::array { 0.40f, 0.70f } }; return uniformMixture (w, r); }
        case 15: { constexpr std::array w { 60, 25, 10, 5 }; constexpr std::array r { std::array { 0.050f, 0.600f }, std::array { 0.600f, 1.800f }, std::array { 0.010f, 0.050f }, std::array { 1.800f, 4.000f } }; return logMixture (w, r); }
        case 16: { constexpr std::array w { 15, 85 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 17: { constexpr std::array w { 65, 25, 8, 2 }; constexpr std::array r { std::array { -30.0f, -16.0f }, std::array { -40.0f, -30.0f }, std::array { -16.0f, -9.0f }, std::array { -9.0f, 0.0f } }; return uniformMixture (w, r); }
        case 18: { constexpr std::array w { 60, 25, 10, 5 }; constexpr std::array r { std::array { 0.006f, 0.020f }, std::array { 0.020f, 0.045f }, std::array { 0.001f, 0.006f }, std::array { 0.045f, 0.080f } }; return uniformMixture (w, r); }
        case 19: { constexpr std::array w { 70, 15, 15 }; constexpr std::array r { std::array { 0.30f, 0.70f }, std::array { 0.15f, 0.30f }, std::array { 0.70f, 0.85f } }; return uniformMixture (w, r); }
        case 20: { constexpr std::array w { 30, 70 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 21: { constexpr std::array w { 70, 20, 8, 2 }; constexpr std::array r { std::array { -42.0f, -24.0f }, std::array { -24.0f, -16.0f }, std::array { -60.0f, -42.0f }, std::array { -16.0f, -8.0f } }; return uniformMixture (w, r); }
        case 22: { constexpr std::array w { 60, 25, 10, 5 }; constexpr std::array r { std::array { 0.040f, 0.250f }, std::array { 0.250f, 0.600f }, std::array { 0.010f, 0.040f }, std::array { 0.600f, 1.200f } }; return logMixture (w, r); }
        case 23: { constexpr std::array w { 70, 15, 15 }; constexpr std::array r { std::array { 0.20f, 0.60f }, std::array { 0.05f, 0.20f }, std::array { 0.60f, 0.80f } }; return uniformMixture (w, r); }
        case 29: { constexpr std::array w { 25, 45, 22, 8 }; return static_cast<float> (drawWeightedIndex (w) + 1); }
        case 30: { constexpr std::array w { 65, 25, 8, 2 }; constexpr std::array r { std::array { 1.0f, 9.0f }, std::array { 9.0f, 16.0f }, std::array { 0.0f, 1.0f }, std::array { 16.0f, 24.0f } }; return uniformMixture (w, r); }
        case 31: { constexpr std::array w { 60, 25, 12, 3 }; constexpr std::array r { std::array { 0.20f, 0.65f }, std::array { 0.00f, 0.20f }, std::array { 0.65f, 0.85f }, std::array { 0.85f, 1.00f } }; return uniformMixture (w, r); }
        case 32: { constexpr std::array w { 55, 30, 10, 5 }; constexpr std::array r { std::array { 0.05f, 0.50f }, std::array { 0.50f, 2.00f }, std::array { 0.02f, 0.05f }, std::array { 2.00f, 6.00f } }; return logMixture (w, r); }
        case 33: { constexpr std::array w { 75, 25 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 34: { constexpr std::array w { 2, 4, 12, 23, 34, 20, 5 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 35: { constexpr std::array w { 45, 30, 15, 10 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 36: { constexpr std::array w { 35, 65 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 37: { constexpr std::array w { 25, 50, 20, 5 }; const auto b = drawWeightedIndex (w); if (b == 0) return 0.0f; constexpr std::array r { std::array { -0.50f, 0.50f }, std::array { -1.20f, 1.20f }, std::array { -2.00f, 2.00f } }; return drawUniform (r[static_cast<std::size_t> (b - 1)][0], r[static_cast<std::size_t> (b - 1)][1]); }
        case 38: { constexpr std::array w { 50, 40, 8, 2 }; const auto b = drawWeightedIndex (w); if (b == 0) return 0.0f; constexpr std::array r { std::array { -7.0f, 7.0f }, std::array { -20.0f, 20.0f }, std::array { -50.0f, 50.0f } }; return drawUniform (r[static_cast<std::size_t> (b - 1)][0], r[static_cast<std::size_t> (b - 1)][1]); }
        case 39: { constexpr std::array w { 30, 50, 15, 5 }; const auto b = drawWeightedIndex (w); if (b == 0) return 0.0f; constexpr std::array r { std::array { -0.35f, 0.35f }, std::array { -0.70f, 0.70f }, std::array { -1.00f, 1.00f } }; return drawUniform (r[static_cast<std::size_t> (b - 1)][0], r[static_cast<std::size_t> (b - 1)][1]); }
        case 44: { constexpr std::array w { 65, 35 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 45: { constexpr std::array w { 70, 25, 5 }; constexpr std::array r { std::array { 0.08f, 0.60f }, std::array { 0.60f, 1.50f }, std::array { 1.50f, 3.50f } }; return logMixture (w, r); }
        case 46: { constexpr std::array w { 65, 25, 8, 2 }; constexpr std::array r { std::array { 0.12f, 0.45f }, std::array { 0.45f, 0.70f }, std::array { 0.03f, 0.12f }, std::array { 0.70f, 0.90f } }; return uniformMixture (w, r); }
        case 47: { constexpr std::array w { 65, 20, 13, 2 }; constexpr std::array r { std::array { 0.35f, 0.75f }, std::array { 0.15f, 0.35f }, std::array { 0.75f, 0.95f }, std::array { 0.00f, 0.15f } }; return uniformMixture (w, r); }
        case 48: { constexpr std::array w { 65, 25, 8, 2 }; constexpr std::array r { std::array { 0.08f, 0.28f }, std::array { 0.28f, 0.42f }, std::array { 0.00f, 0.08f }, std::array { 0.42f, 0.55f } }; return uniformMixture (w, r); }
        case 49: { constexpr std::array w { 55, 45 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 50: { constexpr std::array w { 20, 80 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 51: { constexpr std::array w { 60, 25, 12, 3 }; constexpr std::array r { std::array { 0.18f, 0.55f }, std::array { 0.08f, 0.18f }, std::array { 0.55f, 1.20f }, std::array { 1.20f, 2.00f } }; return logMixture (w, r); }
        case 52: { constexpr std::array w { 14, 28, 28, 10, 8, 10, 2 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 53: { constexpr std::array w { 55, 25, 15, 5 }; constexpr std::array r { std::array { 0.20f, 0.42f }, std::array { 0.42f, 0.58f }, std::array { 0.05f, 0.20f }, std::array { 0.58f, 0.72f } }; return uniformMixture (w, r); }
        case 54: { constexpr std::array w { 65, 20, 12, 3 }; constexpr std::array r { std::array { 3500.0f, 9000.0f }, std::array { 1800.0f, 3500.0f }, std::array { 9000.0f, 15000.0f }, std::array { 800.0f, 1800.0f } }; return logMixture (w, r); }
        case 55: { constexpr std::array w { 65, 25, 8, 2 }; constexpr std::array r { std::array { 0.10f, 0.28f }, std::array { 0.28f, 0.40f }, std::array { 0.02f, 0.10f }, std::array { 0.40f, 0.55f } }; return uniformMixture (w, r); }
        case 56: { constexpr std::array w { 70, 30 }; return static_cast<float> (drawWeightedIndex (w)); }
        case 57: { constexpr std::array w { 60, 25, 12, 3 }; constexpr std::array r { std::array { 0.35f, 0.65f }, std::array { 0.20f, 0.35f }, std::array { 0.65f, 0.82f }, std::array { 0.82f, 0.92f } }; return uniformMixture (w, r); }
        case 58: { constexpr std::array w { 60, 25, 10, 5 }; constexpr std::array r { std::array { 0.008f, 0.030f }, std::array { 0.030f, 0.060f }, std::array { 0.000f, 0.008f }, std::array { 0.060f, 0.100f } }; return uniformMixture (w, r); }
        case 59: { constexpr std::array w { 70, 15, 12, 3 }; constexpr std::array r { std::array { 0.45f, 0.75f }, std::array { 0.25f, 0.45f }, std::array { 0.75f, 0.90f }, std::array { 0.10f, 0.25f } }; return uniformMixture (w, r); }
        case 60: { constexpr std::array w { 65, 25, 8, 2 }; constexpr std::array r { std::array { 0.10f, 0.28f }, std::array { 0.28f, 0.40f }, std::array { 0.02f, 0.10f }, std::array { 0.40f, 0.55f } }; return uniformMixture (w, r); }
        case 61:
        {
            constexpr std::array stayOrMove { 70, 30 };
            const auto branch = drawWeightedIndex (stayOrMove);
            const auto* raw = state.getRawParameterValue (ParameterIDs::fxOrder);
            const auto current = juce::jlimit (0, 5, static_cast<int> (std::lround (
                raw != nullptr ? raw->load (std::memory_order_relaxed) : 0.0f)));
            if (branch == 0)
                return static_cast<float> (current);
            auto other = juce::jlimit (0, 4, static_cast<int> (drawUnit() * 5.0));
            if (other >= current)
                ++other;
            return static_cast<float> (other);
        }
        default: break;
    }
    return 0.0f;
}

std::uint64_t DeepPluckRandomization::makeProductionSeed()
{
    const auto systemRandom = static_cast<std::uint64_t> (
        juce::Random::getSystemRandom().nextInt64());
    const auto ticks = static_cast<std::uint64_t> (juce::Time::getHighResolutionTicks());
    const auto steady = static_cast<std::uint64_t> (
        std::chrono::steady_clock::now().time_since_epoch().count());
    return systemRandom ^ (ticks + 0x9e3779b97f4a7c15ULL + (steady << 6U) + (steady >> 2U));
}
}
