// Sharp X68000 sample audio: MOD playback + the 5-slot digital SFX overlay,
// output through the OKI MSM6258 ADPCM channel.
//
// Shares the decoder and the SFX overlay with DOS and the Atari ST:
// libxmp-lite renders the module, src/core/audioSfxMix.c mixes the SFX slots
// on top, and this file converts the result to 4-bit ADPCM and hands it to the
// chip. Only the output stage is X68000-specific.
//
// WHY THE RATE IS LOW. jlpAudioFrameTick does the render + encode on the main
// thread (libxmp-lite is not interrupt-safe -- same constraint, same answer, as
// DOS and ST). libxmp's C mixer costs ~874 cycles per voice per output sample
// on a 68000, so a 4-voice module at 15625 Hz would need ~5.5x the machine.
// MSM6258_RATE picks the chip's lowest rate to keep that affordable; it is the
// honest bring-up trade, and the fix is a 68000 asm mixer (~56 cyc/voice-sample
// measured), which is the same work the 68k HAL rewrite already schedules.
//
// OUTPUT STAGE. _iocs_adpcmlot walks a linked chain of blocks and IOCS drives
// DMAC channel 3, so no manual descriptor programming is needed. The chain is
// circular, giving gapless playback; jlpAudioFrameTick refills the block the
// chip is not currently walking.
//
// NOT YET VERIFIED AUDIBLY. This compiles and the control flow is exercised,
// but no waveform has been captured out of the emulator, so treat the encoder
// output and the block hand-off as unproven.

#include <string.h>

#include <libxmp-lite/xmp.h>
#include <x68k/iocs.h>

#include "joey/debug.h"
#include "port.h"
#include "audioSfxMixInternal.h"


// MSM6258 rate. NOT the chip's slowest (3906 Hz): libxmp's XMP_MIN_SRATE is
// 4000, so xmp_start_player REJECTS anything below it with XMP_ERROR_INVALID
// (-7) and MOD playback silently never starts. That bug shipped undetected
// until the audio-cost probe reported modPlaying=0 with startRc=-7 -- the
// return value was being discarded, so jlAudioPlayMod looked like it worked.
//
// 7812.5 Hz is the next chip rate clear of that floor, and the measured cost
// makes it affordable: the SFX+encode path benched at ~17 cycles/sample, so
// doubling the rate is ~13% of a 10 MHz 68000 rather than the ~6.6% at 3906.
#define MSM6258_RATE        7812
// _iocs_adpcmmod / _iocs_adpcmlot mode byte: rate selector low, pan high.
// 0x05 = 7.8 kHz, both channels.
#define ADPCM_MODE          0x05

// Catch the 3906 Hz mistake at COMPILE time rather than as silence at runtime.
// xmp_start_player rejects anything under XMP_MIN_SRATE with XMP_ERROR_INVALID,
// and the only symptom is that MOD playback never starts.
typedef char x68kAdpcmRateAboveXmpFloor[(MSM6258_RATE >= XMP_MIN_SRATE) ? 1 : -1];

// Two blocks of PCM per chain entry. 1024 PCM samples = 512 ADPCM bytes =
// ~131 ms at 7812 Hz, so a dropped frame cannot starve the chip.
#define PCM_BLOCK           1024
#define ADPCM_BLOCK         (PCM_BLOCK / 2)
#define BLOCK_COUNT         2

static xmp_context        gXmpCtx     = NULL;
static bool               gXmpLoaded  = false;
static bool               gXmpStarted = false;
static int                gLoopCount  = 0;
static bool               gPcmReady   = false;
static bool               gPlaying    = false;

static AudioSfxSlotT      gSfxSlots[JOEY_AUDIO_SFX_SLOTS];
// libxmp renders unsigned 8-bit mono here; audioSfxOverlayMix adds the SFX
// slots in place, then adpcmEncodeBlock converts it.
static uint8_t            gMixBuf[PCM_BLOCK];
static uint8_t            gAdpcm[BLOCK_COUNT][ADPCM_BLOCK];
static struct iocs_chain2 gChain[BLOCK_COUNT];
static uint8_t            gFillBlock = 0;
// Diagnostics: how many times each stage actually ran. Exposed (non-static) so
// a probe can read them without a debugger -- silent no-ops in the refill path
// are exactly the failure this port hit.
uint32_t gX68kPcmRefills = 0;
uint32_t gX68kPcmChainStarts = 0;
uint8_t  gX68kPcmReady = 0;
uint8_t  gX68kPcmPlaying = 0;
// Last return codes from the libxmp load/start pair, so a failed
// jlAudioPlayMod can be diagnosed without a debugger.
int32_t  gX68kModLoadRc  = 99;
int32_t  gX68kModStartRc = 99;

