pureikyubu/Docs/RE/axout.txt
2020-03-22 13:43:44 +03:00

247 lines
6.6 KiB
Text

// AX library sound output (playback) module
// rev. by org <ogamespec@gmail.com>
// 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 |
-----------------------
/*/