/*---------------------------------------------------------------------------* Project: Revolution AX Stream Demo File: axstream2.c Copyright (C) 2009 Nintendo. All Rights Reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Log: axstream2.c,v $ Revision 1.4 2009/05/13 01:38:16 aka Revised comments. Revision 1.3 2009/05/12 08:25:44 aka Added comments. Revision 1.2 2009/05/11 08:29:45 aka Added description and restrictions. Revision 1.1 2009/05/07 02:26:29 aka Initial version. $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include #include #include #include #include // ########################################################################### // This program is a sample of streaming playback of looped ADPCM data. // ** See axstream for streaming playback of unlooped data. // ########################################################################### // ########################################################################### // Description // ########################################################################### // // For streaming playback of looped ADPCM data, you will need: // // (1) A streaming buffer loop // // (2) An ADPCM data loop // // You must switch between these loops depending on data playback conditions. // // Specifically, you must change these settings. // // + Voice type (AX_PB_TYPE_NORMAL <-> AX_PB_TYPE_STREAM) // // + Loop start address (LoopAddr) // // + Loop end address (EndAddr) // // + Loop context (AXPBADPCMLOOP) // // // // The above settings must be changed only when it is safe to do so, at times when modifying the voice parameter block does not cause any AX malfunction. // // // ---- // // In contrast to axstream (which was a streaming buffer only), this program adds a loop start buffer in order to support looped ADPCM data playback. // // // // In a nutshell, it operates as follows. // // (a) Pre-loads a certain amount of ADPCM data from the start of the loop into the loop start buffer. // // // (b) The program normally plays back the ADPCM data in the streaming buffer as a loop (AX_PB_TYPE_STREAM). // # Same procedure as for axstream. // // // (c) Once the pointer reaches the end of the ADPCM data loop, this sample uses the loop feature to jump from the streaming buffer to the loop start buffer. // This jump is performed using AX_PB_TYPE_NORMAL. // // // (d) After playing back a certain amount of data from the loop start buffer, this sample uses the loop feature again to jump back to the streaming buffer. // This jump is performed using AX_PB_TYPE_STREAM. // // // (e) Returns to step (b). // // To carry out the steps just described, the program changes the aforementioned settings at the following points in time, when it is safe to do so. // // // + Right after the pointer returns to the first half of the streaming buffer. // // + Right after the pointer jumps to the loop start buffer. // // Moreover, to ensure that settings are changed safely, the program adds an extra margin to the above points in time by waiting until: // // // + The ADPCM data loop end (specifically, the frame including the loop end) has definitely entered the second half of the streaming buffer or reached its boundary. // // // + The loop start buffer includes at least STRM_BUFFER_SIZE bytes of data. // // The placement of data in the buffers is adjusted to meet these restrictions. // // // ########################################################################### // ########################################################################### // Restrictions // ########################################################################### // // This program has the following restrictions. // // + The data size from the loop start to loop end must be STRM_BUFFER_SIZE x 2. // If STRM_BUFFER_SIZE is left at the default value, the size is 8KB x 2, or roughly one second at 32000 Hz. // // // + Make sure the frame that includes the loop end never reaches the tail end of the data. // It is no problem if data after the frame that includes the loop end consists of padding, so pad as necessary to make sure there is at least 32 bytes of data. // // // ########################################################################### // Output debugging information #define SHOW_INFO //#undef SHOW_INFO // Playback data #define SAMPLE_ADPCM_L "/axdemo/stream/left_wl.dsp" #define SAMPLE_ADPCM_R "/axdemo/stream/right_wl.dsp" /*---------------------------------------------------------------------------* AX-related *---------------------------------------------------------------------------*/ static void callbackAudioFrame ( void ); /*---------------------------------------------------------------------------* Wii Remote-related *---------------------------------------------------------------------------*/ static void padInit ( MEMHeapHandle* handle ); static u32 padUpdate ( void ); /*---------------------------------------------------------------------------* Streaming playback-related *---------------------------------------------------------------------------*/ // Streaming buffer size (must be a multiple of 32) // ** Change this size as needed depending on system conditions (such as frequency of DVD access outside of streaming playback). // #define STRM_BUFFER_SIZE (8 * 1024) // 8KB -> 448ms@32KHz // Control Structures typedef struct { // Operating status s32 state; // DVD access flag BOOL accessDVD; // Data read information s32 readSize; s32 readRests; s32 readOffset; s32 writeOffset; // Previous current pointer u32 prevPtr; // Loop end addresses to set next u32 nextLoopEndL; u32 nextLoopEndR; // Loop start addresses to set next u32 nextLoopStartL; u32 nextLoopStartR; // Data filename char* dataL; char* dataR; // DVDFileInfo finfoL; DVDFileInfo finfoR; // DSPADPCM header buffer u8* headerBufferL; u8* headerBufferR; // Streaming buffer u8* strmBufferL; u8* strmBufferR; u32 strmBufferLHalfPtr; // Loop start buffer u8* lsBufferL; u8* lsBufferR; u32 lsBufferLTopPtr; // Loop context AXPBADPCMLOOP lsContextL; AXPBADPCMLOOP lsContextR; // Streaming buffer parameters (initial cycle) s32 strm1stAddr; u32 strm1stPrevPtr; s32 strm1stOffset; s32 strm1stCA; s32 strm1stTotalRead; s32 strm1stLoopEnd; // Loop start buffer parameters s32 lsLoopStart; s32 lsLoopEnd; // Streaming buffer parameters (after returning from loop start buffer) s32 strmOffset; s32 strmTotalRead; s32 strmLoopEnd; // AX voice(s) AXVPB* voiceL; AXVPB* voiceR; } STRMInfo; // Return value typedef enum { STRM_ERR_NONE = 0, STRM_ERR_CANNOT_OPEN, STRM_ERR_CANNOT_READ, STRM_ERR_INVALID_STATE, STRM_ERR_CANNOT_ACQUIRE_VOICE, STRM_ERR_BUSY, STRM_ERR_INVALID_DATA, STRM_ERR_NUM_ERRORS } STRM_ERR; // Functions static void STRMInit ( void ); static s32 STRMGetWorkSize ( void ); static STRM_ERR STRMPrepare ( STRMInfo* strm, char* left, char* right, void* work ); static STRM_ERR STRMStart ( STRMInfo* strm ); static STRM_ERR STRMStop ( STRMInfo* strm ); static void STRMUpdate ( void ); /*---------------------------------------------------------------------------* Name: Main Description: Main Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ void main(void) { void* arenaMem2Lo; void* arenaMem2Hi; MEMHeapHandle hExpHeap; void* axBuffer; void* mixBuffer; void* strmWork; STRMInfo strmInfo; STRM_ERR retval; u32 push; // Initialize DEMO DEMOInit(NULL); // Initialize Exp Heap on MEM2 arenaMem2Lo = OSGetMEM2ArenaLo(); arenaMem2Hi = OSGetMEM2ArenaHi(); hExpHeap = MEMCreateExpHeap(arenaMem2Lo, (u32)arenaMem2Hi - (u32) arenaMem2Lo); // Initialize AX and MIX axBuffer = MEMAllocFromExpHeapEx(hExpHeap, AXGetMemorySize(AX_MAX_VOICES), 32); mixBuffer = MEMAllocFromExpHeapEx(hExpHeap, MIXGetMemorySize(AX_MAX_VOICES), 32); AIInit(NULL); AXInitSpecifyMem(AX_MAX_VOICES, axBuffer); MIXInitSpecifyMem(mixBuffer); // Initialize WPAD padInit(&hExpHeap); // Initialize streaming playback STRMInit(); // Register user callback for audio frames notification // ** STRMUpdate() is called in the callback, so register the callback after STRMInit(). // AXRegisterCallback(&callbackAudioFrame); // // Prepare for streaming playback // strmWork = MEMAllocFromExpHeapEx(hExpHeap, (u32)STRMGetWorkSize(), 32); memset(&strmInfo, 0, sizeof(STRMInfo)); // Always zero-clear it the first time. retval = STRMPrepare(&strmInfo, SAMPLE_ADPCM_L, SAMPLE_ADPCM_R, strmWork); if (retval == STRM_ERR_CANNOT_OPEN) { OSHalt("Cannot open stream files.\n"); } else if (retval == STRM_ERR_CANNOT_READ) { OSHalt("Cannot read stream files.\n"); } else if (retval == STRM_ERR_INVALID_STATE) { OSHalt("Error : ??? (invalid STRMInfo state).\n"); } else if (retval == STRM_ERR_INVALID_DATA) { OSHalt("Invalid ADPCM files (too short loop length).\n"); } // // Streaming playback controls // OSReport("Press the A button to play ADPCM stream.\n"); OSReport("Press the B button to stop ADPCM stream.\n"); while (1) { // wait for retrace VIWaitForRetrace(); // check wpad push = padUpdate(); // Start streaming playback if (push & WPAD_BUTTON_A) { retval = STRMStart(&strmInfo); if (retval == STRM_ERR_NONE) { OSReport("Start playing ADPCM stream.\n"); } else if (retval == STRM_ERR_CANNOT_OPEN) { OSHalt("Cannot open stream files.\n"); } else if (retval == STRM_ERR_CANNOT_READ) { OSHalt("Cannot read stream files.\n"); } else if (retval == STRM_ERR_CANNOT_ACQUIRE_VOICE) { OSHalt("Cannot acquire AX voices.\n"); } else if (retval == STRM_ERR_BUSY) { OSReport("Now playing other ADPCM stream.\n"); } else // if (retval == STRM_ERR_INVALID_STATE) { OSReport("Now playing specified ADPCM stream.\n"); } } // Stop streaming playback else if (push & WPAD_BUTTON_B) { retval = STRMStop(&strmInfo); if (retval == STRM_ERR_NONE) { OSReport("Stop playing ADPCM stream.\n"); } else // if (retval == STRM_ERR_INVALID_STATE) { OSReport("Not playing specified ADPCM stream.\n"); } } } } /*---------------------------------------------------------------------------* *---------------------------------------------------------------------------* * Local functions for AX *---------------------------------------------------------------------------* *---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* Name: callbackAudioFrame Description: Function called every audio frame. Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ static void callbackAudioFrame(void) { // tell the mixer to update settings MIXUpdateSettings(); // Update streaming playback STRMUpdate(); } /*---------------------------------------------------------------------------* *---------------------------------------------------------------------------* * Local functions for Wii Remote *---------------------------------------------------------------------------* *---------------------------------------------------------------------------*/ static MEMHeapHandle* padHeap; static void* padAlloc ( u32 size ); static u8 padFree ( void* ptr ); /*---------------------------------------------------------------------------* Name: padInit Description: Initializes pad. Arguments: handle: pointer of MEMHeapHandle. Returns: None. *---------------------------------------------------------------------------*/ static void padInit(MEMHeapHandle* handle) { s32 state; padHeap = handle; WPADRegisterAllocator(padAlloc, padFree); WPADInit(); do { state = WPADGetStatus(); } while (WPAD_STATE_SETUP != state); } /*---------------------------------------------------------------------------* Name: padUpdate Description: Updates pad status. Arguments: None. Returns: current pad status. *---------------------------------------------------------------------------*/ static u32 padUpdate(void) { static u32 prevButton = 0; s32 state; u32 type; WPADStatus padStatus; u32 currPush; state = WPADProbe(0, &type); if (WPAD_ERR_NONE == state) { WPADRead(0, &padStatus); currPush = (prevButton ^ padStatus.button) & padStatus.button; prevButton = padStatus.button; return currPush; } return 0; } /*---------------------------------------------------------------------------* Name: padAlloc Description: Memory alloc routine for wpad. Arguments: size: size of allocating area. Returns: pointer of allocated area. *---------------------------------------------------------------------------*/ static void* padAlloc(u32 size) { void* ptr; ptr = MEMAllocFromExpHeapEx(*padHeap, size, 32); return ptr; } /*---------------------------------------------------------------------------* Name: padFree Description: Memory free routine for wpad. Arguments: ptr: pointer of freeing area. Returns: always 1. *---------------------------------------------------------------------------*/ static u8 padFree(void* ptr) { MEMFreeToExpHeap(*padHeap, ptr); return 1; } /*---------------------------------------------------------------------------* *---------------------------------------------------------------------------* * Local functions for streaming playback *---------------------------------------------------------------------------* *---------------------------------------------------------------------------*/ // Conversion constants #define BYTES_PER_FRAME 8 #define NIBBLES_PER_FRAME 16 // State enum { STRM_NONE = 0, STRM_INITIALIZED, STRM_STARTED, STRM_STOPPING, STRM_STOPPED, STRM_NUM_STATES }; // Streaming during playback static STRMInfo* activeStrm = NULL; // Functions static void getDataL ( void ); static void getDataR ( s32 result, DVDFileInfo* fileInfo ); static void gottenData ( s32 result, DVDFileInfo* fileInfo ); /*---------------------------------------------------------------------------* Name: STRMInit Description: Initialize streaming playback Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ static void STRMInit(void) { activeStrm = NULL; } /*---------------------------------------------------------------------------* Name: STRMGetWorkSize Description: Gets work size Arguments: None. Returns: Work size (bytes) *---------------------------------------------------------------------------*/ static s32 STRMGetWorkSize(void) { s32 size; size = sizeof(DSPADPCM); // DSPADPCM header (96 bytes) size += (STRM_BUFFER_SIZE * 2); // Streaming buffer size += (STRM_BUFFER_SIZE * 2); // Loop start buffer size *= 2; // Stereo size += 32; // Alignment (just in case) return size; } /*---------------------------------------------------------------------------* Name: STRMPrepare Description: Prepares for streaming playback. Arguments: strm: Pointer to STRMInfo. left: Data for left channel. right: Data for right channel. work: Pointer to allocated work region. Returns: STRM_ERR_NONE: OK. STRM_ERR_CANNOT_OPEN: Failed to open file STRM_ERR_CANNOT_READ: Failed to load file STRM_ERR_INVALID_STATE: STRMInfo is in use STRM_ERR_INVALID_DATA: Invalid data (loop is too short) *---------------------------------------------------------------------------*/ static STRM_ERR STRMPrepare(STRMInfo* strm, char* left, char* right, void* work) { u32 ptr; s32 length; DSPADPCM* dspHeaderL; DSPADPCM* dspHeaderR; s32 frameCA, frameLoopStart, frameLoopEnd; s32 deltaToLoopEnd1, deltaToLoopEnd2; s32 buffers1, buffers2; s32 strm1stAddr; u32 strm1stPrevPtr; s32 strm1stOffset; s32 strm1stCA; s32 strm1stTotalRead; s32 strm1stLoopEnd; s32 lsLength; s32 lsOffset; s32 lsLoopStart; s32 lsLoopEnd; s32 strmOffset; s32 strmTotalRead; s32 strmLoopEnd; // // Check the state // if (strm->state > STRM_INITIALIZED) { return STRM_ERR_INVALID_STATE; } // // Memory allocation // // ** Due to the relationship of pointer checks in STRMUpdate(), the streaming buffer must be allocated before allocating the loop start buffer. // // ptr = ((u32)work + 0x1f) & ~0x1f; strm->headerBufferL = (u8*)ptr; ptr += sizeof(DSPADPCM); strm->strmBufferL = (u8*)ptr; ptr += STRM_BUFFER_SIZE * 2; strm->lsBufferL = (u8*)ptr; ptr += STRM_BUFFER_SIZE * 2; strm->headerBufferR = (u8*)ptr; ptr += sizeof(DSPADPCM); strm->strmBufferR = (u8*)ptr; ptr += STRM_BUFFER_SIZE * 2; strm->lsBufferR = (u8*)ptr; ptr += STRM_BUFFER_SIZE * 2; // // Initialize members // // Filename strm->dataL = left; strm->dataR = right; // DVD access flag strm->accessDVD = FALSE; // Buffer management threshold (nibbles, absolute address) strm->strmBufferLHalfPtr = (u32)OSCachedToPhysical(strm->strmBufferL) + STRM_BUFFER_SIZE; strm->strmBufferLHalfPtr *= 2; // Bytes -> nibbles strm->lsBufferLTopPtr = (u32)OSCachedToPhysical(strm->lsBufferL); strm->lsBufferLTopPtr *= 2; // Bytes -> nibbles // // Open the file // if (!DVDOpen(left, &strm->finfoL)) { strm->state = STRM_NONE; return STRM_ERR_CANNOT_OPEN; } if (!DVDOpen(right, &strm->finfoR)) { DVDClose(&strm->finfoL); strm->state = STRM_NONE; return STRM_ERR_CANNOT_OPEN; } // // Load header // length = sizeof(DSPADPCM); if (length != DVDRead(&strm->finfoL, strm->headerBufferL, length, 0) || length != DVDRead(&strm->finfoR, strm->headerBufferR, length, 0)) { DVDClose(&strm->finfoL); DVDClose(&strm->finfoR); strm->state = STRM_NONE; return STRM_ERR_CANNOT_READ; } dspHeaderL = (DSPADPCM*)strm->headerBufferL; dspHeaderR = (DSPADPCM*)strm->headerBufferR; // // Set fixed values for parameters needed to load data (such as address and amount to load) // // ** The addresses are represented by the 'Lch' data. // // Convert each address to a frame offset frameCA = (s32)(dspHeaderL->ca / NIBBLES_PER_FRAME); frameLoopStart = (s32)(dspHeaderL->sa / NIBBLES_PER_FRAME); frameLoopEnd = (s32)(dspHeaderL->ea / NIBBLES_PER_FRAME); // Calculate initial position and amount of data that must be loaded from loop start to loop end deltaToLoopEnd1 = (frameLoopEnd - frameCA + 1) * BYTES_PER_FRAME; deltaToLoopEnd2 = (frameLoopEnd - frameLoopStart + 1) * BYTES_PER_FRAME; // Convert amount of data to load -> number of streaming buffers (single sided) buffers1 = deltaToLoopEnd1 / STRM_BUFFER_SIZE; buffers2 = deltaToLoopEnd2 / STRM_BUFFER_SIZE; if (buffers1 < 2 || buffers2 < 2) { DVDClose(&strm->finfoL); DVDClose(&strm->finfoR); strm->state = STRM_NONE; return STRM_ERR_INVALID_DATA; } #ifdef SHOW_INFO OSReport("ca %ld\n", dspHeaderL->ca); OSReport("sa %ld\n", dspHeaderL->sa); OSReport("es %ld\n", dspHeaderL->ea); OSReport("frameCA %ld\n", frameCA); OSReport("frameLoopStart %ld\n", frameLoopStart); OSReport("frameLoopEnd %ld\n", frameLoopEnd); OSReport("deltaToLoopEnd1 %ld\n", deltaToLoopEnd1); OSReport("deltaToLoopEnd2 %ld\n", deltaToLoopEnd2); OSReport("buffers1 %ld\n", buffers1); OSReport("buffers2 %ld\n", buffers2); #endif // Calculate parameters related to the streaming buffer (initial cycle) // - Load destination for loaded data: strm1stAddr // - Initial value of previous current pointer: strm1stPrevPtr // - Initial value of current address: strm1stCA // - Offset of load data: strm1stOffset // - Amount of data to load until loop end: strm1stTotalRead // - Loop end address: strm1stLoopEnd // // ** The load destination for the initial data (strm1stAddr) is adjusted so that the loop end (specifically, the frame including the loop end) will enter the second half of the streaming buffer or reach its boundary. // if (buffers1 & 0x1) { strm1stAddr = 0; strm1stPrevPtr = (u32)OSCachedToPhysical(strm->strmBufferL) + STRM_BUFFER_SIZE * 2; strm1stPrevPtr *= 2; // Bytes -> nibbles strm1stPrevPtr += -1; // Specify the last nibble } else { strm1stAddr = STRM_BUFFER_SIZE; strm1stPrevPtr = (u32)OSCachedToPhysical(strm->strmBufferL); strm1stPrevPtr *= 2; // Bytes -> nibbles strm1stPrevPtr += 2; // Skip frame header } strm1stOffset = frameCA * BYTES_PER_FRAME + (s32)sizeof(DSPADPCM); strm1stCA = strm1stAddr; strm1stCA *= 2; // Bytes -> nibbles strm1stCA += (s32)(dspHeaderL->ca % NIBBLES_PER_FRAME); strm1stTotalRead = deltaToLoopEnd1; strm1stLoopEnd = STRM_BUFFER_SIZE + strm1stTotalRead % STRM_BUFFER_SIZE - BYTES_PER_FRAME; strm1stLoopEnd *= 2; // Bytes -> nibbles strm1stLoopEnd += (s32)(dspHeaderL->ea % NIBBLES_PER_FRAME); // Calculate parameters related to loop start buffer // - Amount of data to load: lsLength // - Offset of load data: lsOffset // - Loop start address: lsLoopStart // - Loop end address: lsLoopEnd // // ** The amount of data to load into the loop start buffer (lsLength) is adjusted so that the loop end (specifically, the frame including the loop end) enters the second half of the streaming buffer or reaches its boundary. // if (buffers2 & 0x1) { lsLength = STRM_BUFFER_SIZE * 2; } else { lsLength = STRM_BUFFER_SIZE; } lsOffset = frameLoopStart * BYTES_PER_FRAME + (s32)sizeof(DSPADPCM); lsLoopStart = (s32)(dspHeaderL->sa % NIBBLES_PER_FRAME); lsLoopEnd = lsLength; lsLoopEnd *= 2; // Bytes -> nibbles lsLoopEnd += -1; // Specify the last nibble // Calculate parameters related to streaming buffer (these parameters applied after returning from loop start buffer) // - Offset of load data: strmOffset // - Amount of data to load until loop end: strmTotalRead // - Loop end address: strmLoopEnd strmOffset = lsOffset + lsLength; strmTotalRead = deltaToLoopEnd2 - lsLength; strmLoopEnd = STRM_BUFFER_SIZE + strmTotalRead % STRM_BUFFER_SIZE - BYTES_PER_FRAME; strmLoopEnd *= 2; // Bytes -> nibbles strmLoopEnd += (s32)(dspHeaderL->ea % NIBBLES_PER_FRAME); // Save strm->strm1stAddr = strm1stAddr; strm->strm1stPrevPtr = strm1stPrevPtr; strm->strm1stOffset = strm1stOffset; strm->strm1stCA = strm1stCA; strm->strm1stTotalRead = strm1stTotalRead; strm->strm1stLoopEnd = strm1stLoopEnd; strm->lsLoopStart = lsLoopStart; strm->lsLoopEnd = lsLoopEnd; strm->strmOffset = strmOffset; strm->strmTotalRead = strmTotalRead; strm->strmLoopEnd = strmLoopEnd; // // Pre-load data into loop start buffer // if (lsLength != DVDRead(&strm->finfoL, strm->lsBufferL, lsLength, lsOffset) || lsLength != DVDRead(&strm->finfoR, strm->lsBufferR, lsLength, lsOffset)) { DVDClose(&strm->finfoL); DVDClose(&strm->finfoR); strm->state = STRM_NONE; return STRM_ERR_CANNOT_READ; } // // Set loop context // strm->lsContextL.loop_pred_scale = dspHeaderL->lps; strm->lsContextL.loop_yn1 = dspHeaderL->lyn1; strm->lsContextL.loop_yn2 = dspHeaderL->lyn2; strm->lsContextR.loop_pred_scale = dspHeaderR->lps; strm->lsContextR.loop_yn1 = dspHeaderR->lyn1; strm->lsContextR.loop_yn2 = dspHeaderR->lyn2; // // Normal end // DVDClose(&strm->finfoL); DVDClose(&strm->finfoR); strm->state = STRM_INITIALIZED; return STRM_ERR_NONE; } /*---------------------------------------------------------------------------* Name: STRMStart Description: Start streaming playback Arguments: strm: Pointer to STRMInfo. Returns: STRM_ERR_NONE: OK. STRM_ERR_CANNOT_OPEN: Failed to open file STRM_ERR_CANNOT_READ: Failed to load file STRM_ERR_INVALID_STATE: STRMInfo not initialized, or is in use STRM_ERR_CANNOT_ACQUIRE_VOICE Failed to get AX voice STRM_ERR_BUSY: Other stream currently being played back *---------------------------------------------------------------------------*/ static STRM_ERR STRMStart(STRMInfo* strm) { s32 length; DSPADPCM* dspHeader; u32 loopStart; u32 loopEnd; u32 currAddr; AXPBADDR addr; AXPBADPCM adpcm; // // Check the state // if (strm->state != STRM_INITIALIZED) { return STRM_ERR_INVALID_STATE; } else if (activeStrm) { return STRM_ERR_BUSY; } // // Open the file // if (!DVDOpen(strm->dataL, &strm->finfoL)) { return STRM_ERR_CANNOT_OPEN; } if (!DVDOpen(strm->dataR, &strm->finfoR)) { DVDClose(&strm->finfoL); return STRM_ERR_CANNOT_OPEN; } // // Load initial data into streaming buffer // length = STRM_BUFFER_SIZE; if (length != DVDRead(&strm->finfoL, strm->strmBufferL + strm->strm1stAddr, length, strm->strm1stOffset) || length != DVDRead(&strm->finfoR, strm->strmBufferR + strm->strm1stAddr, length, strm->strm1stOffset)) { DVDClose(&strm->finfoL); DVDClose(&strm->finfoR); return STRM_ERR_CANNOT_READ; } strm->prevPtr = strm->strm1stPrevPtr; strm->readRests = strm->strm1stTotalRead - length; strm->readOffset = strm->strm1stOffset + length; // // Prepare for next jump (streaming -> loop start) // // Loop end (= the end of the streaming buffer) strm->nextLoopEndL = (u32)OSCachedToPhysical(strm->strmBufferL); strm->nextLoopEndL *= 2; // Bytes -> nibbles strm->nextLoopEndL += (u32)strm->strm1stLoopEnd; strm->nextLoopEndR = (u32)OSCachedToPhysical(strm->strmBufferR); strm->nextLoopEndR *= 2; // Bytes -> nibbles strm->nextLoopEndR += (u32)strm->strm1stLoopEnd; // Loop start (= beginning of the loop start buffer) strm->nextLoopStartL = (u32)OSCachedToPhysical(strm->lsBufferL); strm->nextLoopStartL *= 2; // Bytes -> nibbles strm->nextLoopStartL += (u32)strm->lsLoopStart; strm->nextLoopStartR = (u32)OSCachedToPhysical(strm->lsBufferR); strm->nextLoopStartR *= 2; // Bytes -> nibbles strm->nextLoopStartR += (u32)strm->lsLoopStart; // // Set AX // // Get AX voice if (!(strm->voiceL = AXAcquireVoice(15, NULL, 0)) || !(strm->voiceR = AXAcquireVoice(15, NULL, 0))) { DVDClose(&strm->finfoL); DVDClose(&strm->finfoR); return STRM_ERR_CANNOT_ACQUIRE_VOICE; } // Set left channel dspHeader = (DSPADPCM*)strm->headerBufferL; loopStart = (u32)OSCachedToPhysical(strm->strmBufferL) * 2; loopEnd = loopStart + (STRM_BUFFER_SIZE * 4) - 1; currAddr = loopStart + (u32)strm->strm1stCA; loopStart += 2; // Skip frame header MIXInitChannel(strm->voiceL, 0, 0, -904, -904, -904, 0, 127, 0); addr.loopFlag = AXPBADDR_LOOP_ON; addr.format = AX_PB_FORMAT_ADPCM; addr.loopAddressHi = (u16)(loopStart >> 16); addr.loopAddressLo = (u16)(loopStart & 0xFFFF); addr.endAddressHi = (u16)(loopEnd >> 16); addr.endAddressLo = (u16)(loopEnd & 0xFFFF); addr.currentAddressHi = (u16)(currAddr >> 16); addr.currentAddressLo = (u16)(currAddr & 0xFFFF); adpcm.a[0][0] = dspHeader->coef[0]; adpcm.a[0][1] = dspHeader->coef[1]; adpcm.a[1][0] = dspHeader->coef[2]; adpcm.a[1][1] = dspHeader->coef[3]; adpcm.a[2][0] = dspHeader->coef[4]; adpcm.a[2][1] = dspHeader->coef[5]; adpcm.a[3][0] = dspHeader->coef[6]; adpcm.a[3][1] = dspHeader->coef[7]; adpcm.a[4][0] = dspHeader->coef[8]; adpcm.a[4][1] = dspHeader->coef[9]; adpcm.a[5][0] = dspHeader->coef[10]; adpcm.a[5][1] = dspHeader->coef[11]; adpcm.a[6][0] = dspHeader->coef[12]; adpcm.a[6][1] = dspHeader->coef[13]; adpcm.a[7][0] = dspHeader->coef[14]; adpcm.a[7][1] = dspHeader->coef[15]; adpcm.gain = dspHeader->gain; adpcm.pred_scale = dspHeader->ps; adpcm.yn1 = dspHeader->yn1; adpcm.yn2 = dspHeader->yn2; AXSetVoiceType (strm->voiceL, AX_PB_TYPE_STREAM); AXSetVoiceAdpcm (strm->voiceL, &adpcm); AXSetVoiceAddr (strm->voiceL, &addr); AXSetVoiceSrcType(strm->voiceL, AX_SRC_TYPE_NONE); // Set right channel dspHeader = (DSPADPCM*)strm->headerBufferR; loopStart = (u32)OSCachedToPhysical(strm->strmBufferR) * 2; loopEnd = loopStart + (STRM_BUFFER_SIZE * 4) - 1; currAddr = loopStart + (u32)strm->strm1stCA; loopStart += 2; // Skip frame header MIXInitChannel(strm->voiceR, 0, 0, -904, -904, -904, 127, 127, 0); addr.loopFlag = AXPBADDR_LOOP_ON; addr.format = AX_PB_FORMAT_ADPCM; addr.loopAddressHi = (u16)(loopStart >> 16); addr.loopAddressLo = (u16)(loopStart & 0xFFFF); addr.endAddressHi = (u16)(loopEnd >> 16); addr.endAddressLo = (u16)(loopEnd & 0xFFFF); addr.currentAddressHi = (u16)(currAddr >> 16); addr.currentAddressLo = (u16)(currAddr & 0xFFFF); adpcm.a[0][0] = dspHeader->coef[0]; adpcm.a[0][1] = dspHeader->coef[1]; adpcm.a[1][0] = dspHeader->coef[2]; adpcm.a[1][1] = dspHeader->coef[3]; adpcm.a[2][0] = dspHeader->coef[4]; adpcm.a[2][1] = dspHeader->coef[5]; adpcm.a[3][0] = dspHeader->coef[6]; adpcm.a[3][1] = dspHeader->coef[7]; adpcm.a[4][0] = dspHeader->coef[8]; adpcm.a[4][1] = dspHeader->coef[9]; adpcm.a[5][0] = dspHeader->coef[10]; adpcm.a[5][1] = dspHeader->coef[11]; adpcm.a[6][0] = dspHeader->coef[12]; adpcm.a[6][1] = dspHeader->coef[13]; adpcm.a[7][0] = dspHeader->coef[14]; adpcm.a[7][1] = dspHeader->coef[15]; adpcm.gain = dspHeader->gain; adpcm.pred_scale = dspHeader->ps; adpcm.yn1 = dspHeader->yn1; adpcm.yn2 = dspHeader->yn2; AXSetVoiceType (strm->voiceR, AX_PB_TYPE_STREAM); AXSetVoiceAdpcm (strm->voiceR, &adpcm); AXSetVoiceAddr (strm->voiceR, &addr); AXSetVoiceSrcType(strm->voiceR, AX_SRC_TYPE_NONE); // // Start playback // AXSetVoiceState(strm->voiceL, AX_PB_STATE_RUN); AXSetVoiceState(strm->voiceR, AX_PB_STATE_RUN); // // Normal end // strm->state = STRM_STARTED; activeStrm = strm; return STRM_ERR_NONE; } /*---------------------------------------------------------------------------* Name: STRMStop Description: Stop streaming playback Arguments: strm: Pointer to STRMInfo. Returns: STRM_ERR_NONE: OK. STRM_ERR_INVALID_STATE: Specified streaming buffer is not currently playing *---------------------------------------------------------------------------*/ static STRM_ERR STRMStop(STRMInfo* strm) { if (strm->state != STRM_STARTED) { return STRM_ERR_INVALID_STATE; } MIXSetFader(strm->voiceL, -960); MIXSetFader(strm->voiceR, -960); strm->state = STRM_STOPPING; return STRM_ERR_NONE; } /*---------------------------------------------------------------------------* Name: STRMUpdate Description: Update streaming playback Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ static void STRMUpdate(void) { u32 currPtr; u32 halfPtr; u32 lsPtr; u32 prevPtr; u32 loopStartL, loopStartR; u32 loopEndL, loopEndR; if (!activeStrm) { return; } switch (activeStrm->state) { case STRM_NONE: case STRM_INITIALIZED: break; case STRM_STARTED: // // Get address // // ** The addresses are represented by the 'Lch' data. // currPtr = (u32)((activeStrm->voiceL->pb.addr.currentAddressHi << 16) | (activeStrm->voiceL->pb.addr.currentAddressLo)); halfPtr = activeStrm->strmBufferLHalfPtr; lsPtr = activeStrm->lsBufferLTopPtr; prevPtr = activeStrm->prevPtr; // // Switch between behavior depending on positions of currPtr and prevPtr // if (currPtr < prevPtr) { // When the current pointer has returned to the first half of the streaming buffer either from the second half of the streaming buffer or from the the loop start buffer... // // // // // // // // -> Check amount of data remaining if (!activeStrm->accessDVD) { if (activeStrm->readRests < STRM_BUFFER_SIZE) { // Amount of data remaining is less than STRM_BUFFER_SIZE // // -> Jump to loop start buffer while processing second half // // -> (1) Set next jump (streaming -> loop start) // (2) Prepare for jump after next (loop start -> streaming) // (3) Fill second half of streaming buffer with data // // (1) Set next jump (streaming -> loop start) // // Set loop end (= end of streaming buffer) AXSetVoiceEndAddr(activeStrm->voiceL, activeStrm->nextLoopEndL); AXSetVoiceEndAddr(activeStrm->voiceR, activeStrm->nextLoopEndR); // Set loop start (= beginning of loop start buffer) AXSetVoiceLoopAddr(activeStrm->voiceL, activeStrm->nextLoopStartL); AXSetVoiceLoopAddr(activeStrm->voiceR, activeStrm->nextLoopStartR); // Set loop context AXSetVoiceAdpcmLoop(activeStrm->voiceL, &activeStrm->lsContextL); AXSetVoiceAdpcmLoop(activeStrm->voiceR, &activeStrm->lsContextR); // Set loop type AXSetVoiceType(activeStrm->voiceL, AX_PB_TYPE_NORMAL); AXSetVoiceType(activeStrm->voiceR, AX_PB_TYPE_NORMAL); // // (2) Prepare for jump after next (loop start -> streaming) // // Loop end (= end of loop start buffer) activeStrm->nextLoopEndL = (u32)OSCachedToPhysical(activeStrm->lsBufferL); activeStrm->nextLoopEndL *= 2; // Bytes -> nibbles activeStrm->nextLoopEndL += (u32)activeStrm->lsLoopEnd; activeStrm->nextLoopEndR = (u32)OSCachedToPhysical(activeStrm->lsBufferR); activeStrm->nextLoopEndR *= 2; // Bytes -> nibbles activeStrm->nextLoopEndR += (u32)activeStrm->lsLoopEnd; // Loop start (= beginning of streaming buffer) activeStrm->nextLoopStartL = (u32)OSCachedToPhysical(activeStrm->strmBufferL); activeStrm->nextLoopStartL *= 2; // Bytes -> nibbles activeStrm->nextLoopStartL += 2; // Skip frame header activeStrm->nextLoopStartR = (u32)OSCachedToPhysical(activeStrm->strmBufferR); activeStrm->nextLoopStartR *= 2; // Bytes -> nibbles activeStrm->nextLoopStartR += 2; // Skip frame header // // (3) Fill second half of streaming buffer with remaining data // activeStrm->readSize = activeStrm->readRests; activeStrm->readRests = 0; } else { // Amount of data remaining is more than STRM_BUFFER_SIZE // // -> Do not jump to the loop start buffer while processing second half // // -> (1) Set the loop to be within the streaming buffer // (2) Fill second half of streaming buffer with data // // (1) Set the loop to be within the streaming buffer // loopStartL = (u32)OSCachedToPhysical(activeStrm->strmBufferL); loopEndL = loopStartL + STRM_BUFFER_SIZE * 2; loopStartL *= 2; // Bytes -> nibbles loopStartL += 2; // Skip frame header loopEndL *= 2; // Bytes -> nibbles loopEndL += -1; // Specify the last nibble loopStartR = (u32)OSCachedToPhysical(activeStrm->strmBufferR); loopEndR = loopStartR + STRM_BUFFER_SIZE * 2; loopStartR *= 2; // Bytes -> nibbles loopStartR += 2; // Skip frame header loopEndR *= 2; // Bytes -> nibbles loopEndR += -1; // Specify the last nibble // Set loop end (= position of end of the streaming buffer) AXSetVoiceEndAddr(activeStrm->voiceL, loopEndL); AXSetVoiceEndAddr(activeStrm->voiceR, loopEndR); // Set loop start (= position of beginning of streaming buffer) AXSetVoiceLoopAddr(activeStrm->voiceL, loopStartL); AXSetVoiceLoopAddr(activeStrm->voiceR, loopStartR); // No need to set loop context // No need to set loop type (already set) // // (2) Fill second half of streaming buffer with data // activeStrm->readSize = STRM_BUFFER_SIZE; activeStrm->readRests -= STRM_BUFFER_SIZE; } // Fill second half of streaming buffer with data if (activeStrm->readSize) { activeStrm->writeOffset = STRM_BUFFER_SIZE; getDataL(); activeStrm->accessDVD = TRUE; } activeStrm->prevPtr = currPtr; } } else if ((currPtr >= lsPtr) && (prevPtr < lsPtr)) { // When the current pointer has jumped to the loop start buffer from the streaming buffer... // // // // // // -> (1) Set next jump (loop start -> streaming) // (2) Prepare for jump after next (streaming -> loop start) // (3) Fill first half of streaming buffer with data if (!activeStrm->accessDVD) { // // (1) Set next jump (loop start -> streaming) // // Set loop end (= end of loop start buffer) AXSetVoiceEndAddr(activeStrm->voiceL, activeStrm->nextLoopEndL); AXSetVoiceEndAddr(activeStrm->voiceR, activeStrm->nextLoopEndR); // Set loop start (= beginning of streaming buffer) AXSetVoiceLoopAddr(activeStrm->voiceL, activeStrm->nextLoopStartL); AXSetVoiceLoopAddr(activeStrm->voiceR, activeStrm->nextLoopStartR); // Set loop context after getting data // Set loop type AXSetVoiceType(activeStrm->voiceL, AX_PB_TYPE_STREAM); AXSetVoiceType(activeStrm->voiceR, AX_PB_TYPE_STREAM); // // (2) Prepare for jump after next (streaming -> loop start) // // Loop end (= the end of the streaming buffer) activeStrm->nextLoopEndL = (u32)OSCachedToPhysical(activeStrm->strmBufferL); activeStrm->nextLoopEndL *= 2; // Bytes -> nibbles activeStrm->nextLoopEndL += (u32)activeStrm->strmLoopEnd; activeStrm->nextLoopEndR = (u32)OSCachedToPhysical(activeStrm->strmBufferR); activeStrm->nextLoopEndR *= 2; // Bytes -> nibbles activeStrm->nextLoopEndR += (u32)activeStrm->strmLoopEnd; // Loop start (= beginning of the loop start buffer) activeStrm->nextLoopStartL = (u32)OSCachedToPhysical(activeStrm->lsBufferL); activeStrm->nextLoopStartL *= 2; // Bytes -> nibbles activeStrm->nextLoopStartL += (u32)activeStrm->lsLoopStart; activeStrm->nextLoopStartR = (u32)OSCachedToPhysical(activeStrm->lsBufferR); activeStrm->nextLoopStartR *= 2; // Bytes -> nibbles activeStrm->nextLoopStartR += (u32)activeStrm->lsLoopStart; // // (3) Fill first half of streaming buffer with data // activeStrm->readSize = STRM_BUFFER_SIZE; activeStrm->readRests = activeStrm->strmTotalRead - STRM_BUFFER_SIZE; activeStrm->readOffset = activeStrm->strmOffset; activeStrm->writeOffset = 0; getDataL(); activeStrm->accessDVD = TRUE; activeStrm->prevPtr = currPtr; } } else if ((currPtr >= halfPtr) && (prevPtr < halfPtr)) { // When the current pointer has jumped to the second half of the streaming buffer from the first half of the streaming buffer... // // // // // // -> Check amount of data remaining if (!activeStrm->accessDVD) { if (activeStrm->readRests > 0) { // There is data remaining // // -> Do not jump to the loop start buffer while processing second half // No need to set loop end // No need to set loop start // Set loop context after getting data // No need to set loop type // // Fill first half of streaming buffer with data // activeStrm->readSize = STRM_BUFFER_SIZE; activeStrm->readRests -= STRM_BUFFER_SIZE; activeStrm->writeOffset = 0; getDataL(); activeStrm->accessDVD = TRUE; } else { // There is no data remaining // // -> Jump to loop start buffer while processing second half // Does not do anything } activeStrm->prevPtr = currPtr; } } else { activeStrm->prevPtr = currPtr; } break; case STRM_STOPPING: // Lch MIXReleaseChannel(activeStrm->voiceL); AXFreeVoice(activeStrm->voiceL); activeStrm->voiceL = NULL; // Rch MIXReleaseChannel(activeStrm->voiceR); AXFreeVoice(activeStrm->voiceR); activeStrm->voiceR = NULL; activeStrm->state = STRM_STOPPED; break; case STRM_STOPPED: if (!activeStrm->accessDVD) { DVDClose(&activeStrm->finfoL); DVDClose(&activeStrm->finfoR); activeStrm->state = STRM_INITIALIZED; activeStrm = NULL; } break; } } /*---------------------------------------------------------------------------* Name: getDataL Description: Loads stream data for Lch. Arguments: None. Returns: None. *---------------------------------------------------------------------------*/ static void getDataL(void) { s32 length; // Regarding loads: // (1) Their size is in units of a single side of the streaming buffer (STRM_BUFFER_SIZE). // (2) However, right before jumping to the loop start buffer, it's possible that an entire single side will not be filled. // (3) Even in case (2), size is a multiple of the frame length (= 8 bytes). // (4) File offset is the frame alignment (= 8 bytes). length = (activeStrm->readSize + 0x1f) & ~0x1f; DVDReadAsync (&activeStrm->finfoL, activeStrm->strmBufferL + activeStrm->writeOffset, length, activeStrm->readOffset, getDataR); } /*---------------------------------------------------------------------------* Name: getDataR Description: Loads stream data for Rch. Arguments: Not used. Returns: None. *---------------------------------------------------------------------------*/ static void getDataR(s32 result, DVDFileInfo* fileInfo) { #pragma unused(result) #pragma unused(fileInfo) s32 length; AXPBADPCMLOOP loop; // Regarding loads: // (1) Their size is in units of a single side of the streaming buffer (STRM_BUFFER_SIZE). // (2) However, right before jumping to the loop start buffer, it's possible that an entire single side will not be filled. // (3) Even in case (2), size is a multiple of the frame length (= 8 bytes). // (4) File offset is the frame alignment (= 8 bytes). length = (activeStrm->readSize + 0x1f) & ~0x1f; DVDReadAsync (&activeStrm->finfoR, activeStrm->strmBufferR + activeStrm->writeOffset, length, activeStrm->readOffset, gottenData); activeStrm->readOffset += activeStrm->readSize; // You must update the loop context if you fill the first half of the streaming buffer with data. // However, since this is an AX_PB_TYPE_STREAM loop, the only setting is 'ps'. if (activeStrm->writeOffset == 0 && activeStrm->state == STRM_STARTED) { loop.loop_pred_scale = (u16)(*((u8*)(activeStrm->strmBufferL))); AXSetVoiceAdpcmLoop(activeStrm->voiceL, &loop); } } /*---------------------------------------------------------------------------* Name: gottenData Description: Function called when DVD read (of L&R ch) is done. Arguments: Not used. Returns: None. *---------------------------------------------------------------------------*/ static void gottenData(s32 result, DVDFileInfo* fileInfo) { #pragma unused(result) #pragma unused(fileInfo) AXPBADPCMLOOP loop; // You must update the loop context if you fill the first half of the streaming buffer with data. // However, since this is an AX_PB_TYPE_STREAM loop, the only setting is 'ps'. if (activeStrm->writeOffset == 0 && activeStrm->state == STRM_STARTED) { loop.loop_pred_scale = (u16)(*((u8*)(activeStrm->strmBufferR))); AXSetVoiceAdpcmLoop(activeStrm->voiceR, &loop); } activeStrm->accessDVD = FALSE; }