// AX library sound output (playback) module
// rev. by org <kvzorganic@mail.ru>

// AX playback init :
// 1. DSPInit - reset DSP, install DSP interrupt handler (see my dsp.txt)
// 2. boot AX slave microcode; DSP jumps to start vector
// 3. DSP signals interrupt and confirm it by "dsp init" mail in input mailbox
// 4. DSP interrupt handler calls AX "dsp init" callback
// 5. callback is setting __AXDSPInitFlag to TRUE
// 6. warm-up AX by playing initial empty AI DMA buffer.

// AX playback flow :
// 1. AI buffer DMA end -> AI interrupt -> AX AI callback
// 2. sending audio list (ALIST) to DSP
// 3. set next output buffer for AI DMA (AI DMA plays all the time)
// 4. DSP confirm end of ALIST execution by DSP interrupt and "dsp resume" mail
// 5. DSP handler syncs DSP processing
// 6. goto 1.

static  u16 __AXOutBuffer[2][320];  // primary and secondary AI DMA buffers
static  u16 __AXOutSBuffer[320];    // software streaming buffer (to mix)

static  BOOL __AXDebugSteppingMode; // step-by-step frames
static  u32 __AXOutDspReady;
static  int __AXOutFrame;           // selects current AI DMA buffer

static  BOOL __AXDSPInitFlag;
static  BOOL __AXDSPDoneFlag;

// called every audio frame (if registered)
static  AXUserCallback __AXUserFrameCallback;

// AX profiler data block, for this module
// not important, but hacked anyway :p
static  AXPROFILE __AXLocalProfile;

// ----------------------------------------------------------------------------

// playback section

void __AXOutNewFrame(OSTick lessDspCycles)
{
    void *cl;

    __AXLocalProfile.axFrameStart = OSGetTime();

    __AXSyncPBs(lessDspCycles);
    __AXPrintStudio();

    // send audio list to DSP
    cl = __AXGetCommandListAddress();

    DSPSendMailToDSP(0xBABE0180);
    while(DSPCheckMailToDSP());

    DSPSendMailToDSP(cl);
    while(DSPCheckMailToDSP());

    // service, callback and stack, yeah :p
    __AXServiceCallbackStack();

    // AUX processing (e.g. new ALIST generation)
    __AXLocalProfile.auxProcessingStart = OSGetTime();
    __AXProcessAux();
    __AXLocalProfile.auxProcessingEnd = OSGetTime();

    // take control to AX user callback
    __AXLocalProfile.userCallbackStart = OSGetTime();
    if(__AXUserFrameCallback) __AXUserFrameCallback();
    __AXLocalProfile.userCallbackEnd = OSGetTime();

    // next frame
    // note : it could simplier to use for toggle : __AXOutFrame ^= 1
    __AXNextFrame(__AXOutSBuffer, &__AXOutBuffer[__AXOutFrame++][0]);
    __AXOutFrame &= 1;
    AIInitDMA(&__AXOutBuffer[__AXOutFrame][0], 640);

    __AXLocalProfile.axFrameEnd  = OSGetTime();
    __AXLocalProfile.axNumVoices = __AXGetNumVoices();

    // copy local profile, or something
    // not interesting crap
    profile = __AXGetCurrentProfile();
    if(profile)
    {
        i, src, dest
    }
}

AIDCallback __AXOutAiCallback()
{
    // update time (for what?)
    if(__AXOutDspReady == 0)
    {
        __AXOsTime = OSGetTime();
    }

    // new frame
    if(__AXOutDspReady == 1)
    {
        __AXOutDspReady = 0;
        __AXOutNewFrame(0);
        return;
    }

    __AXOutDspReady = 2;
    DSPAssertTask(&task);
}

// signal to caller, that DSP is initialized
DSPCallback __AXDSPInitCallback()
{
    __AXDSPInitFlag = TRUE;
}

// set step-by-step debug mode
void AXSetStepMode(BOOL i)
{
    __AXDebugSteppingMode = i;
}