// MSM6258 / OKI ADPCM encoder state. The encoder runs the exact decoder on
// every nibble it emits so its predictor stays bit-identical to the chip's --
// without that the two drift apart and the output degrades into noise.
static int32_t gEncPredictor = 0;
static int16_t gEncStepIndex = 0;

static const int16_t kStepTable[49] = {
     16,  17,  19,  21,  23,  25,  28,  31,  34,  37,  41,  45,
     50,  55,  60,  66,  73,  80,  88,  97, 107, 118, 130, 143,
    157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449,
    494, 544, 598, 658, 724, 796, 876, 963,1060,1166,1282,1411,
   1552
};

static const int8_t kIndexAdjust[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };


// Encode one PCM sample to a 4-bit ADPCM nibble, advancing the predictor
// exactly as the chip's decoder will.
static uint8_t adpcmEncodeSample(int16_t pcm) {
    int32_t step;
    int32_t diff;
    int32_t delta;
    uint8_t nibble;

    step = kStepTable[gEncStepIndex];
    diff = (int32_t)pcm - gEncPredictor;
    nibble = 0u;
    if (diff < 0) {
        nibble = 8u;
        diff = -diff;
    }
    // Three-bit magnitude search, MSB first: the standard ladder.
    delta = step >> 3;
    if (diff >= step) {
        nibble |= 4u;
        diff -= step;
        delta += step;
    }
    if (diff >= (step >> 1)) {
        nibble |= 2u;
        diff -= step >> 1;
        delta += step >> 1;
    }
    if (diff >= (step >> 2)) {
        nibble |= 1u;
        delta += step >> 2;
    }
    // Mirror the decoder so predictor and step index track the chip.
    gEncPredictor += (nibble & 8u) ? -delta : delta;
    if (gEncPredictor > 2047) {
        gEncPredictor = 2047;
    } else if (gEncPredictor < -2048) {
        gEncPredictor = -2048;
    }
    gEncStepIndex = (int16_t)(gEncStepIndex + kIndexAdjust[nibble & 7u]);
    if (gEncStepIndex < 0) {
        gEncStepIndex = 0;
    } else if (gEncStepIndex > 48) {
        gEncStepIndex = 48;
    }
    return nibble;
}


// Convert PCM_BLOCK unsigned-8-bit samples into ADPCM_BLOCK bytes, two
// samples per byte, low nibble first (the chip consumes them in that order).
static void adpcmEncodeBlock(const uint8_t *pcm, uint8_t *out) {
    uint16_t i;
    uint8_t  lo;
    uint8_t  hi;

    for (i = 0u; i < ADPCM_BLOCK; i++) {
        // libxmp gives unsigned 8-bit; the encoder wants signed 12-bit-ish.
        lo = adpcmEncodeSample((int16_t)(((int16_t)pcm[i * 2u] - 128) * 16));
        hi = adpcmEncodeSample((int16_t)(((int16_t)pcm[(i * 2u) + 1u] - 128) * 16));
        out[i] = (uint8_t)(lo | (hi << 4));
    }
}


// Render one block: module (if playing) then the SFX overlay on top.
static void renderBlock(uint8_t *dst) {
    if (gXmpStarted) {
        xmp_play_buffer(gXmpCtx, (void *)dst, PCM_BLOCK, gLoopCount);
    } else {
        memset(dst, 128, PCM_BLOCK);        // unsigned-8 silence
    }
    audioSfxOverlayMix(dst, PCM_BLOCK, gSfxSlots, JOEY_AUDIO_SFX_SLOTS);
}


