#include "amuse/SoundMacroState.hpp"
#include "amuse/Voice.hpp"
#include "amuse/Engine.hpp"
#include "amuse/Common.hpp"
#include "amuse/AudioGroup.hpp"
#include "amuse/AudioGroupPool.hpp"
#include <cstring>

using namespace std::literals;

/* C++17 will error out if an offsetof cannot be computed, so ignore this warning */
#pragma GCC diagnostic ignored "-Winvalid-offsetof"

/* Squelch Win32 macro pollution >.< */
#undef SendMessage
#undef GetMessage

namespace amuse
{

void SoundMacroState::Evaluator::addComponent(uint8_t midiCtrl, float scale, Combine combine, VarType varType)
{
    m_comps.push_back({midiCtrl, scale, combine, varType});
}

float SoundMacroState::Evaluator::evaluate(double time, const Voice& vox, const SoundMacroState& st) const
{
    float value = 0.f;

    /* Iterate each component */
    for (auto it = m_comps.cbegin(); it != m_comps.cend(); ++it)
    {
        const Component& comp = *it;
        float thisValue = 0.f;

        /* Load selected data */
        if (comp.m_varType == VarType::Ctrl)
        {
            switch (comp.m_midiCtrl)
            {
            case 128:
                /* Pitchbend */
                thisValue = (vox.getPitchWheel() * 0.5f + 0.5f) * 127.f;
                break;
            case 129:
                /* Aftertouch */
                thisValue = vox.getAftertouch();
                break;
            case 130:
                /* LFO1 */
                if (vox.m_lfoPeriods[0])
                    thisValue = (std::sin(time / vox.m_lfoPeriods[0] * 2.f * M_PIF) * 0.5f + 0.5f) * 127.f;
                break;
            case 131:
                /* LFO2 */
                if (vox.m_lfoPeriods[1])
                    thisValue = (std::sin(time / vox.m_lfoPeriods[1] * 2.f * M_PIF) * 0.5f + 0.5f) * 127.f;
                break;
            case 132:
                /* Surround panning */
                thisValue = (vox.m_curSpan * 0.5f + 0.5f) * 127.f;
                break;
            case 133:
                /* Macro-starting key */
                thisValue = st.m_initKey;
                break;
            case 134:
                /* Macro-starting velocity */
                thisValue = st.m_initVel;
                break;
            case 135:
                /* Time since macro-start (ms) */
                thisValue = clamp(0.f, float(st.m_execTime * 1000.f), 16383.f);
                break;
            default:
                thisValue = vox.getCtrlValue(comp.m_midiCtrl);
                break;
            }
        }
        else if (comp.m_varType == VarType::Var)
            thisValue = st.m_variables[comp.m_midiCtrl & 0x1f];

        /* Apply scale */
        thisValue *= comp.m_scale;

        /* Combine */
        if (it != m_comps.cbegin())
        {
            switch (comp.m_combine)
            {
            case Combine::Add:
                value += thisValue;
                break;
            case Combine::Mult:
                value *= thisValue;
                break;
            default:
                value = thisValue;
                break;
            }
        }
        else
            value = thisValue;
    }

    return value;
}

void SoundMacroState::initialize(ObjectId id, const SoundMacro* macro, int step)
{
    initialize(id, macro, step, 1000.f, 0, 0, 0);
}

void SoundMacroState::initialize(ObjectId id, const SoundMacro* macro, int step, double ticksPerSec,
                                 uint8_t midiKey, uint8_t midiVel, uint8_t midiMod)
{
    m_ticksPerSec = ticksPerSec;
    m_initKey = midiKey;
    m_initVel = midiVel;
    m_initMod = midiMod;
    m_curVel = midiVel;
    m_curMod = midiMod;
    m_curPitch = midiKey * 100;
    m_pc.clear();
    m_pc.emplace_back(id, macro, macro->assertPC(step));
    m_inWait = false;
    m_execTime = 0.f;
    m_keyoff = false;
    m_sampleEnd = false;
    m_loopCountdown = -1;
    m_lastPlayMacroVid = -1;
    m_useAdsrControllers = false;
    m_portamentoMode = SoundMacro::CmdPortamento::PortState::MIDIControlled;
    m_portamentoTime = 0.5f;
}

template <class T, std::enable_if_t<!std::is_enum_v<T>, int> = 0>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType()
{ return SoundMacro::CmdIntrospection::Field::Type::Invalid; }
template <class T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType()
{ static_assert(sizeof(T) == 1, "Enum must be an 8-bit type");
  return SoundMacro::CmdIntrospection::Field::Type::Choice; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<bool>()
{ return SoundMacro::CmdIntrospection::Field::Type::Bool; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<atInt8>()
{ return SoundMacro::CmdIntrospection::Field::Type::Int8; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<atUint8>()
{ return SoundMacro::CmdIntrospection::Field::Type::UInt8; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<atInt16>()
{ return SoundMacro::CmdIntrospection::Field::Type::Int16; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<atUint16>()
{ return SoundMacro::CmdIntrospection::Field::Type::UInt16; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<atInt32>()
{ return SoundMacro::CmdIntrospection::Field::Type::Int32; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<atUint32>()
{ return SoundMacro::CmdIntrospection::Field::Type::UInt32; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<SoundMacroIdDNA<athena::Little>>()
{ return SoundMacro::CmdIntrospection::Field::Type::SoundMacroId; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<SoundMacroStepDNA<athena::Little>>()
{ return SoundMacro::CmdIntrospection::Field::Type::SoundMacroStep; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<TableIdDNA<athena::Little>>()
{ return SoundMacro::CmdIntrospection::Field::Type::TableId; }
template <>
constexpr SoundMacro::CmdIntrospection::Field::Type GetFieldType<SampleIdDNA<athena::Little>>()
{ return SoundMacro::CmdIntrospection::Field::Type::SampleId; }

#define FIELD_HEAD(tp, var) GetFieldType<decltype(var)>(), offsetof(tp, var)

const SoundMacro::CmdIntrospection SoundMacro::CmdEnd::Introspective =
{
    CmdType::Structure,
    "End"sv,
    "End of the macro. This always appears at the end of a given SoundMacro."sv,
};
bool SoundMacro::CmdEnd::Do(SoundMacroState& st, Voice& vox) const
{
    st._setPC(-1);
    return true;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdStop::Introspective =
{
    CmdType::Structure,
     "Stop"sv,
    "Stops the macro at any point."sv,
};
bool SoundMacro::CmdStop::Do(SoundMacroState& st, Voice& vox) const
{
    st._setPC(-1);
    return true;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSplitKey::Introspective =
{
    CmdType::Structure,
    "Split Key"sv,
    "Conditionally branches macro execution based on MIDI key."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSplitKey, key),
            "Key"sv,
            0, 127, 60
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitKey, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitKey, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdSplitKey::Do(SoundMacroState& st, Voice& vox) const
{
    if (st.m_initKey >= key)
    {
        /* Do Branch */
        if (macro.id == std::get<0>(st.m_pc.back()))
            st._setPC(macroStep.step);
        else
            vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
    }

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSplitVel::Introspective =
{
    CmdType::Structure,
    "Split Velocity"sv,
    "Conditionally branches macro execution based on velocity."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSplitVel, velocity),
            "Key"sv,
            0, 127, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitVel, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitVel, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdSplitVel::Do(SoundMacroState& st, Voice& vox) const
{
    if (st.m_curVel >= velocity)
    {
        /* Do Branch */
        if (macro.id == std::get<0>(st.m_pc.back()))
            st._setPC(macroStep.step);
        else
            vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
    }

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdWaitTicks::Introspective =
{
    CmdType::Structure,
    "Wait Ticks"sv,
    "Suspend SoundMacro execution for specified length of time. Value of 65535 "
    "will wait indefinitely, relying on Key Off or Sample End to signal stop. "
    "Absolute mode waits relative to the start of the SoundMacro."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdWaitTicks, keyOff),
            "Key Off"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitTicks, random),
            "Random"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitTicks, sampleEnd),
            "Sample End"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitTicks, absolute),
            "Absolute"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitTicks, msSwitch),
            "Use Millisec"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitTicks, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 96
        }
    }
};
bool SoundMacro::CmdWaitTicks::Do(SoundMacroState& st, Voice& vox) const
{
    /* Set wait state */
    if (ticksOrMs >= 0)
    {
        float q = msSwitch ? 1000.f : st.m_ticksPerSec;
        float secTime = ticksOrMs / q;
        /* Randomize at the proper resolution */
        if (random)
            secTime = std::fmod(vox.getEngine().nextRandom() / q, secTime);

        if (absolute)
        {
            if (secTime <= st.m_execTime)
                return false;
            st.m_waitCountdown = secTime - st.m_execTime;
        }
        else
            st.m_waitCountdown = secTime;

        st.m_indefiniteWait = false;
    }
    else
        st.m_indefiniteWait = true;

    st.m_inWait = true;
    st.m_keyoffWait = keyOff;
    st.m_sampleEndWait = sampleEnd;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdLoop::Introspective =
{
    CmdType::Structure,
    "Loop"sv,
    "Branch to specified location in a loop for a specified number of Times. "
    "65535 will cause an endless loop, relying on Key Off or Sample End to signal stop."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdLoop, keyOff),
            "Key Off"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdLoop, random),
            "Random"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdLoop, sampleEnd),
            "Sample End"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdLoop, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdLoop, times),
            "Times"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdLoop::Do(SoundMacroState& st, Voice& vox) const
{
    if ((keyOff && st.m_keyoff) || (sampleEnd && st.m_sampleEnd))
    {
        /* Break out of loop */
        st.m_loopCountdown = -1;
        return false;
    }

    uint32_t useTimes = times;
    if (random)
        useTimes = vox.getEngine().nextRandom() % times;

    if (st.m_loopCountdown == -1 && useTimes != -1)
        st.m_loopCountdown = useTimes;

    if (st.m_loopCountdown > 0)
    {
        /* Loop back to step */
        --st.m_loopCountdown;
        st._setPC(macroStep.step);
    }
    else /* Break out of loop */
        st.m_loopCountdown = -1;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdGoto::Introspective =
{
    CmdType::Structure,
    "Goto"sv,
    "Unconditional branch to specified SoundMacro location."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdGoto, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdGoto, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdGoto::Do(SoundMacroState& st, Voice& vox) const
{
    /* Do Branch */
    if (macro.id == std::get<0>(st.m_pc.back()))
        st._setPC(macroStep.step);
    else
        vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdWaitMs::Introspective =
{
    CmdType::Structure,
    "Wait Millisec"sv,
    "Suspend SoundMacro execution for specified length of time. Value of 65535 "
    "will wait indefinitely, relying on Key Off or Sample End to signal stop. "
    "Absolute mode waits relative to the start of the SoundMacro."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdWaitMs, keyOff),
            "Key Off"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitMs, random),
            "Random"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitMs, sampleEnd),
            "Sample End"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitMs, absolute),
            "Absolute"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdWaitMs, ms),
            "Millisec"sv,
            0, 65535, 96
        }
    }
};
bool SoundMacro::CmdWaitMs::Do(SoundMacroState& st, Voice& vox) const
{
    /* Set wait state */
    if (ms >= 0)
    {
        float secTime = ms / 1000.f;
        /* Randomize at the proper resolution */
        if (random)
            secTime = std::fmod(vox.getEngine().nextRandom() / 1000.f, secTime);

        if (absolute)
        {
            if (secTime <= st.m_execTime)
                return false;
            st.m_waitCountdown = secTime - st.m_execTime;
        }
        else
            st.m_waitCountdown = secTime;

        st.m_indefiniteWait = false;
    }
    else
        st.m_indefiniteWait = true;

    st.m_inWait = true;
    st.m_keyoffWait = keyOff;
    st.m_sampleEndWait = sampleEnd;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPlayMacro::Introspective =
{
    CmdType::Structure,
    "Play Macro"sv,
    "Play a SoundMacro in parallel to this one. Add Note is added to the "
    "current SoundMacro note to evaluate the new note."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPlayMacro, addNote),
            "Add Note"sv,
            -128, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPlayMacro, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdPlayMacro, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPlayMacro, priority),
            "Priority"sv,
            0, 127, 50
        },
        {
            FIELD_HEAD(SoundMacro::CmdPlayMacro, maxVoices),
            "Max Voices"sv,
            0, 255, 255
        }
    }
};
bool SoundMacro::CmdPlayMacro::Do(SoundMacroState& st, Voice& vox) const
{
    ObjToken<Voice> sibVox = vox.startChildMacro(addNote, macro.id, macroStep.step);
    if (sibVox)
        st.m_lastPlayMacroVid = sibVox->vid();

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSendKeyOff::Introspective =
{
    CmdType::Structure,
    "Send Key Off"sv,
    "Send Key Off to voice specified by VID stored in a variable or the last started voice."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSendKeyOff, variable),
            "Variable"sv,
            0, 31, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSendKeyOff, lastStarted),
            "Last Started"sv,
            0, 1, 0
        }
    }
};
bool SoundMacro::CmdSendKeyOff::Do(SoundMacroState& st, Voice& vox) const
{
    if (lastStarted)
    {
        if (st.m_lastPlayMacroVid != -1)
        {
            ObjToken<Voice> otherVox = vox.getEngine().findVoice(st.m_lastPlayMacroVid);
            if (otherVox)
                otherVox->keyOff();
        }
    }
    else
    {
        ObjToken<Voice> otherVox = vox.getEngine().findVoice(st.m_variables[variable & 0x1f]);
        if (otherVox)
            otherVox->keyOff();
    }

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSplitMod::Introspective =
{
    CmdType::Structure,
    "Split Mod"sv,
    "Conditionally branch if mod wheel is greater than or equal to specified value."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSplitMod, modValue),
            "Mod Value"sv,
            0, 127, 64
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitMod, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitMod, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdSplitMod::Do(SoundMacroState& st, Voice& vox) const
{
    if (st.m_curMod >= modValue)
    {
        /* Do Branch */
        if (macro.id == std::get<0>(st.m_pc.back()))
            st._setPC(macroStep.step);
        else
            vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
    }

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPianoPan::Introspective =
{
    CmdType::Control,
    "Piano Pan"sv,
    "Gives piano-like sounds a natural-sounding stereo spread. The current key delta "
    "from Center Key is scaled with Scale and biased with Center Pan to evaluate panning."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPianoPan, scale),
            "Scale"sv,
            0, 127, 127
        },
        {
            FIELD_HEAD(SoundMacro::CmdPianoPan, centerKey),
            "Center Key"sv,
            0, 127, 36
        },
        {
            FIELD_HEAD(SoundMacro::CmdPianoPan, centerPan),
            "Center Pan"sv,
            0, 127, 64
        }
    }
};
bool SoundMacro::CmdPianoPan::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t pan = int32_t(st.m_initKey - centerKey) * scale / 127 + centerPan;
    pan = std::max(-127, std::min(127, pan));
    vox.setPan(pan / 127.f);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSetAdsr::Introspective =
{
    CmdType::Volume,
    "Set ADSR"sv,
    "Specify ADSR envelope using a pool object. DLS mode must match setting in ADSR."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetAdsr, table),
            "ADSR"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetAdsr, dlsMode),
            "DLS Mode"sv,
            0, 1, 0
        }
    }
};
bool SoundMacro::CmdSetAdsr::Do(SoundMacroState& st, Voice& vox) const
{
    vox.setAdsr(table.id, dlsMode);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdScaleVolume::Introspective =
{
    CmdType::Volume,
    "Scale Volume"sv,
    "Calculates volume by scaling and biasing velocity. "
    "The result may be passed through an optional Curve."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdScaleVolume, scale),
            "Scale"sv,
            0, 127, 127
        },
        {
            FIELD_HEAD(SoundMacro::CmdScaleVolume, add),
            "Add"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdScaleVolume, table),
            "Curve"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdScaleVolume, originalVol),
            "Original Vol"sv,
            0, 1, 1
        }
    }
};
bool SoundMacro::CmdScaleVolume::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t eval = int32_t(originalVol ? st.m_initVel : st.m_curVel) * scale / 127 + add;
    eval = clamp(0, eval, 127);

    if (table.id != 0)
    {
        const Curve* curveData = vox.getAudioGroup().getPool().tableAsCurves(table.id);
        if (curveData && curveData->data.size() >= 128)
        {
            vox.m_curVol = curveData->data[eval] / 127.f;
            return false;
        }
    }

    vox.m_curVol = eval / 127.f;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPanning::Introspective =
{
    CmdType::Control,
    "Panning"sv,
    "Start pan-sweep from Pan Position offset by Width over specified time period."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPanning, panPosition),
            "Pan Position"sv,
            0, 127, 64
        },
        {
            FIELD_HEAD(SoundMacro::CmdPanning, timeMs),
            "Time Millisec"sv,
            0, 65535, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPanning, width),
            "Width"sv,
            -128, 127, 0
        }
    }
};
bool SoundMacro::CmdPanning::Do(SoundMacroState& st, Voice& vox) const
{
    vox.startPanning(timeMs / 1000.0, panPosition, width);
    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdEnvelope::Introspective =
{
    CmdType::Volume,
    "Envelope"sv,
    "Start a velocity envelope by fading the current velocity to the one "
    "evaluated by Scale and Add. The result is optionally transformed with a Curve object."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdEnvelope, scale),
            "Scale"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdEnvelope, add),
            "Add"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdEnvelope, table),
            "Curve"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdEnvelope, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdEnvelope, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 500
        }
    }
};
bool SoundMacro::CmdEnvelope::Do(SoundMacroState& st, Voice& vox) const
{
    double q = msSwitch ? 1000.0 : st.m_ticksPerSec;
    double secTime = ticksOrMs / q;

    int32_t eval = int32_t(st.m_curVel) * scale / 127 + add;
    eval = clamp(0, eval, 127);

    const Curve* curveData;
    if (table.id != 0)
        curveData = vox.getAudioGroup().getPool().tableAsCurves(table.id);
    else
        curveData = nullptr;

    vox.startEnvelope(secTime, eval, curveData);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdStartSample::Introspective =
{
    CmdType::Sample,
    "Start Sample"sv,
    "Start a Sample playing on the voice. An Offset in samples may be applied. "
    "This offset may be scaled with the current velocity."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdStartSample, sample),
            "Sample"sv,
            0, 65535, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdStartSample, mode),
            "Velocity Scale"sv,
            0, 2, 0,
            {
                "No Scale"sv,
                "Negative"sv,
                "Positive"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdStartSample, offset),
            "Offset"sv,
            0, 0xffffff, 0
        }
    }
};
bool SoundMacro::CmdStartSample::Do(SoundMacroState& st, Voice& vox) const
{
    uint32_t useOffset = offset;

    switch (mode)
    {
    case Mode::Negative:
        useOffset = offset * (127 - st.m_curVel) / 127;
        break;
    case Mode::Positive:
        useOffset = offset * st.m_curVel / 127;
        break;
    default:
        break;
    }

    vox.startSample(sample.id, useOffset);
    vox.setPitchKey(st.m_curPitch);

    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdStopSample::Introspective =
{
    CmdType::Sample,
    "Stop Sample"sv,
    "Stops the sample playing on the voice."sv
};
bool SoundMacro::CmdStopSample::Do(SoundMacroState& st, Voice& vox) const
{
    vox.stopSample();
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdKeyOff::Introspective =
{
    CmdType::Control,
    "Key Off"sv,
    "Sends a Key Off to the current voice."sv
};
bool SoundMacro::CmdKeyOff::Do(SoundMacroState& st, Voice& vox) const
{
    vox._macroKeyOff();
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSplitRnd::Introspective =
{
    CmdType::Structure,
    "Split Rnd"sv,
    "Conditionally branch if a random value is greater than or equal to RND. "
    "A lower RND will cause a higher probability of branching."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSplitRnd, rnd),
            "RND"sv,
            0, 255, 128
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitRnd, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitRnd, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        },
    }
};
bool SoundMacro::CmdSplitRnd::Do(SoundMacroState& st, Voice& vox) const
{
    if (rnd <= vox.getEngine().nextRandom() % 256)
    {
        /* Do branch */
        if (macro.id == std::get<0>(st.m_pc.back()))
            st._setPC(macroStep.step);
        else
            vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod);
    }

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdFadeIn::Introspective =
{
    CmdType::Volume,
    "Fade In"sv,
    "Start a velocity envelope by fading from silence to the velocity "
    "evaluated by Scale and Add. The result is optionally transformed with a Curve object."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdFadeIn, scale),
            "Scale"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdFadeIn, add),
            "Add"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdFadeIn, table),
            "Curve"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdFadeIn, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdFadeIn, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 500
        }
    }
};
bool SoundMacro::CmdFadeIn::Do(SoundMacroState& st, Voice& vox) const
{
    float q = msSwitch ? 1000.f : st.m_ticksPerSec;
    float secTime = ticksOrMs / q;

    int32_t eval = int32_t(st.m_curVel) * scale / 127 + add;
    eval = clamp(0, eval, 127);

    const Curve* curveData;
    if (table.id != 0)
        curveData = vox.getAudioGroup().getPool().tableAsCurves(table.id);
    else
        curveData = nullptr;

    vox.startFadeIn(secTime, eval, curveData);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSpanning::Introspective =
{
    CmdType::Control,
    "Spanning"sv,
    "Start span-sweep from Span Position offset by Width over specified time period."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSpanning, spanPosition),
            "Span Position"sv,
            0, 127, 64
        },
        {
            FIELD_HEAD(SoundMacro::CmdSpanning, timeMs),
            "Time Millisec"sv,
            0, 65535, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSpanning, width),
            "Width"sv,
            -128, 127, 0
        }
    }
};
bool SoundMacro::CmdSpanning::Do(SoundMacroState& st, Voice& vox) const
{
    vox.startSpanning(timeMs / 1000.0, spanPosition, width);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSetAdsrCtrl::Introspective =
{
    CmdType::Volume,
    "Set ADSR Ctrl"sv,
    "Bind MIDI controls to ADSR parameters."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetAdsrCtrl, attack),
            "Attack Ctrl"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetAdsrCtrl, decay),
            "Decay Ctrl"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetAdsrCtrl, sustain),
            "Sustain Ctrl"sv,
            0, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetAdsrCtrl, release),
            "Release Ctrl"sv,
            0, 127, 0
        },
    }
};
bool SoundMacro::CmdSetAdsrCtrl::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_useAdsrControllers = true;
    st.m_midiAttack = attack;
    st.m_midiDecay = decay;
    st.m_midiSustain = sustain;
    st.m_midiRelease = release;

    /* Bootstrap ADSR defaults here */
    if (!vox.getCtrlValue(st.m_midiSustain))
    {
        vox.setCtrlValue(st.m_midiAttack, 10);
        vox.setCtrlValue(st.m_midiSustain, 127);
        vox.setCtrlValue(st.m_midiRelease, 10);
    }

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdRndNote::Introspective =
{
    CmdType::Pitch,
    "Random Note"sv,
    "Sets random pitch between Note Lo and Note Hi, biased by Detune in cents. "
    "If Free is set, the note will not snap to key steps."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdRndNote, noteLo),
            "Note Lo"sv,
            -127, 127, 48
        },
        {
            FIELD_HEAD(SoundMacro::CmdRndNote, detune),
            "Detune"sv,
            0, 99, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdRndNote, noteHi),
            "Note Hi"sv,
            -127, 127, 72
        },
        {
            FIELD_HEAD(SoundMacro::CmdRndNote, fixedFree),
            "Free"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdRndNote, absRel),
            "Absolute"sv,
            0, 1, 0
        }
    }
};
bool SoundMacro::CmdRndNote::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useNoteLo = noteLo;
    int32_t useNoteHi = noteHi;

    if (absRel)
    {
        useNoteLo = st.m_initKey - noteLo;
        useNoteHi = noteLo + noteHi;
    }

    useNoteLo *= 100;
    useNoteHi *= 100;

    if (useNoteHi == useNoteLo)
        st.m_curPitch = useNoteHi;
    else
        st.m_curPitch = (vox.getEngine().nextRandom() % (useNoteHi - useNoteLo)) + useNoteLo;

    if (!fixedFree)
        st.m_curPitch = st.m_curPitch / 100 * 100 + detune;

    vox.setPitchKey(st.m_curPitch);

    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdAddNote::Introspective =
{
    CmdType::Pitch,
    "Add Note"sv,
    "Sets new pitch by adding Add, biased by Detune in cents. "
    "The time parameters behave like a WAIT command when non-zero."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAddNote, add),
            "Add"sv,
            -128, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddNote, detune),
            "Detune"sv,
            -99, 99, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddNote, originalKey),
            "Original Key"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddNote, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddNote, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdAddNote::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_curPitch += add * 100 + detune;

    /* Set wait state */
    if (msSwitch)
    {
        float q = msSwitch ? 1000.f : st.m_ticksPerSec;
        float secTime = ticksOrMs / q;
        st.m_waitCountdown = secTime;
        st.m_inWait = true;
    }

    vox.setPitchKey(st.m_curPitch);

    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdSetNote::Introspective =
{
    CmdType::Pitch,
    "Set Note"sv,
    "Sets new pitch to Key, biased by Detune in cents. "
    "The time parameters behave like a WAIT command when non-zero."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetNote, key),
            "Key"sv,
            0, 127, 60
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetNote, detune),
            "Detune"sv,
            -99, 99, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetNote, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetNote, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdSetNote::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_curPitch = key * 100 + detune;

    /* Set wait state */
    if (ticksOrMs)
    {
        float q = msSwitch ? 1000.f : st.m_ticksPerSec;
        float secTime = ticksOrMs / q;
        st.m_waitCountdown = secTime;
        st.m_inWait = true;
    }

    vox.setPitchKey(st.m_curPitch);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdLastNote::Introspective =
{
    CmdType::Pitch,
    "Last Note"sv,
    "Sets new pitch by adding Add to last played MIDI note, biased by Detune in cents. "
    "The time parameters behave like a WAIT command when non-zero."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdLastNote, add),
            "Key"sv,
            -128, 127, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdLastNote, detune),
            "Detune"sv,
            -99, 99, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdLastNote, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdLastNote, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdLastNote::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_curPitch = (add + vox.getLastNote()) * 100 + detune;

    /* Set wait state */
    if (msSwitch)
    {
        float q = msSwitch ? 1000.f : st.m_ticksPerSec;
        float secTime = ticksOrMs / q;
        st.m_waitCountdown = secTime;
        st.m_inWait = true;
    }

    vox.setPitchKey(st.m_curPitch);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPortamento::Introspective =
{
    CmdType::Setup,
    "Portamento"sv,
    "Setup portamento mode for this voice."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPortamento, portState),
            "Port. State"sv,
            0, 2, 1,
            {
                "Enable"sv,
                "Disable"sv,
                "MIDI Controlled"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPortamento, portType),
            "Port. Type"sv,
            0, 1, 0,
            {
                "Last Pressed"sv,
                "Always"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPortamento, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPortamento, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdPortamento::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_portamentoMode = portState;
    st.m_portamentoType = portType;
    float q = msSwitch ? 1000.f : st.m_ticksPerSec;
    st.m_portamentoTime = ticksOrMs / q;
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdVibrato::Introspective =
{
    CmdType::Pitch,
    "Vibrato"sv,
    "Setup vibrato mode for this voice. Voice pitch will be "
    "modulated using the Level magnitude or the modwheel."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdVibrato, levelNote),
            "Level Note"sv,
            -127, 127, 0,
        },
        {
            FIELD_HEAD(SoundMacro::CmdVibrato, levelFine),
            "Level Fine"sv,
            -99, 99, 15,
        },
        {
            FIELD_HEAD(SoundMacro::CmdVibrato, modwheelFlag),
            "Use Modwheel"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdVibrato, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdVibrato, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 250
        }
    }
};
bool SoundMacro::CmdVibrato::Do(SoundMacroState& st, Voice& vox) const
{
    float q = msSwitch ? 1000.f : st.m_ticksPerSec;
    vox.setVibrato(levelNote * 100 + levelFine, modwheelFlag, ticksOrMs / q);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPitchSweep1::Introspective =
{
    CmdType::Pitch,
    "Pitch Sweep 1"sv,
    "Setup pitch sweep 1 for this voice. Voice pitch will accumulate Add for Times frames. "
    "If the time values are non-zero, this command also functions as a WAIT."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep1, times),
            "Times"sv,
            0, 127, 100,
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep1, add),
            "Add"sv,
            -32768, 32767, 100,
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep1, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep1, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 1000
        }
    }
};
bool SoundMacro::CmdPitchSweep1::Do(SoundMacroState& st, Voice& vox) const
{
    /* Set wait state */
    if (msSwitch)
    {
        float q = msSwitch ? 1000.f : st.m_ticksPerSec;
        float secTime = ticksOrMs / q;
        st.m_waitCountdown = secTime;
        st.m_inWait = true;
    }

    vox.setPitchSweep1(times, add);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPitchSweep2::Introspective =
{
    CmdType::Pitch,
    "Pitch Sweep 2"sv,
    "Setup pitch sweep 2 for this voice. Voice pitch will accumulate Add for Times frames. "
    "If the time values are non-zero, this command also functions as a WAIT."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep2, times),
            "Times"sv,
            0, 127, 100,
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep2, add),
            "Add"sv,
            -32768, 32767, 100,
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep2, msSwitch),
            "Use Millisec"sv,
            0, 1, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchSweep2, ticksOrMs),
            "Ticks/Millisec"sv,
            0, 65535, 1000
        }
    }
};
bool SoundMacro::CmdPitchSweep2::Do(SoundMacroState& st, Voice& vox) const
{
    /* Set wait state */
    if (msSwitch)
    {
        float q = msSwitch ? 1000.f : st.m_ticksPerSec;
        float secTime = ticksOrMs / q;
        st.m_waitCountdown = secTime;
        st.m_inWait = true;
    }

    vox.setPitchSweep2(times, add);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSetPitch::Introspective =
{
    CmdType::Pitch,
    "Set Pitch"sv,
    "Set the playback sample rate directly."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetPitch, hz.val),
            "Hz"sv,
            0, 0xffffff, 22050,
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetPitch, fine),
            "Level Fine"sv,
            0, 65535, 0,
        }
    }
};
bool SoundMacro::CmdSetPitch::Do(SoundMacroState& st, Voice& vox) const
{
    vox.setPitchFrequency(hz, fine);
    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdSetPitchAdsr::Introspective =
{
    CmdType::Pitch,
    "Set Pitch ADSR"sv,
    "Define the pitch ADSR from a DLS ADSR pool object. The pitch range is "
    "specified using Note and Cents parameters."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetPitchAdsr, table),
            "ADSR"sv,
            0, 65535, 65535,
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetPitchAdsr, keys),
            "Note range"sv,
            -128, 127, 0,
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetPitchAdsr, cents),
            "Cents range"sv,
            -99, 99, 0,
        }
    }
};
bool SoundMacro::CmdSetPitchAdsr::Do(SoundMacroState& st, Voice& vox) const
{
    vox.setPitchAdsr(table.id, keys * 100 + cents);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdScaleVolumeDLS::Introspective =
{
    CmdType::Volume,
    "Scale Volume DLS"sv,
    "Sets new volume by scaling the velocity. A value of 4096 == 100%."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdScaleVolumeDLS, scale),
            "Scale"sv,
            0, 16383, 4096,
        },
        {
            FIELD_HEAD(SoundMacro::CmdScaleVolumeDLS, originalVol),
            "Original Vol"sv,
            0, 1, 1,
        }
    }
};
bool SoundMacro::CmdScaleVolumeDLS::Do(SoundMacroState& st, Voice& vox) const
{
    vox.m_curVol = int32_t(originalVol ? st.m_initVel : st.m_curVel) * scale / 4096.f / 127.f;
    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdMod2Vibrange::Introspective =
{
    CmdType::Pitch,
    "Mod 2 Vibrange"sv,
    "Values used to scale the modwheel control for vibrato."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdMod2Vibrange, keys),
            "Key range"sv,
            0, 16383, 4096,
        },
        {
            FIELD_HEAD(SoundMacro::CmdMod2Vibrange, cents),
            "Cent range"sv,
            0, 1, 1,
        }
    }
};
bool SoundMacro::CmdMod2Vibrange::Do(SoundMacroState& st, Voice& vox) const
{
    vox.setMod2VibratoRange(keys * 100 + cents);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSetupTremolo::Introspective =
{
    CmdType::Special,
    "Setup Tremolo"sv,
    "Setup tremolo effect. Must be combined with Tremolo Select to connect "
    "with a configured LFO. A value of 4096 == 100%."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetupTremolo, scale),
            "Scale"sv,
            0, 16383, 8192,
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetupTremolo, modwAddScale),
            "Modw. add scale"sv,
            0, 16383, 0,
        }
    }
};
bool SoundMacro::CmdSetupTremolo::Do(SoundMacroState& st, Voice& vox) const
{
    vox.setTremolo(scale / 4096.f, scale / 4096.f);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdReturn::Introspective =
{
    CmdType::Structure,
    "Return"sv,
    "Branch to after last Go Subroutine command and pop call stack."sv
};
bool SoundMacro::CmdReturn::Do(SoundMacroState& st, Voice& vox) const
{
    if (st.m_pc.size() > 1)
    {
        st.m_pc.pop_back();
        vox._setObjectId(std::get<0>(st.m_pc.back()));
    }
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdGoSub::Introspective =
{
    CmdType::Structure,
    "Go Subroutine"sv,
    "Push location onto call stack and branch to specified location."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSplitRnd, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdSplitRnd, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        },
    }
};
bool SoundMacro::CmdGoSub::Do(SoundMacroState& st, Voice& vox) const
{
    if (macro.id == std::get<0>(st.m_pc.back()))
        st.m_pc.emplace_back(std::get<0>(st.m_pc.back()), std::get<1>(st.m_pc.back()),
            std::get<1>(st.m_pc.back())->assertPC(macroStep.step));
    else
        vox.loadMacroObject(macro.id, macroStep.step, st.m_ticksPerSec, st.m_initKey, st.m_initVel, st.m_initMod, true);

    vox._setObjectId(std::get<0>(st.m_pc.back()));

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdTrapEvent::Introspective =
{
    CmdType::Structure,
    "Trap Event"sv,
    "Register event-based branch to a specified location."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdTrapEvent, event),
            "Event"sv,
            0, 2, 0,
            {
                "Key Off"sv,
                "Sample End"sv,
                "Message Recv"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdTrapEvent, macro),
            "Macro"sv,
            0, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdTrapEvent, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        },
    }
};
bool SoundMacro::CmdTrapEvent::Do(SoundMacroState& st, Voice& vox) const
{
    switch (event)
    {
    case EventType::KeyOff:
        vox.m_keyoffTrap.macroId = macro.id;
        vox.m_keyoffTrap.macroStep = macroStep.step;
        break;
    case EventType::SampleEnd:
        vox.m_sampleEndTrap.macroId = macro.id;
        vox.m_sampleEndTrap.macroStep = macroStep.step;
        break;
    case EventType::MessageRecv:
        vox.m_messageTrap.macroId = macro.id;
        vox.m_messageTrap.macroStep = macroStep.step;
        break;
    default:
        break;
    }

    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdUntrapEvent::Introspective =
{
    CmdType::Structure,
    "Untrap Event"sv,
    "Unregister event-based branch."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdUntrapEvent, event),
            "Event"sv,
            0, 2, 0,
            {
                "Key Off"sv,
                "Sample End"sv,
                "Message Recv"sv
            }
        }
    }
};
bool SoundMacro::CmdUntrapEvent::Do(SoundMacroState& st, Voice& vox) const
{
    switch (event)
    {
    case CmdTrapEvent::EventType::KeyOff:
        vox.m_keyoffTrap.macroId = 0xffff;
        vox.m_keyoffTrap.macroStep = 0xffff;
        break;
    case CmdTrapEvent::EventType::SampleEnd:
        vox.m_sampleEndTrap.macroId = 0xffff;
        vox.m_sampleEndTrap.macroStep = 0xffff;
        break;
    case CmdTrapEvent::EventType::MessageRecv:
        vox.m_messageTrap.macroId = 0xffff;
        vox.m_messageTrap.macroStep = 0xffff;
        break;
    default:
        break;
    }

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSendMessage::Introspective =
{
    CmdType::Special,
    "Send Message"sv,
    "Send message to SoundMacro or Voice referenced in a variable. "
    "The message value is retrieved from a variable."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSendMessage, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSendMessage, macro),
            "Macro"sv,
            1, 65535, 65535
        },
        {
            FIELD_HEAD(SoundMacro::CmdSendMessage, voiceVar),
            "Voice Var"sv,
            0, 31, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSendMessage, valueVar),
            "Value Var"sv,
            0, 31, 0
        },
    }
};
bool SoundMacro::CmdSendMessage::Do(SoundMacroState& st, Voice& vox) const
{
    if (isVar)
    {
        ObjToken<Voice> findVox = vox.getEngine().findVoice(st.m_variables[voiceVar & 0x1f]);
        if (findVox)
            findVox->message(st.m_variables[valueVar & 0x1f]);
    }
    else
        vox.getEngine().sendMacroMessage(macro.id, st.m_variables[valueVar & 0x1f]);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdGetMessage::Introspective =
{
    CmdType::Special,
    "Get Message"sv,
    "Get voice's latest received message and store its value in Variable."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdGetMessage, variable),
            "Variable"sv,
            0, 31, 0
        }
    }
};
bool SoundMacro::CmdGetMessage::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_variables[variable & 0x1f] = vox.m_latestMessage;
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdGetVid::Introspective =
{
    CmdType::Special,
    "Get VID"sv,
    "Get ID of current voice or last voice started by Play Macro command and store in Variable."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdGetVid, variable),
            "Variable"sv,
            0, 31, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdGetVid, playMacro),
            "Play Macro"sv,
            0, 1, 1
        }
    }
};
bool SoundMacro::CmdGetVid::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_variables[variable & 0x1f] = playMacro ? st.m_lastPlayMacroVid : vox.vid();
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdAddAgeCount::Introspective =
{
    CmdType::Special,
    "Add Age Count"sv,
    "Adds a value to the current voice's age counter."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAddAgeCount, add),
            "Add"sv,
            -32768, 32767, -30000
        }
    }
};
bool SoundMacro::CmdAddAgeCount::Do(SoundMacroState& st, Voice& vox) const
{
    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdSetAgeCount::Introspective =
{
    CmdType::Special,
    "Set Age Count"sv,
    "Set a value into the current voice's age counter."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetAgeCount, counter),
            "Counter"sv,
            0, 65535, 0
        }
    }
};
bool SoundMacro::CmdSetAgeCount::Do(SoundMacroState& st, Voice& vox) const
{
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSendFlag::Introspective =
{
    CmdType::Special,
    "Send Flag"sv,
    "Send a flag value to the host application."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSendFlag, flagId),
            "Flag ID"sv,
            0, 15, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSendFlag, value),
            "Value"sv,
            0, 255, 255
        }
    }
};
bool SoundMacro::CmdSendFlag::Do(SoundMacroState& st, Voice& vox) const
{
    /* TODO: figure out a good API */
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPitchWheelR::Introspective =
{
    CmdType::Setup,
    "Pitch Wheel Range"sv,
    "Specifies the number of note steps for the range of the pitch wheel."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPitchWheelR, rangeUp),
            "Range Up"sv,
            0, 127, 2
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchWheelR, rangeDown),
            "Range Down"sv,
            0, 127, 2
        }
    }
};
bool SoundMacro::CmdPitchWheelR::Do(SoundMacroState& st, Voice& vox) const
{
    vox.setPitchWheelRange(rangeUp, rangeDown);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSetPriority::Introspective =
{
    CmdType::Special,
    "Set Priority"sv,
    "Sets the priority of the current voice."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetPriority, prio),
            "Priority"sv,
            0, 254, 50
        }
    }
};
bool SoundMacro::CmdSetPriority::Do(SoundMacroState& st, Voice& vox) const
{
    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdAddPriority::Introspective =
{
    CmdType::Special,
    "Add Priority"sv,
    "Adds to the priority of the current voice."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAddPriority, prio),
            "Priority"sv,
            -255, 255, 1
        }
    }
};
bool SoundMacro::CmdAddPriority::Do(SoundMacroState& st, Voice& vox) const
{
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdAgeCntSpeed::Introspective =
{
    CmdType::Special,
    "Age Count Speed"sv,
    "Sets the speed the current voice's age counter is decremented."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAgeCntSpeed, time),
            "Millisec"sv,
            0, 16777215, 1080000
        }
    }
};
bool SoundMacro::CmdAgeCntSpeed::Do(SoundMacroState& st, Voice& vox) const
{
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdAgeCntVel::Introspective =
{
    CmdType::Special,
    "Age Count Velocity"sv,
    "Sets the current voice's age counter by scaling the velocity."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAgeCntVel, ageBase),
            "Base"sv,
            0, 65535, 60000
        },
        {
            FIELD_HEAD(SoundMacro::CmdAgeCntVel, ageScale),
            "Scale"sv,
            0, 65535, 127
        }
    }
};
bool SoundMacro::CmdAgeCntVel::Do(SoundMacroState& st, Voice& vox) const
{
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdVolSelect::Introspective =
{
    CmdType::Setup,
    "Volume Select"sv,
    "Appends an evaluator component for computing the voice's volume."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdVolSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 7
        },
        {
            FIELD_HEAD(SoundMacro::CmdVolSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdVolSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdVolSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdVolSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdVolSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_volumeSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                SoundMacroState::Evaluator::Combine(combine),
                                SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPanSelect::Introspective =
{
    CmdType::Setup,
    "Pan Select"sv,
    "Appends an evaluator component for computing the voice's pan."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPanSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 10
        },
        {
            FIELD_HEAD(SoundMacro::CmdPanSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdPanSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPanSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPanSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdPanSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_panSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                             SoundMacroState::Evaluator::Combine(combine),
                             SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPitchWheelSelect::Introspective =
{
    CmdType::Setup,
    "Pitch Wheel Select"sv,
    "Appends an evaluator component for computing the voice's pitch wheel."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPitchWheelSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 128
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchWheelSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchWheelSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchWheelSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPitchWheelSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdPitchWheelSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_pitchWheelSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                    SoundMacroState::Evaluator::Combine(combine),
                                    SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdModWheelSelect::Introspective =
{
    CmdType::Setup,
    "Mod Wheel Select"sv,
    "Appends an evaluator component for computing the voice's mod wheel."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdModWheelSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdModWheelSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdModWheelSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdModWheelSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdModWheelSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdModWheelSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_modWheelSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                  SoundMacroState::Evaluator::Combine(combine),
                                  SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPedalSelect::Introspective =
{
    CmdType::Setup,
    "Pedal Select"sv,
    "Appends an evaluator component for computing the voice's pedal."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPedalSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPedalSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdPedalSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPedalSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPedalSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdPedalSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_pedalSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                               SoundMacroState::Evaluator::Combine(combine),
                               SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPortamentoSelect::Introspective =
{
    CmdType::Setup,
    "Portamento Select"sv,
    "Appends an evaluator component for computing the voice's portamento."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPortamentoSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPortamentoSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdPortamentoSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPortamentoSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPortamentoSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdPortamentoSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_portamentoSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                    SoundMacroState::Evaluator::Combine(combine),
                                    SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdReverbSelect::Introspective =
{
    CmdType::Setup,
    "Reverb Select"sv,
    "Appends an evaluator component for computing the voice's reverb."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdReverbSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdReverbSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdReverbSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdReverbSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdReverbSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdReverbSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_reverbSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                SoundMacroState::Evaluator::Combine(combine),
                                SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSpanSelect::Introspective =
{
    CmdType::Setup,
    "Span Select"sv,
    "Appends an evaluator component for computing the voice's span."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSpanSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 19
        },
        {
            FIELD_HEAD(SoundMacro::CmdSpanSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdSpanSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdSpanSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSpanSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdSpanSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_spanSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                              SoundMacroState::Evaluator::Combine(combine),
                              SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdDopplerSelect::Introspective =
{
    CmdType::Setup,
    "Doppler Select"sv,
    "Appends an evaluator component for computing the voice's doppler."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdDopplerSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 132
        },
        {
            FIELD_HEAD(SoundMacro::CmdDopplerSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdDopplerSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdDopplerSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdDopplerSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdDopplerSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_dopplerSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                 SoundMacroState::Evaluator::Combine(combine),
                                 SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdTremoloSelect::Introspective =
{
    CmdType::Setup,
    "Tremolo Select"sv,
    "Appends an evaluator component for computing the voice's tremolo."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdTremoloSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdTremoloSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdTremoloSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdTremoloSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdTremoloSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdTremoloSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_tremoloSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                 SoundMacroState::Evaluator::Combine(combine),
                                 SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPreASelect::Introspective =
{
    CmdType::Setup,
    "PreA Select"sv,
    "Appends an evaluator component for computing the voice's pre-AUXA."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPreASelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreASelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreASelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreASelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreASelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdPreASelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_preAuxASel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                 SoundMacroState::Evaluator::Combine(combine),
                                 SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPreBSelect::Introspective =
{
    CmdType::Setup,
    "PreB Select"sv,
    "Appends an evaluator component for computing the voice's pre-AUXB."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPreBSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreBSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreBSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreBSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPreBSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdPreBSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_preAuxBSel.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                                 SoundMacroState::Evaluator::Combine(combine),
                                 SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdPostBSelect::Introspective =
{
    CmdType::Setup,
    "PostB Select"sv,
    "Appends an evaluator component for computing the voice's post-AUXB."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdPostBSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdPostBSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdPostBSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdPostBSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdPostBSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        }
    }
};
bool SoundMacro::CmdPostBSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_postAuxB.addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                               SoundMacroState::Evaluator::Combine(combine),
                               SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdAuxAFXSelect::Introspective =
{
    CmdType::Setup,
    "AuxA FX Select"sv,
    "Appends an evaluator component for computing the AUXA Parameter."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAuxAFXSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxAFXSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxAFXSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxAFXSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxAFXSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxAFXSelect, paramIndex),
            "Param Index"sv,
            0, 2, 0
        }
    }
};
bool SoundMacro::CmdAuxAFXSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_auxAFxSel[std::min(paramIndex, atUint8(3))].
        addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                     SoundMacroState::Evaluator::Combine(combine),
                     SoundMacroState::Evaluator::VarType(isVar));
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdAuxBFXSelect::Introspective =
{
    CmdType::Setup,
    "AuxB FX Select"sv,
    "Appends an evaluator component for computing the AUXB Parameter."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAuxBFXSelect, midiControl),
            "MIDI Control"sv,
            0, 132, 1
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxBFXSelect, scalingPercentage),
            "Scale Percentage"sv,
            -10000, 10000, 100
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxBFXSelect, combine),
            "Combine Mode"sv,
            0, 2, 0,
            {
                "Set"sv,
                "Add"sv,
                "Mult"sv
            }
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxBFXSelect, isVar),
            "Is Var"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxBFXSelect, fineScaling),
            "Fine Scaling"sv,
            -100, 100, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAuxBFXSelect, paramIndex),
            "Param Index"sv,
            0, 2, 0
        }
    }
};
bool SoundMacro::CmdAuxBFXSelect::Do(SoundMacroState& st, Voice& vox) const
{
    st.m_auxBFxSel[std::min(paramIndex, atUint8(3))].
        addComponent(midiControl, (scalingPercentage + fineScaling / 100.f) / 100.f,
                     SoundMacroState::Evaluator::Combine(combine),
                     SoundMacroState::Evaluator::VarType(isVar));
    return false;
}


const SoundMacro::CmdIntrospection SoundMacro::CmdSetupLFO::Introspective =
{
    CmdType::Setup,
    "Setup LFO"sv,
    "Configures voice's LFO period in milliseconds."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetupLFO, lfoNumber),
            "LFO Number"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetupLFO, periodInMs),
            "Period"sv,
            -10000, 10000, 100
        }
    }
};
bool SoundMacro::CmdSetupLFO::Do(SoundMacroState& st, Voice& vox) const
{
    if (lfoNumber == 0)
        vox.setLFO1Period(periodInMs / 1000.f);
    else if (lfoNumber == 1)
        vox.setLFO2Period(periodInMs / 1000.f);
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdModeSelect::Introspective =
{
    CmdType::Setup,
    "Mode Select"sv,
    "Sets operating modes for current voice."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdModeSelect, dlsVol),
            "DLS Vol"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdModeSelect, itd),
            "ITD"sv,
            0, 1, 0
        }
    }
};
bool SoundMacro::CmdModeSelect::Do(SoundMacroState& st, Voice& vox) const
{
    vox.m_dlsVol = dlsVol;
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSetKeygroup::Introspective =
{
    CmdType::Setup,
    "Set Keygroup"sv,
    "Selects keygroup for current voice."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetKeygroup, group),
            "Group"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetKeygroup, killNow),
            "Kill now"sv,
            0, 1, 0
        }
    }
};
bool SoundMacro::CmdSetKeygroup::Do(SoundMacroState& st, Voice& vox) const
{
    vox.setKeygroup(0);
    if (group)
    {
        vox.getEngine().killKeygroup(group, killNow);
        vox.setKeygroup(group);
    }
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSRCmodeSelect::Introspective =
{
    CmdType::Setup,
    "SRC Mode Select"sv,
    "Sets operating modes for sample rate converter."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSRCmodeSelect, srcType),
            "SRC Type"sv,
            0, 2, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSRCmodeSelect, type0SrcFilter),
            "Type 0 SRC Filter"sv,
            0, 2, 1
        }
    }
};
bool SoundMacro::CmdSRCmodeSelect::Do(SoundMacroState& st, Voice& vox) const
{
    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdAddVars::Introspective =
{
    CmdType::Special,
    "Add Vars"sv,
    "A = B + C"sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAddVars, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddVars, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddVars, varCtrlB),
            "Use Ctrl B"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddVars, b),
            "B"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddVars, varCtrlC),
            "Use Ctrl C"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddVars, c),
            "C"sv,
            0, 255, 0
        },
    }
};
bool SoundMacro::CmdAddVars::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useB, useC;

    if (varCtrlB)
        useB = vox.getCtrlValue(b);
    else
        useB = st.m_variables[b & 0x1f];

    if (varCtrlC)
        useC = vox.getCtrlValue(c);
    else
        useC = st.m_variables[c & 0x1f];

    if (varCtrlA)
        vox.setCtrlValue(a, useB + useC);
    else
        st.m_variables[a & 0x1f] = useB + useC;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSubVars::Introspective =
{
    CmdType::Special,
    "Sub Vars"sv,
    "A = B - C"sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSubVars, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSubVars, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSubVars, varCtrlB),
            "Use Ctrl B"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSubVars, b),
            "B"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSubVars, varCtrlC),
            "Use Ctrl C"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSubVars, c),
            "C"sv,
            0, 255, 0
        },
    }
};
bool SoundMacro::CmdSubVars::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useB, useC;

    if (varCtrlB)
        useB = vox.getCtrlValue(b);
    else
        useB = st.m_variables[b & 0x1f];

    if (varCtrlC)
        useC = vox.getCtrlValue(c);
    else
        useC = st.m_variables[c & 0x1f];

    if (varCtrlA)
        vox.setCtrlValue(a, useB - useC);
    else
        st.m_variables[a & 0x1f] = useB - useC;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdMulVars::Introspective =
{
    CmdType::Special,
    "Mul Vars"sv,
    "A = B * C"sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdMulVars, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdMulVars, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdMulVars, varCtrlB),
            "Use Ctrl B"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdMulVars, b),
            "B"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdMulVars, varCtrlC),
            "Use Ctrl C"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdMulVars, c),
            "C"sv,
            0, 255, 0
        },
    }
};
bool SoundMacro::CmdMulVars::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useB, useC;

    if (varCtrlB)
        useB = vox.getCtrlValue(b);
    else
        useB = st.m_variables[b & 0x1f];

    if (varCtrlC)
        useC = vox.getCtrlValue(c);
    else
        useC = st.m_variables[c & 0x1f];

    if (varCtrlA)
        vox.setCtrlValue(a, useB * useC);
    else
        st.m_variables[a & 0x1f] = useB * useC;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdDivVars::Introspective =
{
    CmdType::Special,
    "Div Vars"sv,
    "A = B / C"sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdDivVars, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdDivVars, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdDivVars, varCtrlB),
            "Use Ctrl B"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdDivVars, b),
            "B"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdDivVars, varCtrlC),
            "Use Ctrl C"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdDivVars, c),
            "C"sv,
            0, 255, 0
        },
    }
};
bool SoundMacro::CmdDivVars::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useB, useC;

    if (varCtrlB)
        useB = vox.getCtrlValue(b);
    else
        useB = st.m_variables[b & 0x1f];

    if (varCtrlC)
        useC = vox.getCtrlValue(c);
    else
        useC = st.m_variables[c & 0x1f];

    if (varCtrlA)
        vox.setCtrlValue(a, useB / useC);
    else
        st.m_variables[a & 0x1f] = useB / useC;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdAddIVars::Introspective =
{
    CmdType::Special,
    "Add Imm Vars"sv,
    "A = B + Immediate"sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdAddIVars, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddIVars, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddIVars, varCtrlB),
            "Use Ctrl B"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddIVars, b),
            "B"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdAddIVars, imm),
            "Immediate"sv,
            -32768, 32767, 0
        },
    }
};
bool SoundMacro::CmdAddIVars::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useB;

    if (varCtrlB)
        useB = vox.getCtrlValue(b);
    else
        useB = st.m_variables[b & 0x1f];

    if (varCtrlA)
        vox.setCtrlValue(a, useB + imm);
    else
        st.m_variables[a & 0x1f] = useB + imm;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdSetVar::Introspective =
{
    CmdType::Special,
    "Set Var"sv,
    "A = Immediate"sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdSetVar, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetVar, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdSetVar, imm),
            "Immediate"sv,
            -32768, 32767, 0
        },
    }
};
bool SoundMacro::CmdSetVar::Do(SoundMacroState& st, Voice& vox) const
{
    if (varCtrlA)
        vox.setCtrlValue(a, imm);
    else
        st.m_variables[a & 0x1f] = imm;

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdIfEqual::Introspective =
{
    CmdType::Structure,
    "If Equal"sv,
    "Branches to specified step if A == B."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdIfEqual, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfEqual, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfEqual, varCtrlB),
            "Use Ctrl B"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfEqual, b),
            "B"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfEqual, notEq),
            "Not"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfEqual, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        },
    }
};
bool SoundMacro::CmdIfEqual::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useA, useB;

    if (varCtrlA)
        useA = vox.getCtrlValue(a);
    else
        useA = st.m_variables[a & 0x1f];

    if (varCtrlB)
        useB = vox.getCtrlValue(b);
    else
        useB = st.m_variables[b & 0x1f];

    if ((useA == useB) ^ notEq)
        st._setPC(macroStep.step);

    return false;
}

const SoundMacro::CmdIntrospection SoundMacro::CmdIfLess::Introspective =
{
    CmdType::Structure,
    "If Less"sv,
    "Branches to specified step if A < B."sv,
    {
        {
            FIELD_HEAD(SoundMacro::CmdIfLess, varCtrlA),
            "Use Ctrl A"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfLess, a),
            "A"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfLess, varCtrlB),
            "Use Ctrl B"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfLess, b),
            "B"sv,
            0, 255, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfLess, notLt),
            "Not"sv,
            0, 1, 0
        },
        {
            FIELD_HEAD(SoundMacro::CmdIfLess, macroStep),
            "Macro Step"sv,
            0, 65535, 0
        },
    }
};
bool SoundMacro::CmdIfLess::Do(SoundMacroState& st, Voice& vox) const
{
    int32_t useA, useB;

    if (varCtrlA)
        useA = vox.getCtrlValue(a);
    else
        useA = st.m_variables[a & 0x1f];

    if (varCtrlB)
        useB = vox.getCtrlValue(b);
    else
        useB = st.m_variables[b & 0x1f];

    if ((useA < useB) ^ notLt)
        st._setPC(macroStep.step);

    return false;
}

bool SoundMacroState::advance(Voice& vox, double dt)
{
    /* Nothing if uninitialized or finished */
    if (m_pc.empty() || std::get<1>(m_pc.back()) == nullptr || std::get<2>(m_pc.back()) == -1)
        return true;

    /* Loop through as many commands as we can for this time period */
    while (true)
    {
        /* Advance wait timer if active, returning if waiting */
        if (m_inWait)
        {
            if (m_keyoffWait && m_keyoff)
                m_inWait = false;
            else if (m_sampleEndWait && m_sampleEnd)
                m_inWait = false;
            else if (!m_indefiniteWait)
            {
                m_waitCountdown -= dt;
                if (m_waitCountdown < 0.f)
                    m_inWait = false;
            }

            if (m_inWait)
            {
                m_execTime += dt;
                return false;
            }
        }

        /* Load next command based on counter */
        const SoundMacro::ICmd& cmd = std::get<1>(m_pc.back())->getCmd(std::get<2>(m_pc.back())++);

        /* Perform function of command */
        if (cmd.Do(*this, vox))
            return true;
    }

    m_execTime += dt;
    return false;
}

void SoundMacroState::keyoffNotify(Voice& vox) { m_keyoff = true; }

void SoundMacroState::sampleEndNotify(Voice& vox) { m_sampleEnd = true; }
}