DSPCallback __AXDSPResumeCallback()
{
    if(__AXDebugSteppingMode)
    {
        __AXOutDspReady = 1;
    }
    else
    {
        if(__AXOutDspReady == 2)
        {
            __AXOutDspReady = 0;
            __AXOutNewFrame(OSGetTime() - __AXOsTime);
        }
        else __AXOutDspReady = 1;
    }
}

DSPCallback __AXDSPDoneCallback()
{
    __AXDSPDoneFlag = TRUE;
}

// ----------------------------------------------------------------------------

// init section

// initialize DSP hardware and boot AX microcode program
void __AXOutInitDSP(void)
{
    static DSPTaskInfo ax_task;

    ax_task.iram_mmem_addr = axDspSlave;
    ax_task.iram_length    = axDspSlaveLength;
    ax_task.iram_addr      = 0;

    ax_task.dram_mmem_addr = ax_dram_image;
    ax_task.dram_length    = 8192;
    ax_task.dram_addr      = 0;

    ax_task.dsp_init_vector   = 0x0010;
    ax_task.dsp_resume_vector = 0x0030;

    ax_task.init_cb = __AXDSPInitCallback;
    ax_task.res_cb  = __AXDSPResumeCallback;
    ax_task.done_cb = __AXDSPDoneCallback;
    ax_task.req_cb  = NULL;

    ax_task.state = DSP_TASK_STATE_INIT;

    __AXDSPInitFlag = FALSE;
    __AXDSPDoneFlag = FALSE;

    // reset DSP
    if(DSPCheckInit() == 0)
    {
        DSPInit();
    }

    DSPAddTask(&ax_task);

    while(__AXDSPInitFlag == 0);
}

// init AX sound output hardware and buffers
void __AXOutInit(void)
{
    OSReport("Initializing AXOut code module\n");

    ASSERT(((u32)&__AXOutBuffer[0][0] & 0x1F) == 0);
    ASSERT(((u32)&__AXOutBuffer[1][0] & 0x1F) == 0);
    ASSERT(((u32)&__AXOutSBuffer[0] & 0x1F) == 0);

    __AXOutFrame = 0;           // current buffer
    __AXDebugSteppingMode = 0;  // only for debug

    // clear output sample buffers
    memset(__AXOutBuffer, 0, sizeof(__AXOutBuffer));
    DCFlushRange(__AXOutBuffer, sizeof(__AXOutBuffer));

    // clear streaming buffer
    memset(__AXOutSBuffer, 0, sizeof(__AXOutSBuffer));
    DCFlushRange(__AXOutSBuffer, sizeof(__AXOutSBuffer));

    // initialize audio hardware
    __AXOutInitDSP();
    AIRegisterDMACallback(__AXOutAiCallback);

    // prepare AX
    __AXNextFrame(__AXOutSBuffer, &__AXOutBuffer[1][0]);
    __AXOutDspReady = 1;
    __AXUserFrameCallback = NULL;

    // warm-up AX by playing first empty buffer
    AIInitDMA(&__AXOutBuffer[__AXOutFrame][0], 640);
    AIStartDMA();
}

// user callback
void AXRegisterCallback(AXUserCallback callback)
{
    __AXUserFrameCallback = callback;
}

// quick AX timing note
/*/
    Gekko CPU clock is 486000000 ticks per second

    1 NTSC frame 486000000 / 50 = 9720000 ticks per second
    1 PAL  frame 486000000 / 60 = 8100000 ticks per second

    AX always plays 160 16-bit stereo samples per audio frame
    so AX frame length is 0.005 seconds for 32000 hz (5 msec)
    and 0.00333333333.. seconds for 48000 hz (4 msec)

    or AX frame length is 2430000 ticks for 32000 hz
    and 1620000 ticks for 48000 hz

    summary (AX frames per single video frame) :

     -----------------------
    | video | 32000 | 48000 |
    |-----------------------|
    |  NTSC |   4   |   6   |
    |  PAL  | 3.333 |   5   |
     -----------------------
/*/