static void startChain(void) {
    uint8_t b;

    for (b = 0u; b < BLOCK_COUNT; b++) {
        renderBlock(gMixBuf);
        adpcmEncodeBlock(gMixBuf, gAdpcm[b]);
        gChain[b].addr = gAdpcm[b];
        gChain[b].len  = ADPCM_BLOCK;
        gChain[b].next = &gChain[(b + 1u) % BLOCK_COUNT];   // circular
    }
    gFillBlock = 0u;
    _iocs_adpcmlot(&gChain[0], ADPCM_MODE);
    gPlaying = true;
    gX68kPcmPlaying = 1u;
    gX68kPcmChainStarts++;
}


// ----- HAL entry points (alphabetical) -----

bool jlpAudioInitPcm(void) {
    gXmpCtx = xmp_create_context();
    if (gXmpCtx == NULL) {
        return false;
    }
    memset(gSfxSlots, 0, sizeof(gSfxSlots));
    gPcmReady = true;
    gX68kPcmReady = 1u;
    return true;
}


bool jlpAudioIsPlayingMod(void) {
    return gXmpStarted;
}


void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
    if (!gPcmReady || data == NULL) {
        return;
    }
    jlpAudioStopMod();
    gX68kModLoadRc = (int32_t)xmp_load_module_from_memory(gXmpCtx, (void *)data, (long)length);
    if (gX68kModLoadRc != 0) {
        return;
    }
    gXmpLoaded = true;
    gX68kModStartRc = (int32_t)xmp_start_player(gXmpCtx, MSM6258_RATE,
                         XMP_FORMAT_MONO | XMP_FORMAT_8BIT | XMP_FORMAT_UNSIGNED);
    if (gX68kModStartRc != 0) {
        // Do not fail silently -- that is exactly how the rate bug survived.
        jlLogF("x68k: xmp_start_player failed rc=%d rate=%d\n",
               (int)gX68kModStartRc, (int)MSM6258_RATE);
        xmp_release_module(gXmpCtx);
        gXmpLoaded = false;
        return;
    }
    // XMP_PLAYER_INTERP is left at the default NEAREST: linear interpolation
    // more than doubles the mixer cost and the detail it preserves is thrown
    // away by the 4-bit re-encode anyway.
    gLoopCount  = loop ? 0 : 1;
    gXmpStarted = true;
    if (!gPlaying) {
        startChain();
    }
}


void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
    if (!gPcmReady || slot >= JOEY_AUDIO_SFX_SLOTS) {
        return;
    }
    audioSfxSlotArm(&gSfxSlots[slot], sample, length, rateHz, MSM6258_RATE);
    if (!gPlaying) {
        startChain();
    }
}


void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
    if (!gPcmReady || slot >= JOEY_AUDIO_SFX_SLOTS) {
        return;
    }
    audioSfxSlotArmStream(&gSfxSlots[slot], fill, ctx, rateHz, MSM6258_RATE);
    if (!gPlaying) {
        startChain();
    }
}


void jlpAudioStopMod(void) {
    if (!gPcmReady) {
        return;
    }
    if (gXmpStarted) {
        xmp_end_player(gXmpCtx);
        gXmpStarted = false;
    }
    if (gXmpLoaded) {
        xmp_release_module(gXmpCtx);
        gXmpLoaded = false;
    }
}


void jlpAudioStopSfx(uint8_t slot) {
    if (slot < JOEY_AUDIO_SFX_SLOTS) {
        memset(&gSfxSlots[slot], 0, sizeof(gSfxSlots[slot]));
    }
}


// Refill one block per frame. At 3906 Hz a block is ~262 ms and there are two,
// so the chip has roughly half a second of audio queued -- ample margin over a
// 55 Hz frame tick even if several frames are missed.
void jlpAudioPcmFrameTick(void) {
    if (!gPcmReady || !gPlaying) {
        return;
    }
    renderBlock(gMixBuf);
    adpcmEncodeBlock(gMixBuf, gAdpcm[gFillBlock]);
    gFillBlock = (uint8_t)((gFillBlock + 1u) % BLOCK_COUNT);
    gX68kPcmRefills++;
}


void jlpAudioShutdownPcm(void) {
    if (!gPcmReady) {
        return;
    }
    jlpAudioStopMod();
    if (gPlaying) {
        _iocs_adpcmmod(0);      // stop the chip
        gPlaying = false;
    }
    xmp_free_context(gXmpCtx);
    gXmpCtx   = NULL;
    gPcmReady = false;
}
