#include "PluginProcessor.h"
#include "PluginEditor.h"
#include "Effects/DeepPluckChorus.h"
#include "Effects/DeepPluckDelay.h"
#include "Effects/DeepPluckEffectMix.h"
#include "Effects/DeepPluckEffectsRack.h"
#include "Effects/DeepPluckReverb.h"
#include "Parameters/DeepPluckParameterState.h"
#include "Parameters/DeepPluckParameters.h"
#include "Presets/DeepPluckPresetManager.h"
#include "Presets/DeepPluckPresets.h"
#include "Randomization/DeepPluckRandomization.h"
#include "Synth/DeepPluckEnvelope.h"
#include "Synth/DeepPluckFilter.h"
#include "Synth/DeepPluckLfo.h"
#include "Synth/DeepPluckOscillator.h"
#include "Synth/DeepPluckSound.h"
#include "Synth/DeepPluckSynthesiser.h"
#include "Synth/DeepPluckTransientNoise.h"
#include "Synth/DeepPluckUnison.h"
#include "Synth/DeepPluckVoice.h"

#include <juce_audio_utils/juce_audio_utils.h>

#include <array>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <memory>
#include <string>
#include <thread>
#include <vector>

namespace
{
struct TestContext
{
    void check (bool condition, const std::string& message)
    {
        if (! condition)
        {
            ++failures;
            std::cerr << "FAIL: " << message << '\n';
        }
    }

    void near (double actual, double expected, double tolerance, const std::string& message)
    {
        check (std::abs (actual - expected) <= tolerance,
               message + " (actual=" + std::to_string (actual)
                   + ", expected=" + std::to_string (expected) + ")");
    }

    int failures = 0;
};

struct CapturedKeyboardEvent
{
    bool noteOn = false;
    int channel = 0;
    int note = 0;
    float velocity = 0.0f;
};

class CapturingKeyboardListener final : public juce::MidiKeyboardState::Listener
{
public:
    void handleNoteOn (juce::MidiKeyboardState*, int channel,
                       int note, float velocity) override
    {
        events.push_back ({ true, channel, note, velocity });
    }

    void handleNoteOff (juce::MidiKeyboardState*, int channel,
                        int note, float velocity) override
    {
        events.push_back ({ false, channel, note, velocity });
    }

    std::vector<CapturedKeyboardEvent> events;
};

std::unique_ptr<DeepPluckAudioProcessor> createProcessorForWrapper (
    juce::AudioProcessor::WrapperType wrapperType)
{
    juce::AudioProcessor::setTypeOfNextNewPlugin (wrapperType);
    auto processor = std::make_unique<DeepPluckAudioProcessor>();
    juce::AudioProcessor::setTypeOfNextNewPlugin (
        juce::AudioProcessor::wrapperType_Undefined);
    return processor;
}

class MinimalProcessor final : public juce::AudioProcessor
{
public:
    MinimalProcessor() : AudioProcessor (BusesProperties()) {}
    void prepareToPlay (double, int) override {}
    void releaseResources() override {}
    void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override {}
    juce::AudioProcessorEditor* createEditor() override { return nullptr; }
    bool hasEditor() const override { return false; }
    const juce::String getName() const override { return "Test"; }
    bool acceptsMidi() const override { return false; }
    bool producesMidi() const override { return false; }
    bool isMidiEffect() const override { return false; }
    double getTailLengthSeconds() const override { return 0.0; }
    int getNumPrograms() override { return 1; }
    int getCurrentProgram() override { return 0; }
    void setCurrentProgram (int) override {}
    const juce::String getProgramName (int) override { return {}; }
    void changeProgramName (int, const juce::String&) override {}
    void getStateInformation (juce::MemoryBlock&) override {}
    void setStateInformation (const void*, int) override {}
};

class CountingPlayHead final : public juce::AudioPlayHead
{
public:
    explicit CountingPlayHead (double tempoToReport) : tempo (tempoToReport) {}

    juce::Optional<PositionInfo> getPosition() const override
    {
        ++queryCount;
        PositionInfo position;
        position.setBpm (tempo);
        return position;
    }

    mutable int queryCount = 0;
    double tempo = 120.0;
};

void setParameter (juce::AudioProcessorValueTreeState& state, const char* id, float value)
{
    auto* parameter = state.getParameter (id);
    jassert (parameter != nullptr);
    parameter->setValueNotifyingHost (parameter->convertTo0to1 (value));
}

bool bufferIsFinite (const juce::AudioBuffer<float>& buffer)
{
    for (auto channel = 0; channel < buffer.getNumChannels(); ++channel)
        for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
            if (! std::isfinite (buffer.getSample (channel, sample)))
                return false;

    return true;
}

juce::Component* findDescendantWithID (juce::Component& parent, const juce::String& id)
{
    for (auto index = 0; index < parent.getNumChildComponents(); ++index)
    {
        auto* child = parent.getChildComponent (index);
        if (child->getComponentID() == id)
            return child;
        if (auto* match = findDescendantWithID (*child, id))
            return match;
    }
    return nullptr;
}

int countDescendantsWithID (juce::Component& parent, const juce::String& id)
{
    auto count = 0;
    for (auto index = 0; index < parent.getNumChildComponents(); ++index)
    {
        auto* child = parent.getChildComponent (index);
        count += child->getComponentID() == id ? 1 : 0;
        count += countDescendantsWithID (*child, id);
    }
    return count;
}

float maximumMagnitude (const juce::AudioBuffer<float>& buffer, int start, int length)
{
    auto maximum = 0.0f;

    for (auto channel = 0; channel < buffer.getNumChannels(); ++channel)
        maximum = juce::jmax (maximum, buffer.getMagnitude (channel, start, length));

    return maximum;
}

float rms (const juce::AudioBuffer<float>& buffer)
{
    auto squaredSum = 0.0;
    const auto count = buffer.getNumChannels() * buffer.getNumSamples();

    for (auto channel = 0; channel < buffer.getNumChannels(); ++channel)
        for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
        {
            const auto value = buffer.getSample (channel, sample);
            squaredSum += static_cast<double> (value * value);
        }

    return count > 0 ? static_cast<float> (std::sqrt (squaredSum / count)) : 0.0f;
}

float channelRms (const juce::AudioBuffer<float>& buffer, int channel)
{
    auto squaredSum = 0.0;

    for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
    {
        const auto value = buffer.getSample (channel, sample);
        squaredSum += static_cast<double> (value * value);
    }

    return buffer.getNumSamples() > 0
             ? static_cast<float> (std::sqrt (squaredSum / buffer.getNumSamples()))
             : 0.0f;
}

float stereoDifferenceRms (const juce::AudioBuffer<float>& buffer)
{
    if (buffer.getNumChannels() < 2)
        return 0.0f;

    auto squaredSum = 0.0;
    for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
    {
        const auto difference = buffer.getSample (0, sample) - buffer.getSample (1, sample);
        squaredSum += static_cast<double> (difference * difference);
    }

    return buffer.getNumSamples() > 0
             ? static_cast<float> (std::sqrt (squaredSum / buffer.getNumSamples()))
             : 0.0f;
}

struct StereoMetricsAccumulator
{
    void add (const juce::AudioBuffer<float>& buffer)
    {
        jassert (buffer.getNumChannels() >= 2);
        for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
        {
            const auto left = static_cast<double> (buffer.getSample (0, sample));
            const auto right = static_cast<double> (buffer.getSample (1, sample));
            const auto mid = 0.5 * (left + right);
            const auto side = 0.5 * (left - right);
            leftPeak = juce::jmax (leftPeak, std::abs (left));
            rightPeak = juce::jmax (rightPeak, std::abs (right));
            monoPeak = juce::jmax (monoPeak, std::abs (mid));
            leftSquared += left * left;
            rightSquared += right * right;
            midSquared += mid * mid;
            sideSquared += side * side;
            ++sampleCount;
        }
    }

    [[nodiscard]] double peak() const noexcept { return juce::jmax (leftPeak, rightPeak); }
    [[nodiscard]] double rms() const noexcept
    {
        return sampleCount > 0
                 ? std::sqrt ((leftSquared + rightSquared) / (2.0 * sampleCount)) : 0.0;
    }
    [[nodiscard]] double leftRms() const noexcept
    {
        return sampleCount > 0 ? std::sqrt (leftSquared / sampleCount) : 0.0;
    }
    [[nodiscard]] double rightRms() const noexcept
    {
        return sampleCount > 0 ? std::sqrt (rightSquared / sampleCount) : 0.0;
    }
    [[nodiscard]] double midRms() const noexcept
    {
        return sampleCount > 0 ? std::sqrt (midSquared / sampleCount) : 0.0;
    }
    [[nodiscard]] double sideRms() const noexcept
    {
        return sampleCount > 0 ? std::sqrt (sideSquared / sampleCount) : 0.0;
    }

    double leftPeak = 0.0;
    double rightPeak = 0.0;
    double monoPeak = 0.0;
    double leftSquared = 0.0;
    double rightSquared = 0.0;
    double midSquared = 0.0;
    double sideSquared = 0.0;
    double sampleCount = 0.0;
};

double nonHarmonicSpectralEnergy (bool usePolyBlep,
                                  double sampleRate = 48000.0,
                                  int fundamentalBin = 40)
{
    constexpr int fftSize = 1024;
    const auto frequency = sampleRate * fundamentalBin / fftSize;
    std::array<double, fftSize> samples {};

    deeppluck::DeepPluckOscillator oscillator;
    oscillator.prepare (sampleRate);
    oscillator.setFrequency (frequency);
    auto naivePhase = 0.0;
    const auto increment = frequency / sampleRate;

    for (auto index = 0; index < fftSize; ++index)
    {
        samples[static_cast<std::size_t> (index)] = usePolyBlep
            ? oscillator.processSample()
            : naivePhase * 2.0 - 1.0;
        naivePhase += increment;
        naivePhase -= std::floor (naivePhase);
    }

    auto residualEnergy = 0.0;
    for (auto bin = 1; bin < fftSize / 2; ++bin)
    {
        if (bin % fundamentalBin == 0)
            continue;

        auto real = 0.0;
        auto imaginary = 0.0;
        for (auto sample = 0; sample < fftSize; ++sample)
        {
            const auto angle = -juce::MathConstants<double>::twoPi * bin * sample / fftSize;
            real += samples[static_cast<std::size_t> (sample)] * std::cos (angle);
            imaginary += samples[static_cast<std::size_t> (sample)] * std::sin (angle);
        }
        residualEnergy += real * real + imaginary * imaginary;
    }

    return residualEnergy;
}

struct WaveformSpectrum
{
    double fundamentalEnergy = 0.0;
    double unwantedEnergy = 0.0;
    double mean = 0.0;
    double peak = 0.0;
};

WaveformSpectrum measureWaveformSpectrum (
    deeppluck::DeepPluckOscillatorWaveform waveform, float pulseWidth,
    double sampleRate, int fundamentalBin, bool production)
{
    constexpr int fftSize = 1024;
    constexpr int warmupSamples = fftSize * 4;
    const auto frequency = sampleRate * fundamentalBin / fftSize;
    const auto increment = frequency / sampleRate;
    std::array<double, fftSize> samples {};
    deeppluck::DeepPluckOscillator oscillator;
    oscillator.prepare (sampleRate);
    oscillator.reset (0.0, waveform, pulseWidth);
    oscillator.setFrequency (frequency);
    auto naivePhase = 0.0;

    const auto naiveSample = [waveform, pulseWidth] (double phase)
    {
        switch (waveform)
        {
            case deeppluck::DeepPluckOscillatorWaveform::saw:
                return phase * 2.0 - 1.0;
            case deeppluck::DeepPluckOscillatorWaveform::triangle:
                return 1.0 - 4.0 * std::abs (phase - 0.5);
            case deeppluck::DeepPluckOscillatorWaveform::square:
            case deeppluck::DeepPluckOscillatorWaveform::pulse:
            {
                const auto width = waveform == deeppluck::DeepPluckOscillatorWaveform::square
                                     ? 0.5 : static_cast<double> (pulseWidth);
                auto value = phase < width ? 1.0 : -1.0;
                value -= 2.0 * width - 1.0;
                value /= 2.0 * juce::jmax (width, 1.0 - width);
                return 0.60 * value;
            }
            case deeppluck::DeepPluckOscillatorWaveform::sine:
                return 0.82 * std::sin (juce::MathConstants<double>::twoPi * phase);
        }
        return 0.0;
    };

    for (auto sample = 0; sample < warmupSamples + fftSize; ++sample)
    {
        const auto value = production ? oscillator.processSample() : naiveSample (naivePhase);
        naivePhase += increment;
        naivePhase -= std::floor (naivePhase);
        if (sample >= warmupSamples)
            samples[static_cast<std::size_t> (sample - warmupSamples)] = value;
    }

    WaveformSpectrum result;
    for (const auto value : samples)
    {
        result.mean += value;
        result.peak = juce::jmax (result.peak, std::abs (value));
    }
    result.mean /= fftSize;

    for (auto bin = 1; bin < fftSize / 2; ++bin)
    {
        auto real = 0.0;
        auto imaginary = 0.0;
        for (auto sample = 0; sample < fftSize; ++sample)
        {
            const auto angle = -juce::MathConstants<double>::twoPi * bin * sample / fftSize;
            real += samples[static_cast<std::size_t> (sample)] * std::cos (angle);
            imaginary += samples[static_cast<std::size_t> (sample)] * std::sin (angle);
        }
        const auto energy = real * real + imaginary * imaginary;
        if (bin == fundamentalBin)
            result.fundamentalEnergy = energy;

        const auto isHarmonic = bin % fundamentalBin == 0;
        const auto harmonicNumber = isHarmonic ? bin / fundamentalBin : 0;
        const auto isExpected = isHarmonic
            && (waveform == deeppluck::DeepPluckOscillatorWaveform::saw
                || waveform == deeppluck::DeepPluckOscillatorWaveform::pulse
                || harmonicNumber % 2 == 1);
        if (! isExpected)
            result.unwantedEnergy += energy;
    }

    return result;
}

double normalisedUnwantedEnergy (const WaveformSpectrum& spectrum)
{
    return spectrum.unwantedEnergy / juce::jmax (1.0e-30, spectrum.fundamentalEnergy);
}

int countActiveVoices (const deeppluck::DeepPluckSynthesiser& synth)
{
    auto count = 0;
    for (auto index = 0; index < synth.getNumVoices(); ++index)
        if (synth.getVoice (index)->isVoiceActive())
            ++count;
    return count;
}

void render (deeppluck::DeepPluckSynthesiser& synth, juce::AudioBuffer<float>& buffer,
             const juce::MidiBuffer& midi = {})
{
    buffer.clear();
    synth.renderNextBlock (buffer, midi, 0, buffer.getNumSamples());
}

double filterSineRms (float cutoffHz, float resonance, float driveDb,
                      double frequencyHz, double sampleRate = 48000.0)
{
    deeppluck::DeepPluckFilter filter;
    filter.prepare (sampleRate);
    filter.setCutoffFrequencyHz (cutoffHz);
    filter.setResonance (resonance);
    filter.setDriveDb (driveDb);
    filter.reset();

    constexpr int warmupSamples = 8192;
    constexpr int measurementSamples = 8192;
    auto phase = 0.0;
    const auto increment = frequencyHz / sampleRate;
    auto squaredSum = 0.0;

    for (auto sample = 0; sample < warmupSamples + measurementSamples; ++sample)
    {
        const auto input = static_cast<float> (0.2 * std::sin (
            juce::MathConstants<double>::twoPi * phase));
        phase += increment;
        phase -= std::floor (phase);
        const auto output = filter.processSample (input);
        if (sample >= warmupSamples)
            squaredSum += static_cast<double> (output) * output;
    }

    return std::sqrt (squaredSum / measurementSamples);
}

void testParameters (TestContext& test)
{
    DeepPluckAudioProcessor processor;
    auto& state = processor.getValueTreeState();

    enum class ParameterKind { floating, integer, boolean, choice };
    struct Expected
    {
        const char* id;
        const char* hostName;
        ParameterKind kind;
        float minimum;
        float maximum;
        float interval;
        float defaultValue;
        float midpoint;
        const char* choices;
    };

    constexpr auto noChoices = "";
    constexpr auto discreteMidpoint = -999.0f;
    constexpr std::array expected {
        Expected { deeppluck::ParameterIDs::outputGain, "Output", ParameterKind::floating, -60.0f, 6.0f, 0.1f, -3.0f, -27.0f, noChoices },
        Expected { deeppluck::ParameterIDs::masterTune, "Master Tune", ParameterKind::floating, 420.0f, 460.0f, 0.1f, 440.0f, 440.0f, noChoices },
        Expected { deeppluck::ParameterIDs::ampAttack, "Attack", ParameterKind::floating, 0.001f, 2.0f, 0.0f, 0.003f, 0.04472136f, noChoices },
        Expected { deeppluck::ParameterIDs::ampDecay, "Decay", ParameterKind::floating, 0.001f, 5.0f, 0.0f, 0.320f, 0.07071068f, noChoices },
        Expected { deeppluck::ParameterIDs::ampSustain, "Sustain", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.0f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::ampRelease, "Release", ParameterKind::floating, 0.001f, 10.0f, 0.0f, 0.180f, 0.1f, noChoices },
        Expected { deeppluck::ParameterIDs::filterEnabled, "Filter", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::filterCutoff, "Filter Cutoff", ParameterKind::floating, 20.0f, 20000.0f, 0.0f, 1800.0f, 632.45553f, noChoices },
        Expected { deeppluck::ParameterIDs::filterResonance, "Filter Resonance", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.18f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::filterDrive, "Filter Drive", ParameterKind::floating, 0.0f, 18.0f, 0.1f, 3.0f, 9.0f, noChoices },
        Expected { deeppluck::ParameterIDs::filterKeyTracking, "Filter Key Track", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.35f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::filterEnvAmount, "Filter Env Amount", ParameterKind::floating, -5.0f, 5.0f, 0.01f, 2.50f, 0.0f, noChoices },
        Expected { deeppluck::ParameterIDs::filterAttack, "Filter Attack", ParameterKind::floating, 0.001f, 2.0f, 0.0f, 0.002f, 0.04472136f, noChoices },
        Expected { deeppluck::ParameterIDs::filterDecay, "Filter Decay", ParameterKind::floating, 0.001f, 5.0f, 0.0f, 0.280f, 0.07071068f, noChoices },
        Expected { deeppluck::ParameterIDs::filterSustain, "Filter Sustain", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.0f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::filterRelease, "Filter Release", ParameterKind::floating, 0.001f, 10.0f, 0.0f, 0.180f, 0.1f, noChoices },
        Expected { deeppluck::ParameterIDs::transientEnabled, "Transient", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::transientLevel, "Transient Level", ParameterKind::floating, -60.0f, 0.0f, 0.1f, -18.0f, -30.0f, noChoices },
        Expected { deeppluck::ParameterIDs::transientDecay, "Transient Decay", ParameterKind::floating, 0.001f, 0.100f, 0.0f, 0.012f, 0.01f, noChoices },
        Expected { deeppluck::ParameterIDs::transientTone, "Transient Tone", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.55f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::noiseEnabled, "Noise", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::noiseLevel, "Noise Level", ParameterKind::floating, -60.0f, 0.0f, 0.1f, -27.0f, -30.0f, noChoices },
        Expected { deeppluck::ParameterIDs::noiseDecay, "Noise Decay", ParameterKind::floating, 0.005f, 1.500f, 0.0f, 0.120f, 0.08660254f, noChoices },
        Expected { deeppluck::ParameterIDs::noiseTone, "Noise Tone", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.35f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::osc2Enabled, "Oscillator 2", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::osc2Level, "Osc 2 Level", ParameterKind::floating, -60.0f, 0.0f, 0.1f, -15.0f, -30.0f, noChoices },
        Expected { deeppluck::ParameterIDs::osc2Octave, "Osc 2 Octave", ParameterKind::integer, -1.0f, 1.0f, 1.0f, 0.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::osc2Semitone, "Osc 2 Semitone", ParameterKind::integer, -12.0f, 12.0f, 1.0f, 0.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::osc2FineTune, "Osc 2 Fine Tune", ParameterKind::floating, -50.0f, 50.0f, 0.1f, -7.0f, 0.0f, noChoices },
        Expected { deeppluck::ParameterIDs::unisonVoices, "Unison Voices", ParameterKind::integer, 1.0f, 4.0f, 1.0f, 2.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::unisonDetune, "Unison Detune", ParameterKind::floating, 0.0f, 30.0f, 0.1f, 4.0f, 5.0f, noChoices },
        Expected { deeppluck::ParameterIDs::stereoSpread, "Stereo Spread", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.35f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::lfoRate, "LFO Rate", ParameterKind::floating, 0.020f, 12.0f, 0.001f, 0.25f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::lfoSync, "LFO Sync", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 0.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::lfoSyncDivision, "LFO Sync Division", ParameterKind::choice, 0.0f, 6.0f, 1.0f, 4.0f, discreteMidpoint, "4/1|2/1|1/1|1/2|1/4|1/8|1/16" },
        Expected { deeppluck::ParameterIDs::lfoWaveform, "LFO Waveform", ParameterKind::choice, 0.0f, 3.0f, 1.0f, 0.0f, discreteMidpoint, "Sine|Triangle|Saw|Square" },
        Expected { deeppluck::ParameterIDs::lfoRestartMode, "LFO Restart", ParameterKind::choice, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, "Free Running|Retrigger First Note" },
        Expected { deeppluck::ParameterIDs::lfoFilterAmount, "LFO Filter Amount", ParameterKind::floating, -2.0f, 2.0f, 0.01f, 0.0f, 0.0f, noChoices },
        Expected { deeppluck::ParameterIDs::lfoPitchAmount, "LFO Pitch Amount", ParameterKind::floating, -50.0f, 50.0f, 0.1f, 0.0f, 0.0f, noChoices },
        Expected { deeppluck::ParameterIDs::lfoSpreadAmount, "LFO Spread Amount", ParameterKind::floating, -1.0f, 1.0f, 0.001f, 0.0f, 0.0f, noChoices },
        Expected { deeppluck::ParameterIDs::osc1Waveform, "Osc 1 Waveform", ParameterKind::choice, 0.0f, 4.0f, 1.0f, 0.0f, discreteMidpoint, "Saw|Triangle|Square|Sine|Pulse" },
        Expected { deeppluck::ParameterIDs::osc1PulseWidth, "Osc 1 Pulse Width", ParameterKind::floating, 0.05f, 0.95f, 0.001f, 0.50f, 0.50f, noChoices },
        Expected { deeppluck::ParameterIDs::osc2Waveform, "Osc 2 Waveform", ParameterKind::choice, 0.0f, 4.0f, 1.0f, 0.0f, discreteMidpoint, "Saw|Triangle|Square|Sine|Pulse" },
        Expected { deeppluck::ParameterIDs::osc2PulseWidth, "Osc 2 Pulse Width", ParameterKind::floating, 0.05f, 0.95f, 0.001f, 0.50f, 0.50f, noChoices },
        Expected { deeppluck::ParameterIDs::chorusBypass, "Chorus Bypass", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::chorusRate, "Chorus Rate", ParameterKind::floating, 0.05f, 4.0f, 0.0f, 0.35f, 1.0f, noChoices },
        Expected { deeppluck::ParameterIDs::chorusDepth, "Chorus Depth", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.25f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::chorusWidth, "Chorus Width", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.65f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::chorusMix, "Chorus Mix", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.20f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::delayBypass, "Delay Bypass", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::delaySync, "Delay Sync", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::delayTime, "Delay Time", ParameterKind::floating, 0.010f, 2.0f, 0.0f, 0.375f, 0.14142136f, noChoices },
        Expected { deeppluck::ParameterIDs::delaySyncDivision, "Delay Sync Division", ParameterKind::choice, 0.0f, 6.0f, 1.0f, 1.0f, discreteMidpoint, "1/4|1/8 D|1/8|1/8 T|1/16 D|1/16|1/16 T" },
        Expected { deeppluck::ParameterIDs::delayFeedback, "Delay Feedback", ParameterKind::floating, 0.0f, 0.75f, 0.001f, 0.32f, 0.375f, noChoices },
        Expected { deeppluck::ParameterIDs::delayTone, "Delay Tone", ParameterKind::floating, 800.0f, 18000.0f, 0.0f, 6000.0f, 3794.73319f, noChoices },
        Expected { deeppluck::ParameterIDs::delayMix, "Delay Mix", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.22f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::reverbBypass, "Reverb Bypass", ParameterKind::boolean, 0.0f, 1.0f, 1.0f, 1.0f, discreteMidpoint, noChoices },
        Expected { deeppluck::ParameterIDs::reverbSize, "Reverb Size", ParameterKind::floating, 0.10f, 0.92f, 0.001f, 0.55f, 0.51f, noChoices },
        Expected { deeppluck::ParameterIDs::reverbPreDelay, "Reverb Pre-Delay", ParameterKind::floating, 0.0f, 0.120f, 0.0001f, 0.018f, 0.040f, noChoices },
        Expected { deeppluck::ParameterIDs::reverbDamping, "Reverb Damping", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.55f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::reverbMix, "Reverb Mix", ParameterKind::floating, 0.0f, 1.0f, 0.001f, 0.18f, 0.5f, noChoices },
        Expected { deeppluck::ParameterIDs::fxOrder, "FX Order", ParameterKind::choice, 0.0f, 5.0f, 1.0f, 0.0f, discreteMidpoint, "Chorus > Delay > Reverb|Chorus > Reverb > Delay|Delay > Chorus > Reverb|Delay > Reverb > Chorus|Reverb > Chorus > Delay|Reverb > Delay > Chorus" }
    };

    test.check (processor.getParameters().size() == static_cast<int> (expected.size()),
                "exactly sixty-two host parameters are exposed");

    const auto& hostParameters = processor.getParameters();
    for (std::size_t index = 0; index < expected.size(); ++index)
    {
        const auto* identified = dynamic_cast<const juce::AudioProcessorParameterWithID*> (
            hostParameters[static_cast<int> (index)]);
        test.check (identified != nullptr,
                    "every host parameter exposes a stable string ID");
        if (identified != nullptr)
        {
            test.check (identified->paramID == expected[index].id,
                        std::string ("host parameter order is append-only at index ")
                            + std::to_string (index));
            test.check (identified->getName (128) == expected[index].hostName,
                        std::string ("host parameter name is frozen at index ")
                            + std::to_string (index));
            test.check (identified->getVersionHint() == 1,
                        std::string ("parameter version hint remains stable at index ")
                            + std::to_string (index));
            test.check (identified->isAutomatable(),
                        std::string ("host parameter remains automatable: ")
                            + expected[index].id);
        }
    }

    for (const auto& item : expected)
    {
        auto* parameter = state.getParameter (item.id);
        test.check (parameter != nullptr, std::string ("parameter exists: ") + item.id);
        if (parameter == nullptr)
            continue;

        const auto& range = parameter->getNormalisableRange();
        const auto valueTolerance = item.maximum > 1000.0f ? 1.0e-3 : 1.0e-4;
        test.near (range.start, item.minimum, 1.0e-6, std::string (item.id) + " minimum");
        test.near (range.end, item.maximum, 1.0e-6, std::string (item.id) + " maximum");
        test.near (range.interval, item.interval, 1.0e-6,
                   std::string (item.id) + " interval");
        test.near (range.convertFrom0to1 (parameter->getDefaultValue()), item.defaultValue,
                   valueTolerance, std::string (item.id) + " default");

        const auto typeMatches = [&]
        {
            switch (item.kind)
            {
                case ParameterKind::floating:
                    return dynamic_cast<juce::AudioParameterFloat*> (parameter) != nullptr;
                case ParameterKind::integer:
                    return dynamic_cast<juce::AudioParameterInt*> (parameter) != nullptr;
                case ParameterKind::boolean:
                    return dynamic_cast<juce::AudioParameterBool*> (parameter) != nullptr;
                case ParameterKind::choice:
                    return dynamic_cast<juce::AudioParameterChoice*> (parameter) != nullptr;
            }
            return false;
        }();
        test.check (typeMatches, std::string (item.id) + " host type is frozen");

        if (item.midpoint != discreteMidpoint)
            test.near (range.convertFrom0to1 (0.5f), item.midpoint,
                       item.maximum > 1000.0f ? 0.02 : 2.0e-5,
                       std::string (item.id) + " midpoint/skew anchor");

        if (item.kind == ParameterKind::choice)
        {
            const auto* choice = dynamic_cast<juce::AudioParameterChoice*> (parameter);
            juce::StringArray frozenChoices;
            frozenChoices.addTokens (item.choices, "|", {});
            test.check (choice != nullptr && choice->choices == frozenChoices,
                        std::string (item.id) + " choice labels and order are frozen");
        }

        for (const auto value : { item.minimum, item.defaultValue, item.maximum })
            test.near (range.convertFrom0to1 (range.convertTo0to1 (value)), value,
                       valueTolerance, std::string (item.id) + " normalisation roundtrip");

        auto previous = range.convertFrom0to1 (0.0f);
        for (auto gridIndex = 1; gridIndex <= 100; ++gridIndex)
        {
            const auto raw = range.convertFrom0to1 (static_cast<float> (gridIndex) / 100.0f);
            test.check (std::isfinite (raw) && raw + valueTolerance >= previous,
                        std::string (item.id) + " dense normalized mapping is finite/monotonic");
            test.near (range.convertFrom0to1 (range.convertTo0to1 (raw)), raw,
                       valueTolerance, std::string (item.id) + " dense mapping roundtrip");
            previous = raw;
        }
    }

    struct TextCase { const char* id; float raw; const char* text; };
    constexpr std::array textCases {
        TextCase { deeppluck::ParameterIDs::ampAttack, 0.003f, "3 ms" },
        TextCase { deeppluck::ParameterIDs::ampDecay, 0.280f, "280 ms" },
        TextCase { deeppluck::ParameterIDs::filterRelease, 1.25f, "1.25 s" },
        TextCase { deeppluck::ParameterIDs::filterCutoff, 850.0f, "850 Hz" },
        TextCase { deeppluck::ParameterIDs::filterCutoff, 1800.0f, "1.80 kHz" },
        TextCase { deeppluck::ParameterIDs::delayTone, 6000.0f, "6.00 kHz" },
        TextCase { deeppluck::ParameterIDs::masterTune, 440.0f, "440.0 Hz" },
        TextCase { deeppluck::ParameterIDs::lfoRate, 0.25f, "0.25 Hz" },
        TextCase { deeppluck::ParameterIDs::chorusRate, 4.0f, "4.00 Hz" },
        TextCase { deeppluck::ParameterIDs::osc2Level, -15.0f, "-15.0 dB" },
        TextCase { deeppluck::ParameterIDs::filterDrive, 3.0f, "3.0 dB" },
        TextCase { deeppluck::ParameterIDs::filterResonance, 0.18f, "18 %" },
        TextCase { deeppluck::ParameterIDs::stereoSpread, 0.65f, "65 %" },
        TextCase { deeppluck::ParameterIDs::lfoSpreadAmount, 0.16f, "+16 %" },
        TextCase { deeppluck::ParameterIDs::filterEnvAmount, 2.50f, "+2.50 oct" },
        TextCase { deeppluck::ParameterIDs::osc2FineTune, -7.0f, "-7 ct" },
        TextCase { deeppluck::ParameterIDs::unisonDetune, 4.0f, "4.0 ct" },
        TextCase { deeppluck::ParameterIDs::osc2Semitone, 7.0f, "+7 st" },
        TextCase { deeppluck::ParameterIDs::osc2Octave, -1.0f, "-1 oct" },
        TextCase { deeppluck::ParameterIDs::unisonVoices, 2.0f, "2" },
        TextCase { deeppluck::ParameterIDs::delayTime, 0.375f, "0.375 s" },
        TextCase { deeppluck::ParameterIDs::reverbPreDelay, 0.018f, "18 ms" }
    };
    for (const auto& item : textCases)
    {
        auto* parameter = state.getParameter (item.id);
        test.check (parameter != nullptr, std::string (item.id) + " text parameter exists");
        if (parameter == nullptr)
            continue;

        const auto normalized = parameter->convertTo0to1 (item.raw);
        const auto hostText = parameter->getText (normalized, 64);
        test.check (hostText == item.text,
                    std::string (item.id) + " exact host text is " + item.text);
        test.check (deeppluck::formatParameterValue (item.id, item.raw) == item.text,
                    std::string (item.id) + " shared text formatter matches host text");
        const auto parsed = parameter->convertFrom0to1 (parameter->getValueForText (item.text));
        test.near (parsed, item.raw, item.raw > 1000.0f ? 0.02 : 2.0e-4,
                   std::string (item.id) + " host text parses back to raw value");
        test.check (parameter->getText (normalized, 4) == item.text,
                    std::string (item.id) + " short maxLength never breaks units");
    }

    test.near (deeppluck::parseParameterValue (deeppluck::ParameterIDs::ampDecay, "280ms"),
               0.280, 1.0e-6, "compact millisecond input parses to seconds");
    test.near (deeppluck::parseParameterValue (deeppluck::ParameterIDs::ampDecay, "0,28"),
               0.280, 1.0e-6, "unitless comma envelope input is raw seconds");
    test.near (deeppluck::parseParameterValue (deeppluck::ParameterIDs::filterCutoff,
                                               "1,80 kHz"),
               1800.0, 1.0e-3, "comma kHz input parses to raw Hz");
    test.near (deeppluck::parseParameterValue (deeppluck::ParameterIDs::reverbPreDelay,
                                               "0.018"),
               0.018, 1.0e-6, "unitless pre-delay input is raw seconds");
    test.near (deeppluck::parseParameterValue (deeppluck::ParameterIDs::filterResonance,
                                               "18 %"),
               0.18, 1.0e-6, "percentage input parses to normalized raw value");

    deeppluck::DeepPluckParameterCache cache (state);
    test.check (cache.isValid(), "complete parameter cache binds once");
    const auto defaults = cache.getSnapshot();
    test.near (defaults.masterTuneHz, 440.0, 1.0e-6, "cache reads master tune");
    test.near (defaults.outputGainDb, -3.0, 1.0e-6, "cache reads musical output default");
    test.near (defaults.ampReleaseSeconds, 0.180, 1.0e-6, "cache reads release");
    test.near (defaults.filterCutoffHz, 1800.0, 1.0e-3, "cache reads filter cutoff");
    test.near (defaults.filterEnvAmountOctaves, 2.50, 1.0e-6,
               "cache reads bipolar filter envelope amount");
    test.near (defaults.transientLevelDb, -18.0, 1.0e-6,
               "cache reads transient level default");
    test.near (defaults.noiseDecaySeconds, 0.120, 1.0e-6,
               "cache reads noise decay default");
    test.near (defaults.osc2Enabled, 1.0, 0.0,
               "cache reads fresh Oscillator 2 enabled default");
    test.near (defaults.osc2LevelDb, -15.0, 1.0e-6,
               "cache reads Oscillator 2 level default");
    test.near (defaults.osc2Octave, 0.0, 0.0,
               "cache reads Oscillator 2 octave default");
    test.near (defaults.osc2Semitone, 0.0, 0.0,
               "cache reads Oscillator 2 semitone default");
    test.near (defaults.osc2FineTuneCents, -7.0, 1.0e-6,
               "cache reads Oscillator 2 fine-tune default");
    test.near (defaults.unisonVoices, 2.0, 0.0,
               "cache reads fresh Unison Voices default");
    test.near (defaults.unisonDetuneCents, 4.0, 1.0e-6,
               "cache reads fresh Unison Detune default");
    test.near (defaults.stereoSpread, 0.35, 1.0e-6,
               "cache reads fresh Stereo Spread default");
    test.near (defaults.lfoRateHz, 0.25, 1.0e-6, "cache reads LFO rate default");
    test.near (defaults.lfoSync, 0.0, 0.0, "cache reads LFO sync default");
    test.near (defaults.lfoSyncDivision, 4.0, 0.0,
               "cache reads quarter-note sync division default");
    test.near (defaults.lfoWaveform, 0.0, 0.0, "cache reads sine waveform default");
    test.near (defaults.lfoRestartMode, 1.0, 0.0,
               "cache reads retrigger-first-note default");
    test.near (defaults.lfoFilterAmountOctaves, 0.0, 0.0,
               "cache reads neutral filter modulation default");
    test.near (defaults.lfoPitchAmountCents, 0.0, 0.0,
               "cache reads neutral pitch modulation default");
    test.near (defaults.lfoSpreadAmount, 0.0, 0.0,
                "cache reads neutral spread modulation default");
    test.near (defaults.osc1Waveform, 0.0, 0.0,
               "cache reads Oscillator 1 Saw default");
    test.near (defaults.osc1PulseWidth, 0.50, 0.0,
               "cache reads Oscillator 1 pulse-width default");
    test.near (defaults.osc2Waveform, 0.0, 0.0,
               "cache reads Oscillator 2 Saw default");
    test.near (defaults.osc2PulseWidth, 0.50, 0.0,
               "cache reads Oscillator 2 pulse-width default");
    test.near (defaults.chorusBypass, 1.0, 0.0, "cache reads neutral Chorus bypass default");
    test.near (defaults.chorusRateHz, 0.35, 1.0e-6, "cache reads Chorus rate default");
    test.near (defaults.chorusDepth, 0.25, 1.0e-6, "cache reads Chorus depth default");
    test.near (defaults.chorusWidth, 0.65, 1.0e-6, "cache reads Chorus width default");
    test.near (defaults.chorusMix, 0.20, 1.0e-6, "cache reads Chorus mix default");
    test.near (defaults.delayBypass, 1.0, 0.0, "cache reads neutral Delay bypass default");
    test.near (defaults.delaySync, 1.0, 0.0, "cache reads Delay sync default");
    test.near (defaults.delayTimeSeconds, 0.375, 1.0e-6, "cache reads Delay time default");
    test.near (defaults.delaySyncDivision, 1.0, 0.0, "cache reads Delay division default");
    test.near (defaults.delayFeedback, 0.32, 1.0e-6, "cache reads Delay feedback default");
    test.near (defaults.delayToneHz, 6000.0, 1.0e-3, "cache reads Delay tone default");
    test.near (defaults.delayMix, 0.22, 1.0e-6, "cache reads Delay mix default");
    test.near (defaults.reverbBypass, 1.0, 0.0, "cache reads neutral Reverb bypass default");
    test.near (defaults.reverbSize, 0.55, 1.0e-6, "cache reads Reverb size default");
    test.near (defaults.reverbPreDelaySeconds, 0.018, 1.0e-6, "cache reads Reverb pre-delay default");
    test.near (defaults.reverbDamping, 0.55, 1.0e-6, "cache reads Reverb damping default");
    test.near (defaults.reverbMix, 0.18, 1.0e-6, "cache reads Reverb mix default");
    test.near (defaults.fxOrder, 0.0, 0.0, "cache reads Chorus-Delay-Reverb order default");

    test.check (dynamic_cast<juce::AudioParameterBool*> (
                    state.getParameter (deeppluck::ParameterIDs::osc2Enabled)) != nullptr,
                "Oscillator 2 enabled uses a boolean host parameter");
    test.check (dynamic_cast<juce::AudioParameterInt*> (
                    state.getParameter (deeppluck::ParameterIDs::osc2Octave)) != nullptr,
                "Oscillator 2 octave uses an integer host parameter");
    test.check (dynamic_cast<juce::AudioParameterInt*> (
                    state.getParameter (deeppluck::ParameterIDs::osc2Semitone)) != nullptr,
                "Oscillator 2 semitone uses an integer host parameter");
    test.check (dynamic_cast<juce::AudioParameterInt*> (
                    state.getParameter (deeppluck::ParameterIDs::unisonVoices)) != nullptr,
                "Unison Voices uses an integer host parameter");
    test.check (dynamic_cast<juce::AudioParameterBool*> (
                    state.getParameter (deeppluck::ParameterIDs::lfoSync)) != nullptr,
                "LFO Sync uses a boolean host parameter");
    for (const auto* boolID : { deeppluck::ParameterIDs::chorusBypass,
                                deeppluck::ParameterIDs::delayBypass,
                                deeppluck::ParameterIDs::delaySync,
                                deeppluck::ParameterIDs::reverbBypass })
        test.check (dynamic_cast<juce::AudioParameterBool*> (state.getParameter (boolID)) != nullptr,
                    std::string (boolID) + " uses a boolean host parameter");
    for (const auto* choiceID : { deeppluck::ParameterIDs::lfoSyncDivision,
                                  deeppluck::ParameterIDs::lfoWaveform,
                                  deeppluck::ParameterIDs::lfoRestartMode,
                                  deeppluck::ParameterIDs::osc1Waveform,
                                  deeppluck::ParameterIDs::osc2Waveform,
                                  deeppluck::ParameterIDs::delaySyncDivision,
                                  deeppluck::ParameterIDs::fxOrder })
        test.check (dynamic_cast<juce::AudioParameterChoice*> (
                        state.getParameter (choiceID)) != nullptr,
                    std::string (choiceID) + " uses a stable choice parameter");

    const juce::StringArray expectedOscillatorChoices {
        "Saw", "Triangle", "Square", "Sine", "Pulse"
    };
    for (const auto* choiceID : { deeppluck::ParameterIDs::osc1Waveform,
                                  deeppluck::ParameterIDs::osc2Waveform })
    {
        const auto* choice = dynamic_cast<juce::AudioParameterChoice*> (
            state.getParameter (choiceID));
        test.check (choice != nullptr, std::string (choiceID) + " is a choice parameter");
        if (choice != nullptr)
            test.check (choice->choices == expectedOscillatorChoices,
                        std::string (choiceID) + " preserves Saw/Triangle/Square/Sine/Pulse order");
    }

    if (const auto* delayDivision = dynamic_cast<juce::AudioParameterChoice*> (
            state.getParameter (deeppluck::ParameterIDs::delaySyncDivision)))
        test.check (delayDivision->choices == juce::StringArray {
                        "1/4", "1/8 D", "1/8", "1/8 T", "1/16 D", "1/16", "1/16 T" },
                    "Delay sync division preserves the seven specified musical labels");
    if (const auto* order = dynamic_cast<juce::AudioParameterChoice*> (
            state.getParameter (deeppluck::ParameterIDs::fxOrder)))
        test.check (order->choices == juce::StringArray {
                        "Chorus > Delay > Reverb", "Chorus > Reverb > Delay",
                        "Delay > Chorus > Reverb", "Delay > Reverb > Chorus",
                        "Reverb > Chorus > Delay", "Reverb > Delay > Chorus" },
                    "FX Order exposes exactly the six stable rack permutations");

    MinimalProcessor minimal;
    juce::AudioProcessorValueTreeState::ParameterLayout incompleteLayout;
    incompleteLayout.add (std::make_unique<juce::AudioParameterFloat> (
        juce::ParameterID { deeppluck::ParameterIDs::outputGain, 1 }, "Output", -60.0f, 6.0f, 0.0f));
    juce::AudioProcessorValueTreeState incompleteState (minimal, nullptr, "Incomplete",
                                                         std::move (incompleteLayout));
    deeppluck::DeepPluckParameterCache incompleteCache (incompleteState);
    test.check (! incompleteCache.isValid(), "missing parameter IDs are reported without dereferencing null");
    const auto fallback = incompleteCache.getSnapshot();
    test.near (fallback.masterTuneHz, 440.0, 1.0e-6, "missing master tune uses controlled fallback");

    DeepPluckAudioProcessor invalidValueProcessor;
    auto& invalidValueState = invalidValueProcessor.getValueTreeState();
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc2Enabled)
        ->store (std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc2Level)
        ->store (std::numeric_limits<float>::infinity(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc2Octave)
        ->store (std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc2Semitone)
        ->store (-99.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc2FineTune)
        ->store (99.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::unisonVoices)
        ->store (99.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::unisonDetune)
        ->store (std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::stereoSpread)
        ->store (-1.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoRate)
        ->store (std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoSync)
        ->store (std::numeric_limits<float>::infinity(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoSyncDivision)
        ->store (99.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoWaveform)
        ->store (-9.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoRestartMode)
        ->store (std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoFilterAmount)
        ->store (9.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoPitchAmount)
        ->store (-99.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::lfoSpreadAmount)
        ->store (std::numeric_limits<float>::infinity(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc1Waveform)
        ->store (std::numeric_limits<float>::quiet_NaN(), std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc1PulseWidth)
        ->store (-1.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc2Waveform)
        ->store (99.0f, std::memory_order_relaxed);
    invalidValueState.getRawParameterValue (deeppluck::ParameterIDs::osc2PulseWidth)
        ->store (std::numeric_limits<float>::infinity(), std::memory_order_relaxed);
    const auto invalidFallback = deeppluck::DeepPluckParameterCache (
        invalidValueState).getSnapshot();
    test.near (invalidFallback.osc2Enabled, 1.0, 0.0,
               "non-finite Oscillator 2 enabled uses safe default");
    test.near (invalidFallback.osc2LevelDb, -15.0, 0.0,
               "non-finite Oscillator 2 level uses safe default");
    test.near (invalidFallback.osc2Octave, 0.0, 0.0,
               "non-finite Oscillator 2 octave uses safe default");
    test.near (invalidFallback.osc2Semitone, -12.0, 0.0,
               "Oscillator 2 semitone is rounded and clamped");
    test.near (invalidFallback.osc2FineTuneCents, 50.0, 0.0,
               "Oscillator 2 fine tune is clamped");
    test.near (invalidFallback.unisonVoices, 4.0, 0.0,
               "Unison Voices is rounded and clamped");
    test.near (invalidFallback.unisonDetuneCents, 4.0, 0.0,
               "non-finite Unison Detune uses safe default");
    test.near (invalidFallback.stereoSpread, 0.0, 0.0,
               "Stereo Spread is clamped");
    test.near (invalidFallback.lfoRateHz, 0.25, 0.0,
               "non-finite LFO Rate uses safe default");
    test.near (invalidFallback.lfoSync, 0.0, 0.0,
               "non-finite LFO Sync uses safe default");
    test.near (invalidFallback.lfoSyncDivision, 6.0, 0.0,
               "LFO Sync Division is rounded and clamped");
    test.near (invalidFallback.lfoWaveform, 0.0, 0.0,
               "LFO Waveform is rounded and clamped");
    test.near (invalidFallback.lfoRestartMode, 1.0, 0.0,
               "non-finite LFO Restart uses safe default");
    test.near (invalidFallback.lfoFilterAmountOctaves, 2.0, 0.0,
               "LFO Filter Amount is clamped");
    test.near (invalidFallback.lfoPitchAmountCents, -50.0, 0.0,
               "LFO Pitch Amount is clamped");
    test.near (invalidFallback.lfoSpreadAmount, 0.0, 0.0,
                "non-finite LFO Spread Amount uses safe default");
    test.near (invalidFallback.osc1Waveform, 0.0, 0.0,
               "non-finite Oscillator 1 waveform uses Saw");
    test.near (invalidFallback.osc1PulseWidth, 0.05, 1.0e-6,
               "Oscillator 1 pulse width is clamped");
    test.near (invalidFallback.osc2Waveform, 4.0, 0.0,
               "Oscillator 2 waveform is rounded and clamped");
    test.near (invalidFallback.osc2PulseWidth, 0.50, 0.0,
               "non-finite Oscillator 2 pulse width uses safe default");

    setParameter (state, deeppluck::ParameterIDs::outputGain, -12.5f);
    setParameter (state, deeppluck::ParameterIDs::masterTune, 446.0f);
    setParameter (state, deeppluck::ParameterIDs::ampAttack, 0.2f);
    setParameter (state, deeppluck::ParameterIDs::ampDecay, 1.25f);
    setParameter (state, deeppluck::ParameterIDs::ampSustain, 0.4f);
    setParameter (state, deeppluck::ParameterIDs::ampRelease, 2.0f);
    setParameter (state, deeppluck::ParameterIDs::filterEnabled, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::filterCutoff, 2750.0f);
    setParameter (state, deeppluck::ParameterIDs::filterResonance, 0.42f);
    setParameter (state, deeppluck::ParameterIDs::filterDrive, 7.5f);
    setParameter (state, deeppluck::ParameterIDs::filterKeyTracking, 0.65f);
    setParameter (state, deeppluck::ParameterIDs::filterEnvAmount, -1.75f);
    setParameter (state, deeppluck::ParameterIDs::filterAttack, 0.08f);
    setParameter (state, deeppluck::ParameterIDs::filterDecay, 0.9f);
    setParameter (state, deeppluck::ParameterIDs::filterSustain, 0.3f);
    setParameter (state, deeppluck::ParameterIDs::filterRelease, 1.2f);
    setParameter (state, deeppluck::ParameterIDs::transientEnabled, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::transientLevel, -9.0f);
    setParameter (state, deeppluck::ParameterIDs::transientDecay, 0.045f);
    setParameter (state, deeppluck::ParameterIDs::transientTone, 0.80f);
    setParameter (state, deeppluck::ParameterIDs::noiseEnabled, 1.0f);
    setParameter (state, deeppluck::ParameterIDs::noiseLevel, -21.0f);
    setParameter (state, deeppluck::ParameterIDs::noiseDecay, 0.650f);
    setParameter (state, deeppluck::ParameterIDs::noiseTone, 0.20f);
    setParameter (state, deeppluck::ParameterIDs::osc2Enabled, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::osc2Level, -9.0f);
    setParameter (state, deeppluck::ParameterIDs::osc2Octave, -1.0f);
    setParameter (state, deeppluck::ParameterIDs::osc2Semitone, 7.0f);
    setParameter (state, deeppluck::ParameterIDs::osc2FineTune, 12.5f);
    setParameter (state, deeppluck::ParameterIDs::unisonVoices, 4.0f);
    setParameter (state, deeppluck::ParameterIDs::unisonDetune, 18.5f);
    setParameter (state, deeppluck::ParameterIDs::stereoSpread, 0.72f);
    setParameter (state, deeppluck::ParameterIDs::lfoRate, 3.125f);
    setParameter (state, deeppluck::ParameterIDs::lfoSync, 1.0f);
    setParameter (state, deeppluck::ParameterIDs::lfoSyncDivision, 6.0f);
    setParameter (state, deeppluck::ParameterIDs::lfoWaveform, 2.0f);
    setParameter (state, deeppluck::ParameterIDs::lfoRestartMode, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::lfoFilterAmount, -1.25f);
    setParameter (state, deeppluck::ParameterIDs::lfoPitchAmount, 23.4f);
    setParameter (state, deeppluck::ParameterIDs::lfoSpreadAmount, -0.375f);
    setParameter (state, deeppluck::ParameterIDs::osc1Waveform, 4.0f);
    setParameter (state, deeppluck::ParameterIDs::osc1PulseWidth, 0.235f);
    setParameter (state, deeppluck::ParameterIDs::osc2Waveform, 1.0f);
    setParameter (state, deeppluck::ParameterIDs::osc2PulseWidth, 0.765f);
    setParameter (state, deeppluck::ParameterIDs::chorusBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::chorusRate, 1.25f);
    setParameter (state, deeppluck::ParameterIDs::chorusDepth, 0.61f);
    setParameter (state, deeppluck::ParameterIDs::chorusWidth, 0.82f);
    setParameter (state, deeppluck::ParameterIDs::chorusMix, 0.37f);
    setParameter (state, deeppluck::ParameterIDs::delayBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::delaySync, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::delayTime, 0.625f);
    setParameter (state, deeppluck::ParameterIDs::delaySyncDivision, 5.0f);
    setParameter (state, deeppluck::ParameterIDs::delayFeedback, 0.57f);
    setParameter (state, deeppluck::ParameterIDs::delayTone, 4800.0f);
    setParameter (state, deeppluck::ParameterIDs::delayMix, 0.41f);
    setParameter (state, deeppluck::ParameterIDs::reverbBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::reverbSize, 0.78f);
    setParameter (state, deeppluck::ParameterIDs::reverbPreDelay, 0.045f);
    setParameter (state, deeppluck::ParameterIDs::reverbDamping, 0.68f);
    setParameter (state, deeppluck::ParameterIDs::reverbMix, 0.36f);
    setParameter (state, deeppluck::ParameterIDs::fxOrder, 4.0f);
    const auto setSnapshot = cache.getSnapshot();
    test.near (setSnapshot.outputGainDb, -12.5, 0.11, "snapshot reads set output");
    test.near (setSnapshot.masterTuneHz, 446.0, 0.11, "snapshot reads set tune");
    test.near (setSnapshot.ampAttackSeconds, 0.2, 1.0e-3, "snapshot reads set attack");
    test.near (setSnapshot.ampDecaySeconds, 1.25, 1.0e-3, "snapshot reads set decay");
    test.near (setSnapshot.ampSustainLevel, 0.4, 1.0e-3, "snapshot reads set sustain");
    test.near (setSnapshot.ampReleaseSeconds, 2.0, 1.0e-3, "snapshot reads set release");
    test.near (setSnapshot.filterEnabled, 0.0, 1.0e-6, "snapshot reads filter bypass");
    test.near (setSnapshot.filterCutoffHz, 2750.0, 0.11, "snapshot reads filter cutoff");
    test.near (setSnapshot.filterResonance, 0.42, 1.0e-3, "snapshot reads resonance");
    test.near (setSnapshot.filterDriveDb, 7.5, 0.11, "snapshot reads filter drive");
    test.near (setSnapshot.filterKeyTracking, 0.65, 1.0e-3, "snapshot reads key tracking");
    test.near (setSnapshot.filterEnvAmountOctaves, -1.75, 0.011,
               "snapshot reads negative envelope amount");
    test.near (setSnapshot.filterReleaseSeconds, 1.2, 1.0e-3,
               "snapshot reads filter release");
    test.near (setSnapshot.transientEnabled, 0.0, 1.0e-6,
               "snapshot reads transient bypass");
    test.near (setSnapshot.transientDecaySeconds, 0.045, 1.0e-4,
               "snapshot reads transient decay");
    test.near (setSnapshot.noiseLevelDb, -21.0, 0.11,
               "snapshot reads noise level");
    test.near (setSnapshot.noiseTone, 0.20, 1.0e-3,
               "snapshot reads noise tone");
    test.near (setSnapshot.osc2Enabled, 0.0, 0.0,
               "snapshot reads Oscillator 2 bypass");
    test.near (setSnapshot.osc2LevelDb, -9.0, 0.11,
               "snapshot reads Oscillator 2 level");
    test.near (setSnapshot.osc2Octave, -1.0, 0.0,
               "snapshot reads Oscillator 2 octave");
    test.near (setSnapshot.osc2Semitone, 7.0, 0.0,
               "snapshot reads Oscillator 2 semitone");
    test.near (setSnapshot.osc2FineTuneCents, 12.5, 0.11,
               "snapshot reads Oscillator 2 fine tune");
    test.near (setSnapshot.unisonVoices, 4.0, 0.0,
               "snapshot reads Unison Voices");
    test.near (setSnapshot.unisonDetuneCents, 18.5, 0.11,
               "snapshot reads Unison Detune");
    test.near (setSnapshot.stereoSpread, 0.72, 1.1e-3,
               "snapshot reads Stereo Spread");
    test.near (setSnapshot.lfoRateHz, 3.125, 1.1e-3, "snapshot reads LFO Rate");
    test.near (setSnapshot.lfoSync, 1.0, 0.0, "snapshot reads LFO Sync");
    test.near (setSnapshot.lfoSyncDivision, 6.0, 0.0,
               "snapshot reads LFO Sync Division");
    test.near (setSnapshot.lfoWaveform, 2.0, 0.0, "snapshot reads LFO Waveform");
    test.near (setSnapshot.lfoRestartMode, 0.0, 0.0, "snapshot reads LFO Restart");
    test.near (setSnapshot.lfoFilterAmountOctaves, -1.25, 0.011,
               "snapshot reads LFO Filter Amount");
    test.near (setSnapshot.lfoPitchAmountCents, 23.4, 0.11,
               "snapshot reads LFO Pitch Amount");
    test.near (setSnapshot.lfoSpreadAmount, -0.375, 1.1e-3,
               "snapshot reads LFO Spread Amount");
    test.near (setSnapshot.osc1Waveform, 4.0, 0.0,
               "snapshot reads Oscillator 1 waveform");
    test.near (setSnapshot.osc1PulseWidth, 0.235, 1.1e-3,
               "snapshot reads Oscillator 1 pulse width");
    test.near (setSnapshot.osc2Waveform, 1.0, 0.0,
               "snapshot reads Oscillator 2 waveform");
    test.near (setSnapshot.osc2PulseWidth, 0.765, 1.1e-3,
               "snapshot reads Oscillator 2 pulse width");
    test.near (setSnapshot.chorusBypass, 0.0, 0.0, "snapshot reads Chorus active state");
    test.near (setSnapshot.chorusRateHz, 1.25, 1.1e-3, "snapshot reads Chorus rate");
    test.near (setSnapshot.chorusWidth, 0.82, 1.1e-3, "snapshot reads Chorus width");
    test.near (setSnapshot.delayBypass, 0.0, 0.0, "snapshot reads Delay active state");
    test.near (setSnapshot.delaySync, 0.0, 0.0, "snapshot reads Delay free mode");
    test.near (setSnapshot.delayTimeSeconds, 0.625, 1.1e-3, "snapshot reads Delay time");
    test.near (setSnapshot.delayFeedback, 0.57, 1.1e-3, "snapshot reads Delay feedback");
    test.near (setSnapshot.reverbBypass, 0.0, 0.0, "snapshot reads Reverb active state");
    test.near (setSnapshot.reverbPreDelaySeconds, 0.045, 1.1e-4,
               "snapshot reads Reverb pre-delay");
    test.near (setSnapshot.fxOrder, 4.0, 0.0, "snapshot reads FX order");
    juce::MemoryBlock binaryState;
    processor.getStateInformation (binaryState);

    DeepPluckAudioProcessor restored;
    restored.setStateInformation (binaryState.getData(), static_cast<int> (binaryState.getSize()));
    const deeppluck::DeepPluckParameterCache restoredCache (restored.getValueTreeState());
    const auto roundtrip = restoredCache.getSnapshot();
    test.near (roundtrip.outputGainDb, -12.5, 0.11, "state roundtrip restores output");
    test.near (roundtrip.masterTuneHz, 446.0, 0.11, "state roundtrip restores tune");
    test.near (roundtrip.ampAttackSeconds, 0.2, 1.0e-3, "state roundtrip restores ADSR");
    test.near (roundtrip.ampDecaySeconds, 1.25, 1.0e-3, "state roundtrip restores decay");
    test.near (roundtrip.ampSustainLevel, 0.4, 1.0e-3, "state roundtrip restores sustain");
    test.near (roundtrip.ampReleaseSeconds, 2.0, 1.0e-3, "state roundtrip restores release");
    test.near (roundtrip.filterEnabled, 0.0, 1.0e-6, "state roundtrip restores filter bypass");
    test.near (roundtrip.filterCutoffHz, 2750.0, 0.11,
               "state roundtrip restores filter cutoff");
    test.near (roundtrip.filterEnvAmountOctaves, -1.75, 0.011,
               "state roundtrip restores bipolar amount");
    test.near (roundtrip.filterReleaseSeconds, 1.2, 1.0e-3,
               "state roundtrip restores filter release");
    test.near (roundtrip.transientEnabled, 0.0, 1.0e-6,
               "state roundtrip restores transient bypass");
    test.near (roundtrip.transientDecaySeconds, 0.045, 1.0e-4,
               "state roundtrip restores transient decay");
    test.near (roundtrip.noiseLevelDb, -21.0, 0.11,
               "state roundtrip restores noise level");
    test.near (roundtrip.noiseTone, 0.20, 1.0e-3,
               "state roundtrip restores noise tone");
    test.near (roundtrip.osc2Enabled, 0.0, 0.0,
               "state roundtrip restores Oscillator 2 bypass");
    test.near (roundtrip.osc2LevelDb, -9.0, 0.11,
               "state roundtrip restores Oscillator 2 level");
    test.near (roundtrip.osc2Octave, -1.0, 0.0,
               "state roundtrip restores Oscillator 2 octave");
    test.near (roundtrip.osc2Semitone, 7.0, 0.0,
               "state roundtrip restores Oscillator 2 semitone");
    test.near (roundtrip.osc2FineTuneCents, 12.5, 0.11,
               "state roundtrip restores Oscillator 2 fine tune");
    test.near (roundtrip.unisonVoices, 4.0, 0.0,
               "state roundtrip restores Unison Voices");
    test.near (roundtrip.unisonDetuneCents, 18.5, 0.11,
               "state roundtrip restores Unison Detune");
    test.near (roundtrip.stereoSpread, 0.72, 1.1e-3,
               "state roundtrip restores Stereo Spread");
    test.near (roundtrip.lfoRateHz, 3.125, 1.1e-3, "state roundtrip restores LFO Rate");
    test.near (roundtrip.lfoSync, 1.0, 0.0, "state roundtrip restores LFO Sync");
    test.near (roundtrip.lfoSyncDivision, 6.0, 0.0,
               "state roundtrip restores LFO Sync Division");
    test.near (roundtrip.lfoWaveform, 2.0, 0.0,
               "state roundtrip restores LFO Waveform");
    test.near (roundtrip.lfoRestartMode, 0.0, 0.0,
               "state roundtrip restores LFO Restart");
    test.near (roundtrip.lfoFilterAmountOctaves, -1.25, 0.011,
               "state roundtrip restores LFO Filter Amount");
    test.near (roundtrip.lfoPitchAmountCents, 23.4, 0.11,
               "state roundtrip restores LFO Pitch Amount");
    test.near (roundtrip.lfoSpreadAmount, -0.375, 1.1e-3,
               "state roundtrip restores LFO Spread Amount");
    test.near (roundtrip.osc1Waveform, 4.0, 0.0,
               "state roundtrip restores Oscillator 1 waveform");
    test.near (roundtrip.osc1PulseWidth, 0.235, 1.1e-3,
               "state roundtrip restores Oscillator 1 pulse width");
    test.near (roundtrip.osc2Waveform, 1.0, 0.0,
               "state roundtrip restores Oscillator 2 waveform");
    test.near (roundtrip.osc2PulseWidth, 0.765, 1.1e-3,
               "state roundtrip restores Oscillator 2 pulse width");
    test.near (roundtrip.chorusRateHz, 1.25, 1.1e-3,
               "state roundtrip restores Chorus parameters");
    test.near (roundtrip.delayFeedback, 0.57, 1.1e-3,
               "state roundtrip restores Delay parameters");
    test.near (roundtrip.reverbPreDelaySeconds, 0.045, 1.1e-4,
               "state roundtrip restores Reverb parameters");
    test.near (roundtrip.fxOrder, 4.0, 0.0,
               "state roundtrip restores FX order");

    const auto checkV075Defaults = [&test] (
                                      const deeppluck::DeepPluckParameterSnapshot& snapshot,
                                      const char* version)
    {
        test.near (snapshot.osc1Waveform, 0.0, 0.0,
                   std::string (version) + " state receives Oscillator 1 Saw");
        test.near (snapshot.osc1PulseWidth, 0.50, 0.0,
                   std::string (version) + " state receives Oscillator 1 50% Pulse Width");
        test.near (snapshot.osc2Waveform, 0.0, 0.0,
                   std::string (version) + " state receives Oscillator 2 Saw");
        test.near (snapshot.osc2PulseWidth, 0.50, 0.0,
                   std::string (version) + " state receives Oscillator 2 50% Pulse Width");
    };

    const auto removeV09Parameters = [] (juce::ValueTree& legacyState)
    {
        for (auto index = legacyState.getNumChildren(); --index >= 0;)
            if (deeppluck::findParameterIndex (
                    legacyState.getChild (index)["id"].toString()) >= 44)
                legacyState.removeChild (index, nullptr);
    };

    auto oldState = state.copyState();
    removeV09Parameters (oldState);
    for (auto index = oldState.getNumChildren(); --index >= 0;)
    {
        const auto id = oldState.getChild (index)["id"].toString();
        if (id.startsWith ("filter") || id.startsWith ("transient") || id.startsWith ("noise")
            || id.startsWith ("osc1") || id.startsWith ("osc2")
            || id.startsWith ("unison") || id.startsWith ("lfo")
            || id == deeppluck::ParameterIDs::stereoSpread
            || id == deeppluck::ParameterIDs::ampAttack || id == deeppluck::ParameterIDs::ampDecay
            || id == deeppluck::ParameterIDs::ampSustain || id == deeppluck::ParameterIDs::ampRelease)
            oldState.removeChild (index, nullptr);
    }

    DeepPluckAudioProcessor migrated;
    const auto merged = deeppluck::mergeRestoredState (migrated.getValueTreeState().copyState(), oldState);
    test.check (merged.isValid(), "V0.1 state is accepted for migration");
    migrated.getValueTreeState().replaceState (merged);
    const auto migratedSnapshot = deeppluck::DeepPluckParameterCache (migrated.getValueTreeState()).getSnapshot();
    checkV075Defaults (migratedSnapshot, "V0.1");
    test.near (migratedSnapshot.outputGainDb, -12.5, 0.11, "V0.1 output value survives migration");
    test.near (migratedSnapshot.masterTuneHz, 446.0, 0.11, "V0.1 tune value survives migration");
    test.near (migratedSnapshot.ampAttackSeconds, 0.003, 1.0e-6, "new attack keeps default");
    test.near (migratedSnapshot.ampDecaySeconds, 0.320, 1.0e-6, "new decay keeps default");
    test.near (migratedSnapshot.ampSustainLevel, 0.0, 1.0e-6, "new sustain keeps default");
    test.near (migratedSnapshot.ampReleaseSeconds, 0.180, 1.0e-6, "new release keeps default");
    test.near (migratedSnapshot.filterCutoffHz, 1800.0, 1.0e-3,
               "V0.1 state receives filter cutoff default");
    test.near (migratedSnapshot.filterEnvAmountOctaves, 2.50, 1.0e-6,
               "V0.1 state receives filter envelope default");
    test.near (migratedSnapshot.transientEnabled, 0.0, 0.0,
               "V0.1 legacy state disables transient");
    test.near (migratedSnapshot.noiseEnabled, 0.0, 0.0,
               "V0.1 legacy state disables noise");
    test.near (migratedSnapshot.osc2Enabled, 0.0, 0.0,
               "V0.1 legacy state disables Oscillator 2");
    test.near (migratedSnapshot.osc2LevelDb, -15.0, 1.0e-6,
               "V0.1 state receives Oscillator 2 level default");
    test.near (migratedSnapshot.unisonVoices, 1.0, 0.0,
               "V0.1 state preserves the legacy single-oscillator-stack sound");
    test.near (migratedSnapshot.unisonDetuneCents, 0.0, 0.0,
               "V0.1 state receives zero legacy Unison Detune");
    test.near (migratedSnapshot.stereoSpread, 0.0, 0.0,
               "V0.1 state receives zero legacy Stereo Spread");

    auto v02State = state.copyState();
    removeV09Parameters (v02State);
    for (auto index = v02State.getNumChildren(); --index >= 0;)
    {
        const auto id = v02State.getChild (index)["id"].toString();
        if (id.startsWith ("filter") || id.startsWith ("transient") || id.startsWith ("noise")
            || id.startsWith ("osc1") || id.startsWith ("osc2")
            || id.startsWith ("unison") || id.startsWith ("lfo")
            || id == deeppluck::ParameterIDs::stereoSpread)
            v02State.removeChild (index, nullptr);
    }

    DeepPluckAudioProcessor migratedV02;
    const auto mergedV02 = deeppluck::mergeRestoredState (
        migratedV02.getValueTreeState().copyState(), v02State);
    test.check (mergedV02.isValid(), "V0.2 state is accepted for migration");
    migratedV02.getValueTreeState().replaceState (mergedV02);
    const auto migratedV02Snapshot = deeppluck::DeepPluckParameterCache (
        migratedV02.getValueTreeState()).getSnapshot();
    checkV075Defaults (migratedV02Snapshot, "V0.2");
    test.near (migratedV02Snapshot.ampAttackSeconds, 0.2, 1.0e-3,
               "V0.2 ADSR value survives migration");
    test.near (migratedV02Snapshot.ampReleaseSeconds, 2.0, 1.0e-3,
               "V0.2 Release value survives migration");
    test.near (migratedV02Snapshot.filterCutoffHz, 1800.0, 1.0e-3,
               "V0.2 state receives filter defaults only for missing parameters");
    test.near (migratedV02Snapshot.transientEnabled, 0.0, 0.0,
               "V0.2 legacy state disables transient");
    test.near (migratedV02Snapshot.noiseEnabled, 0.0, 0.0,
               "V0.2 legacy state disables noise");
    test.near (migratedV02Snapshot.osc2Enabled, 0.0, 0.0,
               "V0.2 legacy state disables Oscillator 2");
    test.near (migratedV02Snapshot.unisonVoices, 1.0, 0.0,
               "V0.2 legacy state migrates to one Unison Voice");
    test.near (migratedV02Snapshot.stereoSpread, 0.0, 0.0,
               "V0.2 legacy state remains mono-compatible");

    auto v03State = state.copyState();
    removeV09Parameters (v03State);
    for (auto index = v03State.getNumChildren(); --index >= 0;)
    {
        const auto id = v03State.getChild (index)["id"].toString();
        if (id.startsWith ("transient") || id.startsWith ("noise")
            || id.startsWith ("osc1") || id.startsWith ("osc2")
            || id.startsWith ("unison") || id.startsWith ("lfo")
            || id == deeppluck::ParameterIDs::stereoSpread)
            v03State.removeChild (index, nullptr);
    }

    DeepPluckAudioProcessor migratedV03;
    const auto mergedV03 = deeppluck::mergeRestoredState (
        migratedV03.getValueTreeState().copyState(), v03State);
    test.check (mergedV03.isValid(), "V0.3 state is accepted for migration");
    migratedV03.getValueTreeState().replaceState (mergedV03);
    const auto migratedV03Snapshot = deeppluck::DeepPluckParameterCache (
        migratedV03.getValueTreeState()).getSnapshot();
    checkV075Defaults (migratedV03Snapshot, "V0.3");
    test.near (migratedV03Snapshot.filterCutoffHz, 2750.0, 0.11,
               "V0.3 filter value survives migration");
    test.near (migratedV03Snapshot.transientEnabled, 0.0, 0.0,
               "V0.3 legacy state disables transient");
    test.near (migratedV03Snapshot.noiseEnabled, 0.0, 0.0,
               "V0.3 legacy state disables noise");
    test.near (migratedV03Snapshot.transientLevelDb, -18.0, 1.0e-6,
               "V0.3 state receives transient parameter defaults");
    test.near (migratedV03Snapshot.noiseDecaySeconds, 0.120, 1.0e-6,
               "V0.3 state receives noise parameter defaults");
    test.near (migratedV03Snapshot.osc2Enabled, 0.0, 0.0,
               "V0.3 legacy state disables Oscillator 2");
    test.near (migratedV03Snapshot.unisonVoices, 1.0, 0.0,
               "V0.3 legacy state migrates to one Unison Voice");

    auto partialV04State = state.copyState();
    removeV09Parameters (partialV04State);
    for (auto index = partialV04State.getNumChildren(); --index >= 0;)
    {
        const auto id = partialV04State.getChild (index)["id"].toString();
        const auto isV04 = id.startsWith ("transient") || id.startsWith ("noise");
        if (id.startsWith ("lfo"))
        {
            partialV04State.removeChild (index, nullptr);
            continue;
        }
        if (id.startsWith ("osc1") || id.startsWith ("osc2"))
        {
            partialV04State.removeChild (index, nullptr);
            continue;
        }
        if (id.startsWith ("unison") || id == deeppluck::ParameterIDs::stereoSpread)
        {
            partialV04State.removeChild (index, nullptr);
            continue;
        }
        if (isV04 && id != deeppluck::ParameterIDs::noiseTone)
            partialV04State.removeChild (index, nullptr);
    }

    DeepPluckAudioProcessor migratedPartialV04;
    auto& partialTargetState = migratedPartialV04.getValueTreeState();
    setParameter (partialTargetState, deeppluck::ParameterIDs::transientEnabled, 0.0f);
    setParameter (partialTargetState, deeppluck::ParameterIDs::transientLevel, -2.0f);
    setParameter (partialTargetState, deeppluck::ParameterIDs::noiseEnabled, 0.0f);
    setParameter (partialTargetState, deeppluck::ParameterIDs::noiseDecay, 1.4f);
    const auto mergedPartialV04 = deeppluck::mergeRestoredState (
        partialTargetState.copyState(), partialV04State);
    partialTargetState.replaceState (mergedPartialV04);
    const auto partialV04Snapshot = deeppluck::DeepPluckParameterCache (
        partialTargetState).getSnapshot();
    checkV075Defaults (partialV04Snapshot, "V0.4");
    test.near (partialV04Snapshot.noiseTone, 0.20, 1.0e-3,
               "present partial V0.4 value survives migration");
    test.near (partialV04Snapshot.transientEnabled, 1.0, 0.0,
               "partial V0.4 state receives fresh enabled default");
    test.near (partialV04Snapshot.transientLevelDb, -18.0, 1.0e-6,
               "partial V0.4 state does not inherit prior live level");
    test.near (partialV04Snapshot.noiseEnabled, 1.0, 0.0,
               "partial V0.4 state receives noise enabled default");
    test.near (partialV04Snapshot.noiseDecaySeconds, 0.120, 1.0e-6,
               "partial V0.4 state does not inherit prior live decay");
    test.near (partialV04Snapshot.osc2Enabled, 0.0, 0.0,
               "partial V0.4 state remains legacy for Oscillator 2");
    test.near (partialV04Snapshot.osc2FineTuneCents, -7.0, 1.0e-6,
               "partial V0.4 state receives safe Oscillator 2 numeric defaults");
    test.near (partialV04Snapshot.unisonVoices, 1.0, 0.0,
               "partial V0.4 state migrates to one Unison Voice");

    auto v04State = state.copyState();
    removeV09Parameters (v04State);
    for (auto index = v04State.getNumChildren(); --index >= 0;)
    {
        const auto id = v04State.getChild (index)["id"].toString();
        if (id.startsWith ("osc1") || id.startsWith ("osc2")
            || id.startsWith ("unison") || id.startsWith ("lfo")
            || id == deeppluck::ParameterIDs::stereoSpread)
            v04State.removeChild (index, nullptr);
    }

    DeepPluckAudioProcessor migratedV04;
    const auto mergedV04 = deeppluck::mergeRestoredState (
        migratedV04.getValueTreeState().copyState(), v04State);
    migratedV04.getValueTreeState().replaceState (mergedV04);
    const auto migratedV04Snapshot = deeppluck::DeepPluckParameterCache (
        migratedV04.getValueTreeState()).getSnapshot();
    test.near (migratedV04Snapshot.transientLevelDb, -9.0, 0.11,
               "V0.4 Transient value survives V0.5 migration");
    test.near (migratedV04Snapshot.noiseTone, 0.20, 1.0e-3,
               "V0.4 Noise value survives V0.5 migration");
    test.near (migratedV04Snapshot.osc2Enabled, 0.0, 0.0,
               "V0.4 legacy state disables Oscillator 2");
    test.near (migratedV04Snapshot.osc2LevelDb, -15.0, 1.0e-6,
               "V0.4 legacy state receives Oscillator 2 level default");
    test.near (migratedV04Snapshot.unisonVoices, 1.0, 0.0,
               "V0.4 legacy state migrates to one Unison Voice");

    auto partialV05State = state.copyState();
    removeV09Parameters (partialV05State);
    for (auto index = partialV05State.getNumChildren(); --index >= 0;)
    {
        const auto id = partialV05State.getChild (index)["id"].toString();
        if (id.startsWith ("lfo") || id.startsWith ("osc1"))
        {
            partialV05State.removeChild (index, nullptr);
            continue;
        }
        if (id.startsWith ("unison") || id == deeppluck::ParameterIDs::stereoSpread)
        {
            partialV05State.removeChild (index, nullptr);
            continue;
        }
        if (id.startsWith ("osc2") && id != deeppluck::ParameterIDs::osc2FineTune)
            partialV05State.removeChild (index, nullptr);
    }

    DeepPluckAudioProcessor migratedPartialV05;
    auto& partialV05Target = migratedPartialV05.getValueTreeState();
    setParameter (partialV05Target, deeppluck::ParameterIDs::osc2Enabled, 0.0f);
    setParameter (partialV05Target, deeppluck::ParameterIDs::osc2Level, -2.0f);
    setParameter (partialV05Target, deeppluck::ParameterIDs::osc2Octave, 1.0f);
    const auto mergedPartialV05 = deeppluck::mergeRestoredState (
        partialV05Target.copyState(), partialV05State);
    partialV05Target.replaceState (mergedPartialV05);
    const auto partialV05Snapshot = deeppluck::DeepPluckParameterCache (
        partialV05Target).getSnapshot();
    checkV075Defaults (partialV05Snapshot, "V0.5");
    test.near (partialV05Snapshot.osc2FineTuneCents, 12.5, 0.11,
               "present partial V0.5 value survives migration");
    test.near (partialV05Snapshot.osc2Enabled, 1.0, 0.0,
               "partial V0.5 state receives fresh enabled default");
    test.near (partialV05Snapshot.osc2LevelDb, -15.0, 1.0e-6,
               "partial V0.5 state does not inherit prior live level");
    test.near (partialV05Snapshot.osc2Octave, 0.0, 0.0,
               "partial V0.5 state receives octave default");
    test.near (partialV05Snapshot.osc2Semitone, 0.0, 0.0,
               "partial V0.5 state receives semitone default");
    test.near (partialV05Snapshot.unisonVoices, 1.0, 0.0,
               "partial V0.5 state migrates to one Unison Voice");
    test.near (partialV05Snapshot.unisonDetuneCents, 0.0, 0.0,
               "partial V0.5 state receives zero legacy Unison Detune");
    test.near (partialV05Snapshot.stereoSpread, 0.0, 0.0,
               "partial V0.5 state receives zero legacy Stereo Spread");

    auto partialV06State = state.copyState();
    removeV09Parameters (partialV06State);
    for (auto index = partialV06State.getNumChildren(); --index >= 0;)
    {
        const auto id = partialV06State.getChild (index)["id"].toString();
        if (id.startsWith ("lfo") || id.startsWith ("osc1")
            || id == deeppluck::ParameterIDs::osc2Waveform
            || id == deeppluck::ParameterIDs::osc2PulseWidth
            || id == deeppluck::ParameterIDs::unisonVoices
            || id == deeppluck::ParameterIDs::unisonDetune)
            partialV06State.removeChild (index, nullptr);
    }
    DeepPluckAudioProcessor migratedPartialV06;
    auto& partialV06Target = migratedPartialV06.getValueTreeState();
    setParameter (partialV06Target, deeppluck::ParameterIDs::unisonVoices, 4.0f);
    setParameter (partialV06Target, deeppluck::ParameterIDs::unisonDetune, 30.0f);
    const auto mergedPartialV06 = deeppluck::mergeRestoredState (
        partialV06Target.copyState(), partialV06State);
    partialV06Target.replaceState (mergedPartialV06);
    const auto partialV06Snapshot = deeppluck::DeepPluckParameterCache (
        partialV06Target).getSnapshot();
    checkV075Defaults (partialV06Snapshot, "V0.6");
    test.near (partialV06Snapshot.unisonVoices, 2.0, 0.0,
               "partial V0.6 state receives fresh Unison Voices default");
    test.near (partialV06Snapshot.unisonDetuneCents, 4.0, 0.0,
               "partial V0.6 state receives fresh Unison Detune default");
    test.near (partialV06Snapshot.stereoSpread, 0.72, 1.1e-3,
               "present partial V0.6 Stereo Spread value survives migration");
    test.near (partialV06Snapshot.lfoRateHz, 0.25, 0.0,
               "V0.6 state receives the safe LFO Rate default");
    test.near (partialV06Snapshot.lfoRestartMode, 1.0, 0.0,
               "V0.6 state receives retrigger-first-note default");
    test.near (partialV06Snapshot.lfoFilterAmountOctaves, 0.0, 0.0,
               "V0.6 state receives neutral LFO Filter Amount");
    test.near (partialV06Snapshot.lfoPitchAmountCents, 0.0, 0.0,
               "V0.6 state receives neutral LFO Pitch Amount");
    test.near (partialV06Snapshot.lfoSpreadAmount, 0.0, 0.0,
               "V0.6 state receives neutral LFO Spread Amount");

    auto partialV07State = state.copyState();
    removeV09Parameters (partialV07State);
    for (auto index = partialV07State.getNumChildren(); --index >= 0;)
    {
        const auto id = partialV07State.getChild (index)["id"].toString();
        if ((id.startsWith ("lfo") && id != deeppluck::ParameterIDs::lfoPitchAmount)
            || id.startsWith ("osc1")
            || id == deeppluck::ParameterIDs::osc2Waveform
            || id == deeppluck::ParameterIDs::osc2PulseWidth)
            partialV07State.removeChild (index, nullptr);
    }
    DeepPluckAudioProcessor migratedPartialV07;
    auto& partialV07Target = migratedPartialV07.getValueTreeState();
    setParameter (partialV07Target, deeppluck::ParameterIDs::lfoRate, 10.0f);
    setParameter (partialV07Target, deeppluck::ParameterIDs::lfoFilterAmount, 2.0f);
    setParameter (partialV07Target, deeppluck::ParameterIDs::lfoSpreadAmount, 1.0f);
    partialV07Target.replaceState (deeppluck::mergeRestoredState (
        partialV07Target.copyState(), partialV07State));
    const auto partialV07Snapshot = deeppluck::DeepPluckParameterCache (
        partialV07Target).getSnapshot();
    checkV075Defaults (partialV07Snapshot, "V0.7");
    test.near (partialV07Snapshot.lfoPitchAmountCents, 23.4, 0.11,
               "present partial V0.7 LFO value survives migration");
    test.near (partialV07Snapshot.lfoRateHz, 0.25, 0.0,
               "missing partial V0.7 rate receives declared default");
    test.near (partialV07Snapshot.lfoFilterAmountOctaves, 0.0, 0.0,
               "missing partial V0.7 filter amount is neutral");
    test.near (partialV07Snapshot.lfoSpreadAmount, 0.0, 0.0,
               "missing partial V0.7 spread amount is neutral");

    auto v08State = state.copyState();
    removeV09Parameters (v08State);
    DeepPluckAudioProcessor migratedV08;
    auto& v08Target = migratedV08.getValueTreeState();
    setParameter (v08Target, deeppluck::ParameterIDs::chorusBypass, 0.0f);
    setParameter (v08Target, deeppluck::ParameterIDs::delayBypass, 0.0f);
    setParameter (v08Target, deeppluck::ParameterIDs::reverbBypass, 0.0f);
    setParameter (v08Target, deeppluck::ParameterIDs::fxOrder, 5.0f);
    v08Target.replaceState (deeppluck::mergeRestoredState (v08Target.copyState(), v08State));
    const auto v08Snapshot = deeppluck::DeepPluckParameterCache (v08Target).getSnapshot();
    test.near (v08Snapshot.chorusBypass, 1.0, 0.0,
               "V0.8 state receives neutral Chorus bypass independent of live state");
    test.near (v08Snapshot.chorusRateHz, 0.35, 1.0e-6,
               "V0.8 state receives Chorus defaults");
    test.near (v08Snapshot.delayBypass, 1.0, 0.0,
               "V0.8 state receives neutral Delay bypass independent of live state");
    test.near (v08Snapshot.delaySyncDivision, 1.0, 0.0,
               "V0.8 state receives Delay defaults");
    test.near (v08Snapshot.reverbBypass, 1.0, 0.0,
               "V0.8 state receives neutral Reverb bypass independent of live state");
    test.near (v08Snapshot.reverbPreDelaySeconds, 0.018, 1.0e-6,
               "V0.8 state receives Reverb defaults");
    test.near (v08Snapshot.fxOrder, 0.0, 0.0,
               "V0.8 state receives canonical FX order independent of live state");

    auto partialV09State = state.copyState();
    for (auto index = partialV09State.getNumChildren(); --index >= 0;)
    {
        const auto id = partialV09State.getChild (index)["id"].toString();
        const auto parameterIndex = deeppluck::findParameterIndex (id);
        if (parameterIndex >= 44 && id != deeppluck::ParameterIDs::chorusRate)
            partialV09State.removeChild (index, nullptr);
    }
    DeepPluckAudioProcessor migratedPartialV09;
    auto& partialV09Target = migratedPartialV09.getValueTreeState();
    setParameter (partialV09Target, deeppluck::ParameterIDs::delayFeedback, 0.70f);
    setParameter (partialV09Target, deeppluck::ParameterIDs::reverbMix, 0.80f);
    partialV09Target.replaceState (deeppluck::mergeRestoredState (
        partialV09Target.copyState(), partialV09State));
    const auto partialV09Snapshot = deeppluck::DeepPluckParameterCache (
        partialV09Target).getSnapshot();
    test.near (partialV09Snapshot.chorusRateHz, 1.25, 1.1e-3,
               "present partial V0.9 value survives migration");
    test.near (partialV09Snapshot.delayFeedback, 0.32, 1.0e-6,
               "missing partial V0.9 Delay value uses declared default");
    test.near (partialV09Snapshot.reverbMix, 0.18, 1.0e-6,
               "missing partial V0.9 Reverb value does not inherit live state");
    test.near (partialV09Snapshot.fxOrder, 0.0, 0.0,
               "missing partial V0.9 order uses canonical default");

    const auto migratedAttackValue = [&] (const char* id, const juce::var& legacyValue)
    {
        auto legacyState = state.copyState();
        if (auto child = legacyState.getChildWithProperty ("id", id); child.isValid())
            child.setProperty ("value", legacyValue, nullptr);
        const auto mergedState = deeppluck::mergeRestoredState (state.copyState(), legacyState);
        const auto child = mergedState.getChildWithProperty ("id", id);
        return child.isValid() ? static_cast<double> (child["value"]) : -1.0;
    };
    test.near (migratedAttackValue (deeppluck::ParameterIDs::ampAttack, 1.25),
               1.25, 1.0e-9, "legacy Amp Attack inside the new range is unchanged");
    test.near (migratedAttackValue (deeppluck::ParameterIDs::ampAttack, 5.0),
               2.0, 1.0e-9, "legacy Amp Attack above two seconds clamps to two seconds");
    test.near (migratedAttackValue (deeppluck::ParameterIDs::filterAttack, -1.0),
               0.001, 1.0e-9, "legacy Filter Attack below one millisecond clamps safely");
    test.near (migratedAttackValue (deeppluck::ParameterIDs::ampAttack,
                                    std::numeric_limits<double>::quiet_NaN()),
               0.003, 1.0e-9, "non-finite legacy Amp Attack uses its declared default");
    test.near (migratedAttackValue (deeppluck::ParameterIDs::filterAttack,
                                    std::numeric_limits<double>::infinity()),
               0.002, 1.0e-9, "non-finite legacy Filter Attack uses its declared default");

    test.check (deeppluck::factoryPresets.size() == 4, "exactly four factory presets exist");
    test.check (deeppluck::findMatchingFactoryPreset (state) == -1,
                "manually edited parameter state is Custom");

    for (auto presetIndex = 0; presetIndex < static_cast<int> (deeppluck::factoryPresets.size());
         ++presetIndex)
    {
        deeppluck::applyFactoryPreset (state, presetIndex);
        test.check (deeppluck::findMatchingFactoryPreset (state) == presetIndex,
                    "factory preset values roundtrip through APVTS");
    }

    constexpr std::array expectedPresetUnison {
        std::array { 2.0f, 4.0f, 0.35f },
        std::array { 3.0f, 6.0f, 0.50f },
        std::array { 1.0f, 0.0f, 0.00f },
        std::array { 4.0f, 9.0f, 0.75f }
    };
    constexpr std::array expectedPresetModulation {
        std::array { 0.25f, 0.0f, 4.0f, 0.0f, 1.0f, 0.00f, 0.0f, 0.00f },
        std::array { 0.12f, 0.0f, 4.0f, 0.0f, 0.0f, 0.12f, 0.0f, 0.00f },
        std::array { 0.25f, 0.0f, 4.0f, 0.0f, 1.0f, 0.00f, 0.0f, 0.00f },
        std::array { 0.08f, 0.0f, 4.0f, 0.0f, 0.0f, 0.00f, 0.0f, 0.20f }
    };
    for (auto index = 0; index < static_cast<int> (deeppluck::factoryPresets.size()); ++index)
    {
        const auto& preset = deeppluck::factoryPresets[static_cast<std::size_t> (index)];
        const auto& expectedUnison = expectedPresetUnison[static_cast<std::size_t> (index)];
        test.near (preset.values.unisonVoices, expectedUnison[0], 0.0,
                   "factory preset has documented Unison Voice count");
        test.near (preset.values.unisonDetuneCents, expectedUnison[1], 0.0,
                   "factory preset has documented Unison Detune");
        test.near (preset.values.stereoSpread, expectedUnison[2], 0.0,
                   "factory preset has documented Stereo Spread");
        const auto& expectedModulation = expectedPresetModulation[static_cast<std::size_t> (index)];
        test.near (preset.values.lfoRateHz, expectedModulation[0], 0.0,
                   "factory preset has documented LFO Rate");
        test.near (preset.values.lfoSync, expectedModulation[1], 0.0,
                   "factory preset uses Free mode");
        test.near (preset.values.lfoSyncDivision, expectedModulation[2], 0.0,
                   "factory preset stores quarter-note division");
        test.near (preset.values.lfoWaveform, expectedModulation[3], 0.0,
                   "factory preset uses Sine");
        test.near (preset.values.lfoRestartMode, expectedModulation[4], 0.0,
                   "factory preset has documented restart mode");
        test.near (preset.values.lfoFilterAmountOctaves, expectedModulation[5], 0.0,
                   "factory preset has documented Filter Amount");
        test.near (preset.values.lfoPitchAmountCents, expectedModulation[6], 0.0,
                   "factory preset has no Pitch Amount");
        test.near (preset.values.lfoSpreadAmount, expectedModulation[7], 0.0,
                   "factory preset has documented Spread Amount");
        test.near (preset.values.osc1Waveform, 0.0, 0.0,
                   "factory preset preserves Oscillator 1 Saw");
        test.near (preset.values.osc1PulseWidth, 0.50, 0.0,
                   "factory preset stores Oscillator 1 pulse width default");
        test.near (preset.values.osc2Waveform, 0.0, 0.0,
                   "factory preset preserves Oscillator 2 Saw");
        test.near (preset.values.osc2PulseWidth, 0.50, 0.0,
                   "factory preset stores Oscillator 2 pulse width default");
        test.near (preset.values.chorusBypass, 1.0, 0.0,
                   "factory preset keeps Chorus neutral");
        test.near (preset.values.delayBypass, 1.0, 0.0,
                   "factory preset keeps Delay neutral");
        test.near (preset.values.reverbBypass, 1.0, 0.0,
                   "factory preset keeps Reverb neutral");
        test.near (preset.values.fxOrder, 0.0, 0.0,
                   "factory preset stores canonical FX order");
    }

    deeppluck::applyFactoryPreset (state, 0);
    setParameter (state, deeppluck::ParameterIDs::osc2FineTune, -6.9f);
    test.check (deeppluck::findMatchingFactoryPreset (state) == -1,
                "changing one Oscillator 2 value makes the preset Custom");
    deeppluck::applyFactoryPreset (state, 0);
    setParameter (state, deeppluck::ParameterIDs::osc1Waveform, 3.0f);
    test.check (deeppluck::findMatchingFactoryPreset (state) == -1,
                "changing one waveform value makes the preset Custom");

    DeepPluckAudioProcessor defaultProcessor;
    test.check (deeppluck::findMatchingFactoryPreset (defaultProcessor.getValueTreeState()) == 0,
                "Deep Pluck exactly matches the plugin defaults");
    const auto stateBeforeEditor = defaultProcessor.getValueTreeState().copyState();
    std::unique_ptr<juce::AudioProcessorEditor> editor (defaultProcessor.createEditor());
    test.check (editor != nullptr && editor->getWidth() == 1536 && editor->getHeight() == 900,
                "V0.10.4 refined editor constructs at its fixed Standalone-friendly size");
    test.check (defaultProcessor.getValueTreeState().copyState().isEquivalentTo (
                    stateBeforeEditor),
                "editor construction leaves all 62 parameter values unchanged");
}

void testOscillator (TestContext& test)
{
    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
    {
        deeppluck::DeepPluckOscillator oscillator;
        oscillator.prepare (sampleRate);
        oscillator.reset();
        oscillator.setFrequency (440.0);

        auto wraps = 0;
        auto previousPhase = oscillator.getPhase();
        for (auto sample = 0; sample < static_cast<int> (sampleRate); ++sample)
        {
            const auto value = oscillator.processSample();
            test.check (std::isfinite (value) && value >= -1.0f && value <= 1.0f,
                        "oscillator remains finite and bounded");
            const auto phase = oscillator.getPhase();
            if (phase < previousPhase)
                ++wraps;
            previousPhase = phase;
        }
        test.check (std::abs (wraps - 440) <= 1, "oscillator pitch is correct for one second");

        oscillator.reset (0.25);
        const auto first = oscillator.processSample();
        oscillator.reset (0.25);
        test.near (oscillator.processSample(), first, 1.0e-7, "oscillator reset is deterministic");

        oscillator.setFrequency (std::numeric_limits<double>::quiet_NaN());
        test.near (oscillator.getFrequency(), 0.0, 0.0, "invalid frequency becomes safe silence frequency");
    }

    deeppluck::DeepPluckOscillator oscillator;
    oscillator.prepare (48000.0);
    oscillator.setFrequency (440.0);
    for (auto i = 0; i < 97; ++i)
        static_cast<void> (oscillator.processSample());
    const auto phaseBeforeTune = oscillator.getPhase();
    oscillator.setFrequency (450.0);
    test.near (oscillator.getPhase(), phaseBeforeTune, 0.0, "frequency update does not reset phase");

    const auto naiveAliasEnergy = nonHarmonicSpectralEnergy (false);
    const auto polyBlepAliasEnergy = nonHarmonicSpectralEnergy (true);
    std::cout << "METRIC: PolyBLEP/non-harmonic-energy ratio = "
              << polyBlepAliasEnergy / naiveAliasEnergy << '\n';
    test.check (polyBlepAliasEnergy < naiveAliasEnergy * 0.65,
                "PolyBLEP substantially reduces non-harmonic alias energy");

    deeppluck::DeepPluckParameterSnapshot snapshot;
    deeppluck::DeepPluckSynthesiser synth;
    deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
    synth.setCurrentPlaybackSampleRate (48000.0);
    synth.noteOn (1, 69, 1.0f);
    auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (synth.getVoice (0));
    test.check (voice != nullptr, "configured voice has production type");
    if (voice != nullptr)
        test.near (voice->getCurrentFrequencyHz(), 440.0, 1.0e-6, "A4 uses master tune reference");
    synth.allNotesOff (0, false);
    synth.noteOn (1, 81, 1.0f);
    for (auto index = 0; index < synth.getNumVoices(); ++index)
        if (auto* current = dynamic_cast<deeppluck::DeepPluckVoice*> (synth.getVoice (index)); current->isVoiceActive())
            test.near (current->getCurrentFrequencyHz(), 880.0, 1.0e-6, "note frequency follows equal temperament");
}

void testOscillator2 (TestContext& test)
{
    const auto makeSnapshot = []
    {
        deeppluck::DeepPluckParameterSnapshot snapshot;
        snapshot.filterEnabled = 0.0f;
        snapshot.transientEnabled = 0.0f;
        snapshot.noiseEnabled = 0.0f;
        snapshot.ampAttackSeconds = 0.001f;
        snapshot.ampDecaySeconds = 0.001f;
        snapshot.ampSustainLevel = 1.0f;
        snapshot.osc2Enabled = 1.0f;
        snapshot.osc2LevelDb = 0.0f;
        snapshot.unisonVoices = 1.0f;
        snapshot.unisonDetuneCents = 0.0f;
        snapshot.stereoSpread = 0.0f;
        return snapshot;
    };

    struct PitchCase
    {
        float octave;
        float semitone;
        float fineTune;
    };

    constexpr std::array pitchCases {
        PitchCase { 0.0f, 0.0f, -7.0f },
        PitchCase { -1.0f, -12.0f, -50.0f },
        PitchCase { 1.0f, 12.0f, 50.0f },
        PitchCase { 0.0f, 7.0f, 12.5f }
    };

    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
        for (const auto pitch : pitchCases)
        {
            auto snapshot = makeSnapshot();
            snapshot.osc2Octave = pitch.octave;
            snapshot.osc2Semitone = pitch.semitone;
            snapshot.osc2FineTuneCents = pitch.fineTune;

            deeppluck::DeepPluckSynthesiser synth;
            deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
            synth.setCurrentPlaybackSampleRate (sampleRate);
            synth.noteOn (1, 69, 1.0f);

            auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (synth.getVoice (0));
            test.check (voice != nullptr, "Oscillator 2 uses a production voice");
            if (voice == nullptr)
                continue;

            const auto semitoneOffset = 12.0 * pitch.octave + pitch.semitone
                                        + pitch.fineTune * 0.01;
            const auto expectedFrequency = 440.0 * std::exp2 (semitoneOffset / 12.0);
            test.near (voice->getOscillatorPhase(), 0.0, 0.0,
                       "primary oscillator keeps its phase-zero note start");
            test.near (voice->getSecondOscillatorPhase(), 0.25, 0.0,
                       "Oscillator 2 uses the defined quarter-cycle note start");
            test.near (voice->getCurrentSecondOscillatorFrequencyHz(), expectedFrequency,
                       1.0e-6, "Oscillator 2 pitch offsets use equal temperament");

            juce::AudioBuffer<float> buffer (2, 257);
            render (synth, buffer);
            test.check (bufferIsFinite (buffer),
                        "Oscillator 2 remains finite across sample rates and pitch ranges");
            test.check (! juce::approximatelyEqual (voice->getSecondOscillatorPhase(), 0.25),
                        "Oscillator 2 phase advances after note start");
        }

    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
        for (const auto fundamentalBin : { 79, 173 })
        {
            const auto naiveEnergy = nonHarmonicSpectralEnergy (
                false, sampleRate, fundamentalBin);
            const auto polyBlepEnergy = nonHarmonicSpectralEnergy (
                true, sampleRate, fundamentalBin);
            std::cout << "METRIC: Oscillator 2 PolyBLEP/non-harmonic-energy ratio at "
                      << sampleRate << " Hz, bin " << fundamentalBin << " = "
                      << polyBlepEnergy / naiveEnergy << '\n';
            test.check (polyBlepEnergy < naiveEnergy * 0.80,
                        "Oscillator 2 PolyBLEP reduces high-frequency alias energy");
        }

    auto smoothingSnapshot = makeSnapshot();
    smoothingSnapshot.osc2FineTuneCents = 0.0f;
    deeppluck::DeepPluckSynthesiser smoothingSynth;
    deeppluck::configureDeepPluckSynthesiser (smoothingSynth, smoothingSnapshot);
    smoothingSynth.setCurrentPlaybackSampleRate (48000.0);
    smoothingSynth.noteOn (1, 69, 1.0f);
    auto* smoothingVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        smoothingSynth.getVoice (0));
    test.check (smoothingVoice != nullptr, "Oscillator 2 smoothing voice exists");

    if (smoothingVoice != nullptr)
    {
        smoothingSnapshot.osc2Enabled = 0.0f;
        smoothingSnapshot.osc2LevelDb = -60.0f;
        smoothingSnapshot.osc2Octave = 1.0f;

        juce::AudioBuffer<float> firstSample (2, 1);
        render (smoothingSynth, firstSample);
        test.check (smoothingVoice->getCurrentSecondOscillatorMix() > 0.0f
                        && smoothingVoice->getCurrentSecondOscillatorMix() < 1.0f,
                    "Oscillator 2 bypass starts a five-millisecond crossfade");
        test.check (smoothingVoice->getCurrentSecondOscillatorLevelGain() > 0.001f
                        && smoothingVoice->getCurrentSecondOscillatorLevelGain() < 1.0f,
                    "Oscillator 2 level starts a twenty-millisecond ramp");
        test.check (smoothingVoice->getCurrentSecondOscillatorFrequencyHz() > 440.0
                        && smoothingVoice->getCurrentSecondOscillatorFrequencyHz() < 880.0,
                    "Oscillator 2 pitch starts a multiplicative ramp");

        juce::AudioBuffer<float> bypassRemainder (2, 239);
        render (smoothingSynth, bypassRemainder);
        test.near (smoothingVoice->getCurrentSecondOscillatorMix(), 0.0, 1.0e-7,
                   "Oscillator 2 bypass crossfade reaches silence in five milliseconds");

        juce::AudioBuffer<float> smoothingRemainder (2, 720);
        render (smoothingSynth, smoothingRemainder);
        test.near (smoothingVoice->getCurrentSecondOscillatorLevelGain(), 0.001, 1.0e-6,
                   "Oscillator 2 level ramp reaches -60 dB in twenty milliseconds");
        test.near (smoothingVoice->getCurrentSecondOscillatorFrequencyHz(), 880.0, 1.0e-5,
                   "Oscillator 2 pitch ramp reaches its target in twenty milliseconds");

        const auto mutedPhase = smoothingVoice->getSecondOscillatorPhase();
        smoothingSnapshot.osc2Enabled = 1.0f;
        render (smoothingSynth, firstSample);
        test.check (smoothingVoice->getCurrentSecondOscillatorMix() > 0.0f
                        && smoothingVoice->getCurrentSecondOscillatorMix() < 0.01f,
                    "Oscillator 2 re-enable begins from the bypassed mix state");
        test.check (! juce::approximatelyEqual (mutedPhase,
                                                smoothingVoice->getSecondOscillatorPhase()),
                    "Oscillator 2 phase continues while bypassed and when re-enabled");
        test.check (! juce::approximatelyEqual (smoothingVoice->getSecondOscillatorPhase(),
                                                0.25),
                    "Oscillator 2 re-enable does not retrigger its phase");

        const auto phaseBeforeCombinedTune = smoothingVoice->getSecondOscillatorPhase();
        smoothingSnapshot.masterTuneHz = 450.0f;
        smoothingSnapshot.osc2Octave = 0.0f;
        smoothingSnapshot.osc2Semitone = -5.0f;
        smoothingSnapshot.osc2FineTuneCents = -50.0f;
        render (smoothingSynth, firstSample);
        test.check (! juce::approximatelyEqual (phaseBeforeCombinedTune,
                                                smoothingVoice->getSecondOscillatorPhase()),
                    "live Master Tune, Semitone, and Fine Tune preserve phase continuity");
        juce::AudioBuffer<float> combinedTuneRamp (2, 959);
        render (smoothingSynth, combinedTuneRamp);
        const auto expectedCombinedFrequency = 450.0 * std::exp2 (-5.5 / 12.0);
        test.near (smoothingVoice->getCurrentSecondOscillatorFrequencyHz(),
                   expectedCombinedFrequency, 1.0e-5,
                   "Master Tune and the -5 semitone musical interval combine correctly");
    }

    auto extremeSnapshot = makeSnapshot();
    extremeSnapshot.osc2Octave = 1.0f;
    extremeSnapshot.osc2Semitone = 12.0f;
    extremeSnapshot.osc2FineTuneCents = 50.0f;
    deeppluck::DeepPluckSynthesiser extremeSynth;
    deeppluck::configureDeepPluckSynthesiser (extremeSynth, extremeSnapshot);
    extremeSynth.setCurrentPlaybackSampleRate (44100.0);
    extremeSynth.noteOn (1, 127, 1.0f);
    juce::AudioBuffer<float> extremeBuffer (2, 2048);
    render (extremeSynth, extremeBuffer);
    test.check (bufferIsFinite (extremeBuffer),
                "Oscillator 2 stays finite above Nyquist at maximum transpose");
}

void testOscillator2Integration (TestContext& test)
{
    const auto makeSnapshot = []
    {
        deeppluck::DeepPluckParameterSnapshot snapshot;
        snapshot.filterEnabled = 0.0f;
        snapshot.transientEnabled = 0.0f;
        snapshot.noiseEnabled = 0.0f;
        snapshot.ampAttackSeconds = 0.001f;
        snapshot.ampDecaySeconds = 0.001f;
        snapshot.ampSustainLevel = 1.0f;
        snapshot.ampReleaseSeconds = 0.050f;
        snapshot.unisonVoices = 1.0f;
        snapshot.unisonDetuneCents = 0.0f;
        snapshot.stereoSpread = 0.0f;
        return snapshot;
    };

    auto bypassedA = makeSnapshot();
    bypassedA.osc2Enabled = 0.0f;
    auto bypassedB = bypassedA;
    bypassedB.osc2LevelDb = 0.0f;
    bypassedB.osc2Octave = 1.0f;
    bypassedB.osc2Semitone = 12.0f;
    bypassedB.osc2FineTuneCents = 50.0f;

    deeppluck::DeepPluckSynthesiser bypassSynthA;
    deeppluck::DeepPluckSynthesiser bypassSynthB;
    deeppluck::configureDeepPluckSynthesiser (bypassSynthA, bypassedA);
    deeppluck::configureDeepPluckSynthesiser (bypassSynthB, bypassedB);
    bypassSynthA.setCurrentPlaybackSampleRate (48000.0);
    bypassSynthB.setCurrentPlaybackSampleRate (48000.0);
    bypassSynthA.noteOn (1, 69, 1.0f);
    bypassSynthB.noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> bypassBufferA (2, 1024);
    juce::AudioBuffer<float> bypassBufferB (2, 1024);
    render (bypassSynthA, bypassBufferA);
    render (bypassSynthB, bypassBufferB);
    auto bypassDifference = 0.0f;
    for (auto sample = 0; sample < bypassBufferA.getNumSamples(); ++sample)
        bypassDifference = juce::jmax (
            bypassDifference,
            std::abs (bypassBufferA.getSample (0, sample)
                      - bypassBufferB.getSample (0, sample)));
    test.near (bypassDifference, 0.0, 0.0,
               "bypassed Oscillator 2 leaves the V0.4 signal path unchanged");

    auto activeSnapshot = makeSnapshot();
    activeSnapshot.osc2Enabled = 1.0f;
    activeSnapshot.osc2LevelDb = 0.0f;
    activeSnapshot.osc2FineTuneCents = -7.0f;
    deeppluck::DeepPluckSynthesiser activeSynth;
    deeppluck::configureDeepPluckSynthesiser (activeSynth, activeSnapshot);
    activeSynth.setCurrentPlaybackSampleRate (48000.0);
    activeSynth.noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> activeBuffer (2, 1024);
    render (activeSynth, activeBuffer);
    auto activeDifference = 0.0f;
    for (auto sample = 0; sample < activeBuffer.getNumSamples(); ++sample)
        activeDifference = juce::jmax (
            activeDifference,
            std::abs (activeBuffer.getSample (0, sample)
                      - bypassBufferA.getSample (0, sample)));
    test.check (activeDifference > 0.01f,
                "enabled Oscillator 2 contributes audibly to the voice sum");
    std::cout << "METRIC: Oscillator 2 level-0 difference peak = "
              << activeDifference << '\n';
    std::cout << "METRIC: both oscillators at Oscillator-2 level 0 peak = "
              << maximumMagnitude (activeBuffer, 0, activeBuffer.getNumSamples()) << '\n';

    auto filteredSnapshot = activeSnapshot;
    filteredSnapshot.filterEnabled = 1.0f;
    filteredSnapshot.filterCutoffHz = 500.0f;
    filteredSnapshot.filterResonance = 0.0f;
    filteredSnapshot.filterKeyTracking = 0.0f;
    filteredSnapshot.filterEnvAmountOctaves = 0.0f;
    deeppluck::DeepPluckSynthesiser filteredSynth;
    deeppluck::configureDeepPluckSynthesiser (filteredSynth, filteredSnapshot);
    filteredSynth.setCurrentPlaybackSampleRate (48000.0);
    filteredSynth.noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> filteredBuffer (2, 1024);
    render (filteredSynth, filteredBuffer);
    test.check (rms (filteredBuffer) < rms (activeBuffer) * 0.75f,
                "Oscillator 2 enters the shared filter before the voice output");

    auto slowAmpSnapshot = activeSnapshot;
    slowAmpSnapshot.ampAttackSeconds = 0.100f;
    deeppluck::DeepPluckSynthesiser slowAmpSynth;
    deeppluck::configureDeepPluckSynthesiser (slowAmpSynth, slowAmpSnapshot);
    slowAmpSynth.setCurrentPlaybackSampleRate (48000.0);
    slowAmpSynth.noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> slowAmpBuffer (2, 256);
    render (slowAmpSynth, slowAmpBuffer);
    test.check (rms (slowAmpBuffer) < rms (activeBuffer) * 0.30f,
                "Oscillator 2 follows the shared amp envelope");

    auto filterEnvelopeSnapshot = activeSnapshot;
    filterEnvelopeSnapshot.filterEnabled = 1.0f;
    filterEnvelopeSnapshot.filterCutoffHz = 1000.0f;
    filterEnvelopeSnapshot.filterKeyTracking = 0.0f;
    filterEnvelopeSnapshot.filterEnvAmountOctaves = 2.0f;
    filterEnvelopeSnapshot.filterAttackSeconds = 0.001f;
    filterEnvelopeSnapshot.filterDecaySeconds = 1.0f;
    filterEnvelopeSnapshot.filterSustainLevel = 1.0f;
    deeppluck::DeepPluckSynthesiser filterEnvelopeSynth;
    deeppluck::configureDeepPluckSynthesiser (filterEnvelopeSynth,
                                              filterEnvelopeSnapshot);
    filterEnvelopeSynth.setCurrentPlaybackSampleRate (48000.0);
    filterEnvelopeSynth.noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> filterEnvelopeBuffer (2, 256);
    render (filterEnvelopeSynth, filterEnvelopeBuffer);
    const auto* filterEnvelopeVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        filterEnvelopeSynth.getVoice (0));
    test.check (filterEnvelopeVoice != nullptr
                    && filterEnvelopeVoice->getCurrentFilterCutoffHz() > 1000.0f,
                "Oscillator 2 follows the shared Filter Envelope path");

    deeppluck::DeepPluckSynthesiser quietVelocitySynth;
    deeppluck::DeepPluckSynthesiser loudVelocitySynth;
    deeppluck::configureDeepPluckSynthesiser (quietVelocitySynth, activeSnapshot);
    deeppluck::configureDeepPluckSynthesiser (loudVelocitySynth, activeSnapshot);
    quietVelocitySynth.setCurrentPlaybackSampleRate (48000.0);
    loudVelocitySynth.setCurrentPlaybackSampleRate (48000.0);
    quietVelocitySynth.noteOn (1, 69, 0.25f);
    loudVelocitySynth.noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> quietVelocityBuffer (2, 512);
    juce::AudioBuffer<float> loudVelocityBuffer (2, 512);
    render (quietVelocitySynth, quietVelocityBuffer);
    render (loudVelocitySynth, loudVelocityBuffer);
    test.near (rms (quietVelocityBuffer) / rms (loudVelocityBuffer), 0.25, 1.0e-5,
               "velocity scales the complete two-oscillator voice sum");

    deeppluck::DeepPluckSynthesiser offsetSynth;
    deeppluck::configureDeepPluckSynthesiser (offsetSynth, activeSnapshot);
    offsetSynth.setCurrentPlaybackSampleRate (48000.0);
    juce::AudioBuffer<float> offsetBuffer (2, 128);
    juce::MidiBuffer offsetMidi;
    offsetMidi.addEvent (juce::MidiMessage::noteOn (1, 69, 1.0f), 73);
    render (offsetSynth, offsetBuffer, offsetMidi);
    test.near (maximumMagnitude (offsetBuffer, 0, 73), 0.0, 0.0,
               "Oscillator 2 note-on remains sample-accurate");
    test.check (maximumMagnitude (offsetBuffer, 73, 55) > 0.0f,
                "Oscillator 2 starts at the requested MIDI sample offset");

    auto deterministicSnapshot = activeSnapshot;
    deterministicSnapshot.transientEnabled = 1.0f;
    deterministicSnapshot.noiseEnabled = 1.0f;
    deeppluck::DeepPluckSynthesiser oneBlockSynth;
    deeppluck::DeepPluckSynthesiser partitionedSynth;
    deeppluck::configureDeepPluckSynthesiser (oneBlockSynth, deterministicSnapshot);
    deeppluck::configureDeepPluckSynthesiser (partitionedSynth, deterministicSnapshot);
    oneBlockSynth.setCurrentPlaybackSampleRate (48000.0);
    partitionedSynth.setCurrentPlaybackSampleRate (48000.0);
    oneBlockSynth.noteOn (1, 64, 0.8f);
    partitionedSynth.noteOn (1, 64, 0.8f);
    juce::AudioBuffer<float> oneBlockBuffer (2, 512);
    render (oneBlockSynth, oneBlockBuffer);
    std::vector<float> partitionedSamples;
    partitionedSamples.reserve (512);
    for (auto block = 0; block < 8; ++block)
    {
        juce::AudioBuffer<float> partition (2, 64);
        render (partitionedSynth, partition);
        for (auto sample = 0; sample < partition.getNumSamples(); ++sample)
            partitionedSamples.push_back (partition.getSample (0, sample));
    }
    auto partitionDifference = 0.0f;
    for (auto sample = 0; sample < oneBlockBuffer.getNumSamples(); ++sample)
        partitionDifference = juce::jmax (
            partitionDifference,
            std::abs (oneBlockBuffer.getSample (0, sample)
                      - partitionedSamples[static_cast<std::size_t> (sample)]));
    test.near (partitionDifference, 0.0, 1.0e-7,
               "Oscillator 2 rendering is deterministic across block partitions");
    std::cout << "METRIC: both oscillators plus Transient and Noise peak = "
              << maximumMagnitude (oneBlockBuffer, 0, oneBlockBuffer.getNumSamples())
              << '\n';

    for (const auto oscillator2Enabled : { 0.0f, 1.0f })
        for (const auto transientEnabled : { 0.0f, 1.0f })
            for (const auto noiseEnabled : { 0.0f, 1.0f })
                for (const auto filterEnabled : { 0.0f, 1.0f })
                {
                    auto combination = activeSnapshot;
                    combination.osc2Enabled = oscillator2Enabled;
                    combination.transientEnabled = transientEnabled;
                    combination.noiseEnabled = noiseEnabled;
                    combination.filterEnabled = filterEnabled;
                    deeppluck::DeepPluckSynthesiser combinationSynth;
                    deeppluck::configureDeepPluckSynthesiser (combinationSynth,
                                                              combination);
                    combinationSynth.setCurrentPlaybackSampleRate (48000.0);
                    combinationSynth.noteOn (1, 60, 1.0f);
                    juce::AudioBuffer<float> combinationBuffer (2, 512);
                    render (combinationSynth, combinationBuffer);
                    test.check (bufferIsFinite (combinationBuffer),
                                "every Oscillator 2, Transient, Noise, and Filter combination is finite");
                }

    auto lifecycleSnapshot = activeSnapshot;
    lifecycleSnapshot.ampReleaseSeconds = 0.005f;
    deeppluck::DeepPluckSynthesiser lifecycleSynth;
    deeppluck::configureDeepPluckSynthesiser (lifecycleSynth, lifecycleSnapshot);
    lifecycleSynth.setCurrentPlaybackSampleRate (48000.0);
    lifecycleSynth.noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> lifecycleBuffer (2, 128);
    render (lifecycleSynth, lifecycleBuffer);
    lifecycleSynth.noteOff (1, 69, 0.0f, true);
    for (auto block = 0; block < 4; ++block)
        render (lifecycleSynth, lifecycleBuffer);
    test.check (countActiveVoices (lifecycleSynth) == 0,
                "the shared Amp Release ends the complete two-oscillator voice");
    render (lifecycleSynth, lifecycleBuffer);
    test.near (maximumMagnitude (lifecycleBuffer, 0, lifecycleBuffer.getNumSamples()),
               0.0, 0.0, "Oscillator 2 is silent after voice lifecycle completion");
    lifecycleSynth.noteOn (1, 69, 1.0f);
    lifecycleSynth.allNotesOff (0, false);
    render (lifecycleSynth, lifecycleBuffer);
    test.near (maximumMagnitude (lifecycleBuffer, 0, lifecycleBuffer.getNumSamples()),
               0.0, 0.0, "Hard Stop clears audible Oscillator 2 state");

    auto stealingSnapshot = activeSnapshot;
    stealingSnapshot.ampReleaseSeconds = 0.500f;
    deeppluck::DeepPluckSynthesiser stealingSynth;
    deeppluck::configureDeepPluckSynthesiser (stealingSynth, stealingSnapshot);
    stealingSynth.setCurrentPlaybackSampleRate (48000.0);
    for (auto note = 60; note < 76; ++note)
        stealingSynth.noteOn (1, note, 1.0f);
    juce::AudioBuffer<float> stealingBuffer (2, 64);
    render (stealingSynth, stealingBuffer);
    stealingSynth.noteOff (1, 60, 0.0f, true);
    render (stealingSynth, stealingBuffer);
    stealingSynth.noteOn (1, 76, 1.0f);
    deeppluck::DeepPluckVoice* stolenVoice = nullptr;
    for (auto index = 0; index < stealingSynth.getNumVoices(); ++index)
        if (auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (
                stealingSynth.getVoice (index));
            voice->getCurrentlyPlayingNote() == 76)
            stolenVoice = voice;
    test.check (stolenVoice != nullptr && countActiveVoices (stealingSynth) == 16,
                "Oscillator 2 integrates with the fixed sixteen-voice stealing policy");
    if (stolenVoice != nullptr)
        test.near (stolenVoice->getSecondOscillatorPhase(), 0.25, 0.0,
                   "a stolen voice restarts Oscillator 2 at the defined phase");
    render (stealingSynth, stealingBuffer);
    test.check (bufferIsFinite (stealingBuffer),
                "Oscillator 2 voice stealing remains finite");
    std::cout << "METRIC: 16-voice Oscillator 2 steal peak = "
              << maximumMagnitude (stealingBuffer, 0, stealingBuffer.getNumSamples())
              << '\n';

    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
    {
        DeepPluckAudioProcessor headroomProcessor;
        auto& state = headroomProcessor.getValueTreeState();
        setParameter (state, deeppluck::ParameterIDs::ampAttack, 0.001f);
        setParameter (state, deeppluck::ParameterIDs::ampDecay, 0.001f);
        setParameter (state, deeppluck::ParameterIDs::ampSustain, 1.0f);
        headroomProcessor.prepareToPlay (sampleRate, 512);
        juce::AudioBuffer<float> headroomBuffer (2, 512);
        juce::MidiBuffer notes;
        for (auto note = 0; note < 16; ++note)
            notes.addEvent (juce::MidiMessage::noteOn (1, 48 + note, 1.0f), 0);
        headroomProcessor.processBlock (headroomBuffer, notes);
        auto peak = maximumMagnitude (headroomBuffer, 0, headroomBuffer.getNumSamples());
        juce::MidiBuffer empty;
        for (auto block = 0; block < 8; ++block)
        {
            headroomProcessor.processBlock (headroomBuffer, empty);
            peak = juce::jmax (peak,
                               maximumMagnitude (headroomBuffer, 0,
                                                 headroomBuffer.getNumSamples()));
        }
        std::cout << "METRIC: V0.5 default 16-voice peak at " << sampleRate
                  << " Hz = " << peak << '\n';
        test.check (bufferIsFinite (headroomBuffer),
                    "Oscillator 2 default chord remains finite across sample rates");
        test.check (peak <= 0.95f,
                    "Oscillator 2 default chord preserves headroom across sample rates");
    }
}

void testUnison (TestContext& test)
{
    constexpr std::array detuneTables {
        std::array { 0.0f, 0.0f, 0.0f, 0.0f },
        std::array { -0.5f, 0.5f, 0.0f, 0.0f },
        std::array { -1.0f, 0.0f, 1.0f, 0.0f },
        std::array { -1.0f, -0.6f, 0.6f, 1.0f }
    };
    constexpr std::array panTables {
        std::array { 0.0f, 0.0f, 0.0f, 0.0f },
        std::array { -1.0f, 1.0f, 0.0f, 0.0f },
        std::array { -1.0f, 0.0f, 1.0f, 0.0f },
        std::array { -1.0f, -1.0f / 3.0f, 1.0f / 3.0f, 1.0f }
    };
    constexpr std::array phaseTables {
        std::array { 0.0, 0.0, 0.0, 0.0 },
        std::array { -1.0 / 32.0, 1.0 / 32.0, 0.0, 0.0 },
        std::array { -1.0 / 12.0, 0.0, 1.0 / 12.0, 0.0 },
        std::array { -1.0 / 8.0, -1.0 / 24.0, 1.0 / 24.0, 1.0 / 8.0 }
    };

    std::array<double, deeppluck::maximumUnisonVoices> meanAbsoluteDetune {};
    std::array<double, deeppluck::maximumUnisonVoices> phaseSpan {};

    for (auto voiceCount = 1; voiceCount <= deeppluck::maximumUnisonVoices; ++voiceCount)
    {
        auto detuneSum = 0.0f;
        auto panSum = 0.0f;
        auto detuneMagnitudeSum = 0.0;
        auto minimumPhase = std::numeric_limits<double>::max();
        auto maximumPhase = std::numeric_limits<double>::lowest();
        for (auto index = 0; index < voiceCount; ++index)
        {
            test.near (deeppluck::DeepPluckUnison::detuneFactor (voiceCount, index),
                       detuneTables[static_cast<std::size_t> (voiceCount - 1)]
                                    [static_cast<std::size_t> (index)],
                       1.0e-7, "Unison Detune uses the specified symmetric table");
            test.near (deeppluck::DeepPluckUnison::panPosition (voiceCount, index),
                       panTables[static_cast<std::size_t> (voiceCount - 1)]
                                [static_cast<std::size_t> (index)],
                       1.0e-7, "Unison pan uses the specified symmetric table");
            test.near (deeppluck::DeepPluckUnison::phaseOffset (voiceCount, index),
                       phaseTables[static_cast<std::size_t> (voiceCount - 1)]
                                  [static_cast<std::size_t> (index)],
                       1.0e-12, "Unison start phase uses the specified deterministic table");
            detuneSum += deeppluck::DeepPluckUnison::detuneFactor (voiceCount, index);
            panSum += deeppluck::DeepPluckUnison::panPosition (voiceCount, index);
            detuneMagnitudeSum += std::abs (
                deeppluck::DeepPluckUnison::detuneFactor (voiceCount, index));
            minimumPhase = juce::jmin (minimumPhase,
                                       deeppluck::DeepPluckUnison::phaseOffset (
                                           voiceCount, index));
            maximumPhase = juce::jmax (maximumPhase,
                                       deeppluck::DeepPluckUnison::phaseOffset (
                                           voiceCount, index));
        }
        meanAbsoluteDetune[static_cast<std::size_t> (voiceCount - 1)]
            = detuneMagnitudeSum / static_cast<double> (voiceCount);
        phaseSpan[static_cast<std::size_t> (voiceCount - 1)] = maximumPhase - minimumPhase;
        test.near (detuneSum, 0.0, 1.0e-7, "Unison Detune remains centred");
        test.near (panSum, 0.0, 1.0e-7, "Unison pan remains centred");
        test.near (deeppluck::DeepPluckUnison::normalisationFor (voiceCount),
                   std::pow (static_cast<double> (voiceCount), -0.90), 1.0e-6,
                   "Unison normalization follows the bounded V0.10.4 N^-0.90 curve");
        const auto coherentGain = voiceCount
                                  * deeppluck::DeepPluckUnison::normalisationFor (voiceCount);
        test.check (coherentGain <= std::pow (4.0, 0.10) + 1.0e-6,
                    "coherent Unison gain grows only by the documented N^0.10");
        std::cout << "METRIC: Unison " << voiceCount
                  << " normalization="
                  << deeppluck::DeepPluckUnison::normalisationFor (voiceCount)
                  << ", coherent factor=" << coherentGain << '\n';

        deeppluck::DeepPluckUnison unison (0);
        unison.prepare (48000.0);
        unison.noteOn (60, voiceCount, 12.0f, 1.0f);
        test.check (unison.getActiveVoiceCount() == voiceCount,
                    "Unison count is latched at Note-On");
        for (auto index = 0; index < voiceCount; ++index)
        {
            const auto expectedPrimaryPhase = phaseTables[static_cast<std::size_t> (
                voiceCount - 1)][static_cast<std::size_t> (index)] < 0.0
                    ? 1.0 + phaseTables[static_cast<std::size_t> (voiceCount - 1)]
                                       [static_cast<std::size_t> (index)]
                    : phaseTables[static_cast<std::size_t> (voiceCount - 1)]
                                 [static_cast<std::size_t> (index)];
            test.near (unison.getPrimaryPhase (index), expectedPrimaryPhase, 1.0e-12,
                       "Oscillator 1 receives its deterministic Unison phase");
            auto phaseDifference = unison.getSecondPhase (index) - unison.getPrimaryPhase (index);
            if (phaseDifference < 0.0)
                phaseDifference += 1.0;
            test.near (phaseDifference, 0.25, 1.0e-12,
                       "Oscillator 2 preserves its quarter-cycle phase relation");

            const auto expectedRatio = std::exp2 (
                12.0 * deeppluck::DeepPluckUnison::detuneFactor (voiceCount, index)
                / 1200.0);
            test.near (unison.getCurrentDetuneRatio (index), expectedRatio, 1.0e-7,
                       "each Unison slot receives the exact cent ratio");
            test.near (std::pow (unison.getCurrentLeftGain (index), 2.0f)
                           + std::pow (unison.getCurrentRightGain (index), 2.0f),
                       2.0, 1.0e-6, "pan law preserves per-slot power");
        }
    }

    test.check (meanAbsoluteDetune[1] < meanAbsoluteDetune[2]
                    && meanAbsoluteDetune[2] < meanAbsoluteDetune[3],
                "two-, three-, and four-slot Unison voicings have progressively denser detune");
    test.check (phaseSpan[1] < phaseSpan[2] && phaseSpan[2] < phaseSpan[3],
                "two-, three-, and four-slot Unison voicings have progressively wider phase spans");

    deeppluck::DeepPluckUnison monoUnison (0);
    monoUnison.prepare (48000.0);
    monoUnison.noteOn (60, 4, 20.0f, 0.0f);
    for (auto index = 0; index < 4; ++index)
    {
        test.near (monoUnison.getCurrentLeftGain (index), 1.0, 0.0,
                   "zero Stereo Spread keeps the left gain centred");
        test.near (monoUnison.getCurrentRightGain (index), 1.0, 0.0,
                   "zero Stereo Spread keeps the right gain centred");
    }

    deeppluck::DeepPluckUnison smoothingUnison (0);
    smoothingUnison.prepare (48000.0);
    smoothingUnison.noteOn (60, 4, 0.0f, 0.0f);
    for (auto index = 0; index < 4; ++index)
        test.near (smoothingUnison.getCurrentDetuneRatio (index), 1.0, 0.0,
                   "zero Unison Detune creates no frequency deviation");
    const auto phaseBeforeAutomation = smoothingUnison.getPrimaryPhase (0);
    smoothingUnison.setTargets (30.0f, 1.0f);
    test.near (smoothingUnison.getPrimaryPhase (0), phaseBeforeAutomation, 0.0,
               "Detune and Spread automation never resets oscillator phase");
    static_cast<void> (smoothingUnison.processSample (440.0, 660.0, 0.5f, 1.0f));
    test.check (smoothingUnison.getCurrentDetuneRatio (0) < 1.0
                    && smoothingUnison.getCurrentDetuneRatio (0)
                         > std::exp2 (-30.0 / 1200.0),
                "Unison Detune starts a bounded twenty-millisecond ramp");
    test.check (smoothingUnison.getCurrentLeftGain (0) > 1.0f
                    && smoothingUnison.getCurrentLeftGain (0) < std::sqrt (2.0f),
                "Stereo Spread starts a bounded twenty-millisecond ramp");
    const auto currentRatio = smoothingUnison.getCurrentDetuneRatio (0);
    const auto currentLeftGain = smoothingUnison.getCurrentLeftGain (0);
    smoothingUnison.setTargets (12.0f, 0.5f);
    test.near (smoothingUnison.getCurrentDetuneRatio (0), currentRatio, 0.0,
               "repeated Detune automation continues from the current value");
    test.near (smoothingUnison.getCurrentLeftGain (0), currentLeftGain, 0.0,
               "repeated Spread automation continues from the current value");
    smoothingUnison.setTargets (30.0f, 1.0f);
    for (auto sample = 0; sample < 960; ++sample)
        static_cast<void> (smoothingUnison.processSample (440.0, 660.0, 0.5f, 1.0f));
    test.near (smoothingUnison.getCurrentDetuneRatio (0),
               std::exp2 (-30.0 / 1200.0), 1.0e-10,
               "Unison Detune reaches its target in twenty milliseconds");
    test.near (smoothingUnison.getCurrentLeftGain (0), std::sqrt (2.0), 1.0e-6,
               "Stereo Spread reaches its target in twenty milliseconds");
    smoothingUnison.setTargets (30.0f, 0.0f);
    for (auto sample = 0; sample < 960; ++sample)
        static_cast<void> (smoothingUnison.processSample (440.0, 660.0, 0.5f, 1.0f));
    for (auto index = 0; index < 4; ++index)
    {
        test.near (smoothingUnison.getCurrentLeftGain (index), 1.0, 1.0e-6,
                   "Spread return reaches exact centred left gain after settling");
        test.near (smoothingUnison.getCurrentRightGain (index), 1.0, 1.0e-6,
                   "Spread return reaches exact centred right gain after settling");
    }

    deeppluck::DeepPluckUnison deterministicA (3);
    deeppluck::DeepPluckUnison deterministicB (3);
    deterministicA.prepare (96000.0);
    deterministicB.prepare (96000.0);
    deterministicA.noteOn (67, 4, 9.0f, 0.75f);
    deterministicB.noteOn (67, 4, 9.0f, 0.75f);
    for (auto sample = 0; sample < 4096; ++sample)
    {
        const auto a = deterministicA.processSample (391.995, 587.993, 0.6f, 1.0f);
        const auto b = deterministicB.processSample (391.995, 587.993, 0.6f, 1.0f);
        test.near (a.left, b.left, 0.0, "Unison left output is deterministic");
        test.near (a.right, b.right, 0.0, "Unison right output is deterministic");
        test.check (std::isfinite (a.left) && std::isfinite (a.right),
                    "Unison output remains finite");
    }
}

void testUnisonIntegration (TestContext& test)
{
    const auto makeSnapshot = []
    {
        deeppluck::DeepPluckParameterSnapshot snapshot;
        snapshot.filterEnabled = 0.0f;
        snapshot.transientEnabled = 0.0f;
        snapshot.noiseEnabled = 0.0f;
        snapshot.ampAttackSeconds = 0.001f;
        snapshot.ampDecaySeconds = 0.001f;
        snapshot.ampSustainLevel = 1.0f;
        snapshot.ampReleaseSeconds = 0.050f;
        snapshot.osc2Enabled = 1.0f;
        snapshot.osc2LevelDb = -9.0f;
        return snapshot;
    };

    auto legacySnapshot = makeSnapshot();
    legacySnapshot.unisonVoices = 1.0f;
    legacySnapshot.unisonDetuneCents = 0.0f;
    legacySnapshot.stereoSpread = 0.0f;
    deeppluck::DeepPluckSynthesiser legacySynth;
    deeppluck::configureDeepPluckSynthesiser (legacySynth, legacySnapshot);
    legacySynth.setCurrentPlaybackSampleRate (48000.0);
    legacySynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> legacyBuffer (2, 2048);
    render (legacySynth, legacyBuffer);
    test.near (stereoDifferenceRms (legacyBuffer), 0.0, 0.0,
               "one Unison Voice with zero spread preserves exact mono output");
    test.check (bufferIsFinite (legacyBuffer), "legacy Unison bypass output remains finite");

    auto wideSnapshot = makeSnapshot();
    wideSnapshot.unisonVoices = 4.0f;
    wideSnapshot.unisonDetuneCents = 9.0f;
    wideSnapshot.stereoSpread = 0.75f;
    deeppluck::DeepPluckSynthesiser wideSynth;
    deeppluck::configureDeepPluckSynthesiser (wideSynth, wideSnapshot);
    wideSynth.setCurrentPlaybackSampleRate (48000.0);
    for (auto note : { 48, 55, 60, 64 })
        wideSynth.noteOn (1, note, 0.7f);
    juce::AudioBuffer<float> wideBuffer (2, 4096);
    render (wideSynth, wideBuffer);
    const auto sideRms = stereoDifferenceRms (wideBuffer);
    const auto leftRms = channelRms (wideBuffer, 0);
    const auto rightRms = channelRms (wideBuffer, 1);
    test.check (sideRms > 1.0e-4f, "multi-voice Unison produces real stereo width");
    test.check (leftRms > 0.0f && rightRms > 0.0f
                    && leftRms / rightRms > 0.60f && leftRms / rightRms < 1.67f,
                "deterministic orientation keeps aggregate stereo energy balanced");
    test.check (bufferIsFinite (wideBuffer), "wide Unison render remains finite");

    auto narrowSnapshot = wideSnapshot;
    narrowSnapshot.stereoSpread = 0.20f;
    deeppluck::DeepPluckSynthesiser narrowSynth;
    deeppluck::configureDeepPluckSynthesiser (narrowSynth, narrowSnapshot);
    narrowSynth.setCurrentPlaybackSampleRate (48000.0);
    for (auto note : { 48, 55, 60, 64 })
        narrowSynth.noteOn (1, note, 0.7f);
    juce::AudioBuffer<float> narrowBuffer (2, 4096);
    render (narrowSynth, narrowBuffer);
    test.check (sideRms > stereoDifferenceRms (narrowBuffer),
                "higher Stereo Spread measurably increases the Side signal");

    auto centredLayerSnapshot = wideSnapshot;
    centredLayerSnapshot.transientEnabled = 1.0f;
    centredLayerSnapshot.transientLevelDb = -6.0f;
    centredLayerSnapshot.noiseEnabled = 1.0f;
    centredLayerSnapshot.noiseLevelDb = -12.0f;
    deeppluck::DeepPluckSynthesiser dryLayerSynth;
    deeppluck::DeepPluckSynthesiser centredLayerSynth;
    deeppluck::configureDeepPluckSynthesiser (dryLayerSynth, wideSnapshot);
    deeppluck::configureDeepPluckSynthesiser (centredLayerSynth, centredLayerSnapshot);
    dryLayerSynth.setCurrentPlaybackSampleRate (48000.0);
    centredLayerSynth.setCurrentPlaybackSampleRate (48000.0);
    dryLayerSynth.noteOn (1, 60, 1.0f);
    centredLayerSynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> dryLayerBuffer (2, 512);
    juce::AudioBuffer<float> centredLayerBuffer (2, 512);
    render (dryLayerSynth, dryLayerBuffer);
    render (centredLayerSynth, centredLayerBuffer);
    auto layerStereoError = 0.0f;
    for (auto sample = 0; sample < dryLayerBuffer.getNumSamples(); ++sample)
    {
        const auto leftLayer = centredLayerBuffer.getSample (0, sample)
                               - dryLayerBuffer.getSample (0, sample);
        const auto rightLayer = centredLayerBuffer.getSample (1, sample)
                                - dryLayerBuffer.getSample (1, sample);
        layerStereoError = juce::jmax (layerStereoError, std::abs (leftLayer - rightLayer));
    }
    test.near (layerStereoError, 0.0, 1.0e-7,
               "Transient and Noise are added once as centred per-voice layers");

    auto filteredSnapshot = wideSnapshot;
    filteredSnapshot.filterEnabled = 1.0f;
    filteredSnapshot.filterCutoffHz = 700.0f;
    filteredSnapshot.filterResonance = 0.65f;
    filteredSnapshot.filterDriveDb = 9.0f;
    filteredSnapshot.filterKeyTracking = 0.0f;
    filteredSnapshot.filterEnvAmountOctaves = 0.0f;
    deeppluck::DeepPluckSynthesiser filteredSynth;
    deeppluck::configureDeepPluckSynthesiser (filteredSynth, filteredSnapshot);
    filteredSynth.setCurrentPlaybackSampleRate (48000.0);
    filteredSynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> filteredBuffer (2, 4096);
    render (filteredSynth, filteredBuffer);
    test.check (stereoDifferenceRms (filteredBuffer) > 1.0e-5f,
                "independent left and right filter states preserve stereo width");
    test.check (bufferIsFinite (filteredBuffer),
                "stereo filtering with resonance and drive remains finite");

    deeppluck::DeepPluckSynthesiser oneBlockSynth;
    deeppluck::DeepPluckSynthesiser partitionedSynth;
    deeppluck::configureDeepPluckSynthesiser (oneBlockSynth, wideSnapshot);
    deeppluck::configureDeepPluckSynthesiser (partitionedSynth, wideSnapshot);
    oneBlockSynth.setCurrentPlaybackSampleRate (48000.0);
    partitionedSynth.setCurrentPlaybackSampleRate (48000.0);
    oneBlockSynth.noteOn (1, 67, 0.8f);
    partitionedSynth.noteOn (1, 67, 0.8f);
    juce::AudioBuffer<float> oneBlockBuffer (2, 512);
    render (oneBlockSynth, oneBlockBuffer);
    juce::AudioBuffer<float> partitionedBuffer (2, 512);
    for (auto block = 0; block < 8; ++block)
    {
        juce::AudioBuffer<float> partition (2, 64);
        render (partitionedSynth, partition);
        for (auto channel = 0; channel < 2; ++channel)
            partitionedBuffer.copyFrom (channel, block * 64, partition, channel, 0, 64);
    }
    auto partitionDifference = 0.0f;
    for (auto channel = 0; channel < 2; ++channel)
        for (auto sample = 0; sample < 512; ++sample)
            partitionDifference = juce::jmax (
                partitionDifference,
                std::abs (oneBlockBuffer.getSample (channel, sample)
                          - partitionedBuffer.getSample (channel, sample)));
    test.near (partitionDifference, 0.0, 1.0e-7,
               "stereo Unison is deterministic across block partitions");

    auto latchingSnapshot = wideSnapshot;
    latchingSnapshot.unisonVoices = 2.0f;
    deeppluck::DeepPluckSynthesiser latchingSynth;
    deeppluck::configureDeepPluckSynthesiser (latchingSynth, latchingSnapshot);
    latchingSynth.setCurrentPlaybackSampleRate (48000.0);
    latchingSynth.noteOn (1, 60, 1.0f);
    auto* firstVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (latchingSynth.getVoice (0));
    test.check (firstVoice != nullptr && firstVoice->getLatchedUnisonVoiceCount() == 2,
                "held note starts with the current Unison Voice count");
    latchingSnapshot.unisonVoices = 4.0f;
    juce::AudioBuffer<float> latchingBuffer (2, 64);
    render (latchingSynth, latchingBuffer);
    test.check (firstVoice != nullptr && firstVoice->getLatchedUnisonVoiceCount() == 2,
                "Unison Voice count changes do not rewrite held notes");
    latchingSynth.noteOn (1, 62, 1.0f);
    auto foundNewCount = false;
    for (auto index = 0; index < latchingSynth.getNumVoices(); ++index)
        if (auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (
                latchingSynth.getVoice (index));
            voice->getCurrentlyPlayingNote() == 62)
            foundNewCount = voice->getLatchedUnisonVoiceCount() == 4;
    test.check (foundNewCount, "new notes use the updated Unison Voice count");

    auto stealingSnapshot = wideSnapshot;
    stealingSnapshot.unisonVoices = 2.0f;
    stealingSnapshot.unisonDetuneCents = 30.0f;
    stealingSnapshot.stereoSpread = 1.0f;
    stealingSnapshot.ampReleaseSeconds = 0.5f;
    deeppluck::DeepPluckSynthesiser stealingSynth;
    deeppluck::configureDeepPluckSynthesiser (stealingSynth, stealingSnapshot);
    stealingSynth.setCurrentPlaybackSampleRate (96000.0);
    for (auto note = 48; note < 64; ++note)
        stealingSynth.noteOn (1, note, 0.7f);
    juce::AudioBuffer<float> stealingBuffer (2, 256);
    render (stealingSynth, stealingBuffer);
    stealingSynth.noteOff (1, 48, 0.0f, true);
    render (stealingSynth, stealingBuffer);
    stealingSnapshot.unisonVoices = 4.0f;
    stealingSynth.noteOn (1, 80, 0.7f);
    auto stolenVoiceUsesCurrentCount = false;
    for (auto index = 0; index < stealingSynth.getNumVoices(); ++index)
        if (auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (
                stealingSynth.getVoice (index));
            voice->getCurrentlyPlayingNote() == 80)
            stolenVoiceUsesCurrentCount = voice->getLatchedUnisonVoiceCount() == 4;
    render (stealingSynth, stealingBuffer);
    test.check (countActiveVoices (stealingSynth) == 16,
                "Unison retains exactly sixteen musical voices during stealing");
    test.check (bufferIsFinite (stealingBuffer),
                "stereo residual-tail stealing remains finite");
    test.check (stolenVoiceUsesCurrentCount,
                "a stolen new note latches the current Unison Voice count");
    test.check (stereoDifferenceRms (stealingBuffer) > 0.0f,
                "stereo residual-tail stealing preserves stereo content");

    std::cout << "METRIC: V0.6 wide chord left RMS = " << leftRms
              << ", right RMS = " << rightRms
              << ", side-difference RMS = " << sideRms << '\n';
}

void testModulation (TestContext& test)
{
    using Waveform = deeppluck::DeepPluckLfoWaveform;
    constexpr std::array phases { 0.0, 0.25, 0.5, 0.75 };
    constexpr std::array sineValues { 0.0f, 1.0f, 0.0f, -1.0f };
    constexpr std::array triangleValues { -1.0f, 0.0f, 1.0f, 0.0f };
    constexpr std::array sawValues { -1.0f, -0.5f, 0.0f, 0.5f };
    constexpr std::array squareValues { 1.0f, 1.0f, -1.0f, -1.0f };
    const std::array expectedValues { sineValues, triangleValues, sawValues, squareValues };

    for (auto waveform = 0; waveform < 4; ++waveform)
        for (auto phaseIndex = 0; phaseIndex < static_cast<int> (phases.size()); ++phaseIndex)
            test.near (deeppluck::DeepPluckLfo::waveformValue (
                           static_cast<Waveform> (waveform), phases[static_cast<std::size_t> (phaseIndex)]),
                       expectedValues[static_cast<std::size_t> (waveform)]
                                     [static_cast<std::size_t> (phaseIndex)],
                       1.0e-6, "LFO waveform follows its declared formula");

    for (auto waveform = 0; waveform < 4; ++waveform)
        for (auto phaseIndex = -200; phaseIndex <= 200; ++phaseIndex)
        {
            const auto value = deeppluck::DeepPluckLfo::waveformValue (
                static_cast<Waveform> (waveform), phaseIndex * 0.037);
            test.check (std::isfinite (value) && value >= -1.0f && value <= 1.0f,
                        "all raw LFO waveforms remain finite and bounded");
        }

    constexpr std::array beats { 16.0, 8.0, 4.0, 2.0, 1.0, 0.5, 0.25 };
    for (auto division = 0; division < static_cast<int> (beats.size()); ++division)
    {
        test.near (deeppluck::DeepPluckLfo::beatsPerCycle (division),
                   beats[static_cast<std::size_t> (division)], 0.0,
                   "LFO division table is stable and append-only");
        for (const auto tempo : { 20.0, 120.0, 400.0 })
            test.near (deeppluck::DeepPluckLfo::synchronisedRateHz (tempo, division),
                       tempo / (60.0 * beats[static_cast<std::size_t> (division)]),
                       1.0e-12, "tempo-sync rate follows BPM/(60*beatsPerCycle)");
    }

    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
    {
        for (const auto freeRate : { 0.020f, 0.25f, 12.0f })
        {
            deeppluck::DeepPluckLfo lfo;
            lfo.prepare (sampleRate);
            lfo.setParameters ({ freeRate, 0.0f, 4.0f, 0.0f, 1.0f, 120.0 });
            test.near (lfo.getTargetRateHz(), freeRate, 1.0e-6,
                       "Free LFO reaches its bounded requested target");
            for (auto sample = 0; sample < static_cast<int> (sampleRate * 0.025); ++sample)
            {
                const auto output = lfo.processSample();
                test.check (std::isfinite (output) && output >= -1.0f && output <= 1.0f,
                            "dezippered LFO output remains finite and bounded");
                test.check (std::isfinite (lfo.getPhase()) && lfo.getPhase() >= 0.0
                                && lfo.getPhase() < 1.0,
                            "LFO phase remains finite in [0,1)");
            }
            test.near (lfo.getCurrentRateHz(), freeRate, 1.0e-6,
                       "20 ms Free Rate smoothing reaches the exact target");
        }
    }

    deeppluck::DeepPluckLfo syncLfo;
    syncLfo.prepare (48000.0);
    for (const auto tempo : { 20.0, 120.0, 400.0 })
        for (auto division = 0; division < 7; ++division)
        {
            syncLfo.setParameters ({ 0.25f, 1.0f, static_cast<float> (division),
                                     0.0f, 1.0f, tempo });
            test.near (syncLfo.getTargetRateHz(),
                       deeppluck::DeepPluckLfo::synchronisedRateHz (tempo, division),
                       1.0e-9, "sync target supports every division and valid host tempo");
        }

    deeppluck::DeepPluckLfo phaseLfo;
    phaseLfo.prepare (48000.0);
    phaseLfo.setParameters ({ 2.0f, 0.0f, 4.0f, 0.0f, 1.0f, 120.0 });
    for (auto sample = 0; sample < 777; ++sample)
        static_cast<void> (phaseLfo.processSample());
    const auto phaseBeforeParameterChange = phaseLfo.getPhase();
    phaseLfo.setParameters ({ 7.0f, 0.0f, 4.0f, 3.0f, 0.0f, 120.0 });
    test.near (phaseLfo.getPhase(), phaseBeforeParameterChange, 0.0,
               "rate, waveform and restart-mode edits preserve phase");
    phaseLfo.restart();
    test.near (phaseLfo.getPhase(), 0.0, 0.0, "explicit restart resets phase exactly");

    deeppluck::DeepPluckLfo first;
    deeppluck::DeepPluckLfo second;
    first.prepare (96000.0);
    second.prepare (96000.0);
    first.setParameters ({ 11.75f, 0.0f, 6.0f, 2.0f, 1.0f, 120.0 });
    second.setParameters ({ 11.75f, 0.0f, 6.0f, 2.0f, 1.0f, 120.0 });
    for (auto sample = 0; sample < 10000; ++sample)
        test.near (first.processSample(), second.processSample(), 0.0,
                   "global LFO is sample-deterministic");
    test.near (first.getPhase(), second.getPhase(), 0.0,
               "deterministic LFOs retain identical phase");
}

void testModulationIntegration (TestContext& test)
{
    const auto configuredSynth = [] (deeppluck::DeepPluckParameterSnapshot& snapshot,
                                      double sampleRate)
    {
        auto synth = std::make_unique<deeppluck::DeepPluckSynthesiser>();
        deeppluck::configureDeepPluckSynthesiser (*synth, snapshot);
        synth->setCurrentPlaybackSampleRate (sampleRate);
        return synth;
    };

    deeppluck::DeepPluckParameterSnapshot mappingSnapshot;
    mappingSnapshot.transientEnabled = 0.0f;
    mappingSnapshot.noiseEnabled = 0.0f;
    mappingSnapshot.filterCutoffHz = 1000.0f;
    mappingSnapshot.filterKeyTracking = 0.0f;
    mappingSnapshot.filterEnvAmountOctaves = 0.0f;
    mappingSnapshot.stereoSpread = 0.5f;
    mappingSnapshot.lfoWaveform = 3.0f;
    mappingSnapshot.lfoRestartMode = 1.0f;
    mappingSnapshot.lfoFilterAmountOctaves = 1.0f;
    mappingSnapshot.lfoPitchAmountCents = 50.0f;
    mappingSnapshot.lfoSpreadAmount = 1.0f;
    auto mappingSynth = configuredSynth (mappingSnapshot, 48000.0);
    mappingSynth->noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> oneSample (2, 1);
    render (*mappingSynth, oneSample);
    auto* mappingVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (mappingSynth->getVoice (0));
    test.check (mappingVoice != nullptr, "modulation mapping uses production voice");
    if (mappingVoice != nullptr)
    {
        test.near (mappingVoice->getLastModulationValue(), 1.0, 0.0,
                   "square restart begins at positive full scale");
        test.near (mappingVoice->getLastEffectivePrimaryFrequencyHz(),
                   440.0 * std::exp2 (50.0 / 1200.0), 1.0e-8,
                   "Pitch Amount follows the exact cents-ratio formula");
        test.near (mappingVoice->getLastEffectiveFilterCutoffHz(), 2000.0, 1.0e-3,
                   "Filter Amount follows the exact octave formula");
        test.near (mappingVoice->getLastEffectiveStereoSpread(), 1.0, 1.0e-6,
                   "Spread Amount follows the endpoint-aware bounded formula");
    }

    auto negativeSnapshot = mappingSnapshot;
    negativeSnapshot.lfoFilterAmountOctaves = -1.0f;
    negativeSnapshot.lfoPitchAmountCents = -50.0f;
    negativeSnapshot.lfoSpreadAmount = -1.0f;
    auto negativeSynth = configuredSynth (negativeSnapshot, 48000.0);
    negativeSynth->noteOn (1, 69, 1.0f);
    render (*negativeSynth, oneSample);
    auto* negativeVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (negativeSynth->getVoice (0));
    if (negativeVoice != nullptr)
    {
        test.near (negativeVoice->getLastEffectivePrimaryFrequencyHz(),
                   440.0 * std::exp2 (-50.0 / 1200.0), 1.0e-8,
                   "negative Pitch Amount inverts the LFO offset");
        test.near (negativeVoice->getLastEffectiveFilterCutoffHz(), 500.0, 1.0e-3,
                   "negative Filter Amount inverts the octave offset");
        test.near (negativeVoice->getLastEffectiveStereoSpread(), 0.0, 1.0e-6,
                   "negative Spread Amount inverts the bounded endpoint movement");
    }

    auto zeroBaseSnapshot = mappingSnapshot;
    zeroBaseSnapshot.unisonVoices = 4.0f;
    zeroBaseSnapshot.stereoSpread = 0.0f;
    auto zeroBaseSynth = configuredSynth (zeroBaseSnapshot, 48000.0);
    zeroBaseSynth->noteOn (1, 69, 1.0f);
    render (*zeroBaseSynth, oneSample);
    auto* zeroBaseVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        zeroBaseSynth->getVoice (0));
    test.check (zeroBaseVoice != nullptr, "zero-base Spread modulation uses production voice");
    if (zeroBaseVoice != nullptr)
        test.near (zeroBaseVoice->getLastEffectiveStereoSpread(), 1.0, 1.0e-6,
                   "positive full-scale LFO movement reaches wide from zero base Spread");

    auto fullBaseSnapshot = mappingSnapshot;
    fullBaseSnapshot.stereoSpread = 1.0f;
    fullBaseSnapshot.lfoSpreadAmount = -1.0f;
    auto fullBaseSynth = configuredSynth (fullBaseSnapshot, 48000.0);
    fullBaseSynth->noteOn (1, 69, 1.0f);
    render (*fullBaseSynth, oneSample);
    auto* fullBaseVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        fullBaseSynth->getVoice (0));
    test.check (fullBaseVoice != nullptr, "full-base Spread modulation uses production voice");
    if (fullBaseVoice != nullptr)
        test.near (fullBaseVoice->getLastEffectiveStereoSpread(), 0.0, 1.0e-6,
                   "negative full-scale LFO movement reaches centre from full base Spread");

    auto centredAudioSnapshot = zeroBaseSnapshot;
    centredAudioSnapshot.filterEnabled = 0.0f;
    centredAudioSnapshot.ampAttackSeconds = 0.001f;
    centredAudioSnapshot.ampDecaySeconds = 0.001f;
    centredAudioSnapshot.ampSustainLevel = 1.0f;
    centredAudioSnapshot.lfoSpreadAmount = 0.0f;
    auto wideAudioSnapshot = centredAudioSnapshot;
    wideAudioSnapshot.lfoSpreadAmount = 1.0f;
    auto centredAudioSynth = configuredSynth (centredAudioSnapshot, 48000.0);
    auto wideAudioSynth = configuredSynth (wideAudioSnapshot, 48000.0);
    centredAudioSynth->noteOn (1, 69, 1.0f);
    wideAudioSynth->noteOn (1, 69, 1.0f);
    juce::AudioBuffer<float> centredAudio (2, 4096);
    juce::AudioBuffer<float> wideAudio (2, 4096);
    render (*centredAudioSynth, centredAudio);
    render (*wideAudioSynth, wideAudio);
    test.near (stereoDifferenceRms (centredAudio), 0.0, 1.0e-7,
               "zero base Spread without modulation remains centred");
    test.check (stereoDifferenceRms (wideAudio) > 0.01f,
                "full Spread modulation from a zero base creates audible stereo energy");
    test.check (bufferIsFinite (wideAudio) && maximumMagnitude (
                    wideAudio, 0, wideAudio.getNumSamples()) <= 0.95f,
                "endpoint-active Spread modulation remains finite and preserves headroom");

    deeppluck::DeepPluckParameterSnapshot globalSnapshot;
    globalSnapshot.ampSustainLevel = 1.0f;
    globalSnapshot.lfoWaveform = 1.0f;
    globalSnapshot.lfoRateHz = 7.0f;
    globalSnapshot.lfoPitchAmountCents = 10.0f;
    auto globalSynth = configuredSynth (globalSnapshot, 48000.0);
    for (const auto note : { 48, 55, 60, 64 })
        globalSynth->noteOn (1, note, 0.8f);
    juce::AudioBuffer<float> globalBuffer (2, 127);
    render (*globalSynth, globalBuffer);
    auto sharedValue = 0.0f;
    auto foundFirst = false;
    for (auto index = 0; index < globalSynth->getNumVoices(); ++index)
        if (auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (globalSynth->getVoice (index));
            voice != nullptr && voice->isVoiceActive())
        {
            if (! foundFirst)
            {
                sharedValue = voice->getLastModulationValue();
                foundFirst = true;
            }
            test.near (voice->getLastModulationValue(), sharedValue, 0.0,
                       "all active musical voices receive the same global LFO sample");
        }

    deeppluck::DeepPluckParameterSnapshot restartSnapshot;
    restartSnapshot.ampReleaseSeconds = 1.0f;
    restartSnapshot.lfoRateHz = 5.0f;
    restartSnapshot.lfoRestartMode = 1.0f;
    auto restartSynth = configuredSynth (restartSnapshot, 48000.0);
    juce::AudioBuffer<float> restartBuffer (2, 97);
    render (*restartSynth, restartBuffer);
    test.check (restartSynth->getLfoPhase() > 0.0,
                "global LFO free-runs while no voice is active");
    restartSynth->noteOn (1, 60, 1.0f);
    test.near (restartSynth->getLfoPhase(), 0.0, 0.0,
               "Retrigger First Note resets after complete silence");
    render (*restartSynth, restartBuffer);
    const auto heldPhase = restartSynth->getLfoPhase();
    restartSynth->noteOn (1, 64, 1.0f);
    test.near (restartSynth->getLfoPhase(), heldPhase, 0.0,
               "a held-note overlap does not retrigger the global LFO");
    restartSynth->noteOff (1, 60, 0.0f, true);
    restartSynth->noteOff (1, 64, 0.0f, true);
    render (*restartSynth, restartBuffer);
    const auto releasePhase = restartSynth->getLfoPhase();
    restartSynth->noteOn (1, 67, 1.0f);
    test.near (restartSynth->getLfoPhase(), releasePhase, 0.0,
               "release tails count as active and prevent retrigger");
    restartSynth->allNotesOff (0, false);
    render (*restartSynth, restartBuffer);
    restartSynth->noteOn (1, 69, 1.0f);
    test.near (restartSynth->getLfoPhase(), 0.0, 0.0,
               "first note after hard silence retriggers exactly");

    auto freeSnapshot = restartSnapshot;
    freeSnapshot.lfoRestartMode = 0.0f;
    auto freeSynth = configuredSynth (freeSnapshot, 48000.0);
    render (*freeSynth, restartBuffer);
    const auto freePhase = freeSynth->getLfoPhase();
    freeSynth->noteOn (1, 60, 1.0f);
    test.near (freeSynth->getLfoPhase(), freePhase, 0.0,
               "Free Running never resets phase on Note On");

    deeppluck::DeepPluckParameterSnapshot neutralA;
    neutralA.lfoRateHz = 0.020f;
    neutralA.lfoWaveform = 0.0f;
    neutralA.lfoRestartMode = 1.0f;
    auto neutralB = neutralA;
    neutralB.lfoRateHz = 12.0f;
    neutralB.lfoSync = 1.0f;
    neutralB.lfoSyncDivision = 6.0f;
    neutralB.lfoWaveform = 3.0f;
    neutralB.lfoRestartMode = 0.0f;
    auto neutralSynthA = configuredSynth (neutralA, 48000.0);
    auto neutralSynthB = configuredSynth (neutralB, 48000.0);
    neutralSynthA->noteOn (1, 60, 0.8f);
    neutralSynthB->noteOn (1, 60, 0.8f);
    juce::AudioBuffer<float> neutralBufferA (2, 257);
    juce::AudioBuffer<float> neutralBufferB (2, 257);
    for (auto block = 0; block < 8; ++block)
    {
        render (*neutralSynthA, neutralBufferA);
        render (*neutralSynthB, neutralBufferB);
        for (auto channel = 0; channel < 2; ++channel)
            for (auto sample = 0; sample < neutralBufferA.getNumSamples(); ++sample)
                test.near (neutralBufferA.getSample (channel, sample),
                           neutralBufferB.getSample (channel, sample), 0.0,
                           "all Amounts zero are sample-identical across LFO modes");
    }

    auto partitionSnapshot = mappingSnapshot;
    partitionSnapshot.lfoWaveform = 0.0f;
    partitionSnapshot.lfoRateHz = 6.75f;
    auto largeBlockSynth = configuredSynth (partitionSnapshot, 48000.0);
    auto smallBlockSynth = configuredSynth (partitionSnapshot, 48000.0);
    largeBlockSynth->noteOn (1, 57, 0.9f);
    smallBlockSynth->noteOn (1, 57, 0.9f);
    constexpr int partitionSamples = 257;
    juce::AudioBuffer<float> largeBlockBuffer (2, partitionSamples);
    render (*largeBlockSynth, largeBlockBuffer);
    std::array<std::vector<float>, 2> partitionedOutput;
    auto remainingSamples = partitionSamples;
    while (remainingSamples > 0)
    {
        const auto blockSize = juce::jmin (17, remainingSamples);
        juce::AudioBuffer<float> smallBlockBuffer (2, blockSize);
        render (*smallBlockSynth, smallBlockBuffer);
        for (auto channel = 0; channel < 2; ++channel)
            for (auto sample = 0; sample < blockSize; ++sample)
                partitionedOutput[static_cast<std::size_t> (channel)].push_back (
                    smallBlockBuffer.getSample (channel, sample));
        remainingSamples -= blockSize;
    }
    for (auto channel = 0; channel < 2; ++channel)
        for (auto sample = 0; sample < partitionSamples; ++sample)
            test.near (partitionedOutput[static_cast<std::size_t> (channel)]
                                        [static_cast<std::size_t> (sample)],
                       largeBlockBuffer.getSample (channel, sample), 1.0e-7,
                       "global modulation audio is independent of host block partitioning");
    test.near (smallBlockSynth->getLfoPhase(), largeBlockSynth->getLfoPhase(), 0.0,
               "global LFO phase is independent of host block partitioning");

    deeppluck::DeepPluckParameterSnapshot liveAmountSnapshot;
    liveAmountSnapshot.ampSustainLevel = 1.0f;
    liveAmountSnapshot.lfoWaveform = 3.0f;
    auto liveAmountSynth = configuredSynth (liveAmountSnapshot, 48000.0);
    liveAmountSynth->noteOn (1, 60, 0.8f);
    liveAmountSnapshot.lfoFilterAmountOctaves = 2.0f;
    liveAmountSnapshot.lfoPitchAmountCents = 50.0f;
    liveAmountSnapshot.lfoSpreadAmount = 1.0f;
    juce::AudioBuffer<float> smoothingBuffer (2, 960);
    render (*liveAmountSynth, smoothingBuffer);
    auto* liveAmountVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        liveAmountSynth->getVoice (0));
    test.check (liveAmountVoice != nullptr, "live Amount smoothing uses production voice");
    if (liveAmountVoice != nullptr)
    {
        test.near (liveAmountVoice->getCurrentLfoFilterAmount(), 2.0, 1.0e-6,
                   "Filter Amount reaches its target after 20 ms");
        test.near (liveAmountVoice->getCurrentLfoPitchAmount(), 50.0, 1.0e-6,
                   "Pitch Amount reaches its target after 20 ms");
        test.near (liveAmountVoice->getCurrentLfoSpreadAmount(), 1.0, 1.0e-6,
                   "Spread Amount reaches its target after 20 ms");
    }
    liveAmountSnapshot.lfoFilterAmountOctaves = 0.0f;
    liveAmountSnapshot.lfoPitchAmountCents = 0.0f;
    liveAmountSnapshot.lfoSpreadAmount = 0.0f;
    render (*liveAmountSynth, smoothingBuffer);
    if (liveAmountVoice != nullptr)
    {
        test.near (liveAmountVoice->getCurrentLfoFilterAmount(), 0.0, 0.0,
                   "Filter Amount reaches exact neutral after its 20 ms ramp");
        test.near (liveAmountVoice->getCurrentLfoPitchAmount(), 0.0, 0.0,
                   "Pitch Amount reaches exact neutral after its 20 ms ramp");
        test.near (liveAmountVoice->getCurrentLfoSpreadAmount(), 0.0, 0.0,
                   "Spread Amount reaches exact neutral after its 20 ms ramp");
    }

    auto bypassA = mappingSnapshot;
    bypassA.filterEnabled = 0.0f;
    bypassA.lfoFilterAmountOctaves = 0.0f;
    bypassA.lfoPitchAmountCents = 0.0f;
    bypassA.lfoSpreadAmount = 0.0f;
    auto bypassB = bypassA;
    bypassB.lfoFilterAmountOctaves = 2.0f;
    auto bypassSynthA = configuredSynth (bypassA, 48000.0);
    auto bypassSynthB = configuredSynth (bypassB, 48000.0);
    bypassSynthA->noteOn (1, 60, 1.0f);
    bypassSynthB->noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> bypassBufferA (2, 512);
    juce::AudioBuffer<float> bypassBufferB (2, 512);
    for (auto block = 0; block < 4; ++block)
    {
        render (*bypassSynthA, bypassBufferA);
        render (*bypassSynthB, bypassBufferB);
        for (auto channel = 0; channel < 2; ++channel)
            for (auto sample = 0; sample < bypassBufferA.getNumSamples(); ++sample)
                test.near (bypassBufferA.getSample (channel, sample),
                           bypassBufferB.getSample (channel, sample), 0.0,
                           "Filter Amount is audio-neutral while filter bypass is settled");
    }

    deeppluck::DeepPluckParameterSnapshot stealingSnapshot;
    stealingSnapshot.ampReleaseSeconds = 1.0f;
    stealingSnapshot.lfoRateHz = 9.0f;
    stealingSnapshot.lfoRestartMode = 1.0f;
    auto stealingSynth = configuredSynth (stealingSnapshot, 48000.0);
    for (auto note = 40; note < 56; ++note)
        stealingSynth->noteOn (1, note, 0.8f);
    juce::AudioBuffer<float> stealingBuffer (2, 64);
    render (*stealingSynth, stealingBuffer);
    const auto phaseBeforeSteal = stealingSynth->getLfoPhase();
    stealingSynth->noteOn (1, 80, 0.8f);
    test.near (stealingSynth->getLfoPhase(), phaseBeforeSteal, 0.0,
               "voice stealing does not retrigger the global LFO");
    test.check (countActiveVoices (*stealingSynth) == 16,
                "modulation preserves exactly sixteen musical voices");

    for (auto waveform = 0; waveform < 4; ++waveform)
    {
        DeepPluckAudioProcessor processor;
        auto& state = processor.getValueTreeState();
        setParameter (state, deeppluck::ParameterIDs::unisonVoices, 4.0f);
        setParameter (state, deeppluck::ParameterIDs::unisonDetune, 30.0f);
        setParameter (state, deeppluck::ParameterIDs::stereoSpread, 0.5f);
        setParameter (state, deeppluck::ParameterIDs::osc2Enabled, 1.0f);
        setParameter (state, deeppluck::ParameterIDs::osc2Level, 0.0f);
        setParameter (state, deeppluck::ParameterIDs::filterCutoff, 10000.0f);
        setParameter (state, deeppluck::ParameterIDs::filterResonance, 1.0f);
        setParameter (state, deeppluck::ParameterIDs::filterDrive, 18.0f);
        setParameter (state, deeppluck::ParameterIDs::lfoRate, 12.0f);
        setParameter (state, deeppluck::ParameterIDs::lfoWaveform,
                      static_cast<float> (waveform));
        setParameter (state, deeppluck::ParameterIDs::lfoFilterAmount, 2.0f);
        setParameter (state, deeppluck::ParameterIDs::lfoPitchAmount, 50.0f);
        setParameter (state, deeppluck::ParameterIDs::lfoSpreadAmount, 1.0f);
        processor.prepareToPlay (96000.0, 127);
        juce::AudioBuffer<float> buffer (2, 127);
        juce::MidiBuffer notes;
        for (auto note = 0; note < 16; ++note)
            notes.addEvent (juce::MidiMessage::noteOn (1, 36 + note * 3, 1.0f), 0);
        processor.processBlock (buffer, notes);
        auto peak = maximumMagnitude (buffer, 0, buffer.getNumSamples());
        juce::MidiBuffer empty;
        for (auto block = 0; block < 160; ++block)
        {
            processor.processBlock (buffer, empty);
            test.check (bufferIsFinite (buffer),
                        "maximum active modulation stress remains finite");
            peak = juce::jmax (peak, maximumMagnitude (buffer, 0, buffer.getNumSamples()));
        }
        std::cout << "METRIC: V0.7 modulation stress waveform " << waveform
                  << " peak = " << peak << '\n';
        test.check (peak <= 0.95f,
                    "maximum active modulation stress preserves documented headroom");
    }
}

void testPerformance (TestContext& test)
{
    const auto measureMilliseconds = [&test] (int internalVoiceCount,
                                              bool oscillator2Enabled,
                                              int musicalVoiceCount,
                                              bool modulationActive = false,
                                              float primaryWaveform = 0.0f,
                                              float secondWaveform = 0.0f)
    {
        std::array<double, 3> measurements {};
        for (auto trial = 0; trial < static_cast<int> (measurements.size()); ++trial)
        {
            deeppluck::DeepPluckParameterSnapshot snapshot;
            snapshot.filterEnabled = 1.0f;
            snapshot.transientEnabled = 1.0f;
            snapshot.noiseEnabled = 1.0f;
            snapshot.ampAttackSeconds = 0.001f;
            snapshot.ampDecaySeconds = 0.001f;
            snapshot.ampSustainLevel = 1.0f;
            snapshot.osc2Enabled = oscillator2Enabled ? 1.0f : 0.0f;
            snapshot.unisonVoices = static_cast<float> (internalVoiceCount);
            snapshot.unisonDetuneCents = internalVoiceCount > 1 ? 9.0f : 0.0f;
            snapshot.stereoSpread = internalVoiceCount > 1 ? 0.75f : 0.0f;
            snapshot.lfoRateHz = 12.0f;
            snapshot.lfoWaveform = 3.0f;
            snapshot.lfoFilterAmountOctaves = modulationActive ? 2.0f : 0.0f;
            snapshot.lfoPitchAmountCents = modulationActive ? 50.0f : 0.0f;
            snapshot.lfoSpreadAmount = modulationActive ? 1.0f : 0.0f;
            snapshot.osc1Waveform = primaryWaveform;
            snapshot.osc2Waveform = secondWaveform;
            snapshot.osc1PulseWidth = primaryWaveform == 4.0f ? 0.05f : 0.50f;
            snapshot.osc2PulseWidth = secondWaveform == 4.0f ? 0.95f : 0.50f;

            deeppluck::DeepPluckSynthesiser synth;
            deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
            synth.setCurrentPlaybackSampleRate (48000.0);
            for (auto note = 0; note < musicalVoiceCount; ++note)
                synth.noteOn (1, 40 + note * 2, 0.7f);
            juce::AudioBuffer<float> buffer (2, 256);
            for (auto warmup = 0; warmup < 8; ++warmup)
                render (synth, buffer);

            const auto start = std::chrono::steady_clock::now();
            for (auto block = 0; block < 96; ++block)
                render (synth, buffer);
            const auto end = std::chrono::steady_clock::now();
            test.check (bufferIsFinite (buffer), "performance render remains finite");
            measurements[static_cast<std::size_t> (trial)] =
                std::chrono::duration<double, std::milli> (end - start).count();
        }
        std::sort (measurements.begin(), measurements.end());
        return measurements[1];
    };

    const auto measureLegacyEquivalentPath = [&test] (bool includeIdleEffectsRack)
    {
        std::array<double, 5> measurements {};
        for (auto trial = 0; trial < static_cast<int> (measurements.size()); ++trial)
        {
            auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
            snapshot.ampAttackSeconds = 0.001f;
            snapshot.ampDecaySeconds = 0.001f;
            snapshot.ampSustainLevel = 1.0f;
            snapshot.unisonVoices = 4.0f;
            snapshot.unisonDetuneCents = 9.0f;
            snapshot.stereoSpread = 0.75f;
            deeppluck::DeepPluckSynthesiser synth;
            deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
            synth.setCurrentPlaybackSampleRate (48000.0);
            for (auto note = 0; note < 16; ++note)
                synth.noteOn (1, 40 + note * 2, 0.7f);
            deeppluck::DeepPluckEffectsRack rack;
            rack.prepare (48000.0, 256);
            juce::AudioBuffer<float> buffer (2, 256);
            for (auto warmup = 0; warmup < 8; ++warmup)
            {
                render (synth, buffer);
                if (includeIdleEffectsRack)
                    rack.process (buffer, snapshot, 120.0);
            }

            const auto start = std::chrono::steady_clock::now();
            for (auto block = 0; block < 256; ++block)
            {
                render (synth, buffer);
                if (includeIdleEffectsRack)
                    rack.process (buffer, snapshot, 120.0);
            }
            measurements[static_cast<std::size_t> (trial)] =
                std::chrono::duration<double, std::milli> (
                    std::chrono::steady_clock::now() - start).count();
            test.check (bufferIsFinite (buffer),
                        "legacy-equivalent all-FX-off performance path remains finite");
        }
        std::sort (measurements.begin(), measurements.end());
        return measurements[measurements.size() / 2];
    };

    const auto measurePairedModulation = [&test]
    {
        std::array<double, 3> inactiveMeasurements {};
        std::array<double, 3> activeMeasurements {};
        std::array<double, 3> ratios {};
        for (auto trial = 0; trial < static_cast<int> (ratios.size()); ++trial)
        {
            auto inactiveSnapshot = deeppluck::DeepPluckParameterSnapshot {};
            inactiveSnapshot.filterEnabled = 1.0f;
            inactiveSnapshot.transientEnabled = 1.0f;
            inactiveSnapshot.noiseEnabled = 1.0f;
            inactiveSnapshot.ampAttackSeconds = 0.001f;
            inactiveSnapshot.ampDecaySeconds = 0.001f;
            inactiveSnapshot.ampSustainLevel = 1.0f;
            inactiveSnapshot.osc2Enabled = 1.0f;
            inactiveSnapshot.unisonVoices = 4.0f;
            inactiveSnapshot.unisonDetuneCents = 9.0f;
            inactiveSnapshot.stereoSpread = 0.75f;
            inactiveSnapshot.lfoRateHz = 12.0f;
            inactiveSnapshot.lfoWaveform = 3.0f;
            auto activeSnapshot = inactiveSnapshot;
            activeSnapshot.lfoFilterAmountOctaves = 2.0f;
            activeSnapshot.lfoPitchAmountCents = 50.0f;
            activeSnapshot.lfoSpreadAmount = 1.0f;

            deeppluck::DeepPluckSynthesiser inactiveSynth;
            deeppluck::DeepPluckSynthesiser activeSynth;
            deeppluck::configureDeepPluckSynthesiser (inactiveSynth, inactiveSnapshot);
            deeppluck::configureDeepPluckSynthesiser (activeSynth, activeSnapshot);
            inactiveSynth.setCurrentPlaybackSampleRate (48000.0);
            activeSynth.setCurrentPlaybackSampleRate (48000.0);
            for (auto note = 0; note < 16; ++note)
            {
                inactiveSynth.noteOn (1, 40 + note * 2, 0.7f);
                activeSynth.noteOn (1, 40 + note * 2, 0.7f);
            }

            juce::AudioBuffer<float> inactiveBuffer (2, 256);
            juce::AudioBuffer<float> activeBuffer (2, 256);
            for (auto warmup = 0; warmup < 8; ++warmup)
            {
                render (inactiveSynth, inactiveBuffer);
                render (activeSynth, activeBuffer);
            }

            const auto timeSynth = [] (auto& synth, auto& buffer)
            {
                const auto start = std::chrono::steady_clock::now();
                for (auto block = 0; block < 96; ++block)
                    render (synth, buffer);
                return std::chrono::duration<double, std::milli> (
                    std::chrono::steady_clock::now() - start).count();
            };

            if (trial % 2 == 0)
            {
                inactiveMeasurements[static_cast<std::size_t> (trial)] =
                    timeSynth (inactiveSynth, inactiveBuffer);
                activeMeasurements[static_cast<std::size_t> (trial)] =
                    timeSynth (activeSynth, activeBuffer);
            }
            else
            {
                activeMeasurements[static_cast<std::size_t> (trial)] =
                    timeSynth (activeSynth, activeBuffer);
                inactiveMeasurements[static_cast<std::size_t> (trial)] =
                    timeSynth (inactiveSynth, inactiveBuffer);
            }

            test.check (bufferIsFinite (inactiveBuffer) && bufferIsFinite (activeBuffer),
                        "paired modulation performance renders remain finite");
            ratios[static_cast<std::size_t> (trial)] =
                activeMeasurements[static_cast<std::size_t> (trial)]
                / juce::jmax (0.001, inactiveMeasurements[static_cast<std::size_t> (trial)]);
        }

        std::sort (inactiveMeasurements.begin(), inactiveMeasurements.end());
        std::sort (activeMeasurements.begin(), activeMeasurements.end());
        std::sort (ratios.begin(), ratios.end());
        return std::array<double, 3> {
            inactiveMeasurements[1], activeMeasurements[1], ratios[1]
        };
    };

#if ! JUCE_DEBUG
    const auto measurePairedWaveform = [&test] (float primaryWaveform,
                                                float secondWaveform)
    {
        std::array<double, 3> candidateMeasurements {};
        std::array<double, 3> ratios {};
        for (auto trial = 0; trial < static_cast<int> (ratios.size()); ++trial)
        {
            const auto makeSnapshot = [] (float primary, float second)
            {
                auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
                snapshot.filterEnabled = 1.0f;
                snapshot.transientEnabled = 1.0f;
                snapshot.noiseEnabled = 1.0f;
                snapshot.ampAttackSeconds = 0.001f;
                snapshot.ampDecaySeconds = 0.001f;
                snapshot.ampSustainLevel = 1.0f;
                snapshot.osc2Enabled = 1.0f;
                snapshot.unisonVoices = 4.0f;
                snapshot.unisonDetuneCents = 9.0f;
                snapshot.stereoSpread = 0.75f;
                snapshot.lfoRateHz = 12.0f;
                snapshot.lfoWaveform = 3.0f;
                snapshot.osc1Waveform = primary;
                snapshot.osc2Waveform = second;
                snapshot.osc1PulseWidth = primary == 4.0f ? 0.05f : 0.50f;
                snapshot.osc2PulseWidth = second == 4.0f ? 0.95f : 0.50f;
                return snapshot;
            };

            deeppluck::DeepPluckSynthesiser sawSynth;
            deeppluck::DeepPluckSynthesiser candidateSynth;
            deeppluck::configureDeepPluckSynthesiser (sawSynth, makeSnapshot (0.0f, 0.0f));
            deeppluck::configureDeepPluckSynthesiser (
                candidateSynth, makeSnapshot (primaryWaveform, secondWaveform));
            sawSynth.setCurrentPlaybackSampleRate (48000.0);
            candidateSynth.setCurrentPlaybackSampleRate (48000.0);
            for (auto note = 0; note < 16; ++note)
            {
                sawSynth.noteOn (1, 40 + note * 2, 0.7f);
                candidateSynth.noteOn (1, 40 + note * 2, 0.7f);
            }

            juce::AudioBuffer<float> sawBuffer (2, 256);
            juce::AudioBuffer<float> candidateBuffer (2, 256);
            for (auto warmup = 0; warmup < 8; ++warmup)
            {
                render (sawSynth, sawBuffer);
                render (candidateSynth, candidateBuffer);
            }

            const auto timeSynth = [] (auto& synth, auto& buffer)
            {
                const auto start = std::chrono::steady_clock::now();
                for (auto block = 0; block < 96; ++block)
                    render (synth, buffer);
                return std::chrono::duration<double, std::milli> (
                    std::chrono::steady_clock::now() - start).count();
            };

            auto sawMilliseconds = 0.0;
            auto candidateMilliseconds = 0.0;
            if (trial % 2 == 0)
            {
                sawMilliseconds = timeSynth (sawSynth, sawBuffer);
                candidateMilliseconds = timeSynth (candidateSynth, candidateBuffer);
            }
            else
            {
                candidateMilliseconds = timeSynth (candidateSynth, candidateBuffer);
                sawMilliseconds = timeSynth (sawSynth, sawBuffer);
            }

            test.check (bufferIsFinite (sawBuffer) && bufferIsFinite (candidateBuffer),
                        "paired waveform performance renders remain finite");
            candidateMeasurements[static_cast<std::size_t> (trial)] =
                candidateMilliseconds;
            ratios[static_cast<std::size_t> (trial)] =
                candidateMilliseconds / juce::jmax (0.001, sawMilliseconds);
        }

        std::sort (candidateMeasurements.begin(), candidateMeasurements.end());
        std::sort (ratios.begin(), ratios.end());
        return std::array<double, 2> { candidateMeasurements[1], ratios[1] };
    };
#endif

    const auto oneInternalBoth = measureMilliseconds (1, true, 16);
    const auto twoInternalBoth = measureMilliseconds (2, true, 16);
    const auto fourInternalBoth = measureMilliseconds (4, true, 16);
    const auto fourInternalOsc1 = measureMilliseconds (4, false, 16);
    const auto fourInternalOneMusical = measureMilliseconds (4, true, 1);
    const auto pairedModulation = measurePairedModulation();
    const auto fourInternalModulation = pairedModulation[1];
    const auto v08EquivalentMilliseconds = measureLegacyEquivalentPath (false);
    const auto v09AllEffectsOffMilliseconds = measureLegacyEquivalentPath (true);
    const auto allEffectsOffRatio = v09AllEffectsOffMilliseconds
                                  / juce::jmax (0.001, v08EquivalentMilliseconds);
    auto mostExpensiveWaveform = fourInternalBoth;
    auto mostExpensiveWaveformRatio = 1.0;
    auto mostExpensivePrimary = 0;
    auto mostExpensiveSecond = 0;
#if ! JUCE_DEBUG
    for (auto primary = 0; primary < 5; ++primary)
        for (auto second = 0; second < 5; ++second)
        {
            const auto measurement = measurePairedWaveform (
                static_cast<float> (primary), static_cast<float> (second));
            if (measurement[1] > mostExpensiveWaveformRatio)
            {
                mostExpensiveWaveform = measurement[0];
                mostExpensiveWaveformRatio = measurement[1];
                mostExpensivePrimary = primary;
                mostExpensiveSecond = second;
            }
        }
#endif
    const auto cpuRatio = fourInternalBoth / juce::jmax (0.001, oneInternalBoth);
    const auto modulationCpuRatio = pairedModulation[2];
    constexpr auto measuredFrames = 96.0 * 256.0;
    const auto framesPerSecond = [measuredFrames] (double milliseconds)
    {
        return measuredFrames * 1000.0 / milliseconds;
    };

    std::cout << "METRIC: CPU median ms, 16 musical voices / both oscillators / Unison 1,2,4 = "
              << oneInternalBoth << ", " << twoInternalBoth << ", "
              << fourInternalBoth << '\n';
    std::cout << "METRIC: CPU median ms, Unison 4 / 16 musical voices / Osc1-only,both = "
              << fourInternalOsc1 << ", " << fourInternalBoth << '\n';
    std::cout << "METRIC: CPU median ms, Unison 4 / both oscillators / musical voices 1,16 = "
              << fourInternalOneMusical << ", " << fourInternalBoth << '\n';
    std::cout << "METRIC: CPU Unison-4/Unison-1 ratio = " << cpuRatio << '\n';
    std::cout << "METRIC: CPU median ms, V0.7 Amount 0 / active maximum modulation = "
              << pairedModulation[0] << ", " << fourInternalModulation
              << "; active/off ratio = " << modulationCpuRatio << '\n';
    std::cout << "METRIC: V0.9.0 Saw/Saw median ms = " << fourInternalBoth
              << "; V0.8.0 measured baseline = 37.4574; ratio = "
              << fourInternalBoth / 37.4574 << '\n';
    std::cout << "METRIC: V0.9.0 worst waveform pair " << mostExpensivePrimary
              << '/' << mostExpensiveSecond << " median ms = "
              << mostExpensiveWaveform << "; worst/Saw ratio = "
              << mostExpensiveWaveformRatio << '\n';
    std::cout << "METRIC: rendered frames/s, Unison 1,2,4 = "
              << framesPerSecond (oneInternalBoth) << ", "
              << framesPerSecond (twoInternalBoth) << ", "
              << framesPerSecond (fourInternalBoth)
              << "; relative time factors = 1, "
              << twoInternalBoth / oneInternalBoth << ", " << cpuRatio << '\n';
    std::cout << "METRIC: V0.8-equivalent/all-FX-off median ms = "
              << v08EquivalentMilliseconds << "/" << v09AllEffectsOffMilliseconds
              << "; all-FX-off ratio = " << allEffectsOffRatio << '\n';

    test.check (oneInternalBoth > 0.0 && twoInternalBoth > 0.0
                    && fourInternalBoth > 0.0 && fourInternalOsc1 > 0.0
                    && fourInternalOneMusical > 0.0 && fourInternalModulation > 0.0,
                "all CPU measurement scenarios produce valid timings");
    test.check (cpuRatio <= 8.0,
                "Unison 4 remains within the specified eight-times CPU ceiling");
    test.check (modulationCpuRatio <= 2.0,
                "active maximum modulation remains within the factor-2 CPU budget");
#if ! JUCE_DEBUG
    test.check (fourInternalBoth <= 37.4574 * 1.05,
                "V0.9.0 Saw/Saw remains within 1.05 times the measured V0.8.0 baseline");
    test.check (allEffectsOffRatio <= 1.05,
                "idle effects rack remains within five percent of the paired V0.8-equivalent path");
    test.check (mostExpensiveWaveformRatio <= 2.0,
                "the most expensive stationary waveform pair remains within twice Saw/Saw");
#endif
}

void testHeadroom (TestContext& test)
{
    const auto renderScenario = [&test] (double sampleRate, int chordSize,
                                         int presetIndex, int unisonVoiceOverride)
    {
        DeepPluckAudioProcessor processor;
        auto& state = processor.getValueTreeState();
        if (presetIndex >= 0)
            deeppluck::applyFactoryPreset (state, presetIndex);
        if (unisonVoiceOverride > 0)
        {
            setParameter (state, deeppluck::ParameterIDs::unisonVoices,
                          static_cast<float> (unisonVoiceOverride));
            setParameter (state, deeppluck::ParameterIDs::unisonDetune,
                          unisonVoiceOverride == 1 ? 0.0f : 9.0f);
            setParameter (state, deeppluck::ParameterIDs::stereoSpread,
                          unisonVoiceOverride == 1 ? 0.0f : 0.75f);
        }

        constexpr int blockSize = 512;
        processor.prepareToPlay (sampleRate, blockSize);
        juce::AudioBuffer<float> buffer (2, blockSize);
        juce::MidiBuffer noteOn;
        for (auto note = 0; note < chordSize; ++note)
            noteOn.addEvent (juce::MidiMessage::noteOn (1, 48 + note, 1.0f), 0);

        StereoMetricsAccumulator metrics;
        processor.processBlock (buffer, noteOn);
        test.check (bufferIsFinite (buffer), "headroom Note-On block remains finite");
        metrics.add (buffer);
        juce::MidiBuffer empty;
        for (auto block = 0; block < 31; ++block)
        {
            processor.processBlock (buffer, empty);
            test.check (bufferIsFinite (buffer), "headroom continuation remains finite");
            metrics.add (buffer);
        }
        test.check (metrics.peak() <= 0.95,
                    "complete synth headroom render remains at or below 0.95");
        return metrics;
    };

    constexpr std::array sampleRates { 44100.0, 48000.0, 96000.0 };
    constexpr std::array chordSizes { 1, 4, 8, 16 };
    for (const auto sampleRate : sampleRates)
        for (const auto chordSize : chordSizes)
            for (auto scenario = -1;
                 scenario < static_cast<int> (deeppluck::factoryPresets.size()); ++scenario)
            {
                const auto metrics = renderScenario (sampleRate, chordSize, scenario, 0);
                const auto name = scenario < 0
                    ? juce::String { "Fresh Default" }
                    : deeppluck::factoryPresets[static_cast<std::size_t> (scenario)].name;
                std::cout << "METRIC: headroom " << name << ", " << sampleRate
                          << " Hz, " << chordSize << " notes: peak=" << metrics.peak()
                          << ", rms=" << metrics.rms() << '\n';
            }

    for (auto unisonVoices = 1; unisonVoices <= 4; ++unisonVoices)
    {
        const auto metrics = renderScenario (48000.0, 16, -1, unisonVoices);
        std::cout << "METRIC: Unison Voices " << unisonVoices
                  << " stereo acceptance: peak=" << metrics.peak()
                  << ", rms=" << metrics.rms()
                  << ", leftPeak=" << metrics.leftPeak
                  << ", rightPeak=" << metrics.rightPeak
                  << ", leftRms=" << metrics.leftRms()
                  << ", rightRms=" << metrics.rightRms()
                  << ", midRms=" << metrics.midRms()
                  << ", sideRms=" << metrics.sideRms()
                  << ", monoPeak=" << metrics.monoPeak
                  << ", monoRms=" << metrics.midRms() << '\n';
        test.check (metrics.midRms() > 0.0 && metrics.monoPeak > 0.0,
                    "Unison headroom render retains a usable mono sum");
        if (unisonVoices == 1)
            test.near (metrics.sideRms(), 0.0, 0.0,
                       "Unison Voices 1 has no Side signal");
    }
}

void testEnvelope (TestContext& test)
{
    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
    {
        deeppluck::DeepPluckEnvelope envelope;
        envelope.prepare (sampleRate);
        envelope.setParameters ({ 0.001f, 0.001f, 0.5f, 0.005f });
        envelope.noteOn();
        test.check (envelope.isActive(), "note-on activates envelope");

        auto previous = 0.0f;
        for (auto sample = 0; sample < static_cast<int> (sampleRate * 0.01); ++sample)
        {
            const auto value = envelope.getNextSample();
            test.check (std::isfinite (value) && value >= 0.0f && value <= 1.0f,
                        "DeepPluckEnvelope remains finite and bounded");
            previous = value;
        }

        envelope.noteOff();
        const auto firstReleaseSample = envelope.getNextSample();
        test.check (firstReleaseSample <= previous + 1.0e-6f,
                    "note-off release begins without an upward discontinuity");
        for (auto sample = 0; sample < static_cast<int> (sampleRate * 0.02); ++sample)
            static_cast<void> (envelope.getNextSample());
        test.check (! envelope.isActive(), "release completes and envelope becomes idle");

        envelope.setParameters ({ 0.010f, 0.010f, 0.25f, 0.010f });
        envelope.noteOn();
        test.check (envelope.isActive(), "new note accepts a new ADSR snapshot");
        envelope.reset();
        test.check (! envelope.isActive(), "hard reset immediately idles envelope");
        test.near (envelope.getNextSample(), 0.0, 0.0, "hard reset produces silence");
    }

    constexpr double sampleRate = 48000.0;
    const auto advance = [] (deeppluck::DeepPluckEnvelope& envelope, int samples)
    {
        auto value = envelope.getCurrentLevel();
        for (auto sample = 0; sample < samples; ++sample)
            value = envelope.getNextSample();
        return value;
    };

    deeppluck::DeepPluckEnvelope attackRelease;
    attackRelease.prepare (sampleRate);
    attackRelease.setParameters ({ 1.0f, 0.5f, 0.0f, 0.05f });
    attackRelease.noteOn();
    const auto attackLevel = advance (attackRelease, 4800);
    test.check (attackRelease.getStage() == deeppluck::DeepPluckEnvelope::Stage::attack,
                "long attack is still active before early note-off");
    attackRelease.noteOff();
    test.check (attackRelease.getStage() == deeppluck::DeepPluckEnvelope::Stage::release,
                "note-off immediately transitions Attack to Release");
    test.near (attackRelease.getCurrentLevel(), attackLevel, 1.0e-7,
               "Attack-to-Release preserves the current level");
    advance (attackRelease, 2401);
    test.check (! attackRelease.isActive(), "early Attack note-off uses Release time, not remaining Attack");

    deeppluck::DeepPluckEnvelope decayRelease;
    decayRelease.prepare (sampleRate);
    decayRelease.setParameters ({ 0.001f, 0.5f, 0.0f, 0.05f });
    decayRelease.noteOn();
    advance (decayRelease, 4800);
    test.check (decayRelease.getStage() == deeppluck::DeepPluckEnvelope::Stage::decay,
                "long decay is active before early note-off");
    const auto decayLevel = decayRelease.getCurrentLevel();
    decayRelease.noteOff();
    test.check (decayRelease.getStage() == deeppluck::DeepPluckEnvelope::Stage::release,
                "note-off immediately transitions Decay to Release");
    test.near (decayRelease.getCurrentLevel(), decayLevel, 1.0e-7,
               "Decay-to-Release preserves the current level");
    advance (decayRelease, 2401);
    test.check (! decayRelease.isActive(), "early Decay note-off never completes the remaining Decay first");

    deeppluck::DeepPluckEnvelope liveRelease;
    liveRelease.prepare (sampleRate);
    liveRelease.setParameters ({ 0.001f, 0.001f, 1.0f, 5.0f });
    liveRelease.noteOn();
    advance (liveRelease, 200);
    liveRelease.noteOff();
    advance (liveRelease, 4800);
    const auto levelBeforeShortening = liveRelease.getCurrentLevel();
    liveRelease.setParameters ({ 0.001f, 0.001f, 1.0f, 0.05f });
    test.near (liveRelease.getCurrentLevel(), levelBeforeShortening, 1.0e-7,
               "shortening Release does not jump the current level");
    advance (liveRelease, 2401);
    test.check (! liveRelease.isActive(), "shortening a running Release changes its remaining time");

    deeppluck::DeepPluckEnvelope extendedRelease;
    extendedRelease.prepare (sampleRate);
    extendedRelease.setParameters ({ 0.001f, 0.001f, 1.0f, 0.05f });
    extendedRelease.noteOn();
    advance (extendedRelease, 200);
    extendedRelease.noteOff();
    advance (extendedRelease, 240);
    const auto levelBeforeExtension = extendedRelease.getCurrentLevel();
    extendedRelease.setParameters ({ 0.001f, 0.001f, 1.0f, 5.0f });
    test.near (extendedRelease.getCurrentLevel(), levelBeforeExtension, 1.0e-7,
               "extending Release does not jump the current level");
    advance (extendedRelease, 4800);
    test.check (extendedRelease.isActive(), "extending a running Release lengthens its remaining time");

    deeppluck::DeepPluckEnvelope liveStages;
    liveStages.prepare (sampleRate);
    liveStages.setParameters ({ 1.0f, 1.0f, 0.5f, 0.1f });
    liveStages.noteOn();
    advance (liveStages, 2400);
    const auto beforeAttackChange = liveStages.getCurrentLevel();
    liveStages.setParameters ({ 0.01f, 1.0f, 0.5f, 0.1f });
    test.near (liveStages.getCurrentLevel(), beforeAttackChange, 1.0e-7,
               "live Attack change preserves level");
    advance (liveStages, 481);
    test.check (liveStages.getStage() == deeppluck::DeepPluckEnvelope::Stage::decay,
                "shortened live Attack reaches Decay promptly");

    const auto beforeDecayChange = liveStages.getCurrentLevel();
    liveStages.setParameters ({ 0.01f, 0.01f, 0.5f, 0.1f });
    test.near (liveStages.getCurrentLevel(), beforeDecayChange, 1.0e-7,
               "live Decay change preserves level");
    advance (liveStages, 481);
    test.check (liveStages.getStage() == deeppluck::DeepPluckEnvelope::Stage::sustain,
                "shortened live Decay reaches Sustain promptly");

    const auto sustainBefore = liveStages.getCurrentLevel();
    liveStages.setParameters ({ 0.01f, 0.01f, 0.1f, 0.1f });
    test.near (liveStages.getCurrentLevel(), sustainBefore, 1.0e-7,
               "live Sustain change begins without a jump");
    const auto sustainAfterOneSample = liveStages.getNextSample();
    test.check (sustainAfterOneSample < sustainBefore && sustainAfterOneSample > 0.1f,
                "live Sustain moves smoothly toward its new target");
    advance (liveStages, 720);
    test.near (liveStages.getCurrentLevel(), 0.1, 1.0e-5,
               "live Sustain reaches its target after the safety ramp");
}

void testFilter (TestContext& test)
{
    // A reused instance must match a fresh one exactly after reset/reprepare,
    // including single-float-step cutoff changes and a return to an earlier rate.
    deeppluck::DeepPluckFilter reused;
    for (const auto sampleRate : { 44100.0, 96000.0, 48000.0, 44100.0 })
    {
        reused.prepare (sampleRate);
        for (const auto cutoff : { 20.0f, 1800.0f, std::nextafter (1800.0f, 2000.0f),
                                  20000.0f, 100000.0f, 1800.0f })
        {
            deeppluck::DeepPluckFilter fresh;
            fresh.prepare (sampleRate);
            reused.setCutoffFrequencyHz (cutoff);
            fresh.setCutoffFrequencyHz (cutoff);
            reused.reset();
            fresh.reset();
            for (auto sample = 0; sample < 257; ++sample)
            {
                const auto input = sample == 0 ? 0.7f : 0.0f;
                test.near (reused.processSample (input), fresh.processSample (input), 0.0,
                           "filter exact coefficient keys survive cutoff/reset/rate transitions");
            }
        }
    }

    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
    {
        deeppluck::DeepPluckFilter filter;
        filter.prepare (sampleRate);
        filter.setCutoffFrequencyHz (1000.0f);
        filter.setResonance (0.0f);
        filter.setDriveDb (0.0f);
        filter.reset();

        for (auto sample = 0; sample < 4096; ++sample)
            test.check (std::isfinite (filter.processSample (sample == 0 ? 1.0f : 0.0f)),
                        "filter impulse response remains finite");

        filter.reset();
        for (auto sample = 0; sample < 512; ++sample)
            test.near (filter.processSample (0.0f), 0.0, 0.0,
                       "filter reset produces deterministic silence");
    }

    deeppluck::DeepPluckFilter compensationSmoothing;
    compensationSmoothing.prepare (48000.0);
    compensationSmoothing.setResonance (0.0f);
    compensationSmoothing.reset();
    test.near (compensationSmoothing.getCurrentResonanceCompensation(), 1.0, 0.0,
               "zero resonance applies unity output compensation");
    compensationSmoothing.setResonance (1.0f);
    static_cast<void> (compensationSmoothing.processSample (0.0f));
    test.check (compensationSmoothing.getCurrentResonanceCompensation() > 1.0f
                    && compensationSmoothing.getCurrentResonanceCompensation() < 2.50f,
                "resonance compensation starts with a smooth gain ramp");
    for (auto sample = 1; sample < 960; ++sample)
        static_cast<void> (compensationSmoothing.processSample (0.0f));
    test.near (compensationSmoothing.getCurrentResonanceCompensation(), 2.50, 1.0e-6,
               "resonance compensation reaches its bounded target in twenty milliseconds");
    test.near (compensationSmoothing.getTargetResonanceCompensation(), 2.50, 0.0,
               "maximum resonance compensation is bounded to +7.96 dB");

    auto minimumCompensatedPassbandRatio = std::numeric_limits<double>::max();
    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
        for (const auto cutoffHz : { 100.0f, 500.0f, 2000.0f, 10000.0f })
            for (const auto driveDb : { 0.0f, 9.0f, 18.0f })
            {
                const auto probeFrequency = juce::jmax (20.0f, cutoffHz * 0.125f);
                const auto unresonantRms = filterSineRms (
                    cutoffHz, 0.0f, driveDb, probeFrequency, sampleRate);
                const auto resonantRms = filterSineRms (
                    cutoffHz, 1.0f, driveDb, probeFrequency, sampleRate);
                const auto ratio = resonantRms / unresonantRms;
                minimumCompensatedPassbandRatio = juce::jmin (
                    minimumCompensatedPassbandRatio, ratio);
                test.check (std::isfinite (ratio) && ratio >= 0.50,
                            "maximum resonance avoids excessive passband loudness loss across cutoff, drive, and sample rate");
            }
    std::cout << "METRIC: minimum compensated high-resonance passband RMS ratio = "
              << minimumCompensatedPassbandRatio << " ("
              << juce::Decibels::gainToDecibels (
                     static_cast<float> (minimumCompensatedPassbandRatio))
              << " dB)\n";

    const auto passband = filterSineRms (1000.0f, 0.0f, 0.0f, 100.0);
    const auto stopband = filterSineRms (1000.0f, 0.0f, 0.0f, 8000.0);
    test.check (passband > stopband * 100.0,
                "24 dB low-pass strongly attenuates high-frequency energy");

    const auto firstOctave = filterSineRms (250.0f, 0.0f, 0.0f, 2000.0);
    const auto secondOctave = filterSineRms (250.0f, 0.0f, 0.0f, 4000.0);
    const auto slopeDb = 20.0 * std::log10 (firstOctave / secondOctave);
    std::cout << "METRIC: filter stopband slope = " << slopeDb << " dB/oct\n";
    test.check (slopeDb >= 20.0 && slopeDb <= 28.0,
                "filter has the nominal 24 dB/octave stopband character");

    const auto unresonant = filterSineRms (1000.0f, 0.0f, 0.0f, 1000.0);
    const auto resonant = filterSineRms (1000.0f, 0.8f, 0.0f, 1000.0);
    test.check (resonant > unresonant * 1.1,
                "resonance increases energy around the cutoff");

    deeppluck::DeepPluckFilter clean;
    deeppluck::DeepPluckFilter driven;
    clean.prepare (48000.0);
    driven.prepare (48000.0);
    clean.setCutoffFrequencyHz (18000.0f);
    driven.setCutoffFrequencyHz (18000.0f);
    clean.setDriveDb (0.0f);
    driven.setDriveDb (18.0f);
    clean.reset();
    driven.reset();
    auto driveDifference = 0.0;
    for (auto sample = 0; sample < 8192; ++sample)
    {
        const auto input = static_cast<float> (0.8 * std::sin (
            juce::MathConstants<double>::twoPi * 997.0 * sample / 48000.0));
        driveDifference += std::abs (clean.processSample (input) - driven.processSample (input));
    }
    test.check (driveDifference > 10.0, "pre-filter drive has an audible nonlinear effect");

    deeppluck::DeepPluckFilter stress;
    stress.prepare (96000.0);
    stress.setResonance (1.0f);
    stress.setDriveDb (18.0f);
    stress.reset();
    auto maximum = 0.0f;
    auto phase = 0.0;
    for (auto sample = 0; sample < 200000; ++sample)
    {
        const auto sweep = static_cast<float> (sample % 4096) / 4095.0f;
        stress.setCutoffFrequencyHz (20.0f * std::exp2 (sweep * std::log2 (1000.0f)));
        const auto input = static_cast<float> (phase * 2.0 - 1.0);
        phase += 440.0 / 96000.0;
        phase -= std::floor (phase);
        const auto output = stress.processSample (input);
        test.check (std::isfinite (output), "maximum resonance/drive sweep remains finite");
        maximum = juce::jmax (maximum, std::abs (output));
    }
    std::cout << "METRIC: worst filter stress peak = " << maximum << '\n';
    test.check (maximum < 4.0f,
                "maximum compensated resonance/drive sweep remains bounded without clipping concealment");
}

void testFilterEnvelope (TestContext& test)
{
    deeppluck::DeepPluckParameterSnapshot snapshot;
    snapshot.ampAttackSeconds = 0.001f;
    snapshot.ampDecaySeconds = 0.001f;
    snapshot.ampSustainLevel = 1.0f;
    snapshot.ampReleaseSeconds = 2.0f;
    snapshot.filterCutoffHz = 1000.0f;
    snapshot.filterKeyTracking = 1.0f;
    snapshot.filterEnvAmountOctaves = 0.0f;
    snapshot.filterAttackSeconds = 1.0f;
    snapshot.filterDecaySeconds = 1.0f;
    snapshot.filterSustainLevel = 0.0f;
    snapshot.filterReleaseSeconds = 0.1f;

    deeppluck::DeepPluckSynthesiser attackSynth;
    deeppluck::configureDeepPluckSynthesiser (attackSynth, snapshot);
    attackSynth.setCurrentPlaybackSampleRate (48000.0);
    juce::AudioBuffer<float> buffer (2, 64);
    attackSynth.noteOn (1, 60, 1.0f);
    render (attackSynth, buffer);
    auto* attackVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (attackSynth.getVoice (0));
    attackSynth.noteOff (1, 60, 0.0f, true);
    test.check (attackVoice->isFilterEnvelopeReleasing(),
                "note-off during filter Attack enters Release immediately");

    snapshot.filterAttackSeconds = 0.001f;
    snapshot.filterDecaySeconds = 1.0f;
    deeppluck::DeepPluckSynthesiser decaySynth;
    deeppluck::configureDeepPluckSynthesiser (decaySynth, snapshot);
    decaySynth.setCurrentPlaybackSampleRate (48000.0);
    decaySynth.noteOn (1, 60, 1.0f);
    render (decaySynth, buffer);
    render (decaySynth, buffer);
    auto* decayVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (decaySynth.getVoice (0));
    decaySynth.noteOff (1, 60, 0.0f, true);
    test.check (decayVoice->isFilterEnvelopeReleasing(),
                "note-off during filter Decay enters Release immediately");

    snapshot.filterDecaySeconds = 0.001f;
    snapshot.filterSustainLevel = 1.0f;
    snapshot.filterReleaseSeconds = 5.0f;
    deeppluck::DeepPluckSynthesiser liveReleaseSynth;
    deeppluck::configureDeepPluckSynthesiser (liveReleaseSynth, snapshot);
    liveReleaseSynth.setCurrentPlaybackSampleRate (48000.0);
    liveReleaseSynth.noteOn (1, 60, 1.0f);
    render (liveReleaseSynth, buffer);
    render (liveReleaseSynth, buffer);
    auto* releaseVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        liveReleaseSynth.getVoice (0));
    liveReleaseSynth.noteOff (1, 60, 0.0f, true);
    render (liveReleaseSynth, buffer);
    snapshot.filterReleaseSeconds = 0.005f;
    for (auto block = 0; block < 8; ++block)
        render (liveReleaseSynth, buffer);
    test.check (! releaseVoice->isFilterEnvelopeActive(),
                "shorter filter Release updates an existing tail");
    test.check (releaseVoice->isVoiceActive(),
                "finished filter envelope does not clear the active amp voice");

    snapshot.filterReleaseSeconds = 0.05f;
    liveReleaseSynth.allNotesOff (0, false);
    liveReleaseSynth.noteOn (1, 60, 1.0f);
    render (liveReleaseSynth, buffer);
    render (liveReleaseSynth, buffer);
    liveReleaseSynth.noteOff (1, 60, 0.0f, true);
    render (liveReleaseSynth, buffer);
    snapshot.filterReleaseSeconds = 1.0f;
    for (auto block = 0; block < 50; ++block)
        render (liveReleaseSynth, buffer);
    test.check (releaseVoice->isFilterEnvelopeActive(),
                "longer filter Release updates an existing tail");

    deeppluck::DeepPluckParameterSnapshot keySnapshot;
    keySnapshot.ampAttackSeconds = 0.001f;
    keySnapshot.ampDecaySeconds = 0.001f;
    keySnapshot.ampSustainLevel = 1.0f;
    keySnapshot.filterCutoffHz = 1000.0f;
    keySnapshot.filterKeyTracking = 1.0f;
    keySnapshot.filterEnvAmountOctaves = 0.0f;
    deeppluck::DeepPluckSynthesiser lowSynth;
    deeppluck::DeepPluckSynthesiser highSynth;
    deeppluck::configureDeepPluckSynthesiser (lowSynth, keySnapshot);
    deeppluck::configureDeepPluckSynthesiser (highSynth, keySnapshot);
    lowSynth.setCurrentPlaybackSampleRate (48000.0);
    highSynth.setCurrentPlaybackSampleRate (48000.0);
    lowSynth.noteOn (1, 60, 1.0f);
    highSynth.noteOn (1, 72, 1.0f);
    render (lowSynth, buffer);
    render (highSynth, buffer);
    const auto* lowVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (lowSynth.getVoice (0));
    const auto* highVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (highSynth.getVoice (0));
    test.near (lowVoice->getCurrentFilterCutoffHz(), 1000.0, 0.1,
               "MIDI 60 is the neutral key-tracking reference");
    test.near (highVoice->getCurrentFilterCutoffHz(), 2000.0, 0.2,
               "100 percent key tracking follows one octave per octave");

    auto positiveSnapshot = keySnapshot;
    positiveSnapshot.filterKeyTracking = 0.0f;
    positiveSnapshot.filterEnvAmountOctaves = 1.0f;
    positiveSnapshot.filterAttackSeconds = 0.001f;
    positiveSnapshot.filterDecaySeconds = 1.0f;
    positiveSnapshot.filterSustainLevel = 1.0f;
    auto negativeSnapshot = positiveSnapshot;
    negativeSnapshot.filterEnvAmountOctaves = -1.0f;
    deeppluck::DeepPluckSynthesiser positiveSynth;
    deeppluck::DeepPluckSynthesiser negativeSynth;
    deeppluck::configureDeepPluckSynthesiser (positiveSynth, positiveSnapshot);
    deeppluck::configureDeepPluckSynthesiser (negativeSynth, negativeSnapshot);
    positiveSynth.setCurrentPlaybackSampleRate (48000.0);
    negativeSynth.setCurrentPlaybackSampleRate (48000.0);
    positiveSynth.noteOn (1, 60, 1.0f);
    negativeSynth.noteOn (1, 60, 1.0f);
    render (positiveSynth, buffer);
    render (negativeSynth, buffer);
    const auto* positiveVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        positiveSynth.getVoice (0));
    const auto* negativeVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        negativeSynth.getVoice (0));
    test.check (positiveVoice->getCurrentFilterCutoffHz() > 1000.0f,
                "positive filter envelope amount opens the cutoff");
    test.check (negativeVoice->getCurrentFilterCutoffHz() < 1000.0f,
                "negative filter envelope amount closes the cutoff");

    keySnapshot.filterEnabled = 1.0f;
    keySnapshot.filterCutoffHz = 500.0f;
    keySnapshot.filterKeyTracking = 0.0f;
    deeppluck::DeepPluckSynthesiser bypassSynth;
    deeppluck::configureDeepPluckSynthesiser (bypassSynth, keySnapshot);
    bypassSynth.setCurrentPlaybackSampleRate (48000.0);
    bypassSynth.noteOn (1, 60, 1.0f);
    render (bypassSynth, buffer);
    auto* bypassVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (bypassSynth.getVoice (0));
    keySnapshot.filterEnabled = 0.0f;
    juce::AudioBuffer<float> oneSampleBuffer (2, 1);
    render (bypassSynth, oneSampleBuffer);
    test.check (bypassVoice->getCurrentFilterMix() < 1.0f
                    && bypassVoice->getCurrentFilterMix() > 0.997f,
                "filter bypass begins with a one-sample ramp instead of a hard switch");
    render (bypassSynth, buffer);
    test.check (bypassVoice->getCurrentFilterMix() > 0.0f
                    && bypassVoice->getCurrentFilterMix() < 1.0f,
                "filter bypass transitions from the current mix value");
    for (auto block = 0; block < 10; ++block)
        render (bypassSynth, buffer);
    test.near (bypassVoice->getCurrentFilterMix(), 0.0, 1.0e-6,
               "filter bypass reaches dry after ten milliseconds");

    keySnapshot.filterEnabled = 1.0f;
    keySnapshot.filterCutoffHz = 1000.0f;
    deeppluck::DeepPluckSynthesiser cutoffSmoothingSynth;
    deeppluck::configureDeepPluckSynthesiser (cutoffSmoothingSynth, keySnapshot);
    cutoffSmoothingSynth.setCurrentPlaybackSampleRate (48000.0);
    cutoffSmoothingSynth.noteOn (1, 60, 1.0f);
    render (cutoffSmoothingSynth, oneSampleBuffer);
    auto* smoothingVoice = dynamic_cast<deeppluck::DeepPluckVoice*> (
        cutoffSmoothingSynth.getVoice (0));
    keySnapshot.filterCutoffHz = 4000.0f;
    render (cutoffSmoothingSynth, oneSampleBuffer);
    test.check (smoothingVoice->getCurrentFilterCutoffHz() > 1000.0f
                    && smoothingVoice->getCurrentFilterCutoffHz() < 4000.0f,
                "cutoff automation ramps in logarithmic space");
    juce::AudioBuffer<float> smoothingBuffer (2, 960);
    render (cutoffSmoothingSynth, smoothingBuffer);
    test.near (smoothingVoice->getCurrentFilterCutoffHz(), 4000.0, 0.5,
               "cutoff logarithmic smoothing reaches target in twenty milliseconds");

    deeppluck::DeepPluckParameterSnapshot dryA;
    dryA.ampAttackSeconds = 0.001f;
    dryA.ampDecaySeconds = 0.001f;
    dryA.ampSustainLevel = 1.0f;
    dryA.filterEnabled = 0.0f;
    auto dryB = dryA;
    dryB.filterCutoffHz = 20.0f;
    dryB.filterResonance = 1.0f;
    dryB.filterDriveDb = 18.0f;
    dryB.filterEnvAmountOctaves = -5.0f;
    deeppluck::DeepPluckSynthesiser drySynthA;
    deeppluck::DeepPluckSynthesiser drySynthB;
    deeppluck::configureDeepPluckSynthesiser (drySynthA, dryA);
    deeppluck::configureDeepPluckSynthesiser (drySynthB, dryB);
    drySynthA.setCurrentPlaybackSampleRate (48000.0);
    drySynthB.setCurrentPlaybackSampleRate (48000.0);
    drySynthA.noteOn (1, 60, 1.0f);
    drySynthB.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> dryBufferA (2, 1024);
    juce::AudioBuffer<float> dryBufferB (2, 1024);
    render (drySynthA, dryBufferA);
    render (drySynthB, dryBufferB);
    auto dryDifference = 0.0f;
    for (auto sample = 0; sample < dryBufferA.getNumSamples(); ++sample)
        dryDifference = juce::jmax (dryDifference,
                                    std::abs (dryBufferA.getSample (0, sample)
                                              - dryBufferB.getSample (0, sample)));
    test.near (dryDifference, 0.0, 1.0e-7,
               "Filter Off preserves the dry V0.2.0b signal path");
}

void testTransientNoise (TestContext& test)
{
    constexpr double sampleRate = 48000.0;

    for (const auto rate : { 44100.0, 48000.0, 96000.0 })
    {
        deeppluck::DeepPluckTransientNoise source (3);
        source.prepare (rate);
        deeppluck::DeepPluckTransientNoiseParameters parameters;
        parameters.transientEnabled = 1.0f;
        parameters.transientLevelDb = 0.0f;
        parameters.transientDecaySeconds = 0.010f;
        parameters.transientTone = 0.5f;
        parameters.noiseEnabled = 0.0f;
        source.setParameters (parameters);
        source.noteOn (60);

        test.near (source.processSample(), 0.0, 1.0e-7,
                   "transient wavelet begins at a zero crossing");
        auto peak = 0.0f;
        const auto renderSamples = static_cast<int> (rate * 0.012) + 64;
        for (auto sample = 1; sample < renderSamples; ++sample)
        {
            const auto value = source.processSample();
            test.check (std::isfinite (value), "transient remains finite at every sample rate");
            peak = juce::jmax (peak, std::abs (value));
        }
        test.check (peak > 0.01f && peak <= 0.451f,
                    "calibrated transient is audible and bounded");
        test.check (! source.isTransientActive(),
                    "transient reaches deterministic silence after its decay");
    }

    deeppluck::DeepPluckTransientNoise timing (0);
    timing.prepare (sampleRate);
    deeppluck::DeepPluckTransientNoiseParameters timingParameters;
    timingParameters.transientEnabled = 1.0f;
    timingParameters.transientLevelDb = 0.0f;
    timingParameters.transientDecaySeconds = 0.010f;
    timingParameters.noiseEnabled = 0.0f;
    timing.setParameters (timingParameters);
    timing.noteOn (60);
    auto transientSamples = 0;
    while (timing.isTransientActive() && transientSamples < 2000)
    {
        static_cast<void> (timing.processSample());
        ++transientSamples;
    }
    std::cout << "METRIC: transient 10 ms decay samples = " << transientSamples << '\n';
    test.check (std::abs (transientSamples - 492) <= 2,
                "transient decay reaches -60 dB after its defined time plus micro attack");

    timingParameters.transientEnabled = 0.0f;
    timingParameters.noiseEnabled = 1.0f;
    timingParameters.noiseLevelDb = 0.0f;
    timingParameters.noiseDecaySeconds = 0.050f;
    timing.setParameters (timingParameters);
    timing.noteOn (60);
    auto noiseSamples = 0;
    auto noiseSum = 0.0;
    auto noisePeak = 0.0f;
    while (timing.isNoiseActive() && noiseSamples < 4000)
    {
        const auto value = timing.processSample();
        noiseSum += value;
        noisePeak = juce::jmax (noisePeak, std::abs (value));
        ++noiseSamples;
    }
    std::cout << "METRIC: noise 50 ms decay samples = " << noiseSamples << '\n';
    test.check (std::abs (noiseSamples - 2424) <= 2,
                "noise decay reaches -60 dB after its defined time plus micro attack");
    test.check (noisePeak > 0.001f && noisePeak < 0.31f,
                "calibrated noise is audible and bounded");
    test.check (std::abs (noiseSum / noiseSamples) < 0.01,
                "noise path remains practically DC-free");

    const auto countTransientCrossings = [] (float tone)
    {
        deeppluck::DeepPluckTransientNoise source (1);
        source.prepare (sampleRate);
        deeppluck::DeepPluckTransientNoiseParameters parameters;
        parameters.transientEnabled = 1.0f;
        parameters.transientLevelDb = 0.0f;
        parameters.transientDecaySeconds = 0.050f;
        parameters.transientTone = tone;
        parameters.noiseEnabled = 0.0f;
        source.setParameters (parameters);
        source.noteOn (60);
        auto crossings = 0;
        auto previous = source.processSample();
        for (auto sample = 1; sample < 2000; ++sample)
        {
            const auto value = source.processSample();
            if ((value >= 0.0f) != (previous >= 0.0f))
                ++crossings;
            previous = value;
        }
        return crossings;
    };
    const auto darkCrossings = countTransientCrossings (0.0f);
    const auto brightCrossings = countTransientCrossings (1.0f);
    test.check (brightCrossings > darkCrossings * 5,
                "Transient Tone moves the wavelet spectral centre upward");

    const auto noiseBrightness = [] (float tone, std::uint32_t voiceIndex)
    {
        deeppluck::DeepPluckTransientNoise source (voiceIndex);
        source.prepare (sampleRate);
        deeppluck::DeepPluckTransientNoiseParameters parameters;
        parameters.transientEnabled = 0.0f;
        parameters.noiseEnabled = 1.0f;
        parameters.noiseLevelDb = 0.0f;
        parameters.noiseDecaySeconds = 0.500f;
        parameters.noiseTone = tone;
        source.setParameters (parameters);
        source.noteOn (60);
        auto previous = source.processSample();
        auto differenceEnergy = 0.0;
        auto signalEnergy = 0.0;
        for (auto sample = 1; sample < 12000; ++sample)
        {
            const auto value = source.processSample();
            const auto difference = value - previous;
            differenceEnergy += static_cast<double> (difference) * difference;
            signalEnergy += static_cast<double> (value) * value;
            previous = value;
        }
        return differenceEnergy / juce::jmax (signalEnergy, 1.0e-20);
    };
    const auto darkNoiseBrightness = noiseBrightness (0.0f, 4);
    const auto brightNoiseBrightness = noiseBrightness (1.0f, 4);
    std::cout << "METRIC: noise bright/dark difference-energy ratio = "
              << brightNoiseBrightness / darkNoiseBrightness << '\n';
    test.check (brightNoiseBrightness > darkNoiseBrightness * 8.0,
                "Noise Tone creates a clearly brighter spectrum");

    deeppluck::DeepPluckTransientNoise noiseA (5);
    deeppluck::DeepPluckTransientNoise noiseB (6);
    deeppluck::DeepPluckTransientNoise noiseARepeat (5);
    deeppluck::DeepPluckTransientNoiseParameters noiseOnly;
    noiseOnly.transientEnabled = 0.0f;
    noiseOnly.noiseEnabled = 1.0f;
    noiseOnly.noiseLevelDb = 0.0f;
    noiseOnly.noiseDecaySeconds = 0.500f;
    for (auto* source : { &noiseA, &noiseB, &noiseARepeat })
    {
        source->prepare (sampleRate);
        source->setParameters (noiseOnly);
        source->noteOn (64);
    }
    auto differenceEnergy = 0.0;
    auto repeatDifference = 0.0;
    auto correlation = 0.0;
    auto energyA = 0.0;
    auto energyB = 0.0;
    for (auto sample = 0; sample < 4096; ++sample)
    {
        const auto a = noiseA.processSample();
        const auto b = noiseB.processSample();
        const auto repeated = noiseARepeat.processSample();
        differenceEnergy += std::abs (a - b);
        repeatDifference = juce::jmax (repeatDifference,
                                       static_cast<double> (std::abs (a - repeated)));
        correlation += static_cast<double> (a) * b;
        energyA += static_cast<double> (a) * a;
        energyB += static_cast<double> (b) * b;
    }
    const auto normalisedCorrelation = correlation
        / std::sqrt (juce::jmax (energyA * energyB, 1.0e-20));
    std::cout << "METRIC: per-voice noise correlation = "
              << normalisedCorrelation << '\n';
    test.check (differenceEnergy > 1.0, "different voices use decorrelated noise sequences");
    test.near (repeatDifference, 0.0, 0.0,
               "identically prepared voice seed renders deterministically");
    test.check (std::abs (normalisedCorrelation) < 0.25,
                "per-voice noise correlation remains low");

    const auto transientRmsAtLevel = [] (float levelDb)
    {
        deeppluck::DeepPluckTransientNoise source (7);
        source.prepare (sampleRate);
        deeppluck::DeepPluckTransientNoiseParameters parameters;
        parameters.transientEnabled = 1.0f;
        parameters.transientLevelDb = levelDb;
        parameters.transientDecaySeconds = 0.040f;
        parameters.noiseEnabled = 0.0f;
        source.setParameters (parameters);
        source.noteOn (60);
        auto energy = 0.0;
        for (auto sample = 0; sample < 1800; ++sample)
        {
            const auto value = source.processSample();
            energy += static_cast<double> (value) * value;
        }
        return std::sqrt (energy / 1800.0);
    };
    const auto loudTransient = transientRmsAtLevel (0.0f);
    const auto quietTransient = transientRmsAtLevel (-20.0f);
    test.check (loudTransient > quietTransient * 9.9
                    && loudTransient < quietTransient * 10.1,
                "Transient Level follows its dB contract");

    deeppluck::DeepPluckTransientNoise live (8);
    live.prepare (sampleRate);
    deeppluck::DeepPluckTransientNoiseParameters liveParameters;
    liveParameters.transientEnabled = 1.0f;
    liveParameters.transientDecaySeconds = 0.100f;
    liveParameters.noiseEnabled = 1.0f;
    liveParameters.noiseDecaySeconds = 1.500f;
    live.setParameters (liveParameters);
    live.noteOn (60);
    for (auto sample = 0; sample < 200; ++sample)
        static_cast<void> (live.processSample());
    const auto transientBeforeShortening = live.getTransientEnvelopeLevel();
    const auto noiseBeforeShortening = live.getNoiseEnvelopeLevel();
    liveParameters.transientDecaySeconds = 0.001f;
    liveParameters.noiseDecaySeconds = 0.005f;
    live.setParameters (liveParameters);
    test.near (live.getTransientEnvelopeLevel(), transientBeforeShortening, 0.0,
               "shortening active Transient Decay preserves level");
    test.near (live.getNoiseEnvelopeLevel(), noiseBeforeShortening, 0.0,
               "shortening active Noise Decay preserves level");
    for (auto sample = 0; sample < 300; ++sample)
        static_cast<void> (live.processSample());
    test.check (! live.isTransientActive() && ! live.isNoiseActive(),
                "shorter live decays finish both active one-shots promptly");

    liveParameters.transientDecaySeconds = 0.005f;
    liveParameters.noiseDecaySeconds = 0.010f;
    live.setParameters (liveParameters);
    live.noteOn (60);
    for (auto sample = 0; sample < 50; ++sample)
        static_cast<void> (live.processSample());
    liveParameters.transientDecaySeconds = 0.100f;
    liveParameters.noiseDecaySeconds = 1.500f;
    live.setParameters (liveParameters);
    for (auto sample = 0; sample < 1000; ++sample)
        static_cast<void> (live.processSample());
    test.check (live.isTransientActive() && live.isNoiseActive(),
                "longer live decays extend both still-active one-shots");

    liveParameters.transientEnabled = 0.0f;
    liveParameters.noiseEnabled = 0.0f;
    live.setParameters (liveParameters);
    static_cast<void> (live.processSample());
    test.check (live.getTransientMix() < 1.0f && live.getTransientMix() > 0.99f,
                "Transient bypass starts with a smooth ramp");
    test.check (live.getNoiseMix() < 1.0f && live.getNoiseMix() > 0.99f,
                "Noise bypass starts with a smooth ramp");
    for (auto sample = 0; sample < 240; ++sample)
        static_cast<void> (live.processSample());
    test.near (live.getTransientMix(), 0.0, 1.0e-6,
               "Transient bypass reaches exact mute after five milliseconds");
    test.near (live.getNoiseMix(), 0.0, 1.0e-6,
               "Noise bypass reaches exact mute after five milliseconds");
}

void testTransientNoiseIntegration (TestContext& test)
{
    constexpr double sampleRate = 48000.0;
    deeppluck::DeepPluckParameterSnapshot dryA;
    dryA.ampAttackSeconds = 0.001f;
    dryA.ampDecaySeconds = 0.001f;
    dryA.ampSustainLevel = 1.0f;
    dryA.filterEnabled = 0.0f;
    dryA.transientEnabled = 0.0f;
    dryA.noiseEnabled = 0.0f;
    dryA.osc2Enabled = 0.0f;
    auto dryB = dryA;
    dryB.transientLevelDb = 0.0f;
    dryB.transientDecaySeconds = 0.100f;
    dryB.transientTone = 1.0f;
    dryB.noiseLevelDb = 0.0f;
    dryB.noiseDecaySeconds = 1.500f;
    dryB.noiseTone = 1.0f;

    deeppluck::DeepPluckSynthesiser drySynthA;
    deeppluck::DeepPluckSynthesiser drySynthB;
    deeppluck::configureDeepPluckSynthesiser (drySynthA, dryA);
    deeppluck::configureDeepPluckSynthesiser (drySynthB, dryB);
    drySynthA.setCurrentPlaybackSampleRate (sampleRate);
    drySynthB.setCurrentPlaybackSampleRate (sampleRate);
    drySynthA.noteOn (1, 60, 1.0f);
    drySynthB.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> dryBufferA (2, 2048);
    juce::AudioBuffer<float> dryBufferB (2, 2048);
    render (drySynthA, dryBufferA);
    render (drySynthB, dryBufferB);
    auto legacyDifference = 0.0f;
    for (auto sample = 0; sample < dryBufferA.getNumSamples(); ++sample)
        legacyDifference = juce::jmax (
            legacyDifference,
            std::abs (dryBufferA.getSample (0, sample) - dryBufferB.getSample (0, sample)));
    test.near (legacyDifference, 0.0, 1.0e-7,
               "both layers Off preserve the exact V0.3 voice path");

    auto active = dryA;
    active.transientEnabled = 1.0f;
    active.transientLevelDb = 0.0f;
    active.transientDecaySeconds = 0.050f;
    active.noiseEnabled = 1.0f;
    active.noiseLevelDb = 0.0f;
    active.noiseDecaySeconds = 0.250f;
    deeppluck::DeepPluckSynthesiser activeSynth;
    deeppluck::configureDeepPluckSynthesiser (activeSynth, active);
    activeSynth.setCurrentPlaybackSampleRate (sampleRate);
    activeSynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> activeBuffer (2, 2048);
    render (activeSynth, activeBuffer);
    auto layerDifference = 0.0f;
    for (auto sample = 0; sample < activeBuffer.getNumSamples(); ++sample)
        layerDifference += std::abs (activeBuffer.getSample (0, sample)
                                     - dryBufferA.getSample (0, sample));
    test.check (layerDifference > 0.1,
                "active transient/noise layers audibly extend the V0.3 source");

    auto filtered = active;
    filtered.filterEnabled = 1.0f;
    filtered.filterCutoffHz = 500.0f;
    filtered.filterKeyTracking = 0.0f;
    filtered.filterEnvAmountOctaves = 0.0f;
    deeppluck::DeepPluckSynthesiser filteredSynth;
    deeppluck::configureDeepPluckSynthesiser (filteredSynth, filtered);
    filteredSynth.setCurrentPlaybackSampleRate (sampleRate);
    filteredSynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> filteredBuffer (2, 2048);
    render (filteredSynth, filteredBuffer);
    test.check (rms (filteredBuffer) < rms (activeBuffer) * 0.8f,
                "main filter shapes the complete source sum including new layers");

    auto slowAttack = active;
    slowAttack.ampAttackSeconds = 1.0f;
    deeppluck::DeepPluckSynthesiser slowAttackSynth;
    deeppluck::configureDeepPluckSynthesiser (slowAttackSynth, slowAttack);
    slowAttackSynth.setCurrentPlaybackSampleRate (sampleRate);
    slowAttackSynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> slowAttackBuffer (2, 512);
    render (slowAttackSynth, slowAttackBuffer);
    test.check (rms (slowAttackBuffer) < rms (activeBuffer) * 0.2f,
                "Amp Envelope controls the complete transient/noise source sum");

    deeppluck::DeepPluckSynthesiser offsetSynth;
    deeppluck::configureDeepPluckSynthesiser (offsetSynth, active);
    offsetSynth.setCurrentPlaybackSampleRate (sampleRate);
    juce::AudioBuffer<float> offsetBuffer (2, 128);
    juce::MidiBuffer offsetMidi;
    offsetMidi.addEvent (juce::MidiMessage::noteOn (1, 60, 1.0f), 37);
    render (offsetSynth, offsetBuffer, offsetMidi);
    test.near (maximumMagnitude (offsetBuffer, 0, 37), 0.0, 0.0,
               "new layers preserve silence before a sample-offset Note-On");
    test.check (maximumMagnitude (offsetBuffer, 37, 91) > 1.0e-6f,
                "new layers start with the voice at the exact MIDI offset");

    auto liveBypass = active;
    deeppluck::DeepPluckSynthesiser liveBypassSynth;
    deeppluck::configureDeepPluckSynthesiser (liveBypassSynth, liveBypass);
    liveBypassSynth.setCurrentPlaybackSampleRate (sampleRate);
    liveBypassSynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> oneSample (2, 1);
    render (liveBypassSynth, oneSample);
    auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (liveBypassSynth.getVoice (0));
    liveBypass.transientEnabled = 0.0f;
    liveBypass.noiseEnabled = 0.0f;
    render (liveBypassSynth, oneSample);
    test.check (voice->getCurrentTransientMix() < 1.0f
                    && voice->getCurrentTransientMix() > 0.99f,
                "voice applies transient bypass as a per-sample ramp");
    test.check (voice->getCurrentNoiseMix() < 1.0f
                    && voice->getCurrentNoiseMix() > 0.99f,
                "voice applies noise bypass as a per-sample ramp");
    juce::AudioBuffer<float> bypassRamp (2, 240);
    render (liveBypassSynth, bypassRamp);
    test.near (voice->getCurrentTransientMix(), 0.0, 1.0e-6,
               "voice transient bypass reaches exact mute in five milliseconds");
    test.near (voice->getCurrentNoiseMix(), 0.0, 1.0e-6,
               "voice noise bypass reaches exact mute in five milliseconds");

    auto lifecycle = active;
    lifecycle.ampReleaseSeconds = 0.005f;
    lifecycle.noiseDecaySeconds = 1.500f;
    deeppluck::DeepPluckSynthesiser lifecycleSynth;
    deeppluck::configureDeepPluckSynthesiser (lifecycleSynth, lifecycle);
    lifecycleSynth.setCurrentPlaybackSampleRate (sampleRate);
    lifecycleSynth.noteOn (1, 60, 1.0f);
    juce::AudioBuffer<float> lifecycleBuffer (2, 128);
    render (lifecycleSynth, lifecycleBuffer);
    lifecycleSynth.noteOff (1, 60, 0.0f, true);
    for (auto block = 0; block < 4; ++block)
        render (lifecycleSynth, lifecycleBuffer);
    test.check (countActiveVoices (lifecycleSynth) == 0,
                "long Noise Decay never extends the Amp-controlled voice lifecycle");
}

void testSynthesiser (TestContext& test)
{
    deeppluck::DeepPluckParameterSnapshot snapshot;
    snapshot.ampAttackSeconds = 0.001f;
    snapshot.ampDecaySeconds = 0.001f;
    snapshot.ampSustainLevel = 1.0f;
    snapshot.ampReleaseSeconds = 0.005f;
    snapshot.unisonVoices = 1.0f;
    snapshot.unisonDetuneCents = 0.0f;
    snapshot.stereoSpread = 0.0f;

    deeppluck::DeepPluckSynthesiser synth;
    deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
    synth.setCurrentPlaybackSampleRate (48000.0);
    test.check (synth.getNumVoices() == deeppluck::voiceCount, "synth owns exactly 16 voices");
    test.check (synth.getNumSounds() == 1, "synth owns exactly one sound");
    test.check (synth.isNoteStealingEnabled(), "JUCE note stealing is enabled");
    test.check (dynamic_cast<deeppluck::DeepPluckSound*> (synth.getSound (0).get()) != nullptr,
                "sound has production type");
    for (auto index = 0; index < synth.getNumVoices(); ++index)
        test.check (dynamic_cast<deeppluck::DeepPluckVoice*> (synth.getVoice (index)) != nullptr,
                    "voice has production type");

    juce::AudioBuffer<float> buffer (2, 1024);
    synth.noteOn (1, 60, 1.0f);
    render (synth, buffer);
    test.check (maximumMagnitude (buffer, 0, buffer.getNumSamples()) > 1.0e-5f,
                "note-on renders audible saw audio");
    auto stereoDifference = 0.0f;
    for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
        stereoDifference = juce::jmax (stereoDifference,
                                       std::abs (buffer.getSample (0, sample) - buffer.getSample (1, sample)));
    test.near (stereoDifference, 0.0, 1.0e-7, "voice renders dual-mono stereo");

    synth.noteOff (1, 60, 0.0f, true);
    for (auto block = 0; block < 4; ++block)
        render (synth, buffer);
    test.check (countActiveVoices (synth) == 0, "released voice clears itself after tail");

    for (auto note = 60; note < 76; ++note)
        synth.noteOn (1, note, 0.8f);
    test.check (countActiveVoices (synth) == 16, "16-note chord uses all voices");
    snapshot.ampReleaseSeconds = 0.5f;
    render (synth, buffer);
    synth.noteOff (1, 60, 0.0f, true);
    render (synth, buffer);
    synth.noteOn (1, 76, 0.8f);
    test.check (countActiveVoices (synth) == 16, "17th note steals without growing voice collection");
    auto newestNotePresent = false;
    auto releasedNoteWasStolen = true;
    for (auto index = 0; index < synth.getNumVoices(); ++index)
    {
        newestNotePresent = newestNotePresent || synth.getVoice (index)->getCurrentlyPlayingNote() == 76;
        releasedNoteWasStolen = releasedNoteWasStolen
                                && synth.getVoice (index)->getCurrentlyPlayingNote() != 60;
    }
    test.check (newestNotePresent, "17th note sounds after JUCE voice stealing");
    test.check (releasedNoteWasStolen, "voice stealing prefers a released voice");
    render (synth, buffer);
    test.check (bufferIsFinite (buffer) && maximumMagnitude (buffer, 0, 1024) > 1.0e-5f,
                "stolen polyphony remains finite and audible");
    test.check (maximumMagnitude (buffer, 0, 1024) <= 1.29f,
                "fixed voice level keeps the theoretical internal sum bounded");
    synth.allNotesOff (0, false);
    test.check (countActiveVoices (synth) == 0, "hard all-notes-off clears every voice");

    const auto velocityLevel = [&] (float velocity)
    {
        deeppluck::DeepPluckSynthesiser velocitySynth;
        deeppluck::configureDeepPluckSynthesiser (velocitySynth, snapshot);
        velocitySynth.setCurrentPlaybackSampleRate (48000.0);
        velocitySynth.noteOn (1, 64, velocity);
        juce::AudioBuffer<float> velocityBuffer (2, 2048);
        render (velocitySynth, velocityBuffer);
        return rms (velocityBuffer);
    };
    test.check (velocityLevel (1.0f) > velocityLevel (0.25f) * 3.5f,
                "velocity controls gain approximately linearly");

    for (auto repetition = 0; repetition < 32; ++repetition)
    {
        synth.noteOn (1, 67, 0.7f);
        render (synth, buffer);
        synth.noteOff (1, 67, 0.0f, repetition % 2 == 0);
    }
    synth.allNotesOff (0, false);
    test.check (countActiveVoices (synth) == 0 && synth.getNumVoices() == 16,
                "repeated notes leave no hangs or collection growth");
}

void testSampleAccuracy (TestContext& test)
{
    deeppluck::DeepPluckParameterSnapshot snapshot;
    snapshot.ampAttackSeconds = 0.001f;
    snapshot.ampDecaySeconds = 0.001f;
    snapshot.ampSustainLevel = 1.0f;
    snapshot.ampReleaseSeconds = 0.001f;

    deeppluck::DeepPluckSynthesiser synth;
    deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
    synth.setCurrentPlaybackSampleRate (48000.0);
    juce::AudioBuffer<float> buffer (2, 128);
    juce::MidiBuffer midi;
    midi.addEvent (juce::MidiMessage::noteOn (1, 69, 1.0f), 37);
    render (synth, buffer, midi);
    test.near (maximumMagnitude (buffer, 0, 37), 0.0, 0.0, "note-on offset keeps preceding samples silent");
    test.check (maximumMagnitude (buffer, 37, 91) > 1.0e-6f, "note-on begins at sample offset 37");

    deeppluck::DeepPluckSynthesiser controlSynth;
    deeppluck::configureDeepPluckSynthesiser (controlSynth, snapshot);
    controlSynth.setCurrentPlaybackSampleRate (48000.0);
    juce::AudioBuffer<float> controlBuffer (2, 128);
    render (controlSynth, controlBuffer, midi);

    juce::MidiBuffer noteOff;
    noteOff.addEvent (juce::MidiMessage::noteOff (1, 69), 73);
    render (synth, buffer, noteOff);
    render (controlSynth, controlBuffer);
    test.check (maximumMagnitude (buffer, 0, 73) > 1.0e-6f, "note renders before note-off offset");
    auto differenceBeforeOffset = 0.0f;
    auto differenceAfterOffset = 0.0f;
    for (auto sample = 0; sample < 128; ++sample)
    {
        const auto difference = std::abs (buffer.getSample (0, sample)
                                          - controlBuffer.getSample (0, sample));
        if (sample < 73)
            differenceBeforeOffset = juce::jmax (differenceBeforeOffset, difference);
        else
            differenceAfterOffset = juce::jmax (differenceAfterOffset, difference);
    }
    test.near (differenceBeforeOffset, 0.0, 1.0e-7,
               "note-off leaves samples before its offset unchanged");
    test.check (differenceAfterOffset > 1.0e-6f,
                "note-off starts the release at its exact offset");
    for (auto block = 0; block < 4; ++block)
        render (synth, buffer);
    test.check (countActiveVoices (synth) == 0, "sample-offset note-off release eventually clears voice");

    deeppluck::DeepPluckSynthesiser partialSynth;
    deeppluck::configureDeepPluckSynthesiser (partialSynth, snapshot);
    partialSynth.setCurrentPlaybackSampleRate (48000.0);
    buffer.clear();
    partialSynth.renderNextBlock (buffer, midi, 20, 80);
    test.near (maximumMagnitude (buffer, 20, 17), 0.0, 0.0, "partial render is silent before event");
    test.check (maximumMagnitude (buffer, 37, 63) > 1.0e-6f, "partial render honours absolute MIDI offset");
    test.near (maximumMagnitude (buffer, 0, 20), 0.0, 0.0, "partial render preserves samples before startSample");
    test.near (maximumMagnitude (buffer, 100, 28), 0.0, 0.0, "partial render preserves samples after range");
}

void testMatrix (TestContext& test)
{
    constexpr std::array sampleRates { 44100.0, 48000.0, 96000.0 };
    constexpr std::array blockSizes { 1, 17, 32, 64, 127, 512, 1024 };

    for (const auto sampleRate : sampleRates)
        for (const auto blockSize : blockSizes)
        {
            DeepPluckAudioProcessor processor;
            auto& state = processor.getValueTreeState();
            setParameter (state, deeppluck::ParameterIDs::ampAttack, 0.001f);
            setParameter (state, deeppluck::ParameterIDs::ampDecay, 0.001f);
            setParameter (state, deeppluck::ParameterIDs::ampSustain, 1.0f);
            setParameter (state, deeppluck::ParameterIDs::ampRelease, 0.005f);
            processor.prepareToPlay (sampleRate, blockSize);

            juce::AudioBuffer<float> buffer (2, blockSize);
            buffer.clear();
            buffer.addSample (0, 0, 1.0f);
            buffer.addSample (1, 0, -1.0f);
            juce::MidiBuffer bypassMidi;
            processor.processBlockBypassed (buffer, bypassMidi);
            test.near (maximumMagnitude (buffer, 0, blockSize), 0.0, 0.0,
                       "bypassed instrument output is deterministically silent");

            juce::MidiBuffer emptyMidi;
            processor.processBlock (buffer, emptyMidi);
            test.near (maximumMagnitude (buffer, 0, blockSize), 0.0, 0.0,
                       "matrix is silent without active voices");

            juce::MidiBuffer midi;
            midi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 0);
            if (blockSize > 1)
                midi.addEvent (juce::MidiMessage::noteOn (1, 64, 0.6f), blockSize - 1);
            processor.processBlock (buffer, midi);
            test.check (bufferIsFinite (buffer), "matrix note-on block remains finite");
            auto heardActiveVoice = maximumMagnitude (buffer, 0, blockSize) > 0.0f;

            for (auto block = 0; block < 3; ++block)
            {
                buffer.clear();
                processor.processBlock (buffer, emptyMidi);
                test.check (bufferIsFinite (buffer), "matrix empty-MIDI continuation remains finite");
                heardActiveVoice = heardActiveVoice
                                   || maximumMagnitude (buffer, 0, blockSize) > 0.0f;
            }
            test.check (heardActiveVoice, "matrix produces audio immediately after note-on");

            juce::MidiBuffer offMidi;
            offMidi.addEvent (juce::MidiMessage::noteOff (1, 60), 0);
            if (blockSize > 1)
                offMidi.addEvent (juce::MidiMessage::noteOff (1, 64), blockSize - 1);
            processor.processBlock (buffer, offMidi);
            test.check (bufferIsFinite (buffer), "matrix last-sample event remains finite");

            juce::MidiBuffer sameBlock;
            sameBlock.addEvent (juce::MidiMessage::noteOn (1, 72, 0.7f), 0);
            sameBlock.addEvent (juce::MidiMessage::noteOff (1, 72), blockSize - 1);
            processor.processBlock (buffer, sameBlock);
            test.check (bufferIsFinite (buffer), "same-block note-on/note-off remains finite");
            processor.releaseResources();
            processor.prepareToPlay (sampleRate, blockSize);
            processor.processBlock (buffer, emptyMidi);
            test.check (bufferIsFinite (buffer), "repeated prepare/release lifecycle remains stable");
            processor.releaseResources();
        }
}

void testWaveform (TestContext& test)
{
    for (const auto rate : { 44100.0, 48000.0, 96000.0, 44117.0 })
    {
        deeppluck::DeepPluckOscillator paused;
        paused.prepare (rate);
        paused.reset();
        paused.setFrequency (0.0);
        for (const auto shape : { deeppluck::DeepPluckOscillatorWaveform::triangle,
                                 deeppluck::DeepPluckOscillatorWaveform::sine,
                                 deeppluck::DeepPluckOscillatorWaveform::pulse })
        {
            paused.setWaveform (shape);
            for (auto i = 0; i < static_cast<int> (rate * 0.005) + 10; ++i)
                test.near (paused.processSample(), 0.0, 0.0,
                           "zero-frequency oscillator stays silent during waveform transition");
            for (auto index = 0; index < 5; ++index)
                test.near (paused.getWaveformMix (
                               static_cast<deeppluck::DeepPluckOscillatorWaveform> (index)),
                           index == static_cast<int> (shape) ? 1.0 : 0.0, 0.0,
                           "all waveform smoothers settle exactly even at zero frequency");
        }
        paused.setFrequency (440.0);
        for (auto i = 0; i < 1024; ++i)
            test.check (std::isfinite (paused.processSample()),
                        "waveform ramp resumes safely after zero-frequency processing");
        test.near (paused.getWaveformMix (deeppluck::DeepPluckOscillatorWaveform::pulse),
                   1.0, 0.0, "settled waveform mix remains exact after wakeup");
    }

    using Waveform = deeppluck::DeepPluckOscillatorWaveform;
    constexpr std::array waveforms {
        Waveform::saw, Waveform::triangle, Waveform::square,
        Waveform::sine, Waveform::pulse
    };

    test.check (deeppluck::DeepPluckOscillator::waveformFromParameter (-9.0f)
                    == Waveform::saw,
                "negative waveform values clamp to Saw");
    test.check (deeppluck::DeepPluckOscillator::waveformFromParameter (99.0f)
                    == Waveform::pulse,
                "high waveform values clamp to Pulse");
    test.check (deeppluck::DeepPluckOscillator::waveformFromParameter (
                    std::numeric_limits<float>::quiet_NaN()) == Waveform::saw,
                "non-finite waveform values use Saw");

    const auto referencePolyBlep = [] (double phase, double increment)
    {
        if (phase < increment)
        {
            const auto x = phase / increment;
            return x + x - x * x - 1.0;
        }
        if (phase > 1.0 - increment)
        {
            const auto x = (phase - 1.0) / increment;
            return x * x + x + x + 1.0;
        }
        return 0.0;
    };
    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
        for (const auto frequency : { 110.0, 997.0, 7000.0 })
            for (const auto startPhase : { 0.0, 0.173, 0.999 })
            {
                deeppluck::DeepPluckOscillator oscillator;
                oscillator.prepare (sampleRate);
                oscillator.reset (startPhase, Waveform::saw, 0.5f);
                oscillator.setFrequency (frequency);
                const auto increment = frequency / sampleRate;
                const auto expected = (startPhase * 2.0 - 1.0)
                                      - referencePolyBlep (startPhase, increment);
                test.near (oscillator.processSample(), expected, 1.0e-7,
                           "Saw output matches the frozen V0.7.0 formula");
                auto expectedPhase = startPhase + increment;
                expectedPhase -= std::floor (expectedPhase);
                test.near (oscillator.getPhase(), expectedPhase, 1.0e-12,
                           "Saw phase matches the frozen V0.7.0 advance");
            }

    deeppluck::DeepPluckOscillator square;
    deeppluck::DeepPluckOscillator pulse;
    square.prepare (48000.0);
    pulse.prepare (48000.0);
    square.reset (0.137, Waveform::square, 0.05f);
    pulse.reset (0.137, Waveform::pulse, 0.50f);
    square.setFrequency (4321.0);
    pulse.setFrequency (4321.0);
    for (auto sample = 0; sample < 4096; ++sample)
    {
        test.near (square.processSample(), pulse.processSample(), 1.0e-7,
                   "Pulse at fifty percent is sample-equal to Square");
        test.near (square.getPhase(), pulse.getPhase(), 1.0e-12,
                   "Pulse and Square retain identical phase");
    }

    for (const auto waveform : waveforms)
    {
        deeppluck::DeepPluckOscillator oscillator;
        oscillator.prepare (48000.0);
        oscillator.reset (0.0, waveform, waveform == Waveform::pulse ? 0.05f : 0.5f);
        oscillator.setFrequency (17891.0);
        auto peak = 0.0f;
        for (auto sample = 0; sample < 48000; ++sample)
        {
            const auto value = oscillator.processSample();
            test.check (std::isfinite (value), "every oscillator waveform stays finite");
            peak = juce::jmax (peak, std::abs (value));
        }
        test.check (peak <= 1.05f, "every oscillator waveform remains peak bounded");
    }

    deeppluck::DeepPluckOscillator sinePeak;
    sinePeak.prepare (48000.0);
    sinePeak.reset (0.25, Waveform::sine, 0.5f);
    sinePeak.setFrequency (1000.0);
    test.near (sinePeak.processSample(), 0.82, 1.0e-5,
               "Sine uses the fixed 0.82 peak calibration");

    auto maximumSawRatio = 0.0;
    auto maximumSquareRatio = 0.0;
    auto maximumPulseRatio = 0.0;
    auto maximumTriangleRatio = 0.0;
    auto maximumSineThdnDb = -300.0;
    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
        for (const auto fundamentalBin : { 3, 37, 173 })
        {
            const auto compare = [sampleRate, fundamentalBin] (
                                     Waveform waveform, float width)
            {
                const auto production = measureWaveformSpectrum (
                    waveform, width, sampleRate, fundamentalBin, true);
                const auto naive = measureWaveformSpectrum (
                    waveform, width, sampleRate, fundamentalBin, false);
                return normalisedUnwantedEnergy (production)
                       / juce::jmax (1.0e-30, normalisedUnwantedEnergy (naive));
            };
            maximumSawRatio = juce::jmax (maximumSawRatio,
                                           compare (Waveform::saw, 0.5f));
            maximumSquareRatio = juce::jmax (maximumSquareRatio,
                                              compare (Waveform::square, 0.5f));
            for (const auto width : { 0.25f, 0.50f, 0.75f })
                maximumPulseRatio = juce::jmax (maximumPulseRatio,
                                                 compare (Waveform::pulse, width));
            maximumTriangleRatio = juce::jmax (maximumTriangleRatio,
                                                compare (Waveform::triangle, 0.5f));
            const auto sine = measureWaveformSpectrum (
                Waveform::sine, 0.5f, sampleRate, fundamentalBin, true);
            const auto sineThdn = normalisedUnwantedEnergy (sine);
            maximumSineThdnDb = juce::jmax (
                maximumSineThdnDb, 10.0 * std::log10 (juce::jmax (1.0e-30, sineThdn)));
            test.check (std::isfinite (sineThdn),
                        "coherent Sine spectrum remains finite");
        }
    std::cout << "METRIC: spectral unwanted/naive maxima Saw,Square,Pulse,Triangle = "
              << maximumSawRatio << ", " << maximumSquareRatio << ", "
              << maximumPulseRatio << ", " << maximumTriangleRatio << '\n';
    std::cout << "METRIC: coherent Sine maximum THD+N = "
              << maximumSineThdnDb << " dB\n";
    test.check (maximumSawRatio <= 0.65, "PolyBLEP Saw meets its alias-energy limit");
    test.check (maximumSquareRatio <= 0.65,
                "dual-edge PolyBLEP Square meets its alias-energy limit");
    test.check (maximumPulseRatio <= 0.65,
                "dual-edge PolyBLEP Pulse meets its alias-energy limit");
    test.check (maximumTriangleRatio <= 0.85,
                "integrated bandlimited Triangle meets its alias-energy limit");
    test.check (maximumSineThdnDb <= -90.0,
                "coherent Sine THD+N remains at or below minus ninety dB");

    for (const auto waveform : waveforms)
    {
        deeppluck::DeepPluckOscillator oscillator;
        oscillator.prepare (48000.0);
        oscillator.reset (0.0, waveform, waveform == Waveform::pulse ? 0.25f : 0.5f);
        oscillator.setFrequency (1000.0);
        auto previous = oscillator.processSample();
        auto risingCrossings = 0;
        for (auto sample = 1; sample < 48000; ++sample)
        {
            const auto current = oscillator.processSample();
            if (previous <= 0.0f && current > 0.0f)
                ++risingCrossings;
            previous = current;
        }
        test.check (std::abs (risingCrossings - 1000) <= 1,
                    "each waveform preserves frequency within 0.1 percent");
    }

    for (const auto width : { 0.05f, 0.25f, 0.50f, 0.75f, 0.95f })
    {
        const auto spectrum = measureWaveformSpectrum (
            Waveform::pulse, width, 48000.0, 8, true);
        test.check (std::abs (spectrum.mean) <= 0.002,
                    "stationary Pulse is DC-corrected across its complete range");
        test.check (spectrum.peak <= 0.61,
                    "Pulse peak calibration remains bounded at width extremes");
    }
    const auto squareSpectrum = measureWaveformSpectrum (
        Waveform::square, 0.5f, 48000.0, 8, true);
    const auto sineSpectrum = measureWaveformSpectrum (
        Waveform::sine, 0.5f, 48000.0, 8, true);
    test.check (std::abs (squareSpectrum.mean) <= 0.001,
                "stationary Square has negligible DC");
    test.check (std::abs (sineSpectrum.mean) <= 1.0e-5,
                "stationary Sine has negligible DC");

    deeppluck::DeepPluckOscillator triangle;
    triangle.prepare (48000.0);
    triangle.reset (0.379, Waveform::triangle, 0.5f);
    triangle.setFrequency (375.0);
    constexpr int triangleWindow = 16384;
    std::array<double, 2> triangleEnergy {};
    for (auto sample = 0; sample < triangleWindow * 4; ++sample)
    {
        const auto value = triangle.processSample();
        if (sample >= triangleWindow * 2)
        {
            const auto window = (sample - triangleWindow * 2) / triangleWindow;
            triangleEnergy[static_cast<std::size_t> (window)] += value * value;
        }
    }
    const auto triangleRmsA = std::sqrt (triangleEnergy[0] / triangleWindow);
    const auto triangleRmsB = std::sqrt (triangleEnergy[1] / triangleWindow);
    test.check (std::abs (triangleRmsB / triangleRmsA - 1.0) <= 0.05,
                "Triangle has no late-window level decay or drift");
    for (const auto startPhase : { 0.0, 0.125, 0.5, 0.875 })
    {
        deeppluck::DeepPluckOscillator first;
        deeppluck::DeepPluckOscillator second;
        first.prepare (48000.0);
        second.prepare (48000.0);
        first.reset (startPhase, Waveform::triangle, 0.5f);
        second.reset (startPhase, Waveform::triangle, 0.5f);
        first.setFrequency (987.0);
        second.setFrequency (987.0);
        for (auto sample = 0; sample < 2048; ++sample)
            test.near (first.processSample(), second.processSample(), 0.0,
                       "Triangle reset is deterministic at every declared start phase");
    }

    for (const auto from : waveforms)
        for (const auto to : waveforms)
        {
            if (from == to)
                continue;
            deeppluck::DeepPluckOscillator switched;
            deeppluck::DeepPluckOscillator sourceReference;
            deeppluck::DeepPluckOscillator targetReference;
            for (auto* oscillator : { &switched, &sourceReference, &targetReference })
            {
                oscillator->prepare (48000.0);
                oscillator->setFrequency (1200.0);
            }
            switched.reset (0.0, from, 0.5f);
            sourceReference.reset (0.0, from, 0.5f);
            targetReference.reset (0.0, to, 0.5f);
            auto previousSwitch = 0.0f;
            auto previousSource = 0.0f;
            auto previousTarget = 0.0f;
            for (auto sample = 0; sample < 64; ++sample)
            {
                previousSwitch = switched.processSample();
                previousSource = sourceReference.processSample();
                previousTarget = targetReference.processSample();
            }
            const auto phaseBefore = switched.getPhase();
            switched.setWaveform (to);
            auto switchJump = 0.0f;
            auto referenceJump = 0.0f;
            for (auto sample = 0; sample < 240; ++sample)
            {
                const auto switchedValue = switched.processSample();
                const auto sourceValue = sourceReference.processSample();
                const auto targetValue = targetReference.processSample();
                switchJump = juce::jmax (switchJump,
                                          std::abs (switchedValue - previousSwitch));
                referenceJump = juce::jmax (
                    referenceJump, std::abs (sourceValue - previousSource),
                    std::abs (targetValue - previousTarget));
                auto weightSum = 0.0f;
                for (const auto waveform : waveforms)
                    weightSum += switched.getWaveformMix (waveform);
                test.near (weightSum, 1.0, 1.0e-5,
                           "waveform crossfade weights retain unity sum");
                test.check (std::isfinite (switchedValue),
                            "every directed live waveform switch remains finite");
                previousSwitch = switchedValue;
                previousSource = sourceValue;
                previousTarget = targetValue;
            }
            test.near (switched.getWaveformMix (to), 1.0, 1.0e-6,
                       "directed waveform switch reaches target after five milliseconds");
            test.near (switched.getPhase(),
                       std::fmod (phaseBefore + 240.0 * 1200.0 / 48000.0, 1.0),
                       1.0e-12, "live waveform switching does not reset phase");
            test.check (switchJump <= 1.25f * referenceJump + 0.02f,
                        "live waveform crossfade bounds adjacent-sample jumps");
        }

    deeppluck::DeepPluckOscillator rapid;
    rapid.prepare (48000.0);
    rapid.reset (0.0, Waveform::saw, 0.5f);
    rapid.setFrequency (733.0);
    for (auto transition = 0; transition < 20; ++transition)
    {
        rapid.setWaveform (waveforms[static_cast<std::size_t> ((transition + 1) % 5)]);
        for (auto sample = 0; sample < 48; ++sample)
            test.check (std::isfinite (rapid.processSample()),
                        "rapid waveform switch chains remain finite during overlapping ramps");
    }

    deeppluck::DeepPluckOscillator widthSmoothing;
    widthSmoothing.prepare (48000.0);
    widthSmoothing.reset (0.0, Waveform::pulse, 0.05f);
    widthSmoothing.setFrequency (375.0);
    const auto pulsePhaseBefore = widthSmoothing.getPhase();
    widthSmoothing.setPulseWidth (0.95f);
    auto transitionMean = 0.0;
    auto transitionPeak = 0.0f;
    auto previousPulse = widthSmoothing.processSample();
    transitionMean += previousPulse;
    for (auto sample = 1; sample < 960; ++sample)
    {
        const auto current = widthSmoothing.processSample();
        transitionMean += current;
        transitionPeak = juce::jmax (transitionPeak, std::abs (current - previousPulse));
        previousPulse = current;
        if (sample == 479)
            test.near (widthSmoothing.getCurrentPulseWidth(), 0.50, 1.0e-4,
                       "Pulse Width is halfway through its twenty-millisecond ramp");
    }
    test.near (widthSmoothing.getCurrentPulseWidth(), 0.95, 1.0e-6,
               "Pulse Width reaches its target after twenty milliseconds");
    test.near (widthSmoothing.getPhase(),
               std::fmod (pulsePhaseBefore + 960.0 * 375.0 / 48000.0, 1.0),
               1.0e-12, "live Pulse Width changes do not reset phase");
    test.check (std::abs (transitionMean / 960.0) <= 0.02,
                "Pulse Width transition remains DC controlled");
    auto stationaryPulseJump = 0.0f;
    for (const auto width : { 0.05f, 0.95f })
    {
        deeppluck::DeepPluckOscillator reference;
        reference.prepare (48000.0);
        reference.reset (0.0, Waveform::pulse, width);
        reference.setFrequency (375.0);
        auto previous = reference.processSample();
        for (auto sample = 1; sample < 960; ++sample)
        {
            const auto current = reference.processSample();
            stationaryPulseJump = juce::jmax (
                stationaryPulseJump, std::abs (current - previous));
            previous = current;
        }
    }
    std::cout << "METRIC: Pulse Width transition/reference maximum jump = "
              << transitionPeak << "/" << stationaryPulseJump << '\n';
    const auto calibratedPulseEdge = juce::jmax (stationaryPulseJump, 0.60f);
    test.check (transitionPeak <= 1.25f * calibratedPulseEdge + 0.02f,
                "Pulse Width smoothing remains bounded by the calibrated Pulse edge");

    for (const auto waveform : { Waveform::saw, Waveform::triangle,
                                 Waveform::square, Waveform::sine })
    {
        deeppluck::DeepPluckOscillator narrow;
        deeppluck::DeepPluckOscillator wide;
        narrow.prepare (48000.0);
        wide.prepare (48000.0);
        narrow.reset (0.25, waveform, 0.05f);
        wide.reset (0.25, waveform, 0.95f);
        narrow.setFrequency (997.0);
        wide.setFrequency (997.0);
        for (auto sample = 0; sample < 4096; ++sample)
            test.near (narrow.processSample(), wide.processSample(), 0.0,
                       "stored Pulse Width is audio-neutral outside Pulse mode");
    }
}

void testWaveformIntegration (TestContext& test)
{
    using Waveform = deeppluck::DeepPluckOscillatorWaveform;
    constexpr std::array waveforms {
        Waveform::saw, Waveform::triangle, Waveform::square,
        Waveform::sine, Waveform::pulse
    };

    for (auto primary = 0; primary < 5; ++primary)
        for (auto second = 0; second < 5; ++second)
        {
            DeepPluckAudioProcessor source;
            auto& sourceState = source.getValueTreeState();
            const auto primaryWidth = std::array { 0.05f, 0.50f, 0.95f }
                [static_cast<std::size_t> ((primary + second) % 3)];
            const auto secondWidth = std::array { 0.95f, 0.05f, 0.50f }
                [static_cast<std::size_t> ((primary * 2 + second) % 3)];
            setParameter (sourceState, deeppluck::ParameterIDs::osc1Waveform,
                          static_cast<float> (primary));
            setParameter (sourceState, deeppluck::ParameterIDs::osc1PulseWidth, primaryWidth);
            setParameter (sourceState, deeppluck::ParameterIDs::osc2Waveform,
                          static_cast<float> (second));
            setParameter (sourceState, deeppluck::ParameterIDs::osc2PulseWidth, secondWidth);
            juce::MemoryBlock stateData;
            source.getStateInformation (stateData);
            DeepPluckAudioProcessor restored;
            restored.setStateInformation (stateData.getData(),
                                          static_cast<int> (stateData.getSize()));
            const auto snapshot = deeppluck::DeepPluckParameterCache (
                restored.getValueTreeState()).getSnapshot();
            test.near (snapshot.osc1Waveform, primary, 0.0,
                       "all Oscillator 1 waveform choices survive state roundtrip");
            test.near (snapshot.osc2Waveform, second, 0.0,
                       "all Oscillator 2 waveform choices survive state roundtrip");
            test.near (snapshot.osc1PulseWidth, primaryWidth, 1.0e-4,
                       "Oscillator 1 Pulse Width survives state roundtrip");
            test.near (snapshot.osc2PulseWidth, secondWidth, 1.0e-4,
                       "Oscillator 2 Pulse Width survives state roundtrip");
        }

    DeepPluckAudioProcessor legacySource;
    auto legacyState = legacySource.getValueTreeState().copyState();
    setParameter (legacySource.getValueTreeState(), deeppluck::ParameterIDs::filterCutoff,
                  4321.0f);
    legacyState = legacySource.getValueTreeState().copyState();
    for (auto index = legacyState.getNumChildren(); --index >= 0;)
    {
        const auto id = legacyState.getChild (index)["id"].toString();
        if (id == deeppluck::ParameterIDs::osc1Waveform
            || id == deeppluck::ParameterIDs::osc1PulseWidth
            || id == deeppluck::ParameterIDs::osc2Waveform
            || id == deeppluck::ParameterIDs::osc2PulseWidth)
            legacyState.removeChild (index, nullptr);
    }
    DeepPluckAudioProcessor legacyTarget;
    legacyTarget.getValueTreeState().replaceState (deeppluck::mergeRestoredState (
        legacyTarget.getValueTreeState().copyState(), legacyState));
    const auto migrated = deeppluck::DeepPluckParameterCache (
        legacyTarget.getValueTreeState()).getSnapshot();
    test.near (migrated.filterCutoffHz, 4321.0, 0.11,
               "V0.7 existing sound values survive waveform migration");
    test.near (migrated.osc1Waveform, 0.0, 0.0,
               "V0.7 state receives Oscillator 1 Saw");
    test.near (migrated.osc1PulseWidth, 0.50, 0.0,
               "V0.7 state receives Oscillator 1 fifty-percent Pulse Width");
    test.near (migrated.osc2Waveform, 0.0, 0.0,
               "V0.7 state receives Oscillator 2 Saw");
    test.near (migrated.osc2PulseWidth, 0.50, 0.0,
               "V0.7 state receives Oscillator 2 fifty-percent Pulse Width");

    constexpr std::array<const char*, 4> newIDs {
        deeppluck::ParameterIDs::osc1Waveform, deeppluck::ParameterIDs::osc1PulseWidth,
        deeppluck::ParameterIDs::osc2Waveform, deeppluck::ParameterIDs::osc2PulseWidth
    };
    for (auto missingIndex = 0; missingIndex < 4; ++missingIndex)
    {
        DeepPluckAudioProcessor partialSource;
        auto& state = partialSource.getValueTreeState();
        setParameter (state, deeppluck::ParameterIDs::osc1Waveform, 3.0f);
        setParameter (state, deeppluck::ParameterIDs::osc1PulseWidth, 0.25f);
        setParameter (state, deeppluck::ParameterIDs::osc2Waveform, 4.0f);
        setParameter (state, deeppluck::ParameterIDs::osc2PulseWidth, 0.75f);
        auto partial = state.copyState();
        for (auto child = partial.getNumChildren(); --child >= 0;)
            if (partial.getChild (child)["id"].toString() == newIDs[missingIndex])
                partial.removeChild (child, nullptr);
        DeepPluckAudioProcessor partialTarget;
        partialTarget.getValueTreeState().replaceState (deeppluck::mergeRestoredState (
            partialTarget.getValueTreeState().copyState(), partial));
        const auto snapshot = deeppluck::DeepPluckParameterCache (
            partialTarget.getValueTreeState()).getSnapshot();
        const std::array actual { snapshot.osc1Waveform, snapshot.osc1PulseWidth,
                                  snapshot.osc2Waveform, snapshot.osc2PulseWidth };
        const std::array supplied { 3.0f, 0.25f, 4.0f, 0.75f };
        const std::array defaults { 0.0f, 0.50f, 0.0f, 0.50f };
        for (auto index = 0; index < 4; ++index)
            test.near (actual[static_cast<std::size_t> (index)],
                       index == missingIndex
                           ? defaults[static_cast<std::size_t> (index)]
                           : supplied[static_cast<std::size_t> (index)],
                       1.0e-4,
                       "partial V0.7.5 states default only the individually missing value");
    }

    for (const auto internalVoices : { 1, 4 })
        for (const auto primary : waveforms)
            for (const auto second : waveforms)
            {
                deeppluck::DeepPluckParameterSnapshot snapshot;
                snapshot.filterEnabled = 1.0f;
                snapshot.filterCutoffHz = 8000.0f;
                snapshot.ampAttackSeconds = 0.001f;
                snapshot.ampDecaySeconds = 0.001f;
                snapshot.ampSustainLevel = 1.0f;
                snapshot.transientEnabled = 1.0f;
                snapshot.noiseEnabled = 1.0f;
                snapshot.osc2Enabled = 1.0f;
                snapshot.osc2LevelDb = -6.0f;
                snapshot.unisonVoices = static_cast<float> (internalVoices);
                snapshot.unisonDetuneCents = internalVoices == 1 ? 0.0f : 9.0f;
                snapshot.stereoSpread = internalVoices == 1 ? 0.0f : 0.75f;
                snapshot.osc1Waveform = static_cast<float> (primary);
                snapshot.osc2Waveform = static_cast<float> (second);
                snapshot.osc1PulseWidth = primary == Waveform::pulse ? 0.05f : 0.50f;
                snapshot.osc2PulseWidth = second == Waveform::pulse ? 0.95f : 0.50f;
                deeppluck::DeepPluckSynthesiser synth;
                deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
                synth.setCurrentPlaybackSampleRate (48000.0);
                synth.noteOn (1, 60, 0.8f);
                juce::AudioBuffer<float> buffer (2, 127);
                for (auto block = 0; block < 8; ++block)
                {
                    render (synth, buffer);
                    test.check (bufferIsFinite (buffer),
                                "all waveform pairs remain finite with envelopes, filter, transient and noise");
                }
                auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (synth.getVoice (0));
                test.check (voice != nullptr, "waveform integration uses a production voice");
                if (voice != nullptr)
                {
                    test.check (voice->getPrimaryWaveform() == primary
                                    && voice->getSecondWaveform() == second,
                                "both oscillator waveform selections remain independent");
                    test.near (voice->getCurrentPrimaryPulseWidth(),
                               snapshot.osc1PulseWidth, 1.0e-5,
                               "Oscillator 1 owns its independent Pulse Width");
                    test.near (voice->getCurrentSecondPulseWidth(),
                               snapshot.osc2PulseWidth, 1.0e-5,
                               "Oscillator 2 owns its independent Pulse Width");
                    test.check (voice->getLatchedUnisonVoiceCount() == internalVoices,
                                "waveform expansion preserves latched Unison slot count");
                }
            }

    deeppluck::DeepPluckParameterSnapshot stealingSnapshot;
    stealingSnapshot.ampAttackSeconds = 0.001f;
    stealingSnapshot.ampDecaySeconds = 0.001f;
    stealingSnapshot.ampSustainLevel = 1.0f;
    stealingSnapshot.ampReleaseSeconds = 2.0f;
    stealingSnapshot.osc1Waveform = 4.0f;
    stealingSnapshot.osc1PulseWidth = 0.05f;
    stealingSnapshot.osc2Waveform = 1.0f;
    stealingSnapshot.osc2PulseWidth = 0.95f;
    deeppluck::DeepPluckSynthesiser stealingSynth;
    deeppluck::configureDeepPluckSynthesiser (stealingSynth, stealingSnapshot);
    stealingSynth.setCurrentPlaybackSampleRate (48000.0);
    for (auto note = 40; note < 57; ++note)
        stealingSynth.noteOn (1, note, 0.8f);
    test.check (stealingSynth.getNumVoices() == 16
                    && countActiveVoices (stealingSynth) == 16,
                "waveform expansion preserves exactly sixteen preallocated active voices");
    auto foundStolenNote = false;
    for (auto index = 0; index < stealingSynth.getNumVoices(); ++index)
        if (stealingSynth.getVoice (index)->getCurrentlyPlayingNote() == 56)
        {
            auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (
                stealingSynth.getVoice (index));
            foundStolenNote = voice != nullptr;
            if (voice != nullptr)
            {
                test.check (voice->getPrimaryWaveform() == Waveform::pulse
                                && voice->getSecondWaveform() == Waveform::triangle,
                            "stolen voice starts with current independent waveforms");
                test.near (voice->getCurrentPrimaryPulseWidth(), 0.05, 1.0e-6,
                           "stolen voice starts with current Pulse Width, not an old ramp");
            }
        }
    test.check (foundStolenNote, "newest note is present after waveform voice stealing");

    deeppluck::DeepPluckParameterSnapshot deterministicSnapshot;
    deterministicSnapshot.filterEnabled = 0.0f;
    deterministicSnapshot.transientEnabled = 0.0f;
    deterministicSnapshot.noiseEnabled = 0.0f;
    deterministicSnapshot.ampAttackSeconds = 0.001f;
    deterministicSnapshot.ampDecaySeconds = 0.001f;
    deterministicSnapshot.ampSustainLevel = 1.0f;
    deterministicSnapshot.osc1Waveform = 4.0f;
    deterministicSnapshot.osc1PulseWidth = 0.23f;
    deterministicSnapshot.osc2Waveform = 1.0f;
    deterministicSnapshot.osc2PulseWidth = 0.81f;
    deeppluck::DeepPluckSynthesiser deterministicA;
    deeppluck::DeepPluckSynthesiser deterministicB;
    deeppluck::configureDeepPluckSynthesiser (deterministicA, deterministicSnapshot);
    deeppluck::configureDeepPluckSynthesiser (deterministicB, deterministicSnapshot);
    deterministicA.setCurrentPlaybackSampleRate (48000.0);
    deterministicB.setCurrentPlaybackSampleRate (48000.0);
    deterministicA.noteOn (1, 64, 0.75f);
    deterministicB.noteOn (1, 64, 0.75f);
    juce::AudioBuffer<float> deterministicBufferA (2, 1024);
    juce::AudioBuffer<float> deterministicBufferB (2, 1024);
    render (deterministicA, deterministicBufferA);
    render (deterministicB, deterministicBufferB);
    for (auto channel = 0; channel < 2; ++channel)
        for (auto sample = 0; sample < 1024; ++sample)
            test.near (deterministicBufferA.getSample (channel, sample),
                       deterministicBufferB.getSample (channel, sample), 0.0,
                       "identical waveform runs produce identical samples");

    auto maximumMatrixPeak = 0.0f;
    for (auto primary = 0; primary < 5; ++primary)
        for (auto second = 0; second < 5; ++second)
        {
            DeepPluckAudioProcessor processor;
            auto& state = processor.getValueTreeState();
            setParameter (state, deeppluck::ParameterIDs::osc1Waveform,
                          static_cast<float> (primary));
            setParameter (state, deeppluck::ParameterIDs::osc1PulseWidth,
                          primary == 4 ? 0.05f : 0.50f);
            setParameter (state, deeppluck::ParameterIDs::osc2Waveform,
                          static_cast<float> (second));
            setParameter (state, deeppluck::ParameterIDs::osc2PulseWidth,
                          second == 4 ? 0.95f : 0.50f);
            setParameter (state, deeppluck::ParameterIDs::osc2Enabled, 1.0f);
            setParameter (state, deeppluck::ParameterIDs::osc2Level, 0.0f);
            setParameter (state, deeppluck::ParameterIDs::unisonVoices, 4.0f);
            setParameter (state, deeppluck::ParameterIDs::unisonDetune, 30.0f);
            setParameter (state, deeppluck::ParameterIDs::stereoSpread, 1.0f);
            setParameter (state, deeppluck::ParameterIDs::filterEnabled, 1.0f);
            setParameter (state, deeppluck::ParameterIDs::filterCutoff, 10000.0f);
            setParameter (state, deeppluck::ParameterIDs::filterResonance, 1.0f);
            setParameter (state, deeppluck::ParameterIDs::filterDrive, 18.0f);
            setParameter (state, deeppluck::ParameterIDs::transientEnabled, 1.0f);
            setParameter (state, deeppluck::ParameterIDs::noiseEnabled, 1.0f);
            setParameter (state, deeppluck::ParameterIDs::ampRelease, 10.0f);
            setParameter (state, deeppluck::ParameterIDs::lfoWaveform, 3.0f);
            setParameter (state, deeppluck::ParameterIDs::lfoRate, 12.0f);
            setParameter (state, deeppluck::ParameterIDs::lfoFilterAmount, 2.0f);
            setParameter (state, deeppluck::ParameterIDs::lfoPitchAmount, 50.0f);
            setParameter (state, deeppluck::ParameterIDs::lfoSpreadAmount, 1.0f);
            processor.prepareToPlay (96000.0, 127);
            juce::AudioBuffer<float> buffer (2, 127);
            juce::MidiBuffer notes;
            for (auto note = 0; note < 17; ++note)
                notes.addEvent (juce::MidiMessage::noteOn (
                                    1, 36 + note * 2, 1.0f), note == 16 ? 126 : 0);
            processor.processBlock (buffer, notes);
            auto peak = maximumMagnitude (buffer, 0, buffer.getNumSamples());
            juce::MidiBuffer empty;
            for (auto block = 0; block < 64; ++block)
            {
                processor.processBlock (buffer, empty);
                test.check (bufferIsFinite (buffer),
                            "maximum waveform matrix stress remains finite");
                peak = juce::jmax (peak,
                                   maximumMagnitude (buffer, 0, buffer.getNumSamples()));
            }
            maximumMatrixPeak = juce::jmax (maximumMatrixPeak, peak);
            test.check (peak <= 0.95f,
                        "every maximum waveform matrix combination preserves headroom");
        }
    std::cout << "METRIC: V0.7.5 25-combination maximum stress peak = "
              << maximumMatrixPeak << '\n';
}

void testStandaloneKeyboard (TestContext& test)
{
    const auto testRoot = juce::File { DEEPPLUCK_TEST_ROOT }
                              .getChildFile ("StandaloneKeyboard");
    static_cast<void> (testRoot.createDirectory());

    CapturingKeyboardListener ignoredVst3Listener;
    auto vst3Processor = createProcessorForWrapper (
        juce::AudioProcessor::wrapperType_VST3);
    auto vst3Preferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
        testRoot.getChildFile ("VST3.properties"));
    vst3Preferences->setWelcomeEnabled (false);
    DeepPluckAudioProcessorEditor vst3Editor (*vst3Processor, vst3Preferences,
                                               &ignoredVst3Listener);
    test.check (vst3Processor->wrapperType == juce::AudioProcessor::wrapperType_VST3,
                "test fixture creates the real VST3 wrapper identity");
    test.check (vst3Editor.getWidth() == 1536 && vst3Editor.getHeight() == 900,
                "VST3 retains the exact 1536 x 900 editor size");
    test.check (! vst3Editor.hasStandaloneKeyboard()
                    && vst3Editor.getStandaloneKeyboardPanelBounds().isEmpty()
                    && findDescendantWithID (vst3Editor, "standalone.keyboard") == nullptr,
                "VST3 creates no keyboard and reserves no keyboard area");
    test.check (ignoredVst3Listener.events.empty(),
                "VST3 ignores the Standalone test listener");

    CapturingKeyboardListener ignoredUndefinedListener;
    auto undefinedProcessor = createProcessorForWrapper (
        juce::AudioProcessor::wrapperType_Undefined);
    auto undefinedPreferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
        testRoot.getChildFile ("Undefined.properties"));
    undefinedPreferences->setWelcomeEnabled (false);
    DeepPluckAudioProcessorEditor undefinedEditor (*undefinedProcessor,
                                                    undefinedPreferences,
                                                    &ignoredUndefinedListener);
    test.check (undefinedProcessor->wrapperType
                        == juce::AudioProcessor::wrapperType_Undefined
                    && undefinedEditor.getWidth() == 1536
                    && undefinedEditor.getHeight() == 900
                    && ! undefinedEditor.hasStandaloneKeyboard()
                    && undefinedEditor.getStandaloneKeyboardPanelBounds().isEmpty(),
                "Undefined wrapper creates no keyboard or reserved keyboard area");
    test.check (ignoredUndefinedListener.events.empty(),
                "Undefined wrapper ignores the Standalone test listener");

    CapturingKeyboardListener capture;
    auto standaloneProcessor = createProcessorForWrapper (
        juce::AudioProcessor::wrapperType_Standalone);
    auto standalonePreferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
        testRoot.getChildFile ("Standalone.properties"));
    standalonePreferences->setWelcomeEnabled (false);

    juce::MemoryBlock stateBefore;
    standaloneProcessor->getStateInformation (stateBefore);
    const auto parameterCountBefore = standaloneProcessor->getParameters().size();

    {
        DeepPluckAudioProcessorEditor editor (*standaloneProcessor,
                                               standalonePreferences, &capture);
        test.check (standaloneProcessor->wrapperType
                        == juce::AudioProcessor::wrapperType_Standalone,
                    "test fixture creates the real Standalone wrapper identity");
        test.check (editor.getWidth() == 1536 && editor.getHeight() == 980,
                    "Standalone extends the fixed editor to 1536 x 980");
        test.check (editor.hasStandaloneKeyboard()
                        && editor.getStandaloneKeyboardPanelBounds()
                               == juce::Rectangle<int> (0, 900, 1536, 80),
                    "Standalone owns the exact 80-pixel keyboard strip below the unchanged GUI");
        test.check (editor.getStandaloneKeyboardBounds().getWidth() > 1300
                        && editor.getStandaloneKeyboardBounds().getHeight() > 50,
                    "piano keys use nearly the full width with a compact playable height");
        constexpr std::array<const char*, 5> unchangedMainComponentIds {
            "deeppluck.header", "deeppluck.oscillatorRow", "deeppluck.tabs",
            "page.main.content", "deeppluck.footer"
        };
        for (const auto* componentId : unchangedMainComponentIds)
        {
            const auto* vst3Component = findDescendantWithID (vst3Editor, componentId);
            const auto* standaloneComponent = findDescendantWithID (editor, componentId);
            test.check (vst3Component != nullptr && standaloneComponent != nullptr
                            && vst3Component->getBounds() == standaloneComponent->getBounds(),
                        std::string ("Standalone preserves VST3 main bounds for ")
                            + componentId);
        }

        auto* keyboard = dynamic_cast<juce::MidiKeyboardComponent*> (
            findDescendantWithID (editor, "standalone.keyboard"));
        test.check (keyboard != nullptr
                        && keyboard->getRangeStart() == 0
                        && keyboard->getRangeEnd() == 127
                        && keyboard->getMidiChannel() == 1,
                    "virtual keyboard is bounded to MIDI 0..127 on channel 1");
        test.check (keyboard != nullptr && ! keyboard->getWantsKeyboardFocus()
                        && ! keyboard->keyPressed (juce::KeyPress ('a')),
                    "computer-key mappings and piano keyboard focus are disabled");
        test.check (findDescendantWithID (editor, "standalone.keyboard.octaveDown") != nullptr
                        && findDescendantWithID (editor, "standalone.keyboard.octaveUp") != nullptr,
                    "Standalone exposes both accessible Octave controls");
        test.check (keyboard != nullptr && keyboard->getOctaveForMiddleC() == 4,
                    "note labels identify MIDI 36 as the documented C2 startup octave");

        test.check (editor.getStandaloneKeyboardLowestVisibleKey() == 36
                        && editor.isStandaloneOctaveDownEnabled()
                        && editor.isStandaloneOctaveUpEnabled(),
                    "virtual keyboard starts at C2 with both octave directions available");
        for (auto step = 0; step < 5; ++step)
            editor.standaloneOctaveDownForTests();
        test.check (editor.getStandaloneKeyboardLowestVisibleKey() == 0
                        && ! editor.isStandaloneOctaveDownEnabled(),
                    "Octave Down clamps at MIDI note zero and disables at the boundary");
        for (auto step = 0; step < 7; ++step)
            editor.standaloneOctaveUpForTests();
        test.check (editor.getStandaloneKeyboardLowestVisibleKey() == 60
                        && ! editor.isStandaloneOctaveUpEnabled(),
                    "Octave Up clamps at MIDI note 60 and disables at the boundary");
        test.check (keyboard != nullptr && keyboard->getLowestVisibleKey() == 60,
                    "Octave controls move the actual JUCE keyboard viewport");
        editor.standaloneOctaveDownForTests();
        editor.standaloneOctaveDownForTests();
        test.check (editor.getStandaloneKeyboardLowestVisibleKey() == 36,
                    "Octave controls return exactly to the documented C2 viewport");

        editor.standaloneNoteOnForTests (60, 0.70f);
        test.check (editor.isStandaloneNoteOnForTests (60)
                        && capture.events.size() == 1
                        && capture.events.back().noteOn
                        && capture.events.back().channel == 1
                        && capture.events.back().note == 60,
                    "virtual Note-On reaches the injected production listener on channel 1");
        test.near (capture.events.back().velocity, 0.70, 1.0e-6,
                   "virtual Note-On preserves a bounded velocity");
        editor.standaloneNoteOffForTests (60);
        test.check (! editor.isStandaloneNoteOnForTests (60)
                        && capture.events.size() == 2
                        && ! capture.events.back().noteOn
                        && capture.events.back().note == 60,
                    "virtual Note-Off reaches the listener and clears the key");

        const auto mouseStateEventStart = capture.events.size();
        editor.standaloneNoteOnForTests (36, 0.82f);
        editor.standaloneNoteOffForTests (36);
        editor.standaloneNoteOnForTests (37, 0.82f);
        editor.standaloneNoteOffForTests (37);
        test.check (capture.events.size() == mouseStateEventStart + 4
                        && capture.events[mouseStateEventStart].noteOn
                        && capture.events[mouseStateEventStart].note == 36
                        && ! capture.events[mouseStateEventStart + 1].noteOn
                        && capture.events[mouseStateEventStart + 1].note == 36
                        && capture.events[mouseStateEventStart + 2].noteOn
                        && capture.events[mouseStateEventStart + 2].note == 37
                        && ! capture.events[mouseStateEventStart + 3].noteOn
                        && capture.events[mouseStateEventStart + 3].note == 37,
                    "white and black key state gestures produce matching On/Off events");

        const auto dragEventStart = capture.events.size();
        editor.standaloneNoteOnForTests (40, 0.82f);
        editor.standaloneNoteOffForTests (40);
        editor.standaloneNoteOnForTests (41, 0.82f);
        editor.standaloneNoteOffForTests (41);
        test.check (capture.events.size() == dragEventStart + 4
                        && capture.events[dragEventStart].noteOn
                        && capture.events[dragEventStart].note == 40
                        && ! capture.events[dragEventStart + 1].noteOn
                        && capture.events[dragEventStart + 1].note == 40
                        && capture.events[dragEventStart + 2].noteOn
                        && capture.events[dragEventStart + 2].note == 41
                        && ! capture.events[dragEventStart + 3].noteOn
                        && capture.events[dragEventStart + 3].note == 41,
                    "drag-like state transition has deterministic On/Off ordering");

        for (auto repeat = 0; repeat < 12; ++repeat)
        {
            editor.standaloneNoteOnForTests (61, 0.8f);
            editor.standaloneNoteOffForTests (61);
        }
        test.check (! editor.isStandaloneNoteOnForTests (61),
                    "rapid repeated notes leave no active virtual key");

        for (const auto note : { 48, 52, 55, 59 })
            editor.standaloneNoteOnForTests (note, 0.65f);
        test.check (editor.isStandaloneNoteOnForTests (48)
                        && editor.isStandaloneNoteOnForTests (52)
                        && editor.isStandaloneNoteOnForTests (55)
                        && editor.isStandaloneNoteOnForTests (59),
                    "virtual keyboard tracks a four-note chord");
        for (const auto note : { 59, 55, 52, 48 })
            editor.standaloneNoteOffForTests (note);
        test.check (! editor.isStandaloneNoteOnForTests (48)
                        && ! editor.isStandaloneNoteOnForTests (52)
                        && ! editor.isStandaloneNoteOnForTests (55)
                        && ! editor.isStandaloneNoteOnForTests (59),
                    "chord Note-Off sequence releases every key");

        const auto eventCountBeforeInvalid = capture.events.size();
        editor.standaloneNoteOnForTests (-1, 0.8f);
        editor.standaloneNoteOnForTests (128, 0.8f);
        test.check (capture.events.size() == eventCountBeforeInvalid,
                    "invalid virtual MIDI notes are rejected before listener dispatch");

        editor.standaloneNoteOnForTests (72, 2.0f);
        test.near (capture.events.back().velocity, 1.0, 0.0,
                   "virtual velocity is clamped to the MIDI-safe normalized maximum");
        test.check (editor.isStandaloneNoteOnForTests (72),
                    "cleanup fixture holds one virtual note before editor destruction");

        test.check (editor.getStandaloneKeyboardLowestVisibleKey() == 36,
                    "snapshot returns to the documented C2 startup viewport");

        const auto standaloneSnapshot = editor.createComponentSnapshot (
            editor.getLocalBounds(), true, 1.0f);
        juce::MemoryOutputStream encoded;
        juce::PNGImageFormat png;
        const auto snapshotFile = testRoot.getChildFile ("StandaloneKeyboard.png");
        const auto snapshotWritten = standaloneSnapshot.isValid()
            && png.writeImageToStream (standaloneSnapshot, encoded)
            && snapshotFile.replaceWithData (encoded.getData(), encoded.getDataSize());
        test.check (snapshotWritten && snapshotFile.getSize() > 10000,
                    "rendered Standalone snapshot includes the virtual keyboard strip");
    }

    test.check (! capture.events.empty()
                    && ! capture.events.back().noteOn
                    && capture.events.back().note == 72,
                "editor destruction sends Note-Off before detaching the listener");

    juce::MemoryBlock stateAfter;
    standaloneProcessor->getStateInformation (stateAfter);
    test.check (parameterCountBefore == 62
                    && standaloneProcessor->getParameters().size() == 62
                    && stateBefore == stateAfter,
                "keyboard use adds no host parameter and changes no APVTS/preset state");

    auto welcomePreferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
        testRoot.getChildFile ("Welcome.properties"));
    welcomePreferences->setWelcomeEnabled (false);
    {
        DeepPluckAudioProcessorEditor welcomeEditor (*standaloneProcessor,
                                                      welcomePreferences, &capture);
        welcomeEditor.standaloneNoteOnForTests (75, 0.7f);
        test.check (welcomeEditor.isStandaloneNoteOnForTests (75),
                    "Welcome transition fixture begins with a held virtual note");
        welcomeEditor.showPage (deeppluck::gui::Page::settings);
        auto* showWelcomeNow = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (welcomeEditor, "settings.showWelcomeNow"));
        if (showWelcomeNow != nullptr)
            showWelcomeNow->onClick();
        auto* overlay = findDescendantWithID (welcomeEditor, "welcome.overlay");
        auto* panel = findDescendantWithID (welcomeEditor, "standalone.keyboard.panel");
        test.check (showWelcomeNow != nullptr && overlay != nullptr && panel != nullptr
                        && overlay->isVisible()
                        && overlay->getBounds() == welcomeEditor.getLocalBounds()
                        && welcomeEditor.getIndexOfChildComponent (overlay)
                               > welcomeEditor.getIndexOfChildComponent (panel),
                    "Welcome overlay covers the complete Standalone editor above the keyboard");
        test.check (overlay != nullptr && overlay->hitTest (768, 940),
                    "Welcome overlay intercepts hit-testing over the hidden keyboard strip");
        test.check (! welcomeEditor.isStandaloneNoteOnForTests (75)
                        && ! capture.events.empty()
                        && ! capture.events.back().noteOn
                        && capture.events.back().note == 75,
                    "showing Welcome releases a held virtual note before blocking the keyboard");
    }

    juce::MidiMessageCollector collector;
    collector.reset (48000.0);
    {
        deeppluck::gui::DeepPluckStandaloneKeyboardPanel panel (&collector);
        auto externalNoteOn = juce::MidiMessage::noteOn (2, 67, 0.5f);
        externalNoteOn.setTimeStamp (juce::Time::getMillisecondCounterHiRes() * 0.001);
        collector.addMessageToQueue (externalNoteOn);
        panel.noteOnForTests (60, 0.75f);
        panel.noteOffForTests (60);

        juce::MidiBuffer merged;
        collector.removeNextBlockOfMessages (merged, 512);
        auto virtualOn = 0;
        auto virtualOff = 0;
        auto externalOn = 0;
        for (const auto metadata : merged)
        {
            const auto message = metadata.getMessage();
            test.check (metadata.samplePosition >= 0 && metadata.samplePosition < 512,
                        "collector keeps merged event positions inside the audio block");
            if (message.isNoteOn() && message.getChannel() == 1
                && message.getNoteNumber() == 60) ++virtualOn;
            if (message.isNoteOff() && message.getChannel() == 1
                && message.getNoteNumber() == 60) ++virtualOff;
            if (message.isNoteOn() && message.getChannel() == 2
                && message.getNoteNumber() == 67) ++externalOn;
        }
        test.check (virtualOn == 1 && virtualOff == 1 && externalOn == 1,
                    "JUCE collector merges external and virtual MIDI before processBlock");

        auto sameExternalOn = juce::MidiMessage::noteOn (1, 65, 0.4f);
        sameExternalOn.setTimeStamp (
            juce::Time::getMillisecondCounterHiRes() * 0.001 - 0.001);
        collector.addMessageToQueue (sameExternalOn);
        panel.noteOnForTests (65, 0.9f);
        panel.noteOffForTests (65);
        auto sameExternalOff = juce::MidiMessage::noteOff (1, 65);
        sameExternalOff.setTimeStamp (
            juce::Time::getMillisecondCounterHiRes() * 0.001 + 0.001);
        collector.addMessageToQueue (sameExternalOff);
        juce::MidiBuffer sameNoteStream;
        collector.removeNextBlockOfMessages (sameNoteStream, 512);
        std::vector<bool> sameNoteKinds;
        for (const auto metadata : sameNoteStream)
        {
            const auto message = metadata.getMessage();
            if (message.getChannel() == 1 && message.getNoteNumber() == 65
                && (message.isNoteOn() || message.isNoteOff()))
                sameNoteKinds.push_back (message.isNoteOn());
        }
        test.check (sameNoteKinds == std::vector<bool> ({ true, true, false, false })
                        && ! panel.isNoteOnForTests (65),
                    "same-note external/virtual stream preserves retrigger then Note-Off order");

        panel.noteOnForTests (64, 0.6f);
        panel.disconnect();
        panel.disconnect();
        juce::MidiBuffer cleanup;
        collector.removeNextBlockOfMessages (cleanup, 512);
        auto cleanupOff = 0;
        for (const auto metadata : cleanup)
            if (metadata.getMessage().isNoteOff()
                && metadata.getMessage().getChannel() == 1
                && metadata.getMessage().getNoteNumber() == 64)
                ++cleanupOff;
        test.check (cleanupOff == 1,
                    "idempotent disconnect queues exactly one Note-Off for an active virtual note");
    }

    auto controlProcessor = createProcessorForWrapper (
        juce::AudioProcessor::wrapperType_VST3);
    auto standaloneAudioProcessor = createProcessorForWrapper (
        juce::AudioProcessor::wrapperType_Standalone);
    controlProcessor->prepareToPlay (48000.0, 128);
    standaloneAudioProcessor->prepareToPlay (48000.0, 128);
    juce::AudioBuffer<float> controlAudio (2, 128);
    juce::AudioBuffer<float> standaloneAudio (2, 128);
    juce::MidiBuffer controlMidi;
    juce::MidiBuffer standaloneMidi;
    controlMidi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 13);
    standaloneMidi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 13);
    controlProcessor->processBlock (controlAudio, controlMidi);
    standaloneAudioProcessor->processBlock (standaloneAudio, standaloneMidi);
    auto maximumDifference = 0.0f;
    for (auto channel = 0; channel < 2; ++channel)
        for (auto sample = 0; sample < 128; ++sample)
            maximumDifference = juce::jmax (
                maximumDifference,
                std::abs (controlAudio.getSample (channel, sample)
                          - standaloneAudio.getSample (channel, sample)));
    test.near (maximumDifference, 0.0, 0.0,
               "unused Standalone keyboard leaves the production audio bit-identical to VST3");

    juce::AudioBuffer<float> stressAudio (2, 128);
    juce::MidiBuffer stressMidi;
    for (auto noteIndex = 0; noteIndex < 17; ++noteIndex)
        stressMidi.addEvent (juce::MidiMessage::noteOn (
                                 1, 36 + noteIndex * 2, 0.75f),
                             noteIndex == 16 ? 127 : 0);
    standaloneAudioProcessor->processBlock (stressAudio, stressMidi);
    test.check (bufferIsFinite (stressAudio)
                    && maximumMagnitude (stressAudio, 0, stressAudio.getNumSamples()) > 0.0f,
                "16 voices plus the 17th-note steal remain finite and audible");
    standaloneAudioProcessor->releaseResources();
    juce::MidiBuffer empty;
    standaloneAudioProcessor->processBlock (stressAudio, empty);
    test.near (maximumMagnitude (stressAudio, 0, stressAudio.getNumSamples()),
               0.0, 0.0,
               "releaseResources hard-stops virtual/external voices without a stuck note");
}

void testGui (TestContext& test)
{
    constexpr auto& parameterIDs = deeppluck::allParameterIds;

    deeppluck::DeepPluckOutputMeter meterTransport;
    test.check (meterTransport.isLockFree(),
                "output meter transport uses lock-free atomics on the reference platform");
    meterTransport.publish (0.25f, 0.75f);
    meterTransport.publish (0.50f, 0.40f);
    const auto transportedPeaks = meterTransport.consume();
    test.near (transportedPeaks[0], 0.50, 0.0, "meter accumulates the left block maximum");
    test.near (transportedPeaks[1], 0.75, 0.0, "meter accumulates the right block maximum");
    const auto consumedAgain = meterTransport.consume();
    test.near (consumedAgain[0] + consumedAgain[1], 0.0, 0.0,
               "meter consumption atomically clears both accumulators");
    meterTransport.publish (std::numeric_limits<float>::quiet_NaN(),
                            std::numeric_limits<float>::infinity());
    const auto invalidPeaks = meterTransport.consume();
    test.near (invalidPeaks[0] + invalidPeaks[1], 0.0, 0.0,
               "meter transport rejects non-finite observations");

    const auto waveformBounds = juce::Rectangle<float> (7.0f, 11.0f, 240.0f, 90.0f);
    for (auto waveform = 0; waveform < 5; ++waveform)
    {
        const auto path = deeppluck::gui::createWaveformPath (
            waveform, waveform == 4 ? 0.37f : 0.5f, waveformBounds);
        test.check (! path.isEmpty() && waveformBounds.expanded (0.01f).contains (path.getBounds()),
                    "waveform display geometry is finite and contained for all five waveforms");
    }
    const auto envelopePath = deeppluck::gui::createEnvelopePath (
        0.003f, 0.32f, 0.4f, 0.18f, waveformBounds);
    test.check (! envelopePath.isEmpty()
                    && waveformBounds.expanded (0.01f).contains (envelopePath.getBounds()),
                "ADSR display geometry is finite and contained");
    const auto minimumEnvelope = deeppluck::gui::calculateEnvelopeDisplayGeometry (
        0.001f, 0.001f, 0.4f, 0.001f, waveformBounds);
    test.near (minimumEnvelope.attackX, waveformBounds.getX(), 0.0,
               "minimum Attack is drawn as a vertical rise");
    test.near (minimumEnvelope.decayX, minimumEnvelope.attackX, 0.0,
               "minimum Decay is drawn as a vertical fall");
    test.near (minimumEnvelope.releaseX, minimumEnvelope.sustainX, 0.0,
               "minimum Release is drawn as a vertical fall");
    const auto longerEnvelope = deeppluck::gui::calculateEnvelopeDisplayGeometry (
        1.0f, 1.0f, 0.4f, 1.0f, waveformBounds);
    test.check (longerEnvelope.attackX > minimumEnvelope.attackX
                    && longerEnvelope.decayX - longerEnvelope.attackX
                           > minimumEnvelope.decayX - minimumEnvelope.attackX
                    && longerEnvelope.releaseX - longerEnvelope.sustainX
                           > minimumEnvelope.releaseX - minimumEnvelope.sustainX,
                "increasing ADSR times widens the matching display segments");
    const auto maximumAttackEnvelope = deeppluck::gui::calculateEnvelopeDisplayGeometry (
        2.0f, 0.001f, 0.4f, 0.001f, waveformBounds);
    test.near (maximumAttackEnvelope.attackX,
               waveformBounds.getX() + waveformBounds.getWidth() * 0.22f, 1.0e-5,
               "Envelope display maps the frozen two-second Attack maximum to full width");
    test.near (deeppluck::gui::normaliseMeterDecibels (-60.0f), 0.0, 0.0,
               "meter maps -60 dB to the bottom of its scale");
    test.near (deeppluck::gui::normaliseMeterDecibels (0.0f), 60.0 / 66.0, 1.0e-6,
               "meter keeps 0 dB below full height on the -60 to +6 dB scale");
    test.near (deeppluck::gui::normaliseMeterDecibels (6.0f), 1.0, 0.0,
               "meter maps +6 dB to full height");

    test.check (deeppluck::gui::getWorkflowIconKind ("random.undo")
                        == deeppluck::gui::WorkflowIconKind::undo,
                "Undo resolves to its dedicated return-arrow icon before random wildcard matching");
    test.check (deeppluck::gui::getWorkflowIconKind ("random.master")
                        == deeppluck::gui::WorkflowIconKind::dice,
                "Master Random retains the dice icon");

    deeppluck::gui::DeepPluckLookAndFeel visualLookAndFeel;
    juce::Slider rotaryVisualProbe;
    juce::Image rotaryImage (juce::Image::ARGB, 72, 72, true);
    {
        juce::Graphics rotaryGraphics (rotaryImage);
        visualLookAndFeel.drawRotarySlider (rotaryGraphics, 4, 4, 64, 64, 0.8f,
                                            -2.5f, 2.5f, rotaryVisualProbe);
    }
    const auto colourBrightness = [] (juce::Colour colour)
    {
        return (colour.getFloatRed() + colour.getFloatGreen() + colour.getFloatBlue()) / 3.0f;
    };
    const auto upperLeftFace = colourBrightness (rotaryImage.getPixelAt (28, 28));
    const auto lowerRightFace = colourBrightness (rotaryImage.getPixelAt (44, 44));
    test.check (upperLeftFace > lowerRightFace + 0.03f,
                "rotary face has a measurable top-left highlight and bottom-right shade ("
                    + std::to_string (upperLeftFace) + " / "
                    + std::to_string (lowerRightFace) + ")");

    DeepPluckAudioProcessor processor;
    auto& state = processor.getValueTreeState();
    const auto initialState = state.copyState().toXmlString();

    {
        DeepPluckAudioProcessorEditor editor (processor);
        test.check (editor.getWidth() == 1536 && editor.getHeight() == 900,
                    "V0.10.4 editor preserves the compact fixed 1536 x 900 size");
        test.check (editor.getCurrentPage() == deeppluck::gui::Page::main,
                    "every editor instance starts on MAIN");
        test.check (editor.isPageVisible (deeppluck::gui::Page::main)
                        && ! editor.isPageVisible (deeppluck::gui::Page::modulation)
                        && ! editor.isPageVisible (deeppluck::gui::Page::effects)
                        && ! editor.isPageVisible (deeppluck::gui::Page::settings),
                    "only MAIN is initially visible");
        test.check (editor.isOscillatorRowVisible(),
                    "oscillator row remains permanently visible above the tabs");
        test.check (editor.isModulationNavigationEnabled(),
                    "MODULATION navigation is active");
        test.check (findDescendantWithID (editor, "page.modulation") != nullptr,
                    "MODULATION navigation is present");
        test.check (editor.isEffectsNavigationEnabled()
                        && findDescendantWithID (editor, "page.effects") != nullptr,
                    "FX navigation is present and active");
        test.check (findDescendantWithID (editor, "page.main") != nullptr
                        && findDescendantWithID (editor, "page.settings") != nullptr
                        && findDescendantWithID (editor, "page.sound") == nullptr
                        && findDescendantWithID (editor, "page.filter") == nullptr
                        && findDescendantWithID (editor, "page.shape") == nullptr,
                    "only MAIN, MODULATION, FX and SETTINGS are exposed as tabs");
        test.check (editor.getAttachmentCount() == 62,
                    "exactly sixty-two APVTS bindings are owned once");
        test.check (findDescendantWithID (editor, "output.meter") != nullptr
                        && findDescendantWithID (editor, "output.meter.l") != nullptr
                        && findDescendantWithID (editor, "output.meter.r") != nullptr,
                    "compact stereo L/R output meter is permanently present");

        const auto snapshotRoot = juce::File { DEEPPLUCK_TEST_ROOT }.getChildFile ("GuiSnapshots");
        static_cast<void> (snapshotRoot.createDirectory());
        const std::array<std::pair<deeppluck::gui::Page, const char*>, 4> snapshotPages {{
            { deeppluck::gui::Page::main, "Main.png" },
            { deeppluck::gui::Page::modulation, "Modulation.png" },
            { deeppluck::gui::Page::effects, "FX.png" },
            { deeppluck::gui::Page::settings, "Settings.png" }
        }};
        for (const auto& [page, name] : snapshotPages)
        {
            editor.showPage (page);
            const auto image = editor.createComponentSnapshot (editor.getLocalBounds(), true, 1.0f);
            juce::MemoryOutputStream encoded;
            juce::PNGImageFormat png;
            const auto encodedOk = image.isValid() && png.writeImageToStream (image, encoded);
            const auto file = snapshotRoot.getChildFile (name);
            const auto written = encodedOk && file.replaceWithData (encoded.getData(), encoded.getDataSize());
            test.check (written && file.getSize() > 10000,
                        std::string ("rendered non-empty final GUI snapshot for ") + name);
        }
        editor.showPage (deeppluck::gui::Page::main);

        constexpr std::array<const char*, 10> moduleIds {
            "oscillator1", "oscillator2", "ampEnvelope", "filter",
            "filterEnvelope", "transient", "noise", "unisonStereo", "modulation",
            "effectsRack"
        };
        for (const auto* moduleId : moduleIds)
        {
            const auto randomId = "random." + juce::String (moduleId);
            const auto lockId = "lock." + juce::String (moduleId);
            auto* randomControl = findDescendantWithID (editor, randomId);
            auto* lockControl = dynamic_cast<juce::ToggleButton*> (
                findDescendantWithID (editor, lockId));
            test.check (countDescendantsWithID (editor, randomId) == 1
                            && countDescendantsWithID (editor, lockId) == 1,
                        std::string ("one Random/Lock pair exists for ") + moduleId);
            test.check (randomControl != nullptr && lockControl != nullptr
                            && randomControl->getWidth() > 0 && randomControl->getHeight() > 0
                            && lockControl->getWidth() > 0 && lockControl->getHeight() > 0,
                        std::string ("Random/Lock pair has positive bounds for ") + moduleId);
            if (randomControl != nullptr && lockControl != nullptr)
            {
                test.check (randomControl->getParentComponent() == lockControl->getParentComponent()
                                && randomControl->getParentComponent()->getLocalBounds().contains (
                                    randomControl->getBounds())
                                && lockControl->getParentComponent()->getLocalBounds().contains (
                                    lockControl->getBounds())
                                && ! randomControl->getBounds().intersects (
                                    lockControl->getBounds()),
                            std::string ("Random/Lock bounds stay in panel without overlap for ")
                                + moduleId);
                lockControl->onClick();
                editor.refreshForTests();
                test.check (! randomControl->isEnabled() && randomControl->getAlpha() < 0.5f,
                            std::string ("locked Random button is disabled and dimmed for ")
                                + moduleId);
                lockControl->onClick();
            }
        }

        constexpr std::array<const char*, 11> headerActionIds {
            "random.master", "random.undo", "random.amount", "preset.previous",
            "preset.selector", "preset.next", "preset.save", "preset.saveAs",
            "preset.delete", "preset.refresh", "preset.openFolder"
        };
        std::vector<juce::Component*> headerActions;
        for (const auto* id : headerActionIds)
        {
            test.check (countDescendantsWithID (editor, id) == 1,
                        std::string ("header action ID is unique: ") + id);
            if (auto* action = findDescendantWithID (editor, id))
            {
                headerActions.push_back (action);
                test.check (action->getWidth() > 0 && action->getHeight() > 0
                                && action->getParentComponent() != nullptr
                                && action->getParentComponent()->getLocalBounds().contains (
                                    action->getBounds()),
                            std::string ("header action has positive in-parent bounds: ") + id);
            }
        }
        for (auto first = std::size_t { 0 }; first < headerActions.size(); ++first)
            for (auto second = first + 1; second < headerActions.size(); ++second)
                test.check (! headerActions[first]->getBounds().intersects (
                                headerActions[second]->getBounds()),
                            "header workflow controls do not overlap");
        auto* presetBrowser = dynamic_cast<juce::ComboBox*> (
            findDescendantWithID (editor, "preset.selector"));
        test.check (presetBrowser != nullptr && presetBrowser->getNumItems() >= 5,
                    "preset browser contains Custom plus the Factory group entries");
        auto* saveAction = findDescendantWithID (editor, "preset.save");
        if (presetBrowser != nullptr && saveAction != nullptr)
            test.check (std::abs (saveAction->getX() - presetBrowser->getX()) <= 6,
                        "preset action row begins under the preset selector");

        auto* masterRandom = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (editor, "random.master"));
        auto* undoRandom = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (editor, "random.undo"));
        auto* randomAmountControl = dynamic_cast<juce::Slider*> (
            findDescendantWithID (editor, "random.amount"));
        test.check (masterRandom != nullptr && masterRandom->getTooltip().isNotEmpty()
                        && randomAmountControl != nullptr
                        && randomAmountControl->getTooltip().isNotEmpty(),
                    "Master Random and Amount expose Hover Help text");
        const auto tooltipText = [] (juce::Component* component)
        {
            if (auto* client = dynamic_cast<juce::TooltipClient*> (component))
                return client->getTooltip();
            return juce::String {};
        };
        for (const auto* id : { deeppluck::ParameterIDs::filterCutoff,
                                "preset.selector", "random.filter", "lock.filter",
                                "page.main", "settings.hoverHelp",
                                "settings.about.website" })
            test.check (tooltipText (findDescendantWithID (editor, id)).isNotEmpty(),
                        std::string ("representative control has Hover Help: ") + id);
        test.check (masterRandom != nullptr && undoRandom != nullptr
                        && randomAmountControl != nullptr
                        && masterRandom->getX() == undoRandom->getX()
                        && masterRandom->getWidth() == undoRandom->getWidth()
                        && masterRandom->getBottom() < undoRandom->getY()
                        && randomAmountControl->getX() > masterRandom->getRight()
                        && randomAmountControl->getSliderStyle()
                               == juce::Slider::RotaryHorizontalVerticalDrag,
                    "Master Random and Undo stack beside the rotary Amount control");
        auto* masterTuneControl = dynamic_cast<juce::Slider*> (
            findDescendantWithID (editor, deeppluck::ParameterIDs::masterTune));
        auto* outputControl = dynamic_cast<juce::Slider*> (
            findDescendantWithID (editor, deeppluck::ParameterIDs::outputGain));
        if (masterRandom != nullptr && presetBrowser != nullptr)
            test.check (masterRandom->getY() == presetBrowser->getY(),
                        "Master Random begins on the raised preset-control line");
        if (randomAmountControl != nullptr && masterTuneControl != nullptr
            && outputControl != nullptr)
        {
            const auto amountLayout = randomAmountControl->getLookAndFeel().getSliderLayout (
                *randomAmountControl);
            const auto tuneLayout = masterTuneControl->getLookAndFeel().getSliderLayout (
                *masterTuneControl);
            const auto outputLayout = outputControl->getLookAndFeel().getSliderLayout (
                *outputControl);
            const auto amountTextY = editor.getLocalPoint (
                randomAmountControl, amountLayout.textBoxBounds.getTopLeft()).y;
            const auto tuneTextY = editor.getLocalPoint (
                masterTuneControl, tuneLayout.textBoxBounds.getTopLeft()).y;
            const auto outputTextY = editor.getLocalPoint (
                outputControl, outputLayout.textBoxBounds.getTopLeft()).y;
            test.check (amountTextY == tuneTextY && tuneTextY == outputTextY
                            && amountLayout.textBoxBounds.getHeight()
                                   == tuneLayout.textBoxBounds.getHeight()
                            && tuneLayout.textBoxBounds.getHeight()
                                   == outputLayout.textBoxBounds.getHeight(),
                        "Amount, Master Tune, and Output value fields share one height line");
            test.check (randomAmountControl->findColour (
                            juce::Slider::textBoxTextColourId)
                            == masterTuneControl->findColour (
                                juce::Slider::textBoxTextColourId)
                            && masterTuneControl->findColour (
                                   juce::Slider::textBoxTextColourId)
                                   == outputControl->findColour (
                                       juce::Slider::textBoxTextColourId),
                        "Amount value field uses the Master Tune and Output text colour");
        }

        auto* outputMeter = findDescendantWithID (editor, "output.meter");
        test.check (outputMeter != nullptr && outputMeter->getParentComponent() != nullptr
                        && outputMeter->getY() == 0
                        && outputMeter->getHeight()
                               == outputMeter->getParentComponent()->getHeight(),
                    "output meter uses the full permanent header height");

        auto* brandKicker = dynamic_cast<juce::Label*> (
            findDescendantWithID (editor, "brand.kicker"));
        auto* brandTitle = dynamic_cast<juce::Label*> (
            findDescendantWithID (editor, "brand.title"));
        test.check (brandKicker != nullptr && brandTitle != nullptr
                        && brandKicker->getText() == "JUST A LIL'"
                        && brandTitle->getText() == "PluckSynth",
                    "header brand uses the requested two-line text without a symbol");
        if (brandKicker != nullptr && brandTitle != nullptr)
        {
            test.check (brandKicker->getFont().isBold()
                            && brandKicker->getFont().isItalic()
                            && brandTitle->getFont().isBold()
                            && brandTitle->getFont().isItalic()
                            && brandTitle->getFont().getHeight()
                                   > brandKicker->getFont().getHeight()
                            && ! brandKicker->getBounds().intersects (brandTitle->getBounds()),
                        "header brand has italic hierarchy with separated lines");
        }

        struct AboutLabelCase { const char* id; juce::String text; };
        const std::array aboutLabels {
            AboutLabelCase { "settings.about.product", "Just A Lil' PluckSynth" },
            AboutLabelCase { "settings.about.version",
                             "Version " + juce::String (DEEPPLUCK_EXPECTED_VERSION) },
            AboutLabelCase { "settings.about.formats", "Formats: VST3 / Standalone" },
            AboutLabelCase { "settings.about.platform", "Platform: 64-bit Windows" },
            AboutLabelCase { "settings.about.juce", "Built with JUCE" },
            AboutLabelCase {
                "settings.about.description",
                "A focused pluck synthesizer designed for deep, warm and emotional electronic music."
            },
            AboutLabelCase {
                "settings.about.credits",
                "prompted & directed by Tonetrax\ncoded by ChatGPT/Codex"
            }
        };
        std::vector<juce::Component*> aboutComponents;
        for (const auto& expected : aboutLabels)
        {
            auto* label = dynamic_cast<juce::Label*> (
                findDescendantWithID (editor, expected.id));
            test.check (label != nullptr && label->getText() == expected.text,
                        std::string ("About exposes exact structured text: ") + expected.id);
            if (label != nullptr) aboutComponents.push_back (label);
        }
        auto* aboutWebsite = dynamic_cast<juce::HyperlinkButton*> (
            findDescendantWithID (editor, "settings.about.website"));
        test.check (aboutWebsite != nullptr
                        && aboutWebsite->getButtonText() == "www.tonetrax-music.de"
                        && aboutWebsite->getURL().toString (true)
                               == "https://www.tonetrax-music.de",
                    "About exposes the exact website text and HTTPS target without opening it");
        if (aboutWebsite != nullptr) aboutComponents.push_back (aboutWebsite);
        for (auto* component : aboutComponents)
            test.check (component->getWidth() > 0 && component->getHeight() > 0
                            && component->getParentComponent() != nullptr
                            && component->getParentComponent()->getLocalBounds().contains (
                                component->getBounds()),
                        "About component has positive in-panel bounds");
        for (auto first = std::size_t { 0 }; first < aboutComponents.size(); ++first)
            for (auto second = first + 1; second < aboutComponents.size(); ++second)
                test.check (! aboutComponents[first]->getBounds().intersects (
                                aboutComponents[second]->getBounds()),
                            "structured About components do not overlap");

        for (const auto* id : parameterIDs)
        {
            if (juce::String (id) == deeppluck::ParameterIDs::fxOrder)
                continue;
            test.check (countDescendantsWithID (editor, id) == 1,
                        std::string ("exactly one GUI control owns parameter ID ") + id);
            auto* component = findDescendantWithID (editor, id);
            auto* parameter = state.getParameter (id);
            test.check (component != nullptr && parameter != nullptr,
                        std::string ("attachment endpoint exists for ") + id);
            if (component == nullptr || parameter == nullptr)
                continue;

            if (auto* slider = dynamic_cast<juce::Slider*> (component))
            {
                const auto target = parameter->getNormalisableRange().convertFrom0to1 (0.371f);
                slider->setValue (target, juce::sendNotificationSync);
                test.near (parameter->getValue(),
                           parameter->getNormalisableRange().convertTo0to1 (
                               static_cast<float> (slider->getValue())),
                           1.0e-5, std::string ("slider attachment controls ") + id);
            }
            else if (auto* button = dynamic_cast<juce::ToggleButton*> (component))
            {
                button->setToggleState (! button->getToggleState(), juce::sendNotificationSync);
                test.near (parameter->getValue(), button->getToggleState() ? 1.0 : 0.0,
                           1.0e-6, std::string ("button attachment controls ") + id);
            }
            else if (auto* choice = dynamic_cast<juce::ComboBox*> (component))
            {
                const auto itemCount = choice->getNumItems();
                const auto targetID = itemCount > 1 ? itemCount : 1;
                choice->setSelectedId (targetID, juce::sendNotificationSync);
                test.near (parameter->getValue(),
                           parameter->getNormalisableRange().convertTo0to1 (
                               static_cast<float> (targetID - 1)),
                           1.0e-6, std::string ("choice attachment controls ") + id);
            }
            else
            {
                test.check (false, std::string ("parameter control has expected type: ") + id);
            }
        }

        std::vector<juce::Component*> parameterControls;
        parameterControls.reserve (parameterIDs.size());
        for (const auto* id : parameterIDs)
        {
            if (juce::String (id) == deeppluck::ParameterIDs::fxOrder)
                continue;
            auto* endpoint = findDescendantWithID (editor, id);
            if (endpoint == nullptr || endpoint->getParentComponent() == nullptr)
                continue;
            auto* control = endpoint->getParentComponent();
            parameterControls.push_back (control);
            test.check (endpoint->getWidth() > 0 && endpoint->getHeight() > 0
                            && control->getLocalBounds().contains (endpoint->getBounds()),
                        std::string ("parameter endpoint has positive in-parent bounds: ") + id);
            if (control->getParentComponent() != nullptr)
                test.check (control->getWidth() > 0 && control->getHeight() > 0
                                && control->getParentComponent()->getLocalBounds().contains (
                                    control->getBounds()),
                            std::string ("parameter control has positive in-panel bounds: ") + id);
        }
        for (auto first = std::size_t { 0 }; first < parameterControls.size(); ++first)
            for (auto second = first + 1; second < parameterControls.size(); ++second)
                if (parameterControls[first] != parameterControls[second]
                    && parameterControls[first]->getParentComponent()
                           == parameterControls[second]->getParentComponent())
                    test.check (! parameterControls[first]->getBounds().intersects (
                                    parameterControls[second]->getBounds()),
                                "parameter controls sharing a panel do not overlap");

        struct GuiTextCase { const char* id; double raw; const char* text; };
        constexpr std::array guiTextCases {
            GuiTextCase { deeppluck::ParameterIDs::ampAttack, 0.003, "3 ms" },
            GuiTextCase { deeppluck::ParameterIDs::ampDecay, 0.280, "280 ms" },
            GuiTextCase { deeppluck::ParameterIDs::filterCutoff, 1800.0, "1.80 kHz" },
            GuiTextCase { deeppluck::ParameterIDs::filterResonance, 0.18, "18 %" },
            GuiTextCase { deeppluck::ParameterIDs::filterEnvAmount, 2.50, "+2.50 oct" },
            GuiTextCase { deeppluck::ParameterIDs::osc2FineTune, -7.0, "-7 ct" },
            GuiTextCase { deeppluck::ParameterIDs::unisonDetune, 4.0, "4.0 ct" },
            GuiTextCase { deeppluck::ParameterIDs::lfoRate, 0.25, "0.25 Hz" },
            GuiTextCase { deeppluck::ParameterIDs::delayTime, 0.375, "0.375 s" },
            GuiTextCase { deeppluck::ParameterIDs::delayTone, 6000.0, "6.00 kHz" },
            GuiTextCase { deeppluck::ParameterIDs::reverbPreDelay, 0.018, "18 ms" }
        };
        const auto stateBeforeGuiFormatting = state.copyState().toXmlString();
        for (const auto& item : guiTextCases)
        {
            auto* slider = dynamic_cast<juce::Slider*> (
                findDescendantWithID (editor, item.id));
            test.check (slider != nullptr,
                        std::string (item.id) + " visible GUI slider exists for text test");
            if (slider == nullptr)
                continue;
            test.check (slider->getTextFromValue (item.raw) == item.text,
                        std::string (item.id) + " visible GUI text matches host semantics");
            test.near (slider->getValueFromText (item.text), item.raw,
                       item.raw > 1000.0 ? 0.02 : 2.0e-4,
                       std::string (item.id) + " GUI text parses to raw value");
        }
        test.check (state.copyState().toXmlString() == stateBeforeGuiFormatting,
                    "GUI text formatting/parsing is state-neutral");

        auto rotaryDiameter = 0;
        if (auto* reference = dynamic_cast<juce::Slider*> (
                findDescendantWithID (editor, deeppluck::ParameterIDs::filterResonance)))
            rotaryDiameter = reference->getLookAndFeel().getSliderLayout (*reference)
                                  .sliderBounds.getWidth();
        test.check (rotaryDiameter == 64,
                    "Filter Resonance establishes the common 64-pixel rotary size");
        auto cutoffDiameter = 0;
        if (auto* cutoffSlider = dynamic_cast<juce::Slider*> (
                findDescendantWithID (editor, deeppluck::ParameterIDs::filterCutoff)))
            cutoffDiameter = cutoffSlider->getLookAndFeel().getSliderLayout (*cutoffSlider)
                                 .sliderBounds.getWidth();
        test.check (cutoffDiameter == 112,
                    "Filter Cutoff is the single intentional 112-pixel rotary exception");
        for (const auto* id : parameterIDs)
        {
            auto* slider = dynamic_cast<juce::Slider*> (findDescendantWithID (editor, id));
            if (slider == nullptr
                || slider->getSliderStyle() != juce::Slider::RotaryHorizontalVerticalDrag)
                continue;
            const auto sliderLayout = slider->getLookAndFeel().getSliderLayout (*slider);
            const auto expectedDiameter = juce::String (id)
                                                   == deeppluck::ParameterIDs::filterCutoff
                                              ? cutoffDiameter : rotaryDiameter;
            test.check (sliderLayout.sliderBounds.getWidth() == expectedDiameter
                            && sliderLayout.sliderBounds.getHeight() == expectedDiameter,
                        std::string ("rotary body uses its declared V0.10.4 size: ") + id);
        }

        const auto wrapperFor = [&editor] (const char* id)
        {
            auto* endpoint = findDescendantWithID (editor, id);
            return endpoint != nullptr ? endpoint->getParentComponent() : nullptr;
        };
        auto* filterOnWrapper = wrapperFor (deeppluck::ParameterIDs::filterEnabled);
        auto* cutoffWrapper = wrapperFor (deeppluck::ParameterIDs::filterCutoff);
        auto* resonanceWrapper = wrapperFor (deeppluck::ParameterIDs::filterResonance);
        auto* driveWrapper = wrapperFor (deeppluck::ParameterIDs::filterDrive);
        auto* keyTrackWrapper = wrapperFor (deeppluck::ParameterIDs::filterKeyTracking);
        auto* envAmountWrapper = wrapperFor (deeppluck::ParameterIDs::filterEnvAmount);
        test.check (filterOnWrapper != nullptr && cutoffWrapper != nullptr
                        && resonanceWrapper != nullptr && driveWrapper != nullptr
                        && keyTrackWrapper != nullptr && envAmountWrapper != nullptr
                        && filterOnWrapper->getX() < cutoffWrapper->getX()
                        && cutoffWrapper->getX() < resonanceWrapper->getX()
                        && resonanceWrapper->getX() == keyTrackWrapper->getX()
                        && driveWrapper->getX() == envAmountWrapper->getX()
                        && resonanceWrapper->getY() < keyTrackWrapper->getY()
                        && driveWrapper->getY() < envAmountWrapper->getY(),
                    "Filter uses On, one large Cutoff, and a strict two-by-two control grid");

        const auto controlBodyCentreY = [&editor] (const char* id)
        {
            auto* endpoint = findDescendantWithID (editor, id);
            if (endpoint == nullptr)
                return std::numeric_limits<int>::min();
            if (auto* slider = dynamic_cast<juce::Slider*> (endpoint))
            {
                const auto layout = slider->getLookAndFeel().getSliderLayout (*slider);
                return editor.getLocalPoint (slider, layout.sliderBounds.getCentre()).y;
            }
            return editor.getLocalPoint (endpoint, endpoint->getLocalBounds().getCentre()).y;
        };
        const auto controlLabelCentreY = [&editor] (const char* id)
        {
            auto* endpoint = findDescendantWithID (editor, id);
            auto* wrapper = endpoint != nullptr ? endpoint->getParentComponent() : nullptr;
            auto* label = wrapper != nullptr && wrapper->getNumChildComponents() > 0
                              ? wrapper->getChildComponent (0) : nullptr;
            return label != nullptr
                       ? editor.getLocalPoint (label, label->getLocalBounds().getCentre()).y
                       : std::numeric_limits<int>::min();
        };
        const auto checkRowAlignment = [&] (const char* referenceId, const char* comparedId)
        {
            test.check (std::abs (controlLabelCentreY (referenceId)
                                  - controlLabelCentreY (comparedId)) <= 1
                            && std::abs (controlBodyCentreY (referenceId)
                                         - controlBodyCentreY (comparedId)) <= 1,
                        std::string ("labels and control bodies share one row: ")
                            + referenceId + " / " + comparedId);
        };
        checkRowAlignment (deeppluck::ParameterIDs::osc2Enabled,
                           deeppluck::ParameterIDs::osc2Level);
        checkRowAlignment (deeppluck::ParameterIDs::filterEnabled,
                           deeppluck::ParameterIDs::filterCutoff);
        checkRowAlignment (deeppluck::ParameterIDs::transientEnabled,
                           deeppluck::ParameterIDs::transientLevel);
        checkRowAlignment (deeppluck::ParameterIDs::noiseEnabled,
                           deeppluck::ParameterIDs::noiseLevel);
        checkRowAlignment (deeppluck::ParameterIDs::lfoSync,
                           deeppluck::ParameterIDs::lfoRate);
        checkRowAlignment (deeppluck::ParameterIDs::chorusBypass,
                           deeppluck::ParameterIDs::chorusMix);
        checkRowAlignment (deeppluck::ParameterIDs::delayBypass,
                           deeppluck::ParameterIDs::delayMix);
        checkRowAlignment (deeppluck::ParameterIDs::reverbBypass,
                           deeppluck::ParameterIDs::reverbMix);

        auto* oscillator1Waveform = dynamic_cast<juce::ComboBox*> (
            findDescendantWithID (editor, deeppluck::ParameterIDs::osc1Waveform));
        auto* oscillator2Waveform = dynamic_cast<juce::ComboBox*> (
            findDescendantWithID (editor, deeppluck::ParameterIDs::osc2Waveform));
        auto* oscillator1PulseWidth = findDescendantWithID (
            editor, deeppluck::ParameterIDs::osc1PulseWidth);
        auto* oscillator2PulseWidth = findDescendantWithID (
            editor, deeppluck::ParameterIDs::osc2PulseWidth);
        auto* oscillator2Enabled = dynamic_cast<juce::ToggleButton*> (
            findDescendantWithID (editor, deeppluck::ParameterIDs::osc2Enabled));
        test.check (oscillator1Waveform != nullptr && oscillator2Waveform != nullptr
                        && oscillator1PulseWidth != nullptr
                        && oscillator2PulseWidth != nullptr
                        && oscillator2Enabled != nullptr,
                    "both oscillator waveform and pulse-width GUI controls are discoverable");
        if (oscillator1Waveform != nullptr && oscillator2Waveform != nullptr
            && oscillator1PulseWidth != nullptr && oscillator2PulseWidth != nullptr
            && oscillator2Enabled != nullptr)
        {
            const juce::StringArray expectedWaveforms {
                "Saw", "Triangle", "Square", "Sine", "Pulse"
            };
            test.check (oscillator1Waveform->getNumItems() == 5
                            && oscillator2Waveform->getNumItems() == 5,
                        "both oscillator choices expose exactly five waveforms");
            for (auto index = 0; index < 5; ++index)
            {
                test.check (oscillator1Waveform->getItemText (index)
                                == expectedWaveforms[index]
                                && oscillator2Waveform->getItemText (index)
                                == expectedWaveforms[index],
                            "both waveform lists preserve the declared order");
            }

            oscillator1Waveform->setSelectedId (1, juce::sendNotificationSync);
            oscillator2Enabled->setToggleState (true, juce::sendNotificationSync);
            oscillator2Waveform->setSelectedId (5, juce::sendNotificationSync);
            editor.refreshForTests();
            test.near (oscillator1PulseWidth->getParentComponent()->getAlpha(),
                       0.45, 0.002,
                       "Oscillator 1 pulse width is dimmed outside Pulse mode");
            test.near (oscillator2PulseWidth->getParentComponent()->getAlpha(),
                       1.0, 1.0e-6,
                       "Oscillator 2 pulse width is fully visible in Pulse mode");
            oscillator2Enabled->setToggleState (false, juce::sendNotificationSync);
            editor.refreshForTests();
            test.near (oscillator2Waveform->getParentComponent()->getAlpha(),
                       0.45, 0.002,
                       "Oscillator 2 waveform follows bypass dimming");
            test.near (oscillator2PulseWidth->getParentComponent()->getAlpha(),
                       0.45, 0.002,
                       "Oscillator 2 bypass also dims Pulse Width");
        }

        editor.showPage (deeppluck::gui::Page::modulation);
        test.check (editor.isPageVisible (deeppluck::gui::Page::modulation),
                    "MODULATION becomes the active functional page");
        auto* syncButton = dynamic_cast<juce::ToggleButton*> (
            findDescendantWithID (editor, deeppluck::ParameterIDs::lfoSync));
        auto* rateSlider = findDescendantWithID (editor, deeppluck::ParameterIDs::lfoRate);
        auto* divisionChoice = findDescendantWithID (
            editor, deeppluck::ParameterIDs::lfoSyncDivision);
        auto* lfoPanel = findDescendantWithID (editor, "modulation.lfo.panel");
        auto* amountsPanel = findDescendantWithID (editor, "modulation.amounts.panel");
        auto* modulationPage = findDescendantWithID (editor, "page.modulation.content");
        test.check (lfoPanel != nullptr && amountsPanel != nullptr && modulationPage != nullptr
                        && lfoPanel->getHeight() == amountsPanel->getHeight()
                        && amountsPanel->getBottom() < modulationPage->getHeight() - 100,
                    "compact equal-height modulation rows reserve a clear lower expansion area");
        test.check (syncButton != nullptr && rateSlider != nullptr && divisionChoice != nullptr,
                    "Free/Sync controls are discoverable");
        if (syncButton != nullptr && rateSlider != nullptr && divisionChoice != nullptr)
        {
            syncButton->setToggleState (false, juce::sendNotificationSync);
            editor.refreshForTests();
            test.check (rateSlider->isEnabled() && ! divisionChoice->isEnabled(),
                        "Free mode enables Free Rate and disables Sync Division");
            const auto rateBefore = state.getRawParameterValue (
                deeppluck::ParameterIDs::lfoRate)->load();
            const auto divisionBefore = state.getRawParameterValue (
                deeppluck::ParameterIDs::lfoSyncDivision)->load();
            syncButton->setToggleState (true, juce::sendNotificationSync);
            editor.refreshForTests();
            test.check (! rateSlider->isEnabled() && divisionChoice->isEnabled(),
                        "Sync mode disables Free Rate and enables Sync Division");
            test.near (state.getRawParameterValue (deeppluck::ParameterIDs::lfoRate)->load(),
                       rateBefore, 0.0, "Free/Sync display does not change stored Free Rate");
            test.near (state.getRawParameterValue (
                           deeppluck::ParameterIDs::lfoSyncDivision)->load(),
                       divisionBefore, 0.0,
                       "Free/Sync display does not change stored Sync Division");
        }

        editor.showPage (deeppluck::gui::Page::effects);
        test.check (editor.isPageVisible (deeppluck::gui::Page::effects),
                    "FX becomes the active functional page");
        constexpr std::array<const char*, 12> fxSliderIds {
            deeppluck::ParameterIDs::chorusMix,
            deeppluck::ParameterIDs::chorusRate,
            deeppluck::ParameterIDs::chorusDepth,
            deeppluck::ParameterIDs::chorusWidth,
            deeppluck::ParameterIDs::delayMix,
            deeppluck::ParameterIDs::delayTime,
            deeppluck::ParameterIDs::delayFeedback,
            deeppluck::ParameterIDs::delayTone,
            deeppluck::ParameterIDs::reverbMix,
            deeppluck::ParameterIDs::reverbSize,
            deeppluck::ParameterIDs::reverbPreDelay,
            deeppluck::ParameterIDs::reverbDamping
        };
        for (const auto* id : fxSliderIds)
        {
            auto* slider = dynamic_cast<juce::Slider*> (findDescendantWithID (editor, id));
            test.check (slider != nullptr,
                        std::string ("FX rotary slider is discoverable: ") + id);
            if (slider != nullptr)
            {
                const auto layout = slider->getLookAndFeel().getSliderLayout (*slider);
                test.check (layout.sliderBounds.getWidth() >= 48
                                && layout.sliderBounds.getHeight() >= 48,
                            std::string ("FX rotary body remains visibly sized: ") + id);
            }
        }
        auto* chorusPosition = dynamic_cast<juce::ComboBox*> (
            findDescendantWithID (editor, "fx.position.chorus"));
        auto* delayPosition = dynamic_cast<juce::ComboBox*> (
            findDescendantWithID (editor, "fx.position.delay"));
        auto* reverbPosition = dynamic_cast<juce::ComboBox*> (
            findDescendantWithID (editor, "fx.position.reverb"));
        auto* resetOrder = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (editor, "fx.resetOrder"));
        auto* rackHeader = findDescendantWithID (editor, "fx.rack.header");
        auto* chorusPanel = findDescendantWithID (editor, "fx.chorus.panel");
        auto* delayPanel = findDescendantWithID (editor, "fx.delay.panel");
        auto* reverbPanel = findDescendantWithID (editor, "fx.reverb.panel");
        test.check (chorusPosition != nullptr && delayPosition != nullptr
                        && reverbPosition != nullptr && resetOrder != nullptr,
                    "three FX position proxies and Reset Order are discoverable");
        test.check (rackHeader != nullptr && chorusPanel != nullptr && delayPanel != nullptr
                        && reverbPanel != nullptr && lfoPanel != nullptr
                        && amountsPanel != nullptr
                        && rackHeader->getY() < chorusPanel->getY()
                        && rackHeader->getBottom() < chorusPanel->getY()
                        && chorusPanel->getHeight() == delayPanel->getHeight()
                        && delayPanel->getHeight() == reverbPanel->getHeight()
                        && lfoPanel->getHeight() == chorusPanel->getHeight()
                        && amountsPanel->getHeight() == chorusPanel->getHeight()
                        && resetOrder != nullptr
                        && resetOrder->getParentComponent() == rackHeader,
                    "FX rack actions live in a non-overlapping header and share row height with modulation");
        if (chorusPosition != nullptr && delayPosition != nullptr
            && reverbPosition != nullptr && resetOrder != nullptr)
        {
            test.check (chorusPosition->getParentComponent()->getX()
                                == delayPosition->getParentComponent()->getX()
                            && delayPosition->getParentComponent()->getX()
                                == reverbPosition->getParentComponent()->getX()
                            && chorusPosition->getX() == delayPosition->getX()
                            && delayPosition->getX() == reverbPosition->getX()
                            && chorusPosition->getWidth() == delayPosition->getWidth()
                            && delayPosition->getWidth() == reverbPosition->getWidth()
                            && chorusPosition->getWidth() <= 110,
                        "FX Position fields form one narrow vertical column");
            checkRowAlignment (deeppluck::ParameterIDs::chorusBypass,
                               "fx.position.chorus");
            checkRowAlignment (deeppluck::ParameterIDs::delayBypass,
                               "fx.position.delay");
            checkRowAlignment (deeppluck::ParameterIDs::reverbBypass,
                               "fx.position.reverb");
            setParameter (state, deeppluck::ParameterIDs::fxOrder, 0.0f);
            editor.refreshForTests();
            chorusPosition->setSelectedId (2, juce::sendNotificationSync);
            test.near (state.getRawParameterValue (deeppluck::ParameterIDs::fxOrder)->load(),
                       2.0, 0.0, "moving Chorus to position two yields D-C-R");
            setParameter (state, deeppluck::ParameterIDs::fxOrder, 0.0f);
            editor.refreshForTests();
            reverbPosition->setSelectedId (1, juce::sendNotificationSync);
            test.near (state.getRawParameterValue (deeppluck::ParameterIDs::fxOrder)->load(),
                       4.0, 0.0, "moving Reverb to position one yields R-C-D");
            delayPosition->setSelectedId (2, juce::sendNotificationSync);
            test.near (state.getRawParameterValue (deeppluck::ParameterIDs::fxOrder)->load(),
                       5.0, 0.0, "moving Delay to position two yields R-D-C");
            setParameter (state, deeppluck::ParameterIDs::chorusMix, 0.417f);
            const auto valuesBeforeReset = deeppluck::captureParameterValues (state);
            resetOrder->onClick();
            const auto valuesAfterReset = deeppluck::captureParameterValues (state);
            test.near (valuesAfterReset[61], 0.0, 0.0, "Reset Order restores C-D-R");
            for (auto index = 0; index < 61; ++index)
                test.near (valuesAfterReset[static_cast<std::size_t> (index)],
                           valuesBeforeReset[static_cast<std::size_t> (index)], 0.0,
                           "Reset Order changes no sound parameter other than fxOrder");
            setParameter (state, deeppluck::ParameterIDs::fxOrder, 3.0f);
            editor.refreshForTests();
            test.check (chorusPosition->getSelectedId() == 3
                            && delayPosition->getSelectedId() == 1
                            && reverbPosition->getSelectedId() == 2,
                        "host fxOrder automation synchronizes all three proxy positions");
        }

        const auto checkFxColumn = [&] (std::initializer_list<const char*> ids,
                                        const char* description)
        {
            auto expectedX = std::numeric_limits<int>::min();
            auto expectedWidth = std::numeric_limits<int>::min();
            auto aligned = true;
            for (const auto* id : ids)
            {
                auto* wrapper = wrapperFor (id);
                if (wrapper == nullptr)
                {
                    aligned = false;
                    continue;
                }
                if (expectedX == std::numeric_limits<int>::min())
                {
                    expectedX = wrapper->getX();
                    expectedWidth = wrapper->getWidth();
                }
                else
                {
                    aligned = aligned && wrapper->getX() == expectedX
                                      && wrapper->getWidth() == expectedWidth;
                }
            }
            test.check (aligned, description);
        };
        checkFxColumn ({ deeppluck::ParameterIDs::chorusMix,
                         deeppluck::ParameterIDs::delayMix,
                         deeppluck::ParameterIDs::reverbMix },
                       "all three FX Mix controls form one strict column");
        checkFxColumn ({ deeppluck::ParameterIDs::chorusRate,
                         deeppluck::ParameterIDs::delayTime,
                         deeppluck::ParameterIDs::reverbSize },
                       "Rate, Free Time, and Size form one strict column");
        checkFxColumn ({ deeppluck::ParameterIDs::chorusDepth,
                         deeppluck::ParameterIDs::delayFeedback,
                         deeppluck::ParameterIDs::reverbPreDelay },
                       "Depth, Feedback, and Pre-Delay form one strict column");
        checkFxColumn ({ deeppluck::ParameterIDs::chorusWidth,
                         deeppluck::ParameterIDs::delayTone,
                         deeppluck::ParameterIDs::reverbDamping },
                       "Width, Tone, and Damping form one strict column");
        auto* delaySyncWrapper = wrapperFor (deeppluck::ParameterIDs::delaySync);
        auto* delayDivisionWrapper = wrapperFor (deeppluck::ParameterIDs::delaySyncDivision);
        test.check (delaySyncWrapper != nullptr && delayDivisionWrapper != nullptr
                        && delaySyncWrapper->getX() == delayDivisionWrapper->getX()
                        && delaySyncWrapper->getWidth() == delayDivisionWrapper->getWidth()
                        && delaySyncWrapper->getBottom() < delayDivisionWrapper->getY(),
                    "Delay Free/Sync and Division are stacked in the narrow auxiliary column");

        deeppluck::applyFactoryPreset (state, 0);
        editor.refreshForTests();
        auto* next = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (editor, "preset.next"));
        auto* previous = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (editor, "preset.previous"));
        test.check (next != nullptr && previous != nullptr,
                    "permanent header exposes previous and next preset buttons");
        if (next != nullptr && previous != nullptr)
        {
            const auto& activeFactory = processor.getFactoryLibrary().entries;
            test.check (! activeFactory.empty(),
                        "approved embedded Factory library is available to the GUI");
            next->onClick();
            test.check (! activeFactory.empty()
                            && processor.getPresetDisplayState().stableId
                                   == activeFactory.front().stableId,
                        "Next from a compatibility-only Legacy sound loads the first active Factory preset");
            previous->onClick();
            test.check (! activeFactory.empty()
                            && processor.getPresetDisplayState().stableId
                                   == activeFactory.back().stableId,
                        "Previous wraps through active Factory presets without exposing Legacy sounds");
            setParameter (state, deeppluck::ParameterIDs::outputGain, -11.0f);
            editor.refreshForTests();
            next->onClick();
            test.check (! activeFactory.empty()
                            && processor.getPresetDisplayState().stableId
                                   == activeFactory.front().stableId,
                        "Next from Custom loads the first active Factory preset");
            setParameter (state, deeppluck::ParameterIDs::outputGain, -11.0f);
            editor.refreshForTests();
            previous->onClick();
            test.check (! activeFactory.empty()
                            && processor.getPresetDisplayState().stableId
                                   == activeFactory.back().stableId,
                        "Previous from Custom loads the last active Factory preset");
        }

        const auto beforeSwitches = state.copyState().toXmlString();
        for (auto iteration = 0; iteration < 50; ++iteration)
        {
            editor.showPage (deeppluck::gui::Page::main);
            test.check (editor.isPageVisible (deeppluck::gui::Page::main),
                        "MAIN becomes the active functional page");
            editor.showPage (deeppluck::gui::Page::modulation);
            test.check (editor.isPageVisible (deeppluck::gui::Page::modulation),
                        "MODULATION becomes the active functional page");
            editor.showPage (deeppluck::gui::Page::effects);
            test.check (editor.isPageVisible (deeppluck::gui::Page::effects),
                        "FX becomes the active functional page");
            editor.showPage (deeppluck::gui::Page::settings);
            test.check (editor.isPageVisible (deeppluck::gui::Page::settings),
                        "SETTINGS becomes the active functional page");
        }
        test.check (state.copyState().toXmlString() == beforeSwitches,
                    "repeated page switches do not alter parameter state or Custom status");
    }

    {
        DeepPluckAudioProcessor meteredProcessor;
        meteredProcessor.prepareToPlay (48000.0, 256);
        juce::MidiBuffer midi;
        midi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.9f), 0);
        juce::AudioBuffer<float> buffer (2, 256);
        meteredProcessor.processBlock (buffer, midi);
        const std::array expectedPeaks { buffer.getMagnitude (0, 0, buffer.getNumSamples()),
                                         buffer.getMagnitude (1, 0, buffer.getNumSamples()) };
        const auto measuredPeaks = meteredProcessor.consumeOutputMeterPeaks();
        test.near (measuredPeaks[0], expectedPeaks[0], 1.0e-7,
                   "left meter observes the final post-output-gain signal");
        test.near (measuredPeaks[1], expectedPeaks[1], 1.0e-7,
                   "right meter observes the final post-output-gain signal");

        auto meterPreferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
            juce::File { DEEPPLUCK_TEST_ROOT }.getChildFile ("GuiPreferences")
                .getChildFile ("Meter.properties"));
        meterPreferences->setWelcomeEnabled (false);
        DeepPluckAudioProcessorEditor editor (meteredProcessor, meterPreferences);
        juce::MidiBuffer continuation;
        meteredProcessor.processBlock (buffer, continuation);
        const std::array visiblePeaks { buffer.getMagnitude (0, 0, buffer.getNumSamples()),
                                        buffer.getMagnitude (1, 0, buffer.getNumSamples()) };
        editor.refreshMeterForTests();
        const auto displayed = editor.getDisplayedMeterLevels();
        test.check (displayed[0] >= visiblePeaks[0] && displayed[1] >= visiblePeaks[1],
                    "GUI meter consumes and displays both processor-owned peak channels");
    }

    {
        const auto preferenceFile = juce::File { DEEPPLUCK_TEST_ROOT }
                                        .getChildFile ("GuiPreferences")
                                        .getChildFile ("RoundTrip.properties");
        static_cast<void> (preferenceFile.deleteFile());
        auto preferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (preferenceFile);
        const auto defaults = preferences->get();
        test.check (defaults.hoverHelp && defaults.welcomeEnabled && ! defaults.welcomeSeen,
                    "missing GUI preference file uses safe first-run defaults");

        DeepPluckAudioProcessor preferenceProcessor;
        const auto parameterStateBefore = preferenceProcessor.getValueTreeState().copyState();
        DeepPluckAudioProcessorEditor editor (preferenceProcessor, preferences);
        test.check (editor.isWelcomeVisible(), "welcome guide appears for a fresh GUI profile");
        struct WelcomeLabelCase { const char* id; const char* text; };
        constexpr std::array welcomeLabels {
            WelcomeLabelCase { "welcome.product", "Just A Lil' PluckSynth" },
            WelcomeLabelCase { "welcome.subtitle",
                               "A compact pluck synthesizer for deep, emotional house sounds." },
            WelcomeLabelCase { "welcome.orientation",
                               "Start with a Factory Preset\nShape the sound on MAIN\n"
                               "Add movement and space with MODULATION and FX" },
            WelcomeLabelCase { "welcome.hint", "Hover over controls for help" },
            WelcomeLabelCase { "welcome.credits",
                               "prompted & directed by Tonetrax\ncoded by ChatGPT/Codex\n"
                               "www.tonetrax-music.de" }
        };
        std::vector<juce::Component*> welcomeComponents;
        for (const auto& expected : welcomeLabels)
        {
            auto* label = dynamic_cast<juce::Label*> (
                findDescendantWithID (editor, expected.id));
            test.check (label != nullptr && label->getText() == expected.text,
                        std::string ("Welcome exposes exact text: ") + expected.id);
            if (label != nullptr) welcomeComponents.push_back (label);
        }
        auto* continueButton = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (editor, "welcome.continue"));
        auto* dontShow = dynamic_cast<juce::ToggleButton*> (
            findDescendantWithID (editor, "welcome.dontShowAgain"));
        test.check (continueButton != nullptr
                        && continueButton->getButtonText() == "Start Playing"
                        && continueButton->getWantsKeyboardFocus()
                        && continueButton->getExplicitFocusOrder() == 1,
                    "welcome guide has a keyboard-focusable Start Playing action");
        test.check (dontShow != nullptr
                        && dontShow->getButtonText() == "Don't show this again"
                        && dontShow->getWantsKeyboardFocus()
                        && dontShow->getExplicitFocusOrder() == 2,
                    "welcome guide has an unambiguous second focus target");
        if (continueButton != nullptr) welcomeComponents.push_back (continueButton);
        if (dontShow != nullptr) welcomeComponents.push_back (dontShow);
        for (auto* component : welcomeComponents)
            test.check (component->getWidth() > 0 && component->getHeight() > 0
                            && component->getParentComponent() != nullptr
                            && component->getParentComponent()->getLocalBounds().contains (
                                component->getBounds()),
                        "Welcome component has positive in-overlay bounds");
        for (auto first = std::size_t { 0 }; first < welcomeComponents.size(); ++first)
            for (auto second = first + 1; second < welcomeComponents.size(); ++second)
                test.check (! welcomeComponents[first]->getBounds().intersects (
                                welcomeComponents[second]->getBounds()),
                            "Welcome components do not overlap");
        {
            const auto snapshotRoot = juce::File { DEEPPLUCK_TEST_ROOT }
                                          .getChildFile ("GuiSnapshots");
            static_cast<void> (snapshotRoot.createDirectory());
            const auto image = editor.createComponentSnapshot (editor.getLocalBounds(), true,
                                                                1.0f);
            juce::MemoryOutputStream encoded;
            juce::PNGImageFormat png;
            const auto written = image.isValid() && png.writeImageToStream (image, encoded)
                && snapshotRoot.getChildFile ("Welcome.png").replaceWithData (
                    encoded.getData(), encoded.getDataSize());
            test.check (written, "rendered non-empty Welcome snapshot evidence");
        }
        if (continueButton != nullptr) continueButton->onClick();
        test.check (! editor.isWelcomeVisible() && preferences->get().welcomeSeen
                        && preferences->get().welcomeEnabled && preferenceFile.existsAsFile(),
                    "Start Playing dismisses first run, persists seen, and keeps Welcome enabled");

        auto secondProcessor = std::make_unique<DeepPluckAudioProcessor>();
        auto secondEditor = std::make_unique<DeepPluckAudioProcessorEditor> (
            *secondProcessor, preferences);
        test.check (! secondEditor->isWelcomeVisible(),
                    "a second instance respects the process-shared completed first run");

        editor.showPage (deeppluck::gui::Page::settings);
        auto* hoverHelp = dynamic_cast<juce::ToggleButton*> (
            findDescendantWithID (editor, "settings.hoverHelp"));
        auto* welcomeEnabled = dynamic_cast<juce::ToggleButton*> (
            findDescendantWithID (editor, "settings.showWelcome"));
        auto* showWelcomeNow = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (editor, "settings.showWelcomeNow"));
        test.check (hoverHelp != nullptr && welcomeEnabled != nullptr
                        && showWelcomeNow != nullptr,
                    "Settings exposes persisted Hover Help and welcome controls");
        if (hoverHelp != nullptr && welcomeEnabled != nullptr && showWelcomeNow != nullptr)
        {
            auto* settingsWebsite = dynamic_cast<juce::HyperlinkButton*> (
                findDescendantWithID (editor, "settings.about.website"));
            test.check (hoverHelp->getExplicitFocusOrder() == 1
                            && welcomeEnabled->getExplicitFocusOrder() == 2
                            && showWelcomeNow->getExplicitFocusOrder() == 3
                            && settingsWebsite != nullptr
                            && settingsWebsite->getExplicitFocusOrder() == 4,
                        "Settings and About expose a logical explicit focus order");
        }
        if (hoverHelp != nullptr)
        {
            hoverHelp->setToggleState (false, juce::sendNotificationSync);
            test.check (! preferences->get().hoverHelp,
                        "Hover Help preference updates immediately and persists");
        }
        secondEditor->showPage (deeppluck::gui::Page::settings);
        auto* secondShowWelcomeNow = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (*secondEditor, "settings.showWelcomeNow"));
        if (secondShowWelcomeNow != nullptr) secondShowWelcomeNow->onClick();
        test.check (! editor.isWelcomeVisible() && secondEditor->isWelcomeVisible()
                        && preferences->shouldShowWelcome(),
                    "Show Welcome Now re-arms shared state but opens only the requested overlay");
        test.check (preferenceProcessor.getValueTreeState().copyState().isEquivalentTo (
                        parameterStateBefore),
                    "GUI-only preferences and welcome workflow never alter APVTS state");

        juce::TextButton tooltipProbe { "Probe" };
        tooltipProbe.setTooltip ("Representative hover help");
        deeppluck::gui::DeepPluckTooltipWindow tooltipWindow { nullptr };
        tooltipWindow.setEnabled (false);
        test.check (tooltipWindow.getTipFor (tooltipProbe).isEmpty(),
                    "Hover Help Off suppresses tooltip text immediately");
        tooltipWindow.setEnabled (true);
        test.check (tooltipWindow.getTipFor (tooltipProbe) == "Representative hover help",
                    "Hover Help On restores tooltip text without an editor restart");

        auto reloaded = deeppluck::gui::DeepPluckGuiPreferences::createForTests (preferenceFile);
        test.check (! reloaded->get().hoverHelp && reloaded->shouldShowWelcome(),
                    "GUI preferences round-trip through the injected test backend");
        for (auto index = 0; index < 20; ++index)
        {
            preferences->setHoverHelp ((index % 2) == 0);
            preferences->setWelcomeEnabled ((index % 3) != 0);
        }
        auto rapidReload = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
            preferenceFile);
        test.check (juce::XmlDocument::parse (preferenceFile) != nullptr
                        && rapidReload->get().hoverHelp == preferences->get().hoverHelp
                        && rapidReload->get().welcomeEnabled
                               == preferences->get().welcomeEnabled,
                    "rapid shared preference changes leave complete valid XML");
        static_cast<void> (preferenceFile.replaceWithText ("<not-valid-xml"));
        auto recovered = deeppluck::gui::DeepPluckGuiPreferences::createForTests (preferenceFile);
        const auto recoveredState = recovered->get();
        test.check (recoveredState.hoverHelp && recoveredState.welcomeEnabled
                        && ! recoveredState.welcomeSeen,
                    "corrupt GUI preferences recover to safe defaults");
        static_cast<void> (preferenceFile.replaceWithText ("<WrongRoot welcomeEnabled=\"0\"/>"));
        auto wrongRoot = deeppluck::gui::DeepPluckGuiPreferences::createForTests (preferenceFile);
        test.check (wrongRoot->get().hoverHelp && wrongRoot->get().welcomeEnabled
                        && ! wrongRoot->get().welcomeSeen,
                    "wrong-root GUI preferences recover to safe first-run defaults");
    }

    {
        const auto preferenceFile = juce::File { DEEPPLUCK_TEST_ROOT }
                                        .getChildFile ("GuiPreferences")
                                        .getChildFile ("DontShow.properties");
        static_cast<void> (preferenceFile.deleteFile());
        auto preferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
            preferenceFile);
        auto processorA = std::make_unique<DeepPluckAudioProcessor>();
        const auto stateBefore = processorA->getValueTreeState().copyState();
        auto editorA = std::make_unique<DeepPluckAudioProcessorEditor> (
            *processorA, preferences);
        auto* dontShow = dynamic_cast<juce::ToggleButton*> (
            findDescendantWithID (*editorA, "welcome.dontShowAgain"));
        auto* start = dynamic_cast<juce::TextButton*> (
            findDescendantWithID (*editorA, "welcome.continue"));
        if (dontShow != nullptr)
            dontShow->setToggleState (true, juce::sendNotificationSync);
        if (start != nullptr) start->onClick();
        test.check (! editorA->isWelcomeVisible() && ! preferences->get().welcomeEnabled,
                    "Don't show this again disables future automatic Welcome display");
        auto processorB = std::make_unique<DeepPluckAudioProcessor>();
        auto editorB = std::make_unique<DeepPluckAudioProcessorEditor> (
            *processorB, preferences);
        test.check (! editorB->isWelcomeVisible(),
                    "a new instance respects the persisted Don't show decision");
        editorB->showPage (deeppluck::gui::Page::settings);
        auto* welcomeEnabled = dynamic_cast<juce::ToggleButton*> (
            findDescendantWithID (*editorB, "settings.showWelcome"));
        if (welcomeEnabled != nullptr)
            welcomeEnabled->setToggleState (true, juce::sendNotificationSync);
        test.check (preferences->shouldShowWelcome(),
                    "Welcome Screen On re-arms automatic display for the next editor");
        auto processorC = std::make_unique<DeepPluckAudioProcessor>();
        auto editorC = std::make_unique<DeepPluckAudioProcessorEditor> (
            *processorC, preferences);
        test.check (editorC->isWelcomeVisible(),
                    "the next editor displays a Welcome screen re-armed in Settings");
        test.check (processorA->getValueTreeState().copyState().isEquivalentTo (stateBefore),
                    "multi-instance Welcome lifecycle remains APVTS-neutral");
    }

    test.check (initialState != state.copyState().toXmlString(),
                "GUI attachment test exercised nontrivial state changes");

    for (auto lifecycle = 0; lifecycle < 4; ++lifecycle)
    {
        const auto beforeOpen = state.copyState().toXmlString();
        auto editor = std::make_unique<DeepPluckAudioProcessorEditor> (processor);
        test.check (editor->getCurrentPage() == deeppluck::gui::Page::main,
                    "reopened editor starts on MAIN");
        test.check (editor->getAttachmentCount() == 62,
                    "reopened editor owns one complete attachment set");
        test.check (state.copyState().toXmlString() == beforeOpen,
                    "opening an editor is state-neutral");
    }

    DeepPluckAudioProcessor controlProcessor;
    DeepPluckAudioProcessor switchedProcessor;
    controlProcessor.prepareToPlay (48000.0, 128);
    switchedProcessor.prepareToPlay (48000.0, 128);
    DeepPluckAudioProcessorEditor switchedEditor (switchedProcessor);

    for (auto block = 0; block < 16; ++block)
    {
        juce::MidiBuffer controlMidi;
        juce::MidiBuffer switchedMidi;
        if (block == 0)
        {
            controlMidi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 0);
            switchedMidi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 0);
        }
        if (block == 12)
        {
            controlMidi.addEvent (juce::MidiMessage::noteOff (1, 60), 64);
            switchedMidi.addEvent (juce::MidiMessage::noteOff (1, 60), 64);
        }

        constexpr std::array pages { deeppluck::gui::Page::main,
                                     deeppluck::gui::Page::modulation,
                                     deeppluck::gui::Page::effects,
                                     deeppluck::gui::Page::settings };
        switchedEditor.showPage (pages[static_cast<std::size_t> (block) % pages.size()]);
        juce::AudioBuffer<float> controlBuffer (2, 128);
        juce::AudioBuffer<float> switchedBuffer (2, 128);
        controlProcessor.processBlock (controlBuffer, controlMidi);
        switchedProcessor.processBlock (switchedBuffer, switchedMidi);
        test.check (bufferIsFinite (switchedBuffer),
                    "audio remains finite during page switching");
        for (auto channel = 0; channel < 2; ++channel)
            for (auto sample = 0; sample < 128; ++sample)
                test.near (switchedBuffer.getSample (channel, sample),
                           controlBuffer.getSample (channel, sample), 1.0e-7,
                           "page switching is audio-bit-neutral");
    }
}

void testSmoothingAndStability (TestContext& test)
{
    deeppluck::DeepPluckParameterSnapshot snapshot;
    snapshot.ampAttackSeconds = 0.001f;
    snapshot.ampDecaySeconds = 0.001f;
    snapshot.ampSustainLevel = 1.0f;

    deeppluck::DeepPluckSynthesiser synth;
    deeppluck::configureDeepPluckSynthesiser (synth, snapshot);
    synth.setCurrentPlaybackSampleRate (48000.0);
    synth.noteOn (1, 69, 1.0f);
    auto* voice = dynamic_cast<deeppluck::DeepPluckVoice*> (synth.getVoice (0));
    juce::AudioBuffer<float> buffer (2, 64);
    render (synth, buffer);
    const auto phaseBefore = voice->getOscillatorPhase();
    const auto audibleLevelBeforeSustainChange = voice->getCurrentAudibleLevel();
    snapshot.ampSustainLevel = 0.0f;
    snapshot.masterTuneHz = 460.0f;
    render (synth, buffer);
    test.check (voice->getCurrentFrequencyHz() > 440.0 && voice->getCurrentFrequencyHz() < 460.0,
                "active voice tune change ramps instead of jumping");
    test.check (! juce::approximatelyEqual (voice->getOscillatorPhase(), 0.0),
                "tune smoothing does not reset oscillator phase");
    test.check (! juce::approximatelyEqual (phaseBefore, voice->getOscillatorPhase()),
                "phase continues advancing during tune change");
    for (auto block = 0; block < 20; ++block)
        render (synth, buffer);
    test.near (voice->getCurrentFrequencyHz(), 460.0, 0.05, "tune ramp reaches target in about 20 ms");
    test.check (voice->isVoiceActive(), "live Sustain change keeps the held voice lifecycle active");
    test.check (voice->getCurrentAudibleLevel() < audibleLevelBeforeSustainChange,
                "active voice responds smoothly to live Sustain change");
    synth.allNotesOff (0, false);
    synth.noteOn (1, 71, 1.0f);
    for (auto block = 0; block < 800; ++block)
        render (synth, buffer);
    test.near (maximumMagnitude (buffer, 0, buffer.getNumSamples()), 0.0, 1.0e-7,
               "changed zero-sustain ADSR snapshot applies to a newly started note");
    test.check (countActiveVoices (synth) == 1,
                "silent zero-sustain voice remains reserved while its key is held");
    synth.noteOff (1, 71, 0.0f, true);
    for (auto block = 0; block < 20 && countActiveVoices (synth) > 0; ++block)
        render (synth, buffer);
    test.check (countActiveVoices (synth) == 0,
                "zero-sustain voice is released after note-off");

    deeppluck::DeepPluckParameterSnapshot releaseSnapshot;
    releaseSnapshot.ampAttackSeconds = 0.001f;
    releaseSnapshot.ampDecaySeconds = 0.001f;
    releaseSnapshot.ampSustainLevel = 1.0f;
    releaseSnapshot.ampReleaseSeconds = 5.0f;
    deeppluck::DeepPluckSynthesiser liveReleaseSynth;
    deeppluck::configureDeepPluckSynthesiser (liveReleaseSynth, releaseSnapshot);
    liveReleaseSynth.setCurrentPlaybackSampleRate (48000.0);
    liveReleaseSynth.noteOn (1, 69, 1.0f);
    render (liveReleaseSynth, buffer);
    liveReleaseSynth.noteOff (1, 69, 0.0f, true);
    render (liveReleaseSynth, buffer);
    releaseSnapshot.ampReleaseSeconds = 0.005f;
    for (auto block = 0; block < 5; ++block)
        render (liveReleaseSynth, buffer);
    test.check (countActiveVoices (liveReleaseSynth) == 0,
                "voice rendering applies a shortened Release to an existing tail");

    releaseSnapshot.ampReleaseSeconds = 0.005f;
    liveReleaseSynth.noteOn (1, 69, 1.0f);
    render (liveReleaseSynth, buffer);
    liveReleaseSynth.noteOff (1, 69, 0.0f, true);
    render (liveReleaseSynth, buffer);
    releaseSnapshot.ampReleaseSeconds = 5.0f;
    for (auto block = 0; block < 100; ++block)
        render (liveReleaseSynth, buffer);
    test.check (countActiveVoices (liveReleaseSynth) == 1,
                "voice rendering applies an extended Release to an existing tail");
    liveReleaseSynth.allNotesOff (0, false);

    deeppluck::DeepPluckParameterSnapshot earlyOffSnapshot;
    earlyOffSnapshot.ampAttackSeconds = 0.001f;
    earlyOffSnapshot.ampDecaySeconds = 0.5f;
    earlyOffSnapshot.ampSustainLevel = 0.0f;
    earlyOffSnapshot.ampReleaseSeconds = 0.05f;
    deeppluck::DeepPluckSynthesiser earlyOffSynth;
    deeppluck::configureDeepPluckSynthesiser (earlyOffSynth, earlyOffSnapshot);
    earlyOffSynth.setCurrentPlaybackSampleRate (48000.0);
    earlyOffSynth.noteOn (1, 69, 1.0f);
    for (auto block = 0; block < 75; ++block)
        render (earlyOffSynth, buffer);
    earlyOffSynth.noteOff (1, 69, 0.0f, true);
    for (auto block = 0; block < 40; ++block)
        render (earlyOffSynth, buffer);
    test.check (countActiveVoices (earlyOffSynth) == 0,
                "voice note-off during Decay enters Release instead of finishing Decay");

    DeepPluckAudioProcessor processor;
    auto& state = processor.getValueTreeState();
    setParameter (state, deeppluck::ParameterIDs::ampAttack, 0.001f);
    setParameter (state, deeppluck::ParameterIDs::ampDecay, 0.001f);
    setParameter (state, deeppluck::ParameterIDs::ampSustain, 1.0f);
    setParameter (state, deeppluck::ParameterIDs::outputGain, -60.0f);
    processor.prepareToPlay (48000.0, 256);
    juce::AudioBuffer<float> processBuffer (2, 256);
    juce::MidiBuffer noteOn;
    noteOn.addEvent (juce::MidiMessage::noteOn (1, 57, 1.0f), 0);
    processor.processBlock (processBuffer, noteOn);
    const auto quietLevel = rms (processBuffer);
    setParameter (state, deeppluck::ParameterIDs::outputGain, 6.0f);
    juce::MidiBuffer empty;
    processor.processBlock (processBuffer, empty);
    const auto rampStartLevel = rms (processBuffer);
    for (auto block = 0; block < 8; ++block)
        processor.processBlock (processBuffer, empty);
    const auto loudLevel = rms (processBuffer);
    test.check (rampStartLevel > quietLevel && loudLevel > rampStartLevel,
                "output gain changes through a multi-block ramp");

    DeepPluckAudioProcessor headroomProcessor;
    auto& headroomState = headroomProcessor.getValueTreeState();
    setParameter (headroomState, deeppluck::ParameterIDs::ampAttack, 0.001f);
    setParameter (headroomState, deeppluck::ParameterIDs::ampDecay, 0.001f);
    setParameter (headroomState, deeppluck::ParameterIDs::ampSustain, 1.0f);
    headroomProcessor.prepareToPlay (48000.0, 512);
    juce::AudioBuffer<float> headroomBuffer (2, 512);
    juce::MidiBuffer headroomNotes;
    for (auto note = 0; note < 16; ++note)
        headroomNotes.addEvent (juce::MidiMessage::noteOn (1, 48 + note, 1.0f), 0);
    headroomProcessor.processBlock (headroomBuffer, headroomNotes);
    auto factoryPeak = maximumMagnitude (headroomBuffer, 0, headroomBuffer.getNumSamples());
    for (auto block = 0; block < 8; ++block)
    {
        headroomProcessor.processBlock (headroomBuffer, empty);
        factoryPeak = juce::jmax (factoryPeak,
                                  maximumMagnitude (headroomBuffer, 0,
                                                    headroomBuffer.getNumSamples()));
    }
    test.check (factoryPeak <= 0.95f, "default 16-voice render preserves factory headroom");
    std::cout << "METRIC: default 16-voice peak = " << factoryPeak << '\n';
    test.check (bufferIsFinite (headroomBuffer), "default 16-voice render stays finite");

    for (auto presetIndex = 0; presetIndex < static_cast<int> (deeppluck::factoryPresets.size());
         ++presetIndex)
    {
        DeepPluckAudioProcessor presetProcessor;
        deeppluck::applyFactoryPreset (presetProcessor.getValueTreeState(), presetIndex);
        presetProcessor.prepareToPlay (48000.0, 512);
        juce::AudioBuffer<float> presetBuffer (2, 512);
        juce::MidiBuffer presetNotes;
        for (auto note = 0; note < 16; ++note)
            presetNotes.addEvent (juce::MidiMessage::noteOn (1, 48 + note, 1.0f), 0);
        presetProcessor.processBlock (presetBuffer, presetNotes);
        auto presetPeak = maximumMagnitude (presetBuffer, 0, presetBuffer.getNumSamples());
        for (auto block = 0; block < 16; ++block)
        {
            presetProcessor.processBlock (presetBuffer, empty);
            presetPeak = juce::jmax (presetPeak,
                                     maximumMagnitude (presetBuffer, 0,
                                                       presetBuffer.getNumSamples()));
        }
        std::cout << "METRIC: "
                  << deeppluck::factoryPresets[static_cast<std::size_t> (presetIndex)].name
                  << " 16-voice peak = " << presetPeak << '\n';
        test.check (bufferIsFinite (presetBuffer), "factory preset render remains finite");
        test.check (presetPeak <= 0.95f, "factory preset preserves 16-voice headroom");
    }

    DeepPluckAudioProcessor worstCaseProcessor;
    auto& worstCaseState = worstCaseProcessor.getValueTreeState();
    setParameter (worstCaseState, deeppluck::ParameterIDs::ampAttack, 0.001f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::ampDecay, 0.001f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::ampSustain, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::filterCutoff, 1200.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::filterResonance, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::filterDrive, 18.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::filterKeyTracking, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::filterEnvAmount, 5.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::filterSustain, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::transientEnabled, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::transientLevel, 0.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::transientDecay, 0.100f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::transientTone, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::noiseEnabled, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::noiseLevel, 0.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::noiseDecay, 1.500f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::noiseTone, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::osc2Enabled, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::osc2Level, 0.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::osc2Octave, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::osc2Semitone, 12.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::osc2FineTune, 50.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::unisonVoices, 4.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::unisonDetune, 30.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::stereoSpread, 1.0f);
    setParameter (worstCaseState, deeppluck::ParameterIDs::ampRelease, 10.0f);
    worstCaseProcessor.prepareToPlay (96000.0, 127);
    juce::AudioBuffer<float> worstCaseBuffer (2, 127);
    juce::MidiBuffer worstCaseNotes;
    for (auto note = 0; note < 16; ++note)
        worstCaseNotes.addEvent (juce::MidiMessage::noteOn (1, 36 + note * 3, 1.0f), 0);
    worstCaseProcessor.processBlock (worstCaseBuffer, worstCaseNotes);
    auto worstCasePeak = maximumMagnitude (worstCaseBuffer, 0,
                                           worstCaseBuffer.getNumSamples());
    for (auto block = 0; block < 400; ++block)
    {
        if (block == 100)
        {
            setParameter (worstCaseState, deeppluck::ParameterIDs::filterEnvAmount, -5.0f);
            setParameter (worstCaseState, deeppluck::ParameterIDs::unisonDetune, 0.0f);
        }
        if (block == 200)
        {
            setParameter (worstCaseState, deeppluck::ParameterIDs::filterCutoff, 20.0f);
            setParameter (worstCaseState, deeppluck::ParameterIDs::stereoSpread, 0.0f);
        }
        if (block == 300)
        {
            setParameter (worstCaseState, deeppluck::ParameterIDs::filterCutoff, 20000.0f);
            setParameter (worstCaseState, deeppluck::ParameterIDs::unisonDetune, 30.0f);
            setParameter (worstCaseState, deeppluck::ParameterIDs::stereoSpread, 1.0f);
        }
        worstCaseProcessor.processBlock (worstCaseBuffer, empty);
        test.check (bufferIsFinite (worstCaseBuffer),
                    "16-voice maximum filter stress remains finite");
        worstCasePeak = juce::jmax (worstCasePeak,
                                    maximumMagnitude (worstCaseBuffer, 0,
                                                      worstCaseBuffer.getNumSamples()));
    }
    std::cout << "METRIC: 16-voice worst V0.6 peak = " << worstCasePeak << '\n';
    test.check (worstCasePeak <= 0.95f,
                "16-voice maximum compensated resonance stress preserves headroom");

    for (const auto chordSize : { 1, 4, 8, 16 })
    {
        deeppluck::DeepPluckSynthesiser chordSynth;
        deeppluck::configureDeepPluckSynthesiser (chordSynth, snapshot);
        chordSynth.setCurrentPlaybackSampleRate (96000.0);
        juce::AudioBuffer<float> chordBuffer (2, 127);

        for (auto repetition = 0; repetition < 20; ++repetition)
        {
            for (auto note = 0; note < chordSize; ++note)
                chordSynth.noteOn (1, 48 + note, 0.5f);
            render (chordSynth, chordBuffer);
            test.check (bufferIsFinite (chordBuffer), "repeated chord render remains finite");
            for (auto note = 0; note < chordSize; ++note)
                chordSynth.noteOff (1, 48 + note, 0.0f, true);
            render (chordSynth, chordBuffer);
            chordSynth.allNotesOff (0, false);
        }

        test.check (countActiveVoices (chordSynth) == 0 && chordSynth.getNumVoices() == 16,
                    "repeated chord stress leaves no hangs or voice growth");
    }
}

bool parameterArraysEqual (const deeppluck::DeepPluckParameterValues& left,
                           const deeppluck::DeepPluckParameterValues& right,
                           float tolerance = 1.0e-6f)
{
    for (auto index = std::size_t { 0 }; index < left.size(); ++index)
        if (std::abs (left[index] - right[index]) > tolerance)
            return false;
    return true;
}

juce::File prepareTestRoot (const juce::String& suite)
{
    const juce::File root { DEEPPLUCK_TEST_ROOT };
    const auto suiteRoot = root.getChildFile (suite);
    if (suiteRoot.exists())
        static_cast<void> (suiteRoot.deleteRecursively());
    static_cast<void> (suiteRoot.createDirectory());
    return suiteRoot;
}

bool writeTestText (const juce::File& file, const juce::String& text)
{
    static_cast<void> (file.getParentDirectory().createDirectory());
    return file.replaceWithText (text, false, false, "\n");
}

void testEffects (TestContext& test)
{
    constexpr std::array wetCalibrations { 0.55f, 0.60f, 0.45f };
    for (const auto calibration : wetCalibrations)
    {
        const auto dry = deeppluck::calculateEffectMixGains (0.0f, calibration);
        test.near (dry.dry, 1.0, 0.0, "effect Mix zero keeps exact unity dry gain");
        test.near (dry.wet, 0.0, 0.0, "effect Mix zero keeps exact zero wet gain");

        const auto centre = deeppluck::calculateEffectMixGains (0.5f, calibration);
        test.near (centre.dry * centre.dry + centre.wet * centre.wet,
                   1.0, 1.0e-6,
                   "effect Mix midpoint retains normalized nominal power");

        const auto wet = deeppluck::calculateEffectMixGains (1.0f, calibration);
        test.near (wet.dry, 0.0, 0.0, "effect Mix one keeps exact zero dry gain");
        test.check (wet.wet <= calibration * deeppluck::maximumEffectMixCompensation
                                + 1.0e-6f,
                    "effect Mix compensation remains hard bounded");

        const auto bypass = deeppluck::calculateEffectMixGains (0.5f, calibration, 1.0f);
        test.near (bypass.dry, 1.0, 0.0,
                   "effect bypass restores exact unity dry gain while tails decay");
        test.check (std::isfinite (bypass.wet),
                    "effect Mix gain calculation remains finite");
    }

    auto serialWetGain = 1.0f;
    for (const auto calibration : wetCalibrations)
        serialWetGain *= deeppluck::calculateEffectMixGains (1.0f, calibration).wet;
    test.check (serialWetGain >= 0.75f,
                "three fully wet effects avoid excessive compounded calibration loss");

    for (auto order = 0; order < 6; ++order)
    {
        std::array<int, 3> positions {
            deeppluck::getEffectPosition (order, deeppluck::EffectKind::chorus),
            deeppluck::getEffectPosition (order, deeppluck::EffectKind::delay),
            deeppluck::getEffectPosition (order, deeppluck::EffectKind::reverb)
        };
        std::sort (positions.begin(), positions.end());
        test.check (positions == std::array<int, 3> { 1, 2, 3 },
                    "each FX order maps to one unique position per effect");
    }
    test.check (deeppluck::moveEffectToPosition (
                    0, deeppluck::EffectKind::chorus, 2) == 2,
                "C1 D2 R3 moving Chorus to two yields D1 C2 R3");
    test.check (deeppluck::moveEffectToPosition (
                    0, deeppluck::EffectKind::reverb, 1) == 4,
                "C1 D2 R3 moving Reverb to one yields R1 C2 D3");
    test.check (deeppluck::moveEffectToPosition (
                    4, deeppluck::EffectKind::delay, 2) == 5,
                "R1 C2 D3 moving Delay to two yields R1 D2 C3");

    for (const auto sampleRate : { 44100.0, 48000.0, 96000.0 })
    {
        deeppluck::DeepPluckDelay delay;
        delay.prepare (sampleRate, 1024);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.delaySync = 1.0f;
        for (const auto tempo : { 20.0, 60.0, 120.0, 128.0, 400.0 })
            for (auto division = 0; division < 7; ++division)
            {
                snapshot.delaySyncDivision = static_cast<float> (division);
                delay.setParameters (snapshot, tempo);
                test.near (delay.getTargetDelaySeconds(),
                           60.0 / tempo
                               * deeppluck::DeepPluckDelay::beatsPerDelay[
                                   static_cast<std::size_t> (division)],
                           1.0e-12,
                           "Delay sync follows the exact tempo/division formula");
            }
        snapshot.delaySync = 0.0f;
        snapshot.delayTimeSeconds = 0.010f;
        delay.setParameters (snapshot, 120.0);
        test.near (delay.getTargetDelaySeconds(), 0.010, 1.0e-12,
                   "Delay Free Time accepts the declared minimum");
        snapshot.delayTimeSeconds = 2.0f;
        delay.setParameters (snapshot, 120.0);
        test.near (delay.getTargetDelaySeconds(), 2.0, 1.0e-12,
                   "Delay Free Time accepts the declared maximum");
    }

    constexpr std::array sampleRates { 44100.0, 48000.0, 96000.0 };
    constexpr std::array blockSizes { 1, 17, 32, 64, 127, 512, 1024 };
    for (const auto sampleRate : sampleRates)
        for (const auto blockSize : blockSizes)
        {
            deeppluck::DeepPluckEffectsRack rack;
            rack.prepare (sampleRate, blockSize);
            auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
            juce::AudioBuffer<float> buffer (2, blockSize);
            juce::AudioBuffer<float> reference (2, blockSize);
            for (auto sample = 0; sample < blockSize; ++sample)
            {
                const auto left = static_cast<float> (
                    0.31 * std::sin (0.071 * (sample + 1)));
                const auto right = static_cast<float> (
                    0.27 * std::cos (0.053 * (sample + 3)));
                buffer.setSample (0, sample, left);
                buffer.setSample (1, sample, right);
                reference.setSample (0, sample, left);
                reference.setSample (1, sample, right);
            }
            rack.process (buffer, snapshot, 120.0);
            for (auto channel = 0; channel < 2; ++channel)
                for (auto sample = 0; sample < blockSize; ++sample)
                    test.near (buffer.getSample (channel, sample),
                               reference.getSample (channel, sample), 0.0,
                               "all-bypassed FX rack is sample-identical");
            test.check (rack.isIdle(), "all-bypassed FX rack remains idle");

            snapshot.chorusBypass = 0.0f;
            snapshot.chorusMix = 0.0f;
            snapshot.delayBypass = 0.0f;
            snapshot.delayMix = 0.0f;
            snapshot.reverbBypass = 0.0f;
            snapshot.reverbMix = 0.0f;
            rack.process (buffer, snapshot, 120.0);
            test.check (bufferIsFinite (buffer),
                        "zero-mix active effects remain finite across the matrix");
            auto zeroMixDifference = 0.0f;
            for (auto channel = 0; channel < 2; ++channel)
                for (auto sample = 0; sample < blockSize; ++sample)
                    zeroMixDifference = juce::jmax (
                        zeroMixDifference,
                        std::abs (buffer.getSample (channel, sample)
                                  - reference.getSample (channel, sample)));
            test.near (zeroMixDifference, 0.0, 0.0,
                       "Mix zero is exactly dry for every active effect");
        }

    {
        constexpr auto sampleRate = 48000.0;
        constexpr auto blockSize = 256;
        deeppluck::DeepPluckChorus chorus;
        chorus.prepare (sampleRate, blockSize);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.chorusBypass = 0.0f;
        snapshot.chorusRateHz = 0.7f;
        snapshot.chorusDepth = 0.6f;
        snapshot.chorusWidth = 1.0f;
        snapshot.chorusMix = 0.55f;
        auto maximum = 0.0f;
        auto difference = 0.0f;
        auto monoPeak = 0.0f;
        double phase = 0.0;
        for (auto block = 0; block < 64; ++block)
        {
            juce::AudioBuffer<float> buffer (2, blockSize);
            juce::AudioBuffer<float> reference (2, blockSize);
            for (auto sample = 0; sample < blockSize; ++sample)
            {
                const auto value = static_cast<float> (0.25 * std::sin (
                    juce::MathConstants<double>::twoPi * phase));
                phase += 330.0 / sampleRate;
                phase -= std::floor (phase);
                buffer.setSample (0, sample, value);
                buffer.setSample (1, sample, value);
                reference.setSample (0, sample, value);
                reference.setSample (1, sample, value);
            }
            chorus.setParameters (snapshot);
            chorus.process (buffer, 0, blockSize);
            test.check (bufferIsFinite (buffer), "Chorus processing remains finite");
            maximum = juce::jmax (maximum, maximumMagnitude (buffer, 0, blockSize));
            for (auto sample = 0; sample < blockSize; ++sample)
                monoPeak = juce::jmax (monoPeak, std::abs (0.5f * (
                    buffer.getSample (0, sample) + buffer.getSample (1, sample))));
            for (auto channel = 0; channel < 2; ++channel)
                for (auto sample = 0; sample < blockSize; ++sample)
                    difference = juce::jmax (
                        difference, std::abs (buffer.getSample (channel, sample)
                                             - reference.getSample (channel, sample)));
        }
        test.check (difference > 0.001f, "active Chorus audibly changes the signal");
        test.check (maximum < 1.0f, "calibrated Chorus preserves comfortable headroom");
        test.check (monoPeak > 0.02f,
                    "wide Chorus retains a stable audible mono centre");

        snapshot.chorusBypass = 1.0f;
        juce::AudioBuffer<float> silence (2, blockSize);
        for (auto block = 0; block < 8; ++block)
        {
            silence.clear();
            chorus.setParameters (snapshot);
            chorus.process (silence, 0, blockSize);
        }
        test.check (chorus.isIdle(), "fully bypassed Chorus resets and becomes idle");
    }

    {
        constexpr auto sampleRate = 48000.0;
        constexpr auto blockSize = 256;
        deeppluck::DeepPluckDelay delay;
        delay.prepare (sampleRate, blockSize);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.delayBypass = 0.0f;
        snapshot.delaySync = 0.0f;
        snapshot.delayTimeSeconds = 0.010f;
        snapshot.delayFeedback = 0.50f;
        snapshot.delayToneHz = 18000.0f;
        snapshot.delayMix = 1.0f;
        juce::AudioBuffer<float> block (2, blockSize);
        for (auto warmup = 0; warmup < 8; ++warmup)
        {
            block.clear();
            delay.setParameters (snapshot, 120.0);
            delay.process (block, 0, blockSize);
        }
        block.clear();
        block.setSample (0, 0, 0.5f);
        block.setSample (1, 0, 0.5f);
        delay.process (block, 0, blockSize);
        auto firstWetSample = -1;
        auto delayPeak = 0.0f;
        for (auto offset = blockSize; offset < 4096; offset += blockSize)
        {
            block.clear();
            delay.process (block, 0, blockSize);
            for (auto sample = 0; sample < blockSize; ++sample)
            {
                const auto value = std::abs (block.getSample (0, sample));
                if (firstWetSample < 0 && value > 1.0e-4f)
                    firstWetSample = offset + sample;
                delayPeak = juce::jmax (delayPeak, value);
            }
        }
        test.check (firstWetSample >= 478 && firstWetSample <= 482,
                    "10 ms Delay impulse appears at the expected interpolated sample");
        test.check (delayPeak > 0.05f && delayPeak < 0.5f,
                    "Delay wet path is effective and calibrated");

        snapshot.delayBypass = 1.0f;
        auto bypassTailPeak = 0.0f;
        for (auto tailBlock = 0; tailBlock < 16; ++tailBlock)
        {
            block.clear();
            delay.setParameters (snapshot, 120.0);
            delay.process (block, 0, blockSize);
            bypassTailPeak = juce::jmax (
                bypassTailPeak, maximumMagnitude (block, 0, blockSize));
        }
        test.check (bypassTailPeak > 1.0e-5f,
                    "Delay bypass stops feed while retaining the existing tail");
        for (auto tailBlock = 0; tailBlock < 600 && ! delay.isIdle(); ++tailBlock)
        {
            block.clear();
            delay.setParameters (snapshot, 120.0);
            delay.process (block, 0, blockSize);
            test.check (bufferIsFinite (block), "Delay tail remains finite while decaying");
        }
        test.check (delay.isIdle(), "bounded Delay feedback tail reaches the idle criterion");
    }

    {
        constexpr auto sampleRate = 48000.0;
        constexpr auto blockSize = 512;
        deeppluck::DeepPluckDelay maximumDelay;
        maximumDelay.prepare (sampleRate, blockSize);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.delayBypass = 0.0f;
        snapshot.delaySync = 1.0f;
        snapshot.delaySyncDivision = 0.0f;
        snapshot.delayFeedback = 0.75f;
        snapshot.delayToneHz = 18000.0f;
        snapshot.delayMix = 1.0f;
        juce::AudioBuffer<float> block (2, blockSize);
        for (auto warmup = 0; warmup < 16; ++warmup)
        {
            block.clear();
            maximumDelay.setParameters (snapshot, 20.0);
            maximumDelay.process (block, 0, blockSize);
        }
        block.clear();
        block.setSample (0, 0, 0.5f);
        block.setSample (1, 0, 0.5f);
        maximumDelay.process (block, 0, blockSize);
        snapshot.delayBypass = 1.0f;
        constexpr auto maximumTailBlocks = static_cast<int> (
            121.0 * sampleRate / blockSize);
        auto tailPeak = 0.0f;
        for (auto tailBlock = 0;
             tailBlock < maximumTailBlocks && ! maximumDelay.isIdle(); ++tailBlock)
        {
            block.clear();
            maximumDelay.setParameters (snapshot, 20.0);
            maximumDelay.process (block, 0, blockSize);
            tailPeak = juce::jmax (tailPeak, maximumMagnitude (block, 0, blockSize));
        }
        test.check (tailPeak > 1.0e-5f,
                    "maximum 20-BPM quarter-note Delay emits a retained tail");
        test.check (maximumDelay.isIdle(),
                    "maximum 0.75-feedback Delay converges within the conservative tail contract");
    }

    {
        constexpr auto sampleRate = 48000.0;
        constexpr auto blockSize = 256;
        deeppluck::DeepPluckReverb reverb;
        reverb.prepare (sampleRate, blockSize);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.reverbBypass = 0.0f;
        snapshot.reverbSize = 0.92f;
        snapshot.reverbPreDelaySeconds = 0.018f;
        snapshot.reverbDamping = 0.55f;
        snapshot.reverbMix = 1.0f;
        juce::AudioBuffer<float> block (2, blockSize);
        for (auto warmup = 0; warmup < 8; ++warmup)
        {
            block.clear();
            reverb.setParameters (snapshot);
            reverb.process (block, 0, blockSize);
        }
        block.clear();
        block.setSample (0, 0, 0.5f);
        block.setSample (1, 0, 0.5f);
        reverb.process (block, 0, blockSize);
        snapshot.reverbBypass = 1.0f;
        auto tailPeak = 0.0f;
        constexpr auto maximumTailBlocks = static_cast<int> (
            120.0 * sampleRate / blockSize);
        for (auto tailBlock = 0;
             tailBlock < maximumTailBlocks && ! reverb.isIdle(); ++tailBlock)
        {
            block.clear();
            reverb.setParameters (snapshot);
            reverb.process (block, 0, blockSize);
            test.check (bufferIsFinite (block), "Reverb tail remains finite while decaying");
            tailPeak = juce::jmax (tailPeak, maximumMagnitude (block, 0, blockSize));
        }
        test.check (tailPeak > 1.0e-5f,
                    "Reverb bypass stops feed while retaining the existing tail");
        test.check (reverb.isIdle(),
                    "maximum-size Reverb tail reaches the idle criterion within 120 seconds");
    }

    auto worstRackPeak = 0.0f;
    for (auto order = 0; order < 6; ++order)
    {
        constexpr auto blockSize = 256;
        deeppluck::DeepPluckEffectsRack rack;
        rack.prepare (48000.0, blockSize);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.chorusBypass = 0.0f;
        snapshot.delayBypass = 0.0f;
        snapshot.delaySync = 0.0f;
        snapshot.delayTimeSeconds = 0.035f;
        snapshot.reverbBypass = 0.0f;
        snapshot.fxOrder = static_cast<float> (order);
        double phase = 0.0;
        for (auto blockIndex = 0; blockIndex < 32; ++blockIndex)
        {
            juce::AudioBuffer<float> buffer (2, blockSize);
            for (auto sample = 0; sample < blockSize; ++sample)
            {
                const auto value = static_cast<float> (0.18 * std::sin (
                    juce::MathConstants<double>::twoPi * phase));
                phase += 220.0 / 48000.0;
                phase -= std::floor (phase);
                buffer.setSample (0, sample, value);
                buffer.setSample (1, sample, value * 0.91f);
            }
            rack.process (buffer, snapshot, 120.0);
            test.check (bufferIsFinite (buffer), "every FX order remains finite");
            worstRackPeak = juce::jmax (
                worstRackPeak, maximumMagnitude (buffer, 0, blockSize));
        }
        test.check (rack.getCurrentOrder() == order,
                    "dry-bridge transition reaches each requested FX order");
    }
    test.check (worstRackPeak < 1.0f,
                "all musical rack orders preserve headroom on a steady probe");
    std::cout << "METRIC: V0.9.0 direct FX-rack peak = " << worstRackPeak << '\n';

    {
        constexpr auto blockSize = 256;
        constexpr auto warmupBlocks = 128;
        constexpr auto measuredBlocks = 384;
        deeppluck::DeepPluckEffectsRack rack;
        rack.prepare (48000.0, blockSize);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.chorusBypass = 0.0f;
        snapshot.chorusMix = 0.50f;
        snapshot.delayBypass = 0.0f;
        snapshot.delaySync = 0.0f;
        snapshot.delayTimeSeconds = 0.080f;
        snapshot.delayFeedback = 0.32f;
        snapshot.delayMix = 0.50f;
        snapshot.reverbBypass = 0.0f;
        snapshot.reverbMix = 0.50f;

        auto randomState = std::uint32_t { 0x62a9f41du };
        auto dryEnergy = 0.0;
        auto wetEnergy = 0.0;
        auto wetPeak = 0.0f;
        for (auto blockIndex = 0;
             blockIndex < warmupBlocks + measuredBlocks; ++blockIndex)
        {
            juce::AudioBuffer<float> buffer (2, blockSize);
            std::array<std::array<float, blockSize>, 2> input {};
            for (auto channel = 0; channel < 2; ++channel)
                for (auto sample = 0; sample < blockSize; ++sample)
                {
                    randomState ^= randomState << 13;
                    randomState ^= randomState >> 17;
                    randomState ^= randomState << 5;
                    const auto value = 0.08f * (static_cast<float> (
                        randomState & 0xffffu) / 32767.5f - 1.0f);
                    input[static_cast<std::size_t> (channel)][
                        static_cast<std::size_t> (sample)] = value;
                    buffer.setSample (channel, sample, value);
                }

            rack.process (buffer, snapshot, 120.0);
            test.check (bufferIsFinite (buffer),
                        "moderate all-FX loudness probe remains finite");
            if (blockIndex < warmupBlocks)
                continue;

            wetPeak = juce::jmax (
                wetPeak, maximumMagnitude (buffer, 0, blockSize));
            for (auto channel = 0; channel < 2; ++channel)
                for (auto sample = 0; sample < blockSize; ++sample)
                {
                    const auto dry = input[static_cast<std::size_t> (channel)][
                        static_cast<std::size_t> (sample)];
                    const auto wet = buffer.getSample (channel, sample);
                    dryEnergy += static_cast<double> (dry) * dry;
                    wetEnergy += static_cast<double> (wet) * wet;
                }
        }

        const auto rmsRatio = std::sqrt (wetEnergy / dryEnergy);
        test.check (rmsRatio >= 0.60 && rmsRatio <= 1.50,
                    "moderate serial FX Mix avoids excessive RMS loss or gain");
        test.check (wetPeak <= 0.95f,
                    "moderate serial FX Mix loudness compensation preserves headroom");
        std::cout << "METRIC: V0.9.0 moderate all-FX RMS ratio = "
                  << rmsRatio << ", peak = " << wetPeak << '\n';
    }

    deeppluck::DeepPluckEffectsRack resetRack;
    resetRack.prepare (48000.0, 256);
    auto resetSnapshot = deeppluck::DeepPluckParameterSnapshot {};
    resetSnapshot.delayBypass = 0.0f;
    resetSnapshot.delaySync = 0.0f;
    resetSnapshot.delayTimeSeconds = 0.010f;
    resetSnapshot.delayFeedback = 0.75f;
    resetSnapshot.delayMix = 1.0f;
    juce::AudioBuffer<float> resetBuffer (2, 256);
    for (auto block = 0; block < 8; ++block)
    {
        resetBuffer.clear();
        resetRack.process (resetBuffer, resetSnapshot, 120.0);
    }
    resetBuffer.clear();
    resetBuffer.setSample (0, 0, 0.5f);
    resetBuffer.setSample (1, 0, 0.5f);
    resetRack.process (resetBuffer, resetSnapshot, 120.0);
    resetRack.requestReset();
    resetSnapshot.delayBypass = 1.0f;
    resetBuffer.clear();
    resetRack.process (resetBuffer, resetSnapshot, 120.0);
    test.near (maximumMagnitude (resetBuffer, 0, resetBuffer.getNumSamples()),
               0.0, 0.0, "load-boundary rack reset prevents stale-tail bursts");
    test.check (resetRack.isIdle(), "load-boundary rack reset returns to idle");
}

void testEffectsIntegration (TestContext& test)
{
    DeepPluckAudioProcessor identityLeft;
    DeepPluckAudioProcessor identityRight;
    test.near (identityLeft.getTailLengthSeconds(), 120.0, 0.0,
               "processor advertises the conservative maximum FX tail");
    identityLeft.prepareToPlay (48000.0, 257);
    identityRight.prepareToPlay (48000.0, 257);
    for (auto blockIndex = 0; blockIndex < 40; ++blockIndex)
    {
        juce::MidiBuffer leftMidi;
        juce::MidiBuffer rightMidi;
        if (blockIndex == 0)
        {
            for (const auto note : { 48, 55, 60, 64 })
            {
                leftMidi.addEvent (juce::MidiMessage::noteOn (1, note, 0.72f), 0);
                rightMidi.addEvent (juce::MidiMessage::noteOn (1, note, 0.72f), 0);
            }
        }
        if (blockIndex == 24)
            for (const auto note : { 48, 55, 60, 64 })
            {
                leftMidi.addEvent (juce::MidiMessage::noteOff (1, note), 17);
                rightMidi.addEvent (juce::MidiMessage::noteOff (1, note), 17);
            }
        juce::AudioBuffer<float> left (2, 257);
        juce::AudioBuffer<float> right (2, 257);
        identityLeft.processBlock (left, leftMidi);
        identityRight.processBlock (right, rightMidi);
        for (auto channel = 0; channel < 2; ++channel)
            for (auto sample = 0; sample < 257; ++sample)
                test.near (left.getSample (channel, sample), right.getSample (channel, sample),
                           0.0, "neutral V0.9 rack preserves deterministic legacy audio");
    }

    CountingPlayHead playHead (128.0);
    DeepPluckAudioProcessor tempoProcessor;
    tempoProcessor.setPlayHead (&playHead);
    tempoProcessor.prepareToPlay (48000.0, 128);
    setParameter (tempoProcessor.getValueTreeState(),
                  deeppluck::ParameterIDs::delayBypass, 0.0f);
    setParameter (tempoProcessor.getValueTreeState(),
                  deeppluck::ParameterIDs::delaySync, 1.0f);
    juce::AudioBuffer<float> tempoBuffer (2, 128);
    juce::MidiBuffer tempoMidi;
    tempoProcessor.processBlock (tempoBuffer, tempoMidi);
    test.check (playHead.queryCount == 1,
                "processor performs exactly one shared playhead query per audio block");

    struct PeakScenario
    {
        const char* name;
        bool chorusActive;
        bool delayActive;
        bool reverbActive;
    };

    constexpr std::array<PeakScenario, 5> peakScenarios {{
        { "V0.8-equivalent all FX off", false, false, false },
        { "Chorus only", true, false, false },
        { "Delay only", false, true, false },
        { "Reverb only", false, false, true },
        { "all FX default order", true, true, true }
    }};

    for (const auto& scenario : peakScenarios)
    {
        DeepPluckAudioProcessor processor;
        auto& state = processor.getValueTreeState();
        setParameter (state, deeppluck::ParameterIDs::chorusBypass,
                      scenario.chorusActive ? 0.0f : 1.0f);
        setParameter (state, deeppluck::ParameterIDs::delayBypass,
                      scenario.delayActive ? 0.0f : 1.0f);
        setParameter (state, deeppluck::ParameterIDs::delaySync, 0.0f);
        setParameter (state, deeppluck::ParameterIDs::delayTime, 0.080f);
        setParameter (state, deeppluck::ParameterIDs::reverbBypass,
                      scenario.reverbActive ? 0.0f : 1.0f);
        processor.prepareToPlay (48000.0, 256);

        auto peak = 0.0f;
        for (auto blockIndex = 0; blockIndex < 40; ++blockIndex)
        {
            juce::AudioBuffer<float> buffer (2, 256);
            juce::MidiBuffer midi;
            if (blockIndex == 0)
                for (auto note = 0; note < 16; ++note)
                    midi.addEvent (juce::MidiMessage::noteOn (
                        1, 36 + note, 0.62f), note % 32);
            processor.processBlock (buffer, midi);
            test.check (bufferIsFinite (buffer),
                        "documented FX peak scenario remains finite");
            peak = juce::jmax (
                peak, maximumMagnitude (buffer, 0, buffer.getNumSamples()));
        }

        test.check (peak <= 0.95f,
                    "documented musical FX peak scenario preserves headroom");
        std::cout << "METRIC: V0.9.0 " << scenario.name << " peak = "
                  << peak << '\n';
    }

    auto activeMaximum = 0.0f;
    auto worstOrder = 0;
    for (auto order = 0; order < 6; ++order)
    {
        DeepPluckAudioProcessor processor;
        auto& state = processor.getValueTreeState();
        setParameter (state, deeppluck::ParameterIDs::chorusBypass, 0.0f);
        setParameter (state, deeppluck::ParameterIDs::delayBypass, 0.0f);
        setParameter (state, deeppluck::ParameterIDs::delaySync, 0.0f);
        setParameter (state, deeppluck::ParameterIDs::delayTime, 0.080f);
        setParameter (state, deeppluck::ParameterIDs::reverbBypass, 0.0f);
        setParameter (state, deeppluck::ParameterIDs::fxOrder, static_cast<float> (order));
        processor.prepareToPlay (48000.0, 256);
        auto orderPeak = 0.0f;
        for (auto blockIndex = 0; blockIndex < 40; ++blockIndex)
        {
            juce::AudioBuffer<float> buffer (2, 256);
            juce::MidiBuffer midi;
            if (blockIndex == 0)
                for (auto note = 0; note < 16; ++note)
                    midi.addEvent (juce::MidiMessage::noteOn (
                        1, 36 + note, 0.62f), note % 32);
            processor.processBlock (buffer, midi);
            test.check (bufferIsFinite (buffer),
                        "16-voice processing remains finite with every FX order");
            orderPeak = juce::jmax (
                orderPeak, maximumMagnitude (buffer, 0, buffer.getNumSamples()));
        }
        if (orderPeak > activeMaximum)
        {
            activeMaximum = orderPeak;
            worstOrder = order;
        }
    }
    test.check (activeMaximum <= 0.95f,
                "musical all-FX 16-voice matrix preserves the 0.95 peak contract");
    std::cout << "METRIC: V0.9.0 musical all-FX 16-voice peak = "
              << activeMaximum << ", worst_order=" << worstOrder << '\n';

    {
        constexpr auto blockSize = 256;
        deeppluck::DeepPluckEffectsRack rack;
        rack.prepare (48000.0, blockSize);
        auto snapshot = deeppluck::DeepPluckParameterSnapshot {};
        snapshot.chorusBypass = 0.0f;
        snapshot.delayBypass = 0.0f;
        snapshot.delaySync = 0.0f;
        snapshot.delayTimeSeconds = 0.080f;
        snapshot.reverbBypass = 0.0f;
        juce::AudioBuffer<float> buffer (2, blockSize);
        for (auto warmup = 0; warmup < 16; ++warmup)
        {
            buffer.clear();
            rack.process (buffer, snapshot, 120.0);
        }
        buffer.clear();
        buffer.setSample (0, 0, 0.35f);
        buffer.setSample (1, 0, 0.35f);
        rack.process (buffer, snapshot, 120.0);

        snapshot.chorusBypass = 1.0f;
        snapshot.delayBypass = 1.0f;
        snapshot.reverbBypass = 1.0f;
        auto tailOnlyPeak = 0.0f;
        for (auto tailBlock = 0; tailBlock < 1024 && ! rack.isIdle(); ++tailBlock)
        {
            buffer.clear();
            rack.process (buffer, snapshot, 120.0);
            test.check (bufferIsFinite (buffer),
                        "tail-only peak render remains finite");
            tailOnlyPeak = juce::jmax (
                tailOnlyPeak, maximumMagnitude (buffer, 0, buffer.getNumSamples()));
        }
        test.check (tailOnlyPeak > 1.0e-6f,
                    "tail-only render contains retained effect output");
        test.check (tailOnlyPeak <= 0.95f,
                    "tail-only render preserves headroom");
        std::cout << "METRIC: V0.9.0 tail-only peak = " << tailOnlyPeak << '\n';
    }

    constexpr std::array<const char*, 9> cpuScenarioNames {
        "all off", "chorus only", "delay free", "delay sync", "reverb only",
        "all default order", "typical order 4", "forced worst case", "tail only"
    };
    std::array<double, cpuScenarioNames.size()> cpuFractions {};
    for (auto scenario = 0; scenario < static_cast<int> (cpuScenarioNames.size()); ++scenario)
    {
        std::array<double, 3> timings {};
        for (auto trial = 0; trial < static_cast<int> (timings.size()); ++trial)
        {
            DeepPluckAudioProcessor processor;
            auto& state = processor.getValueTreeState();
            const auto chorusActive = scenario == 1 || scenario >= 5;
            const auto delayActive = scenario == 2 || scenario == 3 || scenario >= 5;
            const auto reverbActive = scenario == 4 || scenario >= 5;
            setParameter (state, deeppluck::ParameterIDs::chorusBypass,
                          chorusActive ? 0.0f : 1.0f);
            setParameter (state, deeppluck::ParameterIDs::delayBypass,
                          delayActive ? 0.0f : 1.0f);
            setParameter (state, deeppluck::ParameterIDs::delaySync,
                          scenario == 2 ? 0.0f : 1.0f);
            setParameter (state, deeppluck::ParameterIDs::reverbBypass,
                          reverbActive ? 0.0f : 1.0f);
            if (scenario == 6)
                setParameter (state, deeppluck::ParameterIDs::fxOrder, 4.0f);
            if (scenario == 7)
            {
                setParameter (state, deeppluck::ParameterIDs::chorusDepth, 1.0f);
                setParameter (state, deeppluck::ParameterIDs::chorusWidth, 1.0f);
                setParameter (state, deeppluck::ParameterIDs::chorusMix, 1.0f);
                setParameter (state, deeppluck::ParameterIDs::delayTime, 0.010f);
                setParameter (state, deeppluck::ParameterIDs::delayFeedback, 0.75f);
                setParameter (state, deeppluck::ParameterIDs::delayMix, 1.0f);
                setParameter (state, deeppluck::ParameterIDs::reverbSize, 0.92f);
                setParameter (state, deeppluck::ParameterIDs::reverbMix, 1.0f);
                setParameter (state, deeppluck::ParameterIDs::fxOrder, 5.0f);
            }
            processor.prepareToPlay (48000.0, 256);
            juce::AudioBuffer<float> buffer (2, 256);
            for (auto warmup = 0; warmup < 16; ++warmup)
            {
                juce::MidiBuffer midi;
                if (warmup == 0)
                    for (const auto note : { 48, 55, 60, 64 })
                        midi.addEvent (juce::MidiMessage::noteOn (1, note, 0.65f), 0);
                if (scenario == 8 && warmup == 8)
                    for (const auto note : { 48, 55, 60, 64 })
                        midi.addEvent (juce::MidiMessage::noteOff (1, note), 0);
                processor.processBlock (buffer, midi);
            }
            const auto start = std::chrono::steady_clock::now();
            for (auto blockIndex = 0; blockIndex < 192; ++blockIndex)
            {
                juce::MidiBuffer midi;
                processor.processBlock (buffer, midi);
            }
            timings[static_cast<std::size_t> (trial)] =
                std::chrono::duration<double, std::milli> (
                    std::chrono::steady_clock::now() - start).count();
            test.check (bufferIsFinite (buffer),
                        "FX CPU scenario remains finite after its timed render");
        }
        std::sort (timings.begin(), timings.end());
        cpuFractions[static_cast<std::size_t> (scenario)] = timings[1] / 1024.0;
        std::cout << "METRIC: V0.9.0 CPU " << cpuScenarioNames[
                         static_cast<std::size_t> (scenario)]
                  << " median_ms=" << timings[1]
                  << " realtime_fraction="
                  << cpuFractions[static_cast<std::size_t> (scenario)] << '\n';
    }
#if ! JUCE_DEBUG
    for (const auto scenario : { 1, 2, 3, 4, 5, 6 })
        test.check (cpuFractions[static_cast<std::size_t> (scenario)] <= 0.25,
                    "typical active FX scenario stays below twenty-five percent of one core");
#endif

    DeepPluckAudioProcessor extreme;
    auto& extremeState = extreme.getValueTreeState();
    setParameter (extremeState, deeppluck::ParameterIDs::chorusBypass, 0.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::chorusDepth, 1.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::chorusWidth, 1.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::chorusMix, 1.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::delayBypass, 0.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::delaySync, 0.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::delayTime, 0.010f);
    setParameter (extremeState, deeppluck::ParameterIDs::delayFeedback, 0.75f);
    setParameter (extremeState, deeppluck::ParameterIDs::delayMix, 1.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::reverbBypass, 0.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::reverbSize, 0.92f);
    setParameter (extremeState, deeppluck::ParameterIDs::reverbPreDelay, 0.120f);
    setParameter (extremeState, deeppluck::ParameterIDs::reverbMix, 1.0f);
    setParameter (extremeState, deeppluck::ParameterIDs::fxOrder, 5.0f);
    extreme.prepareToPlay (96000.0, 127);
    auto extremePeak = 0.0f;
    for (auto blockIndex = 0; blockIndex < 240; ++blockIndex)
    {
        juce::AudioBuffer<float> buffer (2, 127);
        juce::MidiBuffer midi;
        if (blockIndex == 0)
            for (auto note = 0; note < 20; ++note)
                midi.addEvent (juce::MidiMessage::noteOn (
                    1, 40 + note, 1.0f), note % 64);
        if (blockIndex != 0 && blockIndex % 19 == 0)
        {
            setParameter (extremeState, deeppluck::ParameterIDs::fxOrder,
                          static_cast<float> ((blockIndex / 19) % 6));
            const auto sweep = static_cast<float> (blockIndex % 239) / 238.0f;
            setParameter (extremeState, deeppluck::ParameterIDs::chorusRate,
                          0.05f + sweep * 3.95f);
            setParameter (extremeState, deeppluck::ParameterIDs::chorusDepth, 1.0f - sweep);
            setParameter (extremeState, deeppluck::ParameterIDs::delayTime,
                          0.010f * std::pow (200.0f, sweep));
            setParameter (extremeState, deeppluck::ParameterIDs::delayTone,
                          800.0f * std::pow (22.5f, sweep));
            setParameter (extremeState, deeppluck::ParameterIDs::reverbSize,
                          0.10f + sweep * 0.82f);
            setParameter (extremeState, deeppluck::ParameterIDs::reverbPreDelay,
                          sweep * 0.120f);
            setParameter (extremeState, deeppluck::ParameterIDs::reverbDamping,
                          1.0f - sweep);
        }
        extreme.processBlock (buffer, midi);
        test.check (bufferIsFinite (buffer),
                    "forced legal FX extreme and rapid order automation stay finite");
        extremePeak = juce::jmax (
            extremePeak, maximumMagnitude (buffer, 0, buffer.getNumSamples()));
    }
    test.check (extremePeak <= 2.0f,
                "forced legal FX extreme remains within the 2.0 safety bound");
    std::cout << "METRIC: V0.9.0 forced legal FX extreme peak = "
              << extremePeak << '\n';
}

void testRandomization (TestContext& test)
{
    std::array<int, 62> occurrences {};
    auto mappedCount = 0;
    for (auto moduleIndex = 0;
         moduleIndex < static_cast<int> (deeppluck::RandomizationModule::count);
         ++moduleIndex)
    {
        const auto module = static_cast<deeppluck::RandomizationModule> (moduleIndex);
        test.check (juce::String (deeppluck::getRandomizationModuleId (module)).isNotEmpty(),
                    "every randomization module has a stable ID");
        for (const auto parameterIndex : deeppluck::getRandomizationParameterIndices (module))
        {
            test.check (parameterIndex >= 0 && parameterIndex < 62,
                        "randomization matrix index is in the APVTS range");
            if (parameterIndex >= 0 && parameterIndex < 62)
                ++occurrences[static_cast<std::size_t> (parameterIndex)];
            ++mappedCount;
        }
    }
    test.check (mappedCount == 60, "randomization matrix contains exactly 60 entries");
    for (auto index = 0; index < 62; ++index)
        test.check (occurrences[static_cast<std::size_t> (index)]
                        == (index < 2 ? 0 : 1),
                    "each sound parameter is mapped once and Output/Tune are excluded");

    DeepPluckAudioProcessor deterministicLeft;
    DeepPluckAudioProcessor deterministicRight;
    deeppluck::DeepPluckRandomization leftRandom (
        deterministicLeft.getValueTreeState(), 0x123456789abcdef0ULL);
    deeppluck::DeepPluckRandomization rightRandom (
        deterministicRight.getValueTreeState(), 0x123456789abcdef0ULL);
    const auto leftBefore = deeppluck::captureParameterValues (
        deterministicLeft.getValueTreeState());
    test.check (leftRandom.randomizeAll() && rightRandom.randomizeAll(),
                "deterministic Master Random changes both equivalent states");
    const auto leftAfter = deeppluck::captureParameterValues (
        deterministicLeft.getValueTreeState());
    const auto rightAfter = deeppluck::captureParameterValues (
        deterministicRight.getValueTreeState());
    test.check (parameterArraysEqual (leftAfter, rightAfter, 0.0f),
                "same seed, state, Amount and locks produce identical values");
    test.check (leftAfter[0] == leftBefore[0] && leftAfter[1] == leftBefore[1],
                "Master Random preserves Output Gain and Master Tune exactly");

    DeepPluckAudioProcessor differentSeedProcessor;
    deeppluck::DeepPluckRandomization differentRandom (
        differentSeedProcessor.getValueTreeState(), 0xfedcba9876543210ULL);
    static_cast<void> (differentRandom.randomizeAll());
    test.check (! parameterArraysEqual (
                    leftAfter,
                    deeppluck::captureParameterValues (
                        differentSeedProcessor.getValueTreeState()), 0.0f),
                "different seeds produce useful variation");

    for (auto moduleIndex = 0;
         moduleIndex < static_cast<int> (deeppluck::RandomizationModule::count);
         ++moduleIndex)
    {
        DeepPluckAudioProcessor moduleProcessor;
        auto& state = moduleProcessor.getValueTreeState();
        const auto before = deeppluck::captureParameterValues (state);
        const auto module = static_cast<deeppluck::RandomizationModule> (moduleIndex);
        deeppluck::DeepPluckRandomization random (
            state, static_cast<std::uint64_t> (9000 + moduleIndex));
        test.check (random.randomizeModule (module),
                    "each unlocked module can change its sound values");
        const auto after = deeppluck::captureParameterValues (state);
        auto changedInside = false;
        for (auto index = 0; index < 62; ++index)
        {
            const auto indices = deeppluck::getRandomizationParameterIndices (module);
            const auto belongs = std::find (indices.begin(), indices.end(), index)
                               != indices.end();
            if (belongs)
                changedInside = changedInside || before[static_cast<std::size_t> (index)]
                                                != after[static_cast<std::size_t> (index)];
            else
                test.check (before[static_cast<std::size_t> (index)]
                                == after[static_cast<std::size_t> (index)],
                            "module randomization is isolated from all other IDs");
        }
        test.check (changedInside, "module randomization changes at least one owned ID");
    }

    DeepPluckAudioProcessor lockedProcessor;
    DeepPluckAudioProcessor lockControlProcessor;
    deeppluck::DeepPluckRandomization lockedRandom (
        lockedProcessor.getValueTreeState(), 445566ULL);
    deeppluck::DeepPluckRandomization lockControl (
        lockControlProcessor.getValueTreeState(), 445566ULL);
    lockedRandom.setModuleLocked (deeppluck::RandomizationModule::filter, true);
    test.check (! lockedRandom.randomizeModule (deeppluck::RandomizationModule::filter)
                    && ! lockedRandom.canUndo(),
                "locked module action is a complete no-op without Undo state");
    lockedRandom.setModuleLocked (deeppluck::RandomizationModule::filter, false);
    test.check (lockedRandom.randomizeModule (deeppluck::RandomizationModule::filter)
                    && lockControl.randomizeModule (deeppluck::RandomizationModule::filter),
                "unlocked comparison actions run");
    test.check (parameterArraysEqual (
                    deeppluck::captureParameterValues (lockedProcessor.getValueTreeState()),
                    deeppluck::captureParameterValues (lockControlProcessor.getValueTreeState()),
                    0.0f),
                "locked no-op consumes no RNG values");

    DeepPluckAudioProcessor masterLockProcessor;
    auto& masterLockState = masterLockProcessor.getValueTreeState();
    deeppluck::DeepPluckRandomization masterLockRandom (masterLockState, 778899ULL);
    const auto masterLockBefore = deeppluck::captureParameterValues (masterLockState);
    masterLockRandom.setModuleLocked (deeppluck::RandomizationModule::oscillator1, true);
    static_cast<void> (masterLockRandom.randomizeAll());
    const auto masterLockAfter = deeppluck::captureParameterValues (masterLockState);
    for (const auto index : deeppluck::getRandomizationParameterIndices (
             deeppluck::RandomizationModule::oscillator1))
        test.check (masterLockBefore[static_cast<std::size_t> (index)]
                        == masterLockAfter[static_cast<std::size_t> (index)],
                    "Master Random skips every ID in a locked module");

    for (const auto amount : { 0.10f, 0.25f, 0.50f, 1.00f })
    {
        DeepPluckAudioProcessor targetProcessor;
        DeepPluckAudioProcessor amountProcessor;
        deeppluck::DeepPluckRandomization targetRandom (
            targetProcessor.getValueTreeState(), 991122ULL);
        deeppluck::DeepPluckRandomization amountRandom (
            amountProcessor.getValueTreeState(), 991122ULL);
        const auto target = targetRandom.drawTargetForTests (2);
        auto* parameter = amountProcessor.getValueTreeState().getParameter (
            deeppluck::allParameterIds[2]);
        const auto beforeRaw = deeppluck::captureParameterValues (
            amountProcessor.getValueTreeState())[2];
        const auto expectedNormalized = parameter->convertTo0to1 (beforeRaw)
                                      + amount * (parameter->convertTo0to1 (target)
                                                  - parameter->convertTo0to1 (beforeRaw));
        amountRandom.setAmount (amount);
        static_cast<void> (amountRandom.randomizeModule (
            deeppluck::RandomizationModule::ampEnvelope));
        const auto actual = deeppluck::captureParameterValues (
            amountProcessor.getValueTreeState())[2];
        test.near (parameter->convertTo0to1 (actual), expectedNormalized, 2.0e-6,
                   "continuous Random Amount uses normalized linear interpolation");
    }

    DeepPluckAudioProcessor zeroProcessor;
    deeppluck::DeepPluckRandomization zeroRandom (
        zeroProcessor.getValueTreeState(), 123ULL);
    const auto zeroBefore = deeppluck::captureParameterValues (
        zeroProcessor.getValueTreeState());
    zeroRandom.setAmount (0.0f);
    test.check (! zeroRandom.randomizeAll() && ! zeroRandom.canUndo()
                    && parameterArraysEqual (
                        zeroBefore,
                        deeppluck::captureParameterValues (
                            zeroProcessor.getValueTreeState()), 0.0f),
                "zero-percent Random Amount is a total no-op");
    DeepPluckAudioProcessor zeroSequenceControlProcessor;
    deeppluck::DeepPluckRandomization zeroSequenceControl (
        zeroSequenceControlProcessor.getValueTreeState(), 123ULL);
    zeroRandom.setAmount (1.0f);
    test.check (zeroRandom.randomizeModule (deeppluck::RandomizationModule::ampEnvelope)
                    && zeroSequenceControl.randomizeModule (
                        deeppluck::RandomizationModule::ampEnvelope)
                    && parameterArraysEqual (
                        deeppluck::captureParameterValues (zeroProcessor.getValueTreeState()),
                        deeppluck::captureParameterValues (
                            zeroSequenceControlProcessor.getValueTreeState()), 0.0f),
                "zero-percent no-op consumes no RNG values");

    DeepPluckAudioProcessor allLockedProcessor;
    deeppluck::DeepPluckRandomization allLockedRandom (
        allLockedProcessor.getValueTreeState(), 999ULL);
    const auto allLockedBefore = deeppluck::captureParameterValues (
        allLockedProcessor.getValueTreeState());
    for (auto moduleIndex = 0;
         moduleIndex < static_cast<int> (deeppluck::RandomizationModule::count);
         ++moduleIndex)
        allLockedRandom.setModuleLocked (
            static_cast<deeppluck::RandomizationModule> (moduleIndex), true);
    test.check (! allLockedRandom.randomizeAll() && ! allLockedRandom.canUndo()
                    && parameterArraysEqual (
                        allLockedBefore,
                        deeppluck::captureParameterValues (
                            allLockedProcessor.getValueTreeState()), 0.0f),
                "Master Random with all ten modules locked is a total no-op");

    DeepPluckAudioProcessor undoProcessor;
    test.check (undoProcessor.loadFactoryPreset (0), "Undo test starts from a factory preset");
    test.check (undoProcessor.randomizeAll(), "first processor Random creates Undo snapshot");
    const auto firstRandom = deeppluck::captureParameterValues (
        undoProcessor.getValueTreeState());
    test.check (undoProcessor.randomizeAll() && undoProcessor.undoRandomization(),
                "Random/Random/Undo succeeds");
    test.check (parameterArraysEqual (
                    firstRandom,
                    deeppluck::captureParameterValues (undoProcessor.getValueTreeState()),
                    1.0e-6f),
                "one-step Undo restores the state before the second Random");
    test.check (! undoProcessor.undoRandomization(), "Undo has exactly one level");
    test.check (undoProcessor.randomizeAll(), "new Random replaces the consumed snapshot");
    setParameter (undoProcessor.getValueTreeState(), deeppluck::ParameterIDs::outputGain, -3.0f);
    setParameter (undoProcessor.getValueTreeState(), deeppluck::ParameterIDs::filterCutoff,
                  9999.0f);
    test.check (undoProcessor.undoRandomization(), "manual edit does not destroy Undo snapshot");
    const auto undone = deeppluck::captureParameterValues (undoProcessor.getValueTreeState());
    test.near (undone[0], -3.0, 1.0e-6, "Undo keeps current excluded Output Gain");
    for (auto index = 2; index < 62; ++index)
        test.near (undone[static_cast<std::size_t> (index)],
                   firstRandom[static_cast<std::size_t> (index)], 1.0e-5,
                   "Undo restores each randomizable value");
    test.check (undoProcessor.loadFactoryPreset (1)
                    && ! undoProcessor.getRandomization().canUndo(),
                "preset load invalidates Random Undo");
    const auto factoryDisplay = undoProcessor.getPresetDisplayState();
    undoProcessor.getRandomization().setAmount (0.33f);
    undoProcessor.toggleRandomizationLock (deeppluck::RandomizationModule::noise);
    test.check (undoProcessor.getPresetDisplayState().stableId == factoryDisplay.stableId,
                "Amount and Lock controls are neutral to preferred preset display");
    undoProcessor.toggleRandomizationLock (deeppluck::RandomizationModule::noise);
    static_cast<void> (undoProcessor.randomizeAll());
    test.check (undoProcessor.getPresetDisplayState().custom,
                "successful Random normally changes the display to Custom");
    static_cast<void> (undoProcessor.undoRandomization());
    test.check (undoProcessor.getPresetDisplayState().stableId == factoryDisplay.stableId,
                "Undo restores the preferred factory display when values match");

    struct DistributionSpec { int parameterIndex; std::vector<int> weights; };
    const std::vector<DistributionSpec> distributions {
        { 2, { 70, 23, 6, 1 } }, { 3, { 55, 25, 15, 5 } },
        { 4, { 65, 25, 10 } }, { 5, { 50, 30, 15, 5 } },
        { 6, { 10, 90 } }, { 7, { 10, 55, 25, 10 } },
        { 8, { 65, 25, 8, 2 } }, { 9, { 55, 30, 12, 3 } },
        { 10, { 60, 25, 15 } }, { 11, { 70, 15, 10, 5 } },
        { 12, { 75, 20, 4, 1 } }, { 13, { 60, 25, 10, 5 } },
        { 14, { 70, 20, 10 } }, { 15, { 60, 25, 10, 5 } },
        { 16, { 15, 85 } }, { 17, { 65, 25, 8, 2 } },
        { 18, { 60, 25, 10, 5 } }, { 19, { 70, 15, 15 } },
        { 20, { 30, 70 } }, { 21, { 70, 20, 8, 2 } },
        { 22, { 60, 25, 10, 5 } }, { 23, { 70, 15, 15 } },
        { 24, { 15, 85 } }, { 25, { 60, 25, 10, 5 } },
        { 26, { 30, 60, 10 } }, { 27, { 38, 12, 10, 10, 10, 6, 6, 2, 2, 2, 2 } },
        { 28, { 65, 25, 10 } }, { 29, { 25, 45, 22, 8 } },
        { 30, { 65, 25, 8, 2 } }, { 31, { 60, 25, 12, 3 } },
        { 32, { 55, 30, 10, 5 } }, { 33, { 75, 25 } },
        { 34, { 2, 4, 12, 23, 34, 20, 5 } }, { 35, { 45, 30, 15, 10 } },
        { 36, { 35, 65 } }, { 37, { 25, 50, 20, 5 } },
        { 38, { 50, 40, 8, 2 } }, { 39, { 30, 50, 15, 5 } },
        { 40, { 35, 25, 12, 20, 8 } }, { 41, { 75, 20, 5 } },
        { 42, { 25, 20, 10, 35, 10 } }, { 43, { 75, 20, 5 } },
        { 44, { 65, 35 } }, { 45, { 70, 25, 5 } },
        { 46, { 65, 25, 8, 2 } }, { 47, { 65, 20, 13, 2 } },
        { 48, { 65, 25, 8, 2 } }, { 49, { 55, 45 } },
        { 50, { 20, 80 } }, { 51, { 60, 25, 12, 3 } },
        { 52, { 14, 28, 28, 10, 8, 10, 2 } }, { 53, { 55, 25, 15, 5 } },
        { 54, { 65, 20, 12, 3 } }, { 55, { 65, 25, 8, 2 } },
        { 56, { 70, 30 } }, { 57, { 60, 25, 12, 3 } },
        { 58, { 60, 25, 10, 5 } }, { 59, { 70, 15, 12, 3 } },
        { 60, { 65, 25, 8, 2 } }, { 61, { 70, 30 } }
    };
    DeepPluckAudioProcessor distributionProcessor;
    deeppluck::DeepPluckRandomization distributionRandom (
        distributionProcessor.getValueTreeState(), 0x0ddc0ffeULL);
    constexpr auto distributionSamples = 10000;
    for (const auto& spec : distributions)
    {
        std::vector<int> counts (spec.weights.size(), 0);
        for (auto sample = 0; sample < distributionSamples; ++sample)
        {
            const auto target = distributionRandom.drawTargetForTests (spec.parameterIndex);
            const auto branch = distributionRandom.getLastDistributionBranchForTests();
            test.check (std::isfinite (target), "weighted distribution target is finite");
            if (branch >= 0 && branch < static_cast<int> (counts.size()))
                ++counts[static_cast<std::size_t> (branch)];
        }
        for (auto branch = std::size_t { 0 }; branch < counts.size(); ++branch)
        {
            const auto observed = static_cast<double> (counts[branch]) / distributionSamples;
            const auto expected = static_cast<double> (spec.weights[branch]) / 100.0;
            test.near (observed, expected, 0.03,
                       "weighted distribution branch stays within three percentage points");
            test.check (counts[branch] > 0, "every declared extreme branch is reachable");
        }
    }

    constexpr std::array ampAttackRanges {
        std::array { 0.001f, 0.015f }, std::array { 0.015f, 0.080f },
        std::array { 0.080f, 0.500f }, std::array { 0.500f, 2.000f }
    };
    constexpr std::array filterAttackRanges {
        std::array { 0.001f, 0.020f }, std::array { 0.020f, 0.100f },
        std::array { 0.100f, 0.800f }, std::array { 0.800f, 2.000f }
    };
    for (const auto [parameterIndex, ranges] : {
             std::pair { 2, ampAttackRanges }, std::pair { 12, filterAttackRanges } })
        for (auto sample = 0; sample < 20000; ++sample)
        {
            const auto target = distributionRandom.drawTargetForTests (parameterIndex);
            const auto branch = distributionRandom.getLastDistributionBranchForTests();
            test.check (branch >= 0 && branch < static_cast<int> (ranges.size()),
                        "Attack randomization returns a declared branch");
            if (branch >= 0 && branch < static_cast<int> (ranges.size()))
                test.check (target >= ranges[static_cast<std::size_t> (branch)][0]
                                && target <= ranges[static_cast<std::size_t> (branch)][1]
                                && target <= 2.0f,
                            "Attack random target stays inside its frozen branch and range");
        }

    setParameter (distributionProcessor.getValueTreeState(),
                  deeppluck::ParameterIDs::fxOrder, 2.0f);
    std::array<int, 6> orderCounts {};
    constexpr auto orderSamples = 20000;
    for (auto sample = 0; sample < orderSamples; ++sample)
    {
        const auto target = static_cast<int> (distributionRandom.drawTargetForTests (61));
        test.check (target >= 0 && target < 6, "FX order Random always returns a valid order");
        if (target >= 0 && target < 6)
            ++orderCounts[static_cast<std::size_t> (target)];
    }
    test.near (static_cast<double> (orderCounts[2]) / orderSamples, 0.70, 0.03,
               "FX order Random preserves the current order about seventy percent of draws");
    for (auto order = 0; order < 6; ++order)
        if (order != 2)
            test.near (static_cast<double> (orderCounts[static_cast<std::size_t> (order)])
                           / orderSamples,
                       0.06, 0.02,
                       "FX order Random distributes moves across the other five orders");

    DeepPluckAudioProcessor boundsProcessor;
    auto& boundsState = boundsProcessor.getValueTreeState();
    deeppluck::DeepPluckRandomization boundsRandom (boundsState, 0x5555aaaaULL);
    constexpr std::array<float, 4> amounts { 0.10f, 0.25f, 0.50f, 1.00f };
    for (auto action = 0; action < 10000; ++action)
    {
        const auto module = static_cast<deeppluck::RandomizationModule> (
            action % static_cast<int> (deeppluck::RandomizationModule::count));
        boundsRandom.setAmount (amounts[static_cast<std::size_t> (action % amounts.size())]);
        if (action % 37 == 0)
            boundsRandom.setModuleLocked (module, true);
        if (action % 37 == 1)
            boundsRandom.setModuleLocked (module, false);
        static_cast<void> (action % 11 == 0 ? boundsRandom.randomizeAll()
                                             : boundsRandom.randomizeModule (module));
        for (auto index = 0; index < 62; ++index)
        {
            auto* parameter = boundsState.getParameter (
                deeppluck::allParameterIds[static_cast<std::size_t> (index)]);
            const auto value = parameter->getValue();
            test.check (std::isfinite (value) && value >= 0.0f && value <= 1.0f,
                        "10,000-action stress keeps every parameter canonical and finite");
        }
        test.check (deeppluck::captureParameterValues (boundsState)[0]
                            == allLockedBefore[0]
                        && deeppluck::captureParameterValues (boundsState)[1]
                            == allLockedBefore[1],
                    "Output Gain and Master Tune remain exact across many actions");
    }

    auto maximumRandomPeak = 0.0f;
    for (auto stateIndex = 0; stateIndex < 32; ++stateIndex)
    {
        DeepPluckAudioProcessor audioProcessor;
        auto& audioState = audioProcessor.getValueTreeState();
        deeppluck::DeepPluckRandomization audioRandom (
            audioState, static_cast<std::uint64_t> (0x800000 + stateIndex));
        static_cast<void> (audioRandom.randomizeAll());
        if (stateIndex < 4)
        {
            setParameter (audioState, deeppluck::ParameterIDs::filterEnabled, 1.0f);
            setParameter (audioState, deeppluck::ParameterIDs::filterResonance, 0.95f);
            setParameter (audioState, deeppluck::ParameterIDs::filterDrive, 18.0f);
            setParameter (audioState, deeppluck::ParameterIDs::unisonVoices, 4.0f);
            setParameter (audioState, deeppluck::ParameterIDs::transientEnabled, 1.0f);
            setParameter (audioState, deeppluck::ParameterIDs::noiseEnabled, 1.0f);
        }
        audioProcessor.prepareToPlay (48000.0, 256);
        juce::AudioBuffer<float> buffer (2, 256);
        juce::MidiBuffer notes;
        for (auto note = 0; note < 20; ++note)
            notes.addEvent (juce::MidiMessage::noteOn (1, 36 + note, 0.8f), note % 32);
        for (auto block = 0; block < 12; ++block)
        {
            audioProcessor.processBlock (buffer, notes);
            notes.clear();
            test.check (bufferIsFinite (buffer),
                        "randomized 16-voice/voice-stealing audio remains finite");
            maximumRandomPeak = juce::jmax (
                maximumRandomPeak,
                maximumMagnitude (buffer, 0, buffer.getNumSamples()));
        }
    }
    std::cout << "METRIC: V0.9.0 randomized 16-voice peak = "
              << maximumRandomPeak << '\n';
    test.check (maximumRandomPeak <= 0.95f,
                "representative randomized and forced high-risk states preserve headroom");
}

void testPresetFiles (TestContext& test)
{
    const auto root = prepareTestRoot ("preset_files");
    DeepPluckAudioProcessor processor (root, false);
    auto& state = processor.getValueTreeState();
    setParameter (state, deeppluck::ParameterIDs::filterCutoff, 2345.0f);
    setParameter (state, deeppluck::ParameterIDs::osc2Enabled, 1.0f);
    setParameter (state, deeppluck::ParameterIDs::chorusBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::chorusMix, 0.37f);
    setParameter (state, deeppluck::ParameterIDs::delayBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::delayFeedback, 0.51f);
    setParameter (state, deeppluck::ParameterIDs::reverbBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::reverbMix, 0.33f);
    setParameter (state, deeppluck::ParameterIDs::fxOrder, 5.0f);
    const auto utf8Author = juce::String ("T") + juce::String::charToString (0x00eb)
                          + "st " + juce::String::charToString (0x00c4) + "uthor";
    auto& manager = processor.getPresetManager();
    deeppluck::UserPresetEntry saved;
    juce::String error;
    const auto saveResult = manager.savePreset (
        state, "My: Deep*Pluck. ", utf8Author, "UTF-8: tief & warm",
        false, saved, error);
    test.check (saveResult == deeppluck::PresetSaveResult::saved
                    && saved.file.existsAsFile(),
                "Save creates one atomic User preset in the injected root");
    test.check (! saved.file.getFileName().containsAnyOf ("<>:\"/\\|?*")
                    && saved.file.hasFileExtension (".deeppluckpreset"),
                "Save sanitizes Windows filename characters and appends the extension");
    test.check (manager.waitForScanForTests (10000), "post-save scan completes");
    const auto savedText = saved.file.loadFileAsString();
    juce::var savedJson;
    const auto savedParse = juce::JSON::parse (savedText, savedJson);
    const auto* savedObject = savedJson.getDynamicObject();
    const auto* savedParameters = savedObject != nullptr
        ? savedObject->getProperty ("parameters").getDynamicObject() : nullptr;
    test.check (savedParse.wasOk() && savedObject != nullptr
                    && savedObject->getProperty ("format").toString()
                           == "JustALilPluckPreset"
                    && static_cast<int> (savedObject->getProperty ("schemaVersion")) == 1
                    && savedObject->getProperty ("pluginVersion").toString()
                           == DEEPPLUCK_EXPECTED_VERSION
                    && savedParameters != nullptr
                    && savedParameters->getProperties().size() == 62,
                "saved UTF-8 JSON contains schema metadata and exactly 62 values");
    test.check (root.findChildFiles (juce::File::findFiles, true, "*.tmp-*").isEmpty(),
                "atomic save leaves no temporary file behind");

    deeppluck::UserPresetData loaded;
    test.check (manager.loadPresetFile (saved.file, loaded, error)
                    && parameterArraysEqual (
                        loaded.values, deeppluck::captureParameterValues (state), 1.0e-5f)
                    && loaded.author == utf8Author,
                "Load restores all 62 saved values tolerance-correctly");

    const auto originalText = saved.file.loadFileAsString();
    deeppluck::UserPresetEntry saveAsEntry;
    test.check (manager.savePreset (state, "Second Preset", "Author", {}, false,
                                    saveAsEntry, error)
                    == deeppluck::PresetSaveResult::saved
                    && saveAsEntry.file != saved.file
                    && saved.file.loadFileAsString() == originalText,
                "Save As creates a new file and leaves the original unchanged");
    deeppluck::UserPresetEntry overwriteEntry;
    test.check (manager.savePreset (state, "Second Preset", "Author", {}, false,
                                    overwriteEntry, error)
                    == deeppluck::PresetSaveResult::needsOverwriteConfirmation
                    && manager.savePreset (state, "Second Preset", "Author", {}, true,
                                           overwriteEntry, error)
                           == deeppluck::PresetSaveResult::saved,
                "filename collision requires explicit overwrite permission");

    const auto makeVariant = [&] (const juce::String& filename,
                                  const std::function<void (juce::DynamicObject&)>& mutate)
    {
        juce::var value;
        static_cast<void> (juce::JSON::parse (originalText, value));
        mutate (*value.getDynamicObject());
        const auto file = root.getChildFile (filename);
        test.check (writeTestText (file, juce::JSON::toString (value, true)),
                    "test preset variant is written inside injected root");
        return file;
    };

    const auto missing = makeVariant ("Missing.deeppluckpreset", [] (auto& object)
    {
        object.getProperty ("parameters").getDynamicObject()->removeProperty (
            deeppluck::ParameterIDs::filterCutoff);
        object.setProperty ("name", "Missing");
    });
    deeppluck::UserPresetData missingData;
    test.check (manager.loadPresetFile (missing, missingData, error),
                "schema-1 preset with a missing parameter loads");
    const auto defaults = deeppluck::captureParameterDefaults (state);
    test.near (missingData.values[7], defaults[7], 1.0e-6,
               "missing parameter uses the current APVTS default, not prior sound");

    const auto legacyAttack = makeVariant ("LegacyAttack.deeppluckpreset", [] (auto& object)
    {
        auto* parameters = object.getProperty ("parameters").getDynamicObject();
        parameters->setProperty (deeppluck::ParameterIDs::ampAttack, 4.5);
        parameters->setProperty (deeppluck::ParameterIDs::filterAttack, 3.2);
        object.setProperty ("name", "Legacy Attack");
    });
    deeppluck::UserPresetData legacyAttackData;
    test.check (manager.loadPresetFile (legacyAttack, legacyAttackData, error),
                "legacy preset with finite pre-freeze Attack values loads");
    test.near (legacyAttackData.values[2], 2.0, 1.0e-6,
               "legacy preset Amp Attack above two seconds clamps to two seconds");
    test.near (legacyAttackData.values[12], 2.0, 1.0e-6,
               "legacy preset Filter Attack above two seconds clamps to two seconds");

    const auto legacyV08 = makeVariant ("LegacyV08.deeppluckpreset", [] (auto& object)
    {
        auto* parameters = object.getProperty ("parameters").getDynamicObject();
        for (auto index = std::size_t { 44 }; index < deeppluck::allParameterIds.size(); ++index)
            parameters->removeProperty (deeppluck::allParameterIds[index]);
        object.setProperty ("name", "Legacy V0.8");
    });
    deeppluck::UserPresetData legacyV08Data;
    test.check (manager.loadPresetFile (legacyV08, legacyV08Data, error),
                "schema-1 V0.8 preset without FX values loads");
    for (auto index = std::size_t { 44 }; index < deeppluck::allParameterIds.size(); ++index)
        test.near (legacyV08Data.values[index], defaults[index], 1.0e-6,
                   "legacy preset receives each declared neutral/default FX value");

    const auto unknown = makeVariant ("Unknown.deeppluckpreset", [] (auto& object)
    {
        object.getProperty ("parameters").getDynamicObject()->setProperty (
            "future_parameter", 123.0);
        object.setProperty ("name", "Unknown");
    });
    deeppluck::UserPresetData unknownData;
    test.check (manager.loadPresetFile (unknown, unknownData, error)
                    && parameterArraysEqual (
                        unknownData.values,
                        deeppluck::captureParameterValues (state), 1.0e-5f),
                "unknown future parameter is ignored while known values load");

    const auto badSchema = makeVariant ("BadSchema.deeppluckpreset", [] (auto& object)
    {
        object.setProperty ("schemaVersion", 2);
        object.setProperty ("name", "Bad Schema");
    });
    const auto badFormat = makeVariant ("BadFormat.deeppluckpreset", [] (auto& object)
    {
        object.setProperty ("format", "NotDeepPluck");
        object.setProperty ("name", "Bad Format");
    });
    const auto badFinite = makeVariant ("BadFinite.deeppluckpreset", [] (auto& object)
    {
        object.getProperty ("parameters").getDynamicObject()->setProperty (
            deeppluck::ParameterIDs::ampAttack, "NaN");
        object.setProperty ("name", "Bad Finite");
    });
    const auto corrupt = root.getChildFile ("Corrupt.deeppluckpreset");
    test.check (writeTestText (corrupt, "{ this is not json"),
                "corrupt test file is created");
    const auto beforeRejectedLoads = deeppluck::captureParameterValues (state);
    deeppluck::UserPresetData rejected;
    test.check (! manager.loadPresetFile (badSchema, rejected, error)
                    && ! manager.loadPresetFile (badFormat, rejected, error)
                    && ! manager.loadPresetFile (badFinite, rejected, error)
                    && ! manager.loadPresetFile (corrupt, rejected, error)
                    && parameterArraysEqual (
                        beforeRejectedLoads,
                        deeppluck::captureParameterValues (state), 0.0f),
                "bad schema/format/type/corrupt files reject transactionally");

    const auto oversized = root.getChildFile ("Oversized.deeppluckpreset");
    test.check (writeTestText (oversized, juce::String::repeatedString (
                    "x", 1024 * 1024 + 1))
                    && ! manager.loadPresetFile (oversized, rejected, error),
                "preset larger than one MiB is rejected");

    const auto packA = root.getChildFile ("PackWithoutMetadata");
    const auto packB = root.getChildFile ("PackWithMetadata");
    const auto category = packB.getChildFile ("Bass/Deep");
    static_cast<void> (packA.createDirectory());
    static_cast<void> (category.createDirectory());
    test.check (saved.file.copyFileTo (packA.getChildFile ("A.deeppluckpreset"))
                    && saved.file.copyFileTo (category.getChildFile ("B.deeppluckpreset"))
                    && writeTestText (packB.getChildFile ("pack.json"),
                        "{\"format\":\"JustALilPluckPack\",\"schemaVersion\":1,"
                        "\"packName\":\"Curated Pack\"}"),
                "pack fixtures are created inside the test root");
    const auto brokenPack = root.getChildFile ("BrokenPack");
    static_cast<void> (brokenPack.createDirectory());
    test.check (saved.file.copyFileTo (brokenPack.getChildFile ("C.deeppluckpreset"))
                    && writeTestText (brokenPack.getChildFile ("pack.json"), "broken"),
                "broken pack metadata fixture is created");
    const auto imported = root.getChildFile ("Imported.deeppluckpreset");
    test.check (saved.file.copyFileTo (imported), "direct-root Imported fixture is created");

    auto deep = root.getChildFile ("TooDeep");
    for (auto depth = 0; depth < 10; ++depth)
        deep = deep.getChildFile ("L" + juce::String (depth));
    static_cast<void> (deep.createDirectory());
    test.check (saved.file.copyFileTo (deep.getChildFile ("Hidden.deeppluckpreset")),
                "depth-limit fixture is created");

    manager.requestScan();
    const auto scanStart = std::chrono::steady_clock::now();
    test.check (manager.waitForScanForTests (10000), "folder and pack scan completes");
    const auto catalog = manager.getCatalog();
    const auto scanMilliseconds = std::chrono::duration<double, std::milli> (
        std::chrono::steady_clock::now() - scanStart).count();
    auto sawUser = false;
    auto sawFallback = false;
    auto sawMetadata = false;
    auto sawBrokenFallback = false;
    auto sawCategory = false;
    auto sawImported = false;
    auto sawTooDeep = false;
    for (const auto& entry : catalog.entries)
    {
        sawUser = sawUser || entry.source == deeppluck::PresetSource::user;
        sawFallback = sawFallback || entry.group == "PackWithoutMetadata";
        sawMetadata = sawMetadata || entry.group == "Curated Pack";
        sawBrokenFallback = sawBrokenFallback || entry.group == "BrokenPack";
        sawCategory = sawCategory || entry.category == "Bass/Deep";
        sawImported = sawImported || entry.source == deeppluck::PresetSource::imported;
        sawTooDeep = sawTooDeep || entry.relativePath.contains ("Hidden");
    }
    test.check (sawUser && sawFallback && sawMetadata && sawBrokenFallback
                    && sawCategory && sawImported && ! sawTooDeep,
                "scan groups User/Packs/Imported, metadata fallback, categories and depth limit");
    test.check (catalog.skippedErrors >= 4,
                "scan skips corrupt, invalid, and oversized files while keeping valid entries");
    for (auto index = std::size_t { 1 }; index < catalog.entries.size(); ++index)
    {
        test.check (static_cast<int> (catalog.entries[index - 1].source)
                        <= static_cast<int> (catalog.entries[index].source),
                    "catalog keeps stable source ordering");
        if (catalog.entries[index - 1].source == catalog.entries[index].source
            && catalog.entries[index - 1].group == catalog.entries[index].group)
            test.check (catalog.entries[index - 1].relativePath.compareIgnoreCase (
                            catalog.entries[index].relativePath) <= 0,
                        "catalog keeps stable case-insensitive relative-path ordering");
    }
    std::cout << "METRIC: V0.8.0 preset scan entries=" << catalog.entries.size()
              << " time_ms=" << scanMilliseconds << '\n';

    test.check (deeppluck::DeepPluckPresetManager::sanitizePresetBaseName (
                    "<>:\"/\\|?*name. ") == "_________name"
                    && deeppluck::DeepPluckPresetManager::sanitizePresetBaseName ("CON")
                           == "_CON"
                    && deeppluck::DeepPluckPresetManager::sanitizePresetBaseName ("LPT9")
                           == "_LPT9"
                    && deeppluck::DeepPluckPresetManager::sanitizePresetBaseName (
                           juce::String::repeatedString ("a", 100)).length() == 80
                    && deeppluck::DeepPluckPresetManager::sanitizePresetBaseName ("   ").isEmpty(),
                "Windows invalid, trailing, reserved, empty and length filename rules hold");
    test.check (deeppluck::DeepPluckPresetManager::hasPresetExtension (
                    root.getChildFile ("Case.DEEPPLUCKPRESET")),
                "preset extension match is case-insensitive");

    deeppluck::UserPresetEntry escaped;
    test.check (manager.savePreset (state, "Escape", "Author", {}, true,
                                    escaped, error,
                                    root.getSiblingFile ("escaped.deeppluckpreset"))
                    == deeppluck::PresetSaveResult::failed
                    && ! manager.deleteUserPreset (packA.getChildFile ("A.deeppluckpreset"),
                                                    error),
                "path escape and Pack overwrite/delete are denied");
    test.check (saved.file.existsAsFile(), "delete cancellation leaves User file present");
    test.check (manager.deleteUserPreset (saved.file, error) && ! saved.file.existsAsFile(),
                "confirmed delete only removes an editable file in the test User root");

    const auto limitRoot = prepareTestRoot ("preset_file_limit");
    for (auto index = 0; index < 2001; ++index)
        test.check (writeTestText (
                        limitRoot.getChildFile (
                            "P" + juce::String (index).paddedLeft ('0', 4)
                            + ".deeppluckpreset"),
                        originalText),
                    "file-count limit fixture is created in the test root");
    DeepPluckAudioProcessor limitProcessor (limitRoot, false);
    limitProcessor.getPresetManager().requestScan();
    test.check (limitProcessor.getPresetManager().waitForScanForTests (30000),
                "2,001-file limit scan completes");
    test.check (limitProcessor.getPresetManager().getCatalog().entries.size() == 2000,
                "scan enforces the 2,000-preset file limit");
}

void testPresetIntegration (TestContext& test)
{
    const auto root = prepareTestRoot ("preset_integration");
    DeepPluckAudioProcessor processor (root, true);
    auto& state = processor.getValueTreeState();
    setParameter (state, deeppluck::ParameterIDs::filterCutoff, 3210.0f);
    setParameter (state, deeppluck::ParameterIDs::noiseEnabled, 1.0f);
    setParameter (state, deeppluck::ParameterIDs::noiseLevel, -30.0f);
    setParameter (state, deeppluck::ParameterIDs::chorusBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::chorusMix, 0.31f);
    setParameter (state, deeppluck::ParameterIDs::delayBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::delaySyncDivision, 3.0f);
    setParameter (state, deeppluck::ParameterIDs::reverbBypass, 0.0f);
    setParameter (state, deeppluck::ParameterIDs::reverbSize, 0.73f);
    setParameter (state, deeppluck::ParameterIDs::fxOrder, 4.0f);
    const auto userValues = deeppluck::captureParameterValues (state);
    juce::String error;
    test.check (processor.saveUserPreset ("Integration User", "Tester", false,
                                          false, error)
                    == deeppluck::PresetSaveResult::saved
                    && processor.getPresetManager().waitForScanForTests (10000),
                "processor saves and refreshes an injected User preset");
    const auto savedDisplay = processor.getPresetDisplayState();
    test.check (! savedDisplay.custom && savedDisplay.editable
                    && savedDisplay.name == "Integration User",
                "saved User preset becomes the preferred editable display");

    setParameter (state, deeppluck::ParameterIDs::filterCutoff, 7777.0f);
    test.check (processor.getPresetDisplayState().custom,
                "manual edit after User load displays Custom");
    test.check (processor.loadPresetByStableId (savedDisplay.stableId, error)
                    && parameterArraysEqual (
                        userValues, deeppluck::captureParameterValues (state), 1.0e-5f),
                "stable catalog ID reloads all User values and preferred display");

    test.check (processor.loadFactoryPreset (0)
                    && processor.saveUserPreset ("Factory Copy", "Tester", false,
                                                 false, error)
                           == deeppluck::PresetSaveResult::saved,
                "Save As can duplicate a Factory sound into User");
    test.check (processor.getPresetDisplayState().name == "Factory Copy"
                    && processor.getPresetDisplayState().editable,
                "Factory-value duplicate deliberately shows its User name");
    const auto factoryCopyFile = processor.getPresetDisplayState().file;
    test.check (processor.getPresetManager().waitForScanForTests (10000),
                "Factory-copy catalog refresh completes");

    DeepPluckAudioProcessor factoryMatcher;
    deeppluck::applyFactoryPreset (factoryMatcher.getValueTreeState(), 2);
    test.check (factoryMatcher.getPresetDisplayState().stableId == "factory:2",
                "factory match is inferred without a preferred external entry");

    setParameter (state, deeppluck::ParameterIDs::outputGain, -11.123f);
    const auto expectedFirstFactoryId = processor.getFactoryLibrary().entries.empty()
                                            ? juce::String { "factory:0" }
                                            : processor.getFactoryLibrary().entries.front().stableId;
    test.check (processor.getPresetDisplayState().custom
                    && processor.loadRelativePreset (1, error)
                    && processor.getPresetDisplayState().stableId == expectedFirstFactoryId,
                "Next from Custom starts at the first Factory preset");
    setParameter (state, deeppluck::ParameterIDs::outputGain, -11.123f);
    test.check (processor.loadRelativePreset (-1, error)
                    && ! processor.getPresetDisplayState().custom,
                "Previous from Custom reaches the last stable catalog entry");

    test.check (processor.loadPresetByStableId (savedDisplay.stableId, error),
                "User preset reloads before host-state independence test");
    juce::MemoryBlock hostState;
    processor.getStateInformation (hostState);
    const auto stateBeforeFileRemoval = deeppluck::captureParameterValues (state);
    test.check (savedDisplay.file.deleteFile(),
                "test removes only its injected User preset file");
    DeepPluckAudioProcessor restoredProcessor;
    restoredProcessor.setStateInformation (hostState.getData(),
                                            static_cast<int> (hostState.getSize()));
    test.check (parameterArraysEqual (
                    stateBeforeFileRemoval,
                    deeppluck::captureParameterValues (
                        restoredProcessor.getValueTreeState()), 1.0e-5f)
                    && restoredProcessor.getPresetDisplayState().custom,
                "host state restores all values without the external preset file or ID");

    test.check (processor.loadFactoryPreset (1) && processor.randomizeAll(),
                "Random creates Undo for non-invalidating workflow actions");
    processor.getRandomization().setAmount (0.5f);
    processor.toggleRandomizationLock (deeppluck::RandomizationModule::transient);
    processor.refreshUserPresets();
    test.check (processor.getPresetManager().waitForScanForTests (10000)
                    && processor.getRandomization().canUndo(),
                "Amount, Lock and Refresh do not invalidate Random Undo");
    test.check (processor.saveUserPreset ("Undo Neutral Save", "Tester", false,
                                          false, error)
                    == deeppluck::PresetSaveResult::saved
                    && processor.getRandomization().canUndo(),
                "Save does not invalidate Random Undo");
    test.check (processor.loadFactoryPreset (2)
                    && ! processor.getRandomization().canUndo(),
                "Factory load is an explicit Undo boundary");

    const auto refreshPack = root.getChildFile ("RefreshPack");
    static_cast<void> (refreshPack.createDirectory());
    test.check (factoryCopyFile.copyFileTo (
                    refreshPack.getChildFile ("Refresh.deeppluckpreset")),
                "new pack is copied into the injected test root after initial scan");
    processor.refreshUserPresets();
    processor.refreshUserPresets();
    processor.refreshUserPresets();
    test.check (processor.getPresetManager().waitForScanForTests (10000),
                "coalesced explicit Refresh completes without restart");
    const auto refreshed = processor.getUserPresetCatalog();
    const auto refreshCount = std::count_if (
        refreshed.entries.begin(), refreshed.entries.end(), [] (const auto& entry)
        {
            return entry.group == "RefreshPack";
        });
    test.check (refreshCount == 1, "Refresh discovers one pack entry without duplicates");

    DeepPluckAudioProcessor scanAudioProcessor (root, false);
    DeepPluckAudioProcessor controlAudioProcessor;
    std::atomic<int> observedFileOperations { 0 };
    std::atomic<int> audioThreadFileOperations { 0 };
    const auto audioThreadId = std::this_thread::get_id();
    scanAudioProcessor.getPresetManager().setFileOperationObserverForTests (
        [&observedFileOperations, &audioThreadFileOperations, audioThreadId]
        {
            ++observedFileOperations;
            if (std::this_thread::get_id() == audioThreadId)
                ++audioThreadFileOperations;
        });
    scanAudioProcessor.prepareToPlay (48000.0, 128);
    controlAudioProcessor.prepareToPlay (48000.0, 128);
    scanAudioProcessor.getPresetManager().requestScan();
    for (auto block = 0; block < 32; ++block)
    {
        juce::MidiBuffer scanMidi;
        juce::MidiBuffer controlMidi;
        if (block == 0)
        {
            scanMidi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 0);
            controlMidi.addEvent (juce::MidiMessage::noteOn (1, 60, 0.8f), 0);
        }
        juce::AudioBuffer<float> scanBuffer (2, 128);
        juce::AudioBuffer<float> controlBuffer (2, 128);
        scanAudioProcessor.processBlock (scanBuffer, scanMidi);
        controlAudioProcessor.processBlock (controlBuffer, controlMidi);
        test.check (bufferIsFinite (scanBuffer),
                    "audio remains finite during a parallel preset scan");
        for (auto channel = 0; channel < 2; ++channel)
            for (auto sample = 0; sample < 128; ++sample)
                test.near (scanBuffer.getSample (channel, sample),
                           controlBuffer.getSample (channel, sample), 1.0e-7,
                           "preset scanning is audio-bit-neutral");
    }
    test.check (scanAudioProcessor.getPresetManager().waitForScanForTests (10000)
                    && observedFileOperations.load() > 0
                    && audioThreadFileOperations.load() == 0,
                "file-operation hook observes worker activity and none on audio thread");

    for (auto lifecycle = 0; lifecycle < 3; ++lifecycle)
    {
        DeepPluckAudioProcessorEditor editor (processor);
        editor.refreshForTests();
        editor.showPage (deeppluck::gui::Page::modulation);
        editor.showPage (deeppluck::gui::Page::main);
        test.check (editor.getAttachmentCount() == 62,
                    "editor scan lifecycle preserves exactly 62 bindings");
    }
}
}

int main (int argc, char** argv)
{
    juce::ScopedJuceInitialiser_GUI initialiseJuce;

    const auto guiPreferenceRoot = juce::File { DEEPPLUCK_TEST_ROOT }.getChildFile ("GuiPreferences");
    static_cast<void> (guiPreferenceRoot.createDirectory());
    const auto sharedPreferenceFile = guiPreferenceRoot.getChildFile ("Shared.properties");
    static_cast<void> (sharedPreferenceFile.deleteFile());
    auto sharedPreferences = deeppluck::gui::DeepPluckGuiPreferences::createForTests (
        sharedPreferenceFile);
    sharedPreferences->setWelcomeEnabled (false);
    deeppluck::gui::DeepPluckGuiPreferences::installSharedForTests (sharedPreferences);

    const std::map<std::string, std::function<void (TestContext&)>> suites {
        { "parameters", testParameters },
        { "oscillator", testOscillator },
        { "oscillator2", testOscillator2 },
        { "oscillator2_integration", testOscillator2Integration },
        { "unison", testUnison },
        { "unison_integration", testUnisonIntegration },
        { "headroom", testHeadroom },
        { "performance", testPerformance },
        { "envelope", testEnvelope },
        { "filter", testFilter },
        { "filter_envelope", testFilterEnvelope },
        { "transient_noise", testTransientNoise },
        { "transient_noise_integration", testTransientNoiseIntegration },
        { "synthesiser", testSynthesiser },
        { "sample_accuracy", testSampleAccuracy },
        { "matrix", testMatrix },
        { "gui", testGui },
        { "standalone_keyboard", testStandaloneKeyboard },
        { "smoothing_stability", testSmoothingAndStability },
        { "modulation", testModulation },
        { "modulation_integration", testModulationIntegration },
        { "waveform", testWaveform },
        { "waveform_integration", testWaveformIntegration },
        { "effects", testEffects },
        { "effects_integration", testEffectsIntegration },
        { "randomization", testRandomization },
        { "preset_files", testPresetFiles },
        { "preset_integration", testPresetIntegration }
    };

    if (argc != 2 || ! suites.contains (argv[1]))
    {
        std::cerr << "Usage: DeepPluckTests <suite>\n";
        return 2;
    }

    TestContext context;
    suites.at (argv[1]) (context);

    if (context.failures == 0)
        std::cout << "PASS: " << argv[1] << '\n';
    else
        std::cerr << context.failures << " failure(s) in " << argv[1] << '\n';

    deeppluck::gui::DeepPluckGuiPreferences::resetSharedForTests();
    return context.failures == 0 ? 0 : 1;
}
