/*
 * The DSP voice mixers as register machines. Included by both the emulator
 * (cpu/c_dspproc.c) and the difftests, so what ships is what was proven
 * bit-identical to the assembly.
 *
 * Textual include: the includer supplies the integer typedefs, the DSP globals
 * the bodies name, and the DSPInterpolate function pointer.
 *
 * Each w_<name> has the `mixfn` ABI from c_dspproc.c: reads the voice, the
 * DSP-buffer index (*pesi), the increment (*pebx) and the decoded samples
 * (edi), advances *pesi and, in the pitch-modulation variants, *pebx. They are
 * static inline so a partial includer draws no unused-function warnings;
 * taking their address for paramhack[] still works.
 */
#ifndef DSP_MIXERS_H
#define DSP_MIXERS_H

/* A 32-bit x86 register: byte/word sub-registers alias (al=b[0], ah=b[1],
 * ax=w, eax=e), so partial writes are just field writes. */
typedef union {
    u4 e;
    u2 w;
    u1 b[4];
} x86reg;

/* ProcessPMod ebp: a side-effect that stores one byte into PModBuffer[esi].
 * Preserves the caller's ecx/edx (taken by value). */
static inline void mix_ProcessPMod(u4 ebp, u4 esi, s2* edi, x86reg edx, x86reg ecx)
{
    x86reg eax;
    ecx.b[0] = ((u1*)&Voice0EnvInc[ebp])[2]; /* mov cl,[Voice0EnvInc+ebp*4+2] */
    eax.w = *(u2*)((u1*)edi + edx.e * 2);      /* mov ax,[edi+edx*2] */
    {                                          /* imul cx -> dx:ax = ax * cx */
        s4 p = (s2)eax.w * (s2)ecx.w;
        eax.w = (u2)p;
        edx.w = (u2)(p >> 16);
    }
    eax.w >>= 7;                          /* shr ax,7 */
    edx.b[0] = (u1)(edx.b[0] + edx.b[0]); /* add dl,dl */
    eax.b[1] |= edx.b[0];                  /* or ah,dl */
    PModBuffer[esi] = eax.b[1];            /* mov [PModBuffer+esi],ah */
}

/* Volume lookup: index VolumeConvTable by (envinc<<8 | volbyte). */
static inline u2 mix_vconv(u4 voice, u1 volbyte)
{
    x86reg idx;
    idx.e = volbyte;                          /* movzx eax,volbyte */
    idx.b[1] = ((u1*)&Voice0EnvInc[voice])[2]; /* mov ah,[Voice0EnvInc+voice*4+2] */
    return *(u2*)((u1*)VolumeConvTable + idx.e * 2);
}

/* Volume scale of one sample: (sample * volconv) >> 7, truncated to 16 bits.
 * The asm's `imul cx; shr ax,7; add dl,dl; or ah,dl; movsx eax,ax` reconstructs
 * exactly this (proven by the diff-tests). */
static inline s4 mix_scale(s2 sample, u2 volconv)
{
    s4 p = (s2)sample * (s2)volconv;
    return (s2)(p >> 7);
}

/* Fetch this voice's next sample: noise, or the decoded sample (with a
 * ProcessPMod side-effect on the way when a following voice pitch-modulates). */
static inline s2 mix_sample(u4 voice, u4 esi, s2* edi, x86reg edx, x86reg ecx)
{
    if (UniqueSoundv != 0) {
        if (DSPMem[0x3D] & (u1)powhack) { /* test DSPMem[3D],al / jnz -> noise */
            NoisePointer += NoiseInc;
            return *(s2*)(NoiseData + (NoisePointer >> 18) * 2);
        }
        mix_ProcessPMod(voice, esi, edi, edx, ecx);
    }
    return *(s2*)((u1*)edi + edx.e * 2);
}

/* Interpolated fetch: like mix_sample, but the sample comes from DSPInterpolate
 * (noise still bypasses interpolation). The interpolated mixers never load cx
 * before their ProcessPMod, so it runs with ch=0 - a defined replacement for
 * the asm's undefined ch (dispatcher register residue). */
static inline s2 mix_sample_interp(u4 voice, u4 esi, s2* edi, x86reg edx)
{
    if (UniqueSoundv != 0) {
        if (DSPMem[0x3D] & (u1)powhack) {
            NoisePointer += NoiseInc;
            return *(s2*)(NoiseData + (NoisePointer >> 18) * 2);
        }
        x86reg ecx;
        ecx.e = 0; /* ch = 0 */
        mix_ProcessPMod(voice, esi, edi, edx, ecx);
    }
    return (s2)DSPInterpolate(edx.e, voice);
}

/* CalculatePMod ebp: the pitch-modulated increment for the next sample. */
static inline u4 mix_CalculatePMod(u4 voice, u4 esi)
{
    u1 m = (u1)(PModBuffer[esi] + 0x80);                       /* movzx + add al,80h */
    unsigned long long p = (unsigned long long)m * Voice0Freq[voice]; /* mul ebx */
    return (u4)(p >> 7);                                       /* shr eax,7 | shl edx,25 */
}

/* Common prologue: load the sample position and the first channel's volconv,
 * then fetch the sample. */
#define MIX_FETCH(voice, esi, edi, volbyte, sample, ecx)                     \
    u4 esi = *pesi;                                                          \
    x86reg edx;                                                             \
    edx.e = *(u4*)((u1*)BRRPlace0 + (voice) * 8 + 3);                        \
    x86reg ecx;                                                             \
    ecx.e = 0;                                                              \
    ecx.w = mix_vconv((voice), (volbyte));                                   \
    s2 sample = mix_sample((voice), esi, edi, edx, ecx)

/* Non-pitch-mod tail: advance esi and step BRRPlace0 by the frequency. */
#define MIX_TAIL(voice, esi)                                                 \
    esi += 2;                                                               \
    *(u4*)((u1*)BRRPlace0 + (voice) * 8) += *pebx;                           \
    *pesi = esi

/* Frequency-reload tail: variants that clobbered ebx to hold the sample reload
 * Voice0Freq for the BRRPlace0 step (mov ebx,[Voice0Freq+ebp*4]) and leave it
 * in *pebx. */
#define MIX_TAIL_FREQ(voice, esi)                                            \
    esi += 2;                                                               \
    *pebx = Voice0Freq[voice];                                              \
    *(u4*)((u1*)BRRPlace0 + (voice) * 8) += *pebx;                           \
    *pesi = esi

/* Pitch-mod tail: advance esi, recompute the increment, step BRRPlace0. */
#define MIX_TAIL_PM(voice, esi)                                              \
    esi += 2;                                                               \
    {                                                                       \
        u4 nb = mix_CalculatePMod((voice), esi);                            \
        *(u4*)((u1*)BRRPlace0 + (voice) * 8) += nb;                         \
        *pebx = nb;                                                         \
    }                                                                       \
    *pesi = esi

static inline void w_NonEchoMono(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0Volume[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 2) += mix_scale(s, ecx.w);
    MIX_TAIL(voice, esi);
}

static inline void w_NonEchoStereo(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0VolumeR[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 4) += mix_scale(s, ecx.w);
    *(s4*)((u1*)DSPBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeL[voice]));
    MIX_TAIL(voice, esi);
}

static inline void w_EchoMono(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0Volume[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 2) += mix_scale(s, ecx.w);
    *(s4*)((u1*)EchoBuffer + esi * 2) += mix_scale(s, mix_vconv(voice, Voice0Volumee[voice]));
    MIX_TAIL(voice, esi);
}

static inline void w_EchoStereo(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0VolumeR[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 4) += mix_scale(s, ecx.w);
    *(s4*)((u1*)EchoBuffer + esi * 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeRe[voice]));
    *(s4*)((u1*)DSPBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeL[voice]));
    *(s4*)((u1*)EchoBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeLe[voice]));
    /* This variant reused ebx to hold the sample, so it reloads Voice0Freq. */
    MIX_TAIL_FREQ(voice, esi);
}

static inline void w_NonEchoMonoPM(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0Volume[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 2) += mix_scale(s, ecx.w);
    MIX_TAIL_PM(voice, esi);
}

static inline void w_NonEchoStereoPM(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0VolumeR[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 4) += mix_scale(s, ecx.w);
    *(s4*)((u1*)DSPBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeL[voice]));
    MIX_TAIL_PM(voice, esi);
}

static inline void w_EchoMonoPM(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0Volume[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 2) += mix_scale(s, ecx.w);
    *(s4*)((u1*)EchoBuffer + esi * 2) += mix_scale(s, mix_vconv(voice, Voice0Volumee[voice]));
    MIX_TAIL_PM(voice, esi);
}

static inline void w_EchoStereoPM(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    MIX_FETCH(voice, esi, edi, Voice0VolumeR[voice], s, ecx);
    *(s4*)((u1*)DSPBuffer + esi * 4) += mix_scale(s, ecx.w);
    *(s4*)((u1*)EchoBuffer + esi * 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeRe[voice]));
    *(s4*)((u1*)DSPBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeL[voice]));
    *(s4*)((u1*)EchoBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeLe[voice]));
    MIX_TAIL_PM(voice, esi);
}

/* Interpolated variants: the non-interpolated channel layout, but the sample
 * comes from mix_sample_interp and all of them step BRRPlace0 by Voice0Freq
 * (MIX_TAIL_FREQ). EchoMonoInterpolated's asm left the decoded sample in ebx,
 * which is dead - the dispatcher reloads Voice0Freq first - so it returns
 * Voice0Freq here too. */
static inline void w_NonEchoMonoInterpolated(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    u4 esi = *pesi;
    x86reg edx;
    edx.e = *(u4*)((u1*)BRRPlace0 + voice * 8 + 3);
    s2 s = mix_sample_interp(voice, esi, edi, edx);
    *(s4*)((u1*)DSPBuffer + esi * 2) += mix_scale(s, mix_vconv(voice, Voice0Volume[voice]));
    MIX_TAIL_FREQ(voice, esi);
}

static inline void w_NonEchoStereoInterpolated(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    u4 esi = *pesi;
    x86reg edx;
    edx.e = *(u4*)((u1*)BRRPlace0 + voice * 8 + 3);
    s2 s = mix_sample_interp(voice, esi, edi, edx);
    *(s4*)((u1*)DSPBuffer + esi * 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeR[voice]));
    *(s4*)((u1*)DSPBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeL[voice]));
    MIX_TAIL_FREQ(voice, esi);
}

static inline void w_EchoMonoInterpolated(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    u4 esi = *pesi;
    x86reg edx;
    edx.e = *(u4*)((u1*)BRRPlace0 + voice * 8 + 3);
    s2 s = mix_sample_interp(voice, esi, edi, edx);
    *(s4*)((u1*)DSPBuffer + esi * 2) += mix_scale(s, mix_vconv(voice, Voice0Volume[voice]));
    *(s4*)((u1*)EchoBuffer + esi * 2) += mix_scale(s, mix_vconv(voice, Voice0Volumee[voice]));
    MIX_TAIL_FREQ(voice, esi);
}

static inline void w_EchoStereoInterpolated(u4 voice, u4* const pesi, u4* const pebx, s2* edi)
{
    u4 esi = *pesi;
    x86reg edx;
    edx.e = *(u4*)((u1*)BRRPlace0 + voice * 8 + 3);
    s2 s = mix_sample_interp(voice, esi, edi, edx);
    *(s4*)((u1*)DSPBuffer + esi * 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeR[voice]));
    *(s4*)((u1*)EchoBuffer + esi * 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeRe[voice]));
    *(s4*)((u1*)DSPBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeL[voice]));
    *(s4*)((u1*)EchoBuffer + esi * 4 + 4) += mix_scale(s, mix_vconv(voice, Voice0VolumeLe[voice]));
    MIX_TAIL_FREQ(voice, esi);
}

#endif /* DSP_MIXERS_H */
