// AX library sound output (playback) module // rev. by org // 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 (160 * 2 * 2 = 640 bytes) 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 | ----------------------- /*/