#ifndef ULTRA64_ABI_H
#define ULTRA64_ABI_H

/* Audio commands: */
#define A_SPNOOP        0
#define A_ADPCM         1
#define A_CLEARBUFF     2
#define A_UNK3          3
#define A_ADDMIXER      4
#define A_RESAMPLE      5
#define A_RESAMPLE_ZOH  6
#define A_FILTER        7
#define A_SETBUFF       8
#define A_DUPLICATE     9
#define A_DMEMMOVE      10
#define A_LOADADPCM     11
#define A_MIXER         12
#define A_INTERLEAVE    13
#define A_HILOGAIN      14
#define A_SETLOOP       15
#define A_INTERL        17
#define A_ENVSETUP1     18
#define A_ENVMIXER      19
#define A_LOADBUFF      20
#define A_SAVEBUFF      21
#define A_ENVSETUP2     22
#define A_S8DEC         23
#define A_UNK19         25

#define ACMD_SIZE 32

/*
 * Audio flags
 */
#define A_INIT      0x01
#define A_CONTINUE  0x00
#define A_LOOP      0x02
#define A_OUT       0x02
#define A_LEFT      0x02
#define A_RIGHT     0x00
#define A_VOL       0x04
#define A_RATE      0x00
#define A_AUX       0x08
#define A_NOAUX     0x00
#define A_MAIN      0x00
#define A_MIX       0x10

/*
 * Data Structures.
 */

typedef struct Aadpcm {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 gain  : 16;
    u32 addr;
} Aadpcm;

typedef struct Apolef {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 gain  : 16;
    u32 addr;
} Apolef;

typedef struct Aenvelope {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 pad1  : 16;
    u32 addr;
} Aenvelope;

typedef struct Aclearbuff {
    u32 cmd   : 8;
    u32 pad1  : 8;
    u32 dmem  : 16;
    u32 pad2  : 16;
    u32 count : 16;
} Aclearbuff;

typedef struct Ainterleave {
    u32 cmd  : 8;
    u32 pad1 : 8;
    u32 pad2 : 16;
    u32 inL  : 16;
    u32 inR  : 16;
} Ainterleave;

typedef struct Aloadbuff {
    u32 cmd  : 8;
    u32 pad1 : 24;
    u32 addr;
} Aloadbuff;

typedef struct Aenvmixer {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 pad1  : 16;
    u32 addr;
} Aenvmixer;

typedef struct Amixer {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 gain  : 16;
    u32 dmemi : 16;
    u32 dmemo : 16;
} Amixer;

typedef struct Apan {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 dmem2 : 16;
    u32 addr;
} Apan;

typedef struct Aresample {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 pitch : 16;
    u32 addr;
} Aresample;

typedef struct Areverb {
    u32 cmd   : 8;
    u32 flags : 8;
    u32 pad1  : 16;
    u32 addr;
} Areverb;

typedef struct Asavebuff {
    u32 cmd  : 8;
    u32 pad1 : 24;
    u32 addr;
} Asavebuff;

typedef struct Asegment {
    u32 cmd    : 8;
    u32 pad1   : 24;
    u32 pad2   : 2;
    u32 number : 4;
    u32 base   : 24;
} Asegment;

typedef struct Asetbuff {
    u32 cmd     : 8;
    u32 flags   : 8;
    u32 dmemin  : 16;
    u32 dmemout : 16;
    u32 count   : 16;
} Asetbuff;

typedef struct Asetvol {
    u32 cmd     : 8;
    u32 flags   : 8;
    u32 vol     : 16;
    u32 voltgt  : 16;
    u32 volrate : 16;
} Asetvol;

typedef struct Admemmove {
    u32 cmd     : 8;
    u32 pad1    : 8;
    u32 dmemin  : 16;
    u32 dmemout : 16;
    u32 count   : 16;
} Admemmove;

typedef struct Aloadadpcm {
    u32 cmd   : 8;
    u32 pad1  : 8;
    u32 count : 16;
    u32 addr;
} Aloadadpcm;

typedef struct Asetloop {
    u32 cmd  : 8;
    u32 pad1 : 8;
    u32 pad2 : 16;
    u32 addr;
} Asetloop;

/*
 * Generic Acmd Packet
 */

typedef struct Awords {
    u32 w0;
    u32 w1;
} Awords;

typedef union Acmd {
    Awords      words;
    Aadpcm      adpcm;
    Apolef      polef;
    Aclearbuff  clearbuff;
    Aenvelope   envelope;
    Ainterleave interleave;
    Aloadbuff   loadbuff;
    Aenvmixer   envmixer;
    Aresample   resample;
    Areverb     reverb;
    Asavebuff   savebuff;
    Asegment    segment;
    Asetbuff    setbuff;
    Asetvol     setvol;
    Admemmove   dmemmove;
    Aloadadpcm  loadadpcm;
    Amixer      mixer;
    Asetloop    setloop;
    long long int force_union_align; /* dummy, force alignment */
} Acmd;

/*
 * ADPCM State
 */
#define ADPCMVSIZE 8
#define ADPCMFSIZE 16
typedef short ADPCM_STATE[ADPCMFSIZE];

/*
 * Pole filter state
 */
typedef short POLEF_STATE[4];

/*
 * Resampler state
 */
typedef short RESAMPLE_STATE[16];

/*
 * Resampler constants
 */
#define UNITY_PITCH 0x8000
#define MAX_RATIO 1.99996 /* within .03 cents of +1 octave */

/*
 * Enveloper/Mixer state
 */
typedef short ENVMIX_STATE[40];

/*
 * Macros to assemble the audio command list
 */

#define aADPCMdec(pkt, f, s)                                        \
{                                                                   \
        Acmd *_a = (Acmd *)pkt;                                     \
                                                                    \
        _a->words.w0 = _SHIFTL(A_ADPCM, 24, 8) | _SHIFTL(f, 16, 8); \
        _a->words.w1 = (u32)(s);                                    \
}

#define aPoleFilter(pkt, f, g, s)                                     \
{                                                                     \
        Acmd *_a = (Acmd *)pkt;                                       \
                                                                      \
        _a->words.w0 = (_SHIFTL(A_POLEF, 24, 8) | _SHIFTL(f, 16, 8) | \
                        _SHIFTL(g, 0, 16));                           \
        _a->words.w1 = (u32)(s);                                      \
}

#define aHiLoGain(pkt, gain, count, dmem, a4)                          \
{                                                                      \
        Acmd *_a = (Acmd *)pkt;                                        \
                                                                       \
        _a->words.w0 = (_SHIFTL(A_HILOGAIN, 24, 8) |                   \
                        _SHIFTL(gain, 16, 8) | _SHIFTL(count, 0, 16)); \
        _a->words.w1 = _SHIFTL(dmem, 16, 16) | _SHIFTL(a4, 0, 16);     \
}

#define aUnkCmd3(pkt, a1, a2, a3)                                   \
{                                                                   \
        Acmd *_a = (Acmd *)pkt;                                     \
                                                                    \
        _a->words.w0 = _SHIFTL(A_UNK3, 24, 8) | _SHIFTL(a3, 0, 16); \
        _a->words.w1 = _SHIFTL(a1, 16, 16) | _SHIFTL(a2, 0, 16);    \
}

#define aUnkCmd19(pkt, a1, a2, a3, a4)                                 \
{                                                                      \
        Acmd *_a = (Acmd *)pkt;                                        \
                                                                       \
        _a->words.w0 = (_SHIFTL(A_UNK19, 24, 8) | _SHIFTL(a1, 16, 8) | \
                        _SHIFTL(a2, 0, 16));                           \
        _a->words.w1 = _SHIFTL(a3, 16, 16) | _SHIFTL(a4, 0, 16);       \
}

#define aS8Dec(pkt, a1, a2)                                          \
{                                                                    \
        Acmd *_a = (Acmd *)pkt;                                      \
                                                                     \
        _a->words.w0 = _SHIFTL(A_S8DEC, 24, 8) | _SHIFTL(a1, 16, 8); \
        _a->words.w1 = (u32)(a2);                                    \
}

/*
 * Clears DMEM by writing zeros.
 *
 * @param pkt pointer to an Acmd buffer
 * @param dmem DMEM address to clear
 * @param size number of bytes to clear (rounded up to the next multiple of 16)
 */
#define aClearBuffer(pkt, dmem, size)                                      \
    {                                                                      \
        Acmd* _a = (Acmd*)pkt;                                             \
                                                                           \
        _a->words.w0 = _SHIFTL(A_CLEARBUFF, 24, 8) | _SHIFTL(dmem, 0, 24); \
        _a->words.w1 = (uintptr_t)(size);                                  \
    }

#define aEnvMixer(pkt, dmemi, count, swapLR, x0, x1, x2, x3, m, bits)  \
{                                                                      \
        Acmd *_a = (Acmd *)pkt;                                        \
                                                                       \
        _a->words.w0 = (bits | _SHIFTL(dmemi >> 4, 16, 8) |            \
                        _SHIFTL(count, 8, 8) | _SHIFTL(swapLR, 4, 1) | \
                        _SHIFTL(x0, 3, 1) | _SHIFTL(x1, 2, 1) |        \
                        _SHIFTL(x2, 1, 1) | _SHIFTL(x3, 0, 1));        \
        _a->words.w1 = (u32)(m);                                       \
}

#define aInterleave(pkt, o, l, r, c)                                 \
{                                                                    \
        Acmd *_a = (Acmd *)pkt;                                      \
                                                                     \
        _a->words.w0 = (_SHIFTL(A_INTERLEAVE, 24, 8) |               \
                        _SHIFTL(c >> 4, 16, 8) | _SHIFTL(o, 0, 16)); \
        _a->words.w1 = _SHIFTL(l, 16, 16) | _SHIFTL(r, 0, 16);       \
}

#define aInterl(pkt, dmemi, dmemo, count)                                  \
{                                                                          \
        Acmd *_a = (Acmd*)pkt;                                             \
                                                                           \
        _a->words.w0 = (_SHIFTL(A_INTERL, 24, 8) | _SHIFTL(count, 0, 16)); \
        _a->words.w1 = _SHIFTL(dmemi, 16, 16) | _SHIFTL(dmemo, 0, 16);     \
}

/*
 * Loads a buffer to DMEM from any physical source address, KSEG0, or KSEG1
 *
 * @param pkt pointer to an Acmd buffer
 * @param addrSrc Any physical source address, KSEG0, or KSEG1
 * @param dmemDest DMEM destination address
 * @param size number of bytes to copy (rounded down to the next multiple of 16)
 */
#define aLoadBuffer(pkt, addrSrc, dmemDest, size)                                                             \
    {                                                                                                         \
        Acmd* _a = (Acmd*)pkt;                                                                                \
                                                                                                              \
        _a->words.w0 = (_SHIFTL(A_LOADBUFF, 24, 8) | _SHIFTL((size) >> 4, 16, 8) | _SHIFTL(dmemDest, 0, 16)); \
        _a->words.w1 = (uintptr_t)(addrSrc);                                                                  \
    }

#define aMix(pkt, f, g, i, o)                                         \
{                                                                     \
        Acmd *_a = (Acmd *)pkt;                                       \
                                                                      \
        _a->words.w0 = (_SHIFTL(A_MIXER, 24, 8) | _SHIFTL(f, 16, 8) | \
                        _SHIFTL(g, 0, 16));                           \
        _a->words.w1 = _SHIFTL(i, 16, 16) | _SHIFTL(o, 0, 16);        \
}

#define aPan(pkt, f, d, s)                                          \
{                                                                   \
        Acmd *_a = (Acmd *)pkt;                                     \
                                                                    \
        _a->words.w0 = (_SHIFTL(A_PAN, 24, 8) | _SHIFTL(f, 16, 8) | \
                        _SHIFTL(d, 0, 16));                         \
        _a->words.w1 = (u32)(s);                                    \
}

#define aResample(pkt, f, p, s)                                 \
{                                                               \
        Acmd *_a = (Acmd *)pkt;                                 \
                                                                \
        _a->words.w0 = (_SHIFTL(A_RESAMPLE, 24, 8) |            \
                        _SHIFTL(f, 16, 8) | _SHIFTL(p, 0, 16)); \
        _a->words.w1 = (u32)(s);                                \
}

/*
 * Stores a buffer from DMEM to any physical source address, KSEG0, or KSEG1
 *
 * @param pkt pointer to an Acmd buffer
 * @param dmemSrc DMEM source address
 * @param addrDest Any physical source address, KSEG0, or KSEG1
 * @param size number of bytes to copy (rounded down to the next multiple of 16)
 */
#define aSaveBuffer(pkt, dmemSrc, addrDest, size)                                                            \
    {                                                                                                        \
        Acmd* _a = (Acmd*)pkt;                                                                               \
                                                                                                             \
        _a->words.w0 = (_SHIFTL(A_SAVEBUFF, 24, 8) | _SHIFTL((size) >> 4, 16, 8) | _SHIFTL(dmemSrc, 0, 16)); \
        _a->words.w1 = (uintptr_t)(addrDest);                                                                \
    }

#define aSegment(pkt, s, b)                                   \
{                                                             \
        Acmd *_a = (Acmd *)pkt;                               \
                                                              \
        _a->words.w0 = _SHIFTL(A_SEGMENT, 24, 8);             \
        _a->words.w1 = _SHIFTL(s, 24, 8) | _SHIFTL(b, 0, 24); \
}

#define aSetBuffer(pkt, f, i, o, c)                                     \
{                                                                       \
        Acmd *_a = (Acmd *)pkt;                                         \
                                                                        \
        _a->words.w0 = (_SHIFTL(A_SETBUFF, 24, 8) | _SHIFTL(f, 16, 8) | \
                        _SHIFTL(i, 0, 16));                             \
        _a->words.w1 = _SHIFTL(o, 16, 16) | _SHIFTL(c, 0, 16);          \
}

#define aSetVolume(pkt, f, v, t, r)                                     \
{                                                                       \
        Acmd *_a = (Acmd *)pkt;                                         \
                                                                        \
        _a->words.w0 = (_SHIFTL(A_SETVOL, 24, 8) | _SHIFTL(f, 16, 16) | \
                        _SHIFTL(v, 0, 16));                             \
        _a->words.w1 = _SHIFTL(r, 0, 16) | _SHIFTL(t, 16, 16);          \
}

#define aSetVolume32(pkt, f, v, tr)                                     \
{                                                                       \
        Acmd *_a = (Acmd *)pkt;                                         \
                                                                        \
        _a->words.w0 = (_SHIFTL(A_SETVOL, 24, 8) | _SHIFTL(f, 16, 16) | \
                        _SHIFTL(v, 0, 16));                             \
        _a->words.w1 = (u32)(tr);                                       \
}

#define aSetLoop(pkt, a)                          \
{                                                 \
        Acmd *_a = (Acmd *)pkt;                   \
                                                  \
        _a->words.w0 = _SHIFTL(A_SETLOOP, 24, 8); \
        _a->words.w1 = (u32)(a);                  \
}

#define aDMEMMove(pkt, i, o, c)                                        \
{                                                                      \
        Acmd *_a = (Acmd *)pkt;                                        \
                                                                       \
        _a->words.w0 = _SHIFTL(A_DMEMMOVE, 24, 8) | _SHIFTL(i, 0, 24); \
        _a->words.w1 = _SHIFTL(o, 16, 16) | _SHIFTL(c, 0, 16);         \
}

#define aLoadADPCM(pkt, c, d)                                           \
{                                                                       \
        Acmd *_a = (Acmd *)pkt;                                         \
                                                                        \
        _a->words.w0 = _SHIFTL(A_LOADADPCM, 24, 8) | _SHIFTL(c, 0, 24); \
        _a->words.w1 = (u32)d;                                          \
}

#define aEnvSetup1(pkt, a, b, c, d)                             \
{                                                               \
        Acmd *_a = (Acmd *)pkt;                                 \
                                                                \
        _a->words.w0 = (_SHIFTL(A_ENVSETUP1, 24, 8) |           \
                        _SHIFTL(a, 16, 8) | _SHIFTL(b, 0, 16)); \
        _a->words.w1 = _SHIFTL(c, 16, 16) | _SHIFTL(d, 0, 16);  \
}

#define aEnvSetup2(pkt, volLeft, volRight)          \
{                                                   \
        Acmd *_a = (Acmd *)pkt;                     \
                                                    \
        _a->words.w0 = _SHIFTL(A_ENVSETUP2, 24, 8); \
        _a->words.w1 = (_SHIFTL(volLeft, 16, 16) |  \
                        _SHIFTL(volRight, 0, 16));  \
}

#define aFilter(pkt, f, countOrBuf, addr)                               \
{                                                                       \
        Acmd *_a = (Acmd *)pkt;                                         \
                                                                        \
        _a->words.w0 = (_SHIFTL(A_FILTER, 24, 8) | _SHIFTL(f, 16, 8) |  \
                        _SHIFTL(countOrBuf, 0, 16));                    \
        _a->words.w1 = (u32)(addr);                                     \
}

/*
 * Duplicates 128 bytes of data a specified number of times.
 *
 * @param pkt pointer to an Acmd buffer
 * @param numCopies number of times to duplicate 128 bytes from src
 * @param dmemSrc DMEM source address
 * @param dmemDest DMEM destination address for the duplicates, size 128 * numCopies
 */
#define aDuplicate(pkt, numCopies, dmemSrc, dmemDest)                                                       \
    {                                                                                                       \
        Acmd* _a = (Acmd*)pkt;                                                                              \
                                                                                                            \
        _a->words.w0 = (_SHIFTL(A_DUPLICATE, 24, 8) | _SHIFTL(numCopies, 16, 8) | _SHIFTL(dmemSrc, 0, 16)); \
        _a->words.w1 = _SHIFTL(dmemDest, 16, 16) | _SHIFTL(0x80, 0, 16);                                    \
    }

#define aAddMixer(pkt, count, dmemi, dmemo, a4)                           \
{                                                                         \
        Acmd *_a = (Acmd *)pkt;                                           \
                                                                          \
        _a->words.w0 = (_SHIFTL(A_ADDMIXER, 24, 8) |                      \
                        _SHIFTL(count >> 4, 16, 8) | _SHIFTL(a4, 0, 16)); \
        _a->words.w1 = _SHIFTL(dmemi, 16, 16) | _SHIFTL(dmemo, 0, 16);    \
}

#define aResampleZoh(pkt, pitch, pitchAccu)              \
{                                                        \
        Acmd *_a = (Acmd *)pkt;                          \
                                                         \
        _a->words.w0 = (_SHIFTL(A_RESAMPLE_ZOH, 24, 8) | \
                        _SHIFTL(pitch, 0, 16));          \
        _a->words.w1 = _SHIFTL(pitchAccu, 0, 16);        \
}

#endif /* ULTRA64_ABI_H */