/*---------------------------------------------------------------------------* Project: Revolution AX simple demo File: axsimple.c Copyright (C)1998-2006 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: axsimple.c,v $ Revision 1.4 02/03/2006 12:12:08 aka Revised comment. Revision 1.3 02/02/2006 07:56:31 aka Modified using MEM functions instead of OSAlloc()/OSFree(). Revision 1.2 02/01/2006 05:42:37 aka Added #ifndef(#ifdef) HOLLYWOOD_REV - #else - #endif. Revision 1.1 11/04/2005 05:01:39 aka Imported from dolphin tree. 11 03/04/04 13:25:00 Suzuki remove the definition of DSPADPCM 10 12/04/02 6:23p Dante Fixed pred_scale, yn1, & yn2 references for AXPBADPCM structure. 9 02/11/20 7:39 Dante Added looped sample support, and fixed dropped sample callback support 8 02/11/13 11:06 Dante Bug fix: GetDSPADPCMDataSize32B's conversion from nibbles to bytes must occur after round up. Clean up (removed unused variables). 7 02/11/13 4:03 Dante Fixed GetDSPADPCMDataSize32B to return bytes not nibbles. Changed OSReports to reflect size. 6 02/11/12 9:52 Dante Removed redundant SRC commands 5 02/11/09 12:37 Dante Additions from SDK 2002-Dec-05 Patch1 4 02/11/09 12:34 Dante Added support for non-native sample rate ADPCM files and a printed intro. 3 02/10/30 6:31 Dante Set the loop address for one shot samples to the zero buffer $NoKeywords: $ *---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* This program loads and initializes ADPCM samples. The samples were created using DSPADPCM.exe. The files are expected to have a 96 byte header. *---------------------------------------------------------------------------*/ #include #include #ifdef HOLLYWOOD_REV #include #endif // User defines #define NUM_SAMPLES 10 #define NUM_BUTTON_MAPS 11 // Filenames for samples to be played char SampleFiles [NUM_SAMPLES][256] = { "axdemo/simple/snare.dsp", "axdemo/simple/tom.dsp", "axdemo/simple/cymbal.dsp", "axdemo/simple/ride.dsp", "axdemo/simple/cowbell.dsp", "axdemo/simple/kick.dsp", "axdemo/simple/bongo1.dsp", "axdemo/simple/bongo2.dsp", "axdemo/simple/bongo3.dsp", "axdemo/simple/bongo4.dsp", }; // Button mappings for samples {Button, index in file table} u32 ButtonMap [NUM_BUTTON_MAPS][2] = { {PAD_BUTTON_A, 0}, {PAD_BUTTON_B, 1}, {PAD_BUTTON_X, 2}, {PAD_BUTTON_Y, 3}, {PAD_TRIGGER_Z, 4}, {PAD_TRIGGER_L, 5}, {PAD_TRIGGER_R, 5}, {PAD_BUTTON_DOWN, 6}, {PAD_BUTTON_UP, 7}, {PAD_BUTTON_LEFT, 8}, {PAD_BUTTON_RIGHT, 9} }; // This demo uses a very simple mixing paradigm. All sounds are played at // a volume of 1.0 (0x8000). Please see the MIX library for a more // comprehensive mixing library #ifndef HOLLYWOOD_REV AXPBMIX g_mix = { 0x8000, // volume left 0x0000, // volume ramp left 0x8000, // volume right 0x0000, // volume ramp right 0x0000, // volume AUX A left 0x0000, // volume ramp AUX A left 0x0000, // volume AUX A right 0x0000, // volume ramp AUX A right 0x0000, // volume AUX B left 0x0000, // volume ramp AUX B left 0x0000, // volume AUX B right 0x0000, // volume ramp AUX B right 0x0000, // volume AUX B surround 0x0000, // volume ramp AUX B surround 0x0000, // volume surround 0x0000, // volume ramp surround 0x0000, // volume AUX A surround 0x0000, // volume ramp AUX A surround }; #else AXPBMIX g_mix = { 0x8000, // volume left 0x0000, // volume ramp left 0x8000, // volume right 0x0000, // volume ramp right 0x0000, // volume AUX A left 0x0000, // volume ramp AUX A left 0x0000, // volume AUX A right 0x0000, // volume ramp AUX A right 0x0000, // volume AUX B left 0x0000, // volume ramp AUX B left 0x0000, // volume AUX B right 0x0000, // volume ramp AUX B right 0x0000, // volume AUX C left 0x0000, // volume ramp AUX C left 0x0000, // volume AUX C right 0x0000, // volume ramp AUX C right 0x0000, // volume surround 0x0000, // volume ramp surround 0x0000, // volume AUX A surround 0x0000, // volume ramp AUX A surround 0x0000, // volume AUX B surround 0x0000, // volume ramp AUX B surround 0x0000, // volume AUX C surround 0x0000, // volume ramp AUX C surround }; #endif AXPBVE g_ve = { 0x8000, // volume at start of frame, 0x8000 = 1.0 0 // signed per sample delta (160 samples per frame) }; // User Structures to keep track of data #ifndef HOLLYWOOD_REV typedef struct { void *mramAddr; void *aramAddr; u32 aramLength; } SampleInfo; #else typedef struct { void *mramAddr; } SampleInfo; #endif typedef struct { AXVPB *voice; u32 state; } VoiceInfo; // Voice Defines #define VOICE_PRIO_HIGH 31 #define VOICE_PRIO_MED 15 #define VOICE_PRIO_LOW 1 #define VOICE_STATE_STOPPED 0 #define VOICE_STATE_START 1 #define VOICE_STATE_STARTED 2 #define VOICE_STATE_PLAYING 3 #define VOICE_STATE_STOP 4 #ifdef HOLLYWOOD_REV // Exp Heap static MEMHeapHandle hExpHeap; #endif // zero buffer size #define ZEROBUFFER_BYTES 256 #ifdef HOLLYWOOD_REV static u8 *zeroBuffer; #endif // Utility Macro Functions #define RoundUp64(x) (((u32)(x) + 64 - 1) & ~(64 - 1)) #define Bytes2Nibbles(n) (n << 1) #define Nibbles2Bytes(n) (n >> 1) #define GetDSPADPCMDataAddress(a) ((void*)((u32)a + sizeof(DSPADPCM))) #define GetDSPADPCMDataSize32B(a) (RoundUp64(((DSPADPCM*)a)->num_adpcm_nibbles) >> 1) #define GetVoiceCurrentAddr32(v) (*(u32 *)(&((v)->pb.addr.currentAddressHi))) #define GetVoiceLoopAddr32(v) (*(u32 *)(&((v)->pb.addr.loopAddressHi))) #define GetVoiceEndAddr32(v) (*(u32 *)(&((v)->pb.addr.endAddressHi))) #ifndef HOLLYWOOD_REV // Pointer to the base address of ARAM. Some of ARAM is reserved. // Note that the zero buffer is located at this address in this demo. void* ARAMBaseAddress; #endif // Sample Info static SampleInfo Samples[NUM_SAMPLES]; // AX Voice info static VoiceInfo Voices[AX_MAX_VOICES]; #ifndef HOLLYWOOD_REV // Global Variable used to wait for ARAM DMA callback static volatile BOOL IsVoiceLoading = FALSE; #endif // function Prototypes static void * LoadFileIntoRam(char *path); static void AudioFrameCallback(void); #ifndef HOLLYWOOD_REV static void AramRequestCallback(u32 task); static void LoadToARAM(void *pMRAMStart, void* pARAMStart, u32 length); static AXVPB* AquireVoiceADPCM(void *pDSPADPCMData, void* pARAMStart); #else static AXVPB* AquireVoiceADPCM(void *pDSPADPCMData); #endif static void LoadSamples(void); static void PlaySample(SampleInfo *sample); static void VoiceCallback(void * voiceIn); static void PrintIntro(void); /*---------------------------------------------------------------------------* *---------------------------------------------------------------------------*/ void main () { u32 i; u32 button; #ifdef HOLLYWOOD_REV void *arenaMem2Lo; void *arenaMem2Hi; #endif // Initialize OS & Audio DEMOInit(NULL); DEMOPadInit(); #ifndef HOLLYWOOD_REV ARInit(NULL, 0); ARQInit(); #else // initialize Exp Heap on MEM2 arenaMem2Lo = OSGetMEM2ArenaLo(); arenaMem2Hi = OSGetMEM2ArenaHi(); hExpHeap = MEMCreateExpHeap(arenaMem2Lo, (u32)arenaMem2Hi - (u32) arenaMem2Lo); #endif AIInit(NULL); AXInit(); #ifndef HOLLYWOOD_REV // Get Base ARAM address. ARAMBaseAddress = (void*) ARGetBaseAddress(); #endif // Load Voice data into MRAM LoadSamples(); // Register Callback with AX for audio processing AXRegisterCallback(&AudioFrameCallback); // Print Intro PrintIntro(); // Spin while (1) { VIWaitForRetrace(); // User Input DEMOPadRead(); button = DEMOPadGetButtonDown(0); // Stop all sounds when START/PAUSE is pressed if (button & PAD_BUTTON_START) { // Stop all voices for (i = 0; i < AX_MAX_VOICES; i++) { Voices[i].state = VOICE_STATE_STOP; } continue; } // Use Button map to start sounds for (i = 0; i < NUM_BUTTON_MAPS; i++) { if (button & ButtonMap[i][0]) { PlaySample(&Samples[ButtonMap[i][1]]); } } } } /*---------------------------------------------------------------------------* Name: LoadSamples Description: Loads ADPCM files into Main Memory (Header + Data) and ARAM (Data) Arguments: none Returns: none *---------------------------------------------------------------------------*/ #ifndef HOLLYWOOD_REV static void LoadSamples(void) { u32 i; u32 offset = 0; // Variable that stores the offsets from base ARAM address u8 *zeroBuffer; // Address for temporary zero buffer void *pMRAMStart; // Create zero buffer zeroBuffer = (u8*)OSAlloc(ZEROBUFFER_BYTES); for (i = 0; i < ZEROBUFFER_BYTES; i++) { zeroBuffer[i] = 0; } // Load Zero Buffer into ARAM LoadToARAM(zeroBuffer, ARAMBaseAddress, ZEROBUFFER_BYTES); // Store offset offset = ZEROBUFFER_BYTES; OSFree(zeroBuffer); // Load samples for (i = 0; i < NUM_SAMPLES; i++) { // Load ADPCM file into MRAM (96 byte header included) Samples[i].mramAddr = LoadFileIntoRam(SampleFiles[i]); // Sanity Check if (Samples[i].mramAddr == NULL) { OSReport("WARNING! Sample %d not loaded\n", i); continue; } // Specify ARAM address (Adding offset from previous files) Samples[i].aramAddr = (void *)((u32)ARAMBaseAddress + offset); // Length of data (Rounded to 32B) Samples[i].aramLength = GetDSPADPCMDataSize32B(Samples[i].mramAddr); // The Start address in memory (after 96 Byte header) pMRAMStart = GetDSPADPCMDataAddress(Samples[i].mramAddr); // Store Length of sample (extracted from header) offset += Samples[i].aramLength; // Load Sample into ARAM LoadToARAM(pMRAMStart, Samples[i].aramAddr, Samples[i].aramLength); OSReport("axsimple: Loading %s at ARAM 0x%08x [%d bytes]\n", SampleFiles[i], Samples[i].aramAddr, Samples[i].aramLength); } } #else static void LoadSamples(void) { u32 i; // Zero Buffer zeroBuffer = MEMAllocFromExpHeapEx(hExpHeap, ZEROBUFFER_BYTES, 8); memset(zeroBuffer, 0, ZEROBUFFER_BYTES); DCFlushRange(zeroBuffer, ZEROBUFFER_BYTES); // Load samples for (i = 0; i < NUM_SAMPLES; i++) { // Load ADPCM file into MRAM (96 byte header included) Samples[i].mramAddr = LoadFileIntoRam(SampleFiles[i]); // Sanity Check if (Samples[i].mramAddr == NULL) { OSReport("WARNING! Sample %d not loaded\n", i); continue; } } } #endif /*---------------------------------------------------------------------------* Name: PlaySample Description: Utility function that will play a sample Arguments: sample Pointer to the sample information Returns: pointer to the allocated voice *---------------------------------------------------------------------------*/ static void PlaySample(SampleInfo *sample) { AXVPB *voice; if (sample->mramAddr == NULL) { OSReport("WARNING! Sample not loaded!\n"); return; } // Acquire Voice and start #ifndef HOLLYWOOD_REV voice = AquireVoiceADPCM(sample->mramAddr, sample->aramAddr); #else voice = AquireVoiceADPCM(sample->mramAddr); #endif if (voice == NULL) { OSReport("WARNING: Ran out of voices!\n"); return; } Voices[voice->index].voice = voice; Voices[voice->index].state = VOICE_STATE_START; } /*---------------------------------------------------------------------------* Name: AudioFrameCallback Description: Callback that process audio data per 5ms audio frame. In this case, it stops, resets, and starts a voice. Arguments: none Returns: none *---------------------------------------------------------------------------*/ static void AudioFrameCallback(void) { u32 i; BOOL bStopFlag = FALSE; // Monitor each voice and process states. This must be done in the // callback for (i = 0; i < AX_MAX_VOICES; i++) { // Skip NULL entries if (Voices[i].voice == NULL) continue; switch (Voices[i].state) { case VOICE_STATE_STOPPED: break; case VOICE_STATE_START: // Start the voice AXSetVoiceState(Voices[i].voice, AX_PB_STATE_RUN); Voices[i].state = VOICE_STATE_STARTED; break; case VOICE_STATE_STARTED: // Skip a frame Voices[i].state = VOICE_STATE_PLAYING; break; case VOICE_STATE_PLAYING: // Check to see if the voice is finished, if so, stop it if (Voices[i].voice->pb.state == AX_PB_STATE_STOP) bStopFlag = TRUE; break; case VOICE_STATE_STOP: // Force a voice to stop bStopFlag = TRUE; break; } // A voice must be stopped if (bStopFlag) { AXSetVoiceState(Voices[i].voice, AX_PB_STATE_STOP); AXFreeVoice(Voices[i].voice); Voices[i].voice = NULL; Voices[i].state = VOICE_STATE_STOPPED; bStopFlag = FALSE; } } } #ifndef HOLLYWOOD_REV /*---------------------------------------------------------------------------* Name: AramRequestCallback Description: Callback for ARAM DMA completion. Zero's a wait variable Arguments: unused Returns: none *---------------------------------------------------------------------------*/ static void AramRequestCallback(u32 task) { #pragma unused(task) IsVoiceLoading = FALSE; } #endif #ifndef HOLLYWOOD_REV /*---------------------------------------------------------------------------* Name: LoadToARAM Description: Loads voice data into ARAM sequentially. Arguments: pMRAMStart MRAM Start address pARAMStart ARAM Start address length length of data Returns: pointer to the allocated voice *---------------------------------------------------------------------------*/ static void LoadToARAM(void *pMRAMStart, void* pARAMStart, u32 length) { ARQRequest taskM; ARQPostRequest( &taskM, 0, ARQ_TYPE_MRAM_TO_ARAM, ARQ_PRIORITY_HIGH, (u32)pMRAMStart, (u32)pARAMStart, length, AramRequestCallback ); // Callback will release IsVoiceLoading = TRUE; while (IsVoiceLoading) { } } #endif /*---------------------------------------------------------------------------* Name: VoiceCallback Description: Callback for when a voice is dropped. Arguments: unused Returns: none *---------------------------------------------------------------------------*/ static void VoiceCallback(void * voiceIn) { AXVPB *voice = (AXVPB*)voiceIn; // Note: Voice is auto-magically stopped by AX layer when dropped // Application clean-up Voices[voice->index].voice = NULL; Voices[voice->index].state = VOICE_STATE_STOPPED; } /*---------------------------------------------------------------------------* Name: AquireVoiceADPCM Description: Parses the ADPCM header, sets voice parameters Arguments: pDSPADPCMData Pointer to the ADPCM data in MRAM pARAMStart ARAM Start address Returns: pointer to the allocated voice or NULL *---------------------------------------------------------------------------*/ #ifndef HOLLYWOOD_REV static AXVPB* AquireVoiceADPCM(void *pDSPADPCMData, void* pARAMStart) #else static AXVPB* AquireVoiceADPCM(void *pDSPADPCMData) #endif { DSPADPCM *ps = (DSPADPCM*)pDSPADPCMData; AXPBADDR addr; AXPBADPCM adpcm; AXPBSRC src; AXPBADPCMLOOP adpcmLoop; AXVPB* voice; u32 srcBits; #ifndef HOLLYWOOD_REV u32 aramAddress; #else u32 mramAddress; u32 pMRAMStart; #endif // Allocate a voice for use voice = AXAcquireVoice(VOICE_PRIO_MED, VoiceCallback, 0); if (voice == NULL) { OSReport("WARNING! Voice Acquisition failed!\n"); return NULL; } // Fill AXPBADDR structure // All the following addresses are in nibbles addr.loopFlag = ps->loop_flag; addr.format = ps->format; #ifndef HOLLYWOOD_REV // Support for looping if (addr.loopFlag) { adpcmLoop.loop_pred_scale = ps->lps; adpcmLoop.loop_yn1 = ps->lyn1; adpcmLoop.loop_yn2 = ps->lyn2; aramAddress = (ps->sa + Bytes2Nibbles((u32)pARAMStart)); } else { // The loop address for one-shot samples should be the zero buffer // The "+ 2" is to avoid the frame header aramAddress = Bytes2Nibbles((u32)ARAMBaseAddress) + 2; } addr.loopAddressHi = (u16)(aramAddress >> 16); addr.loopAddressLo = (u16)(aramAddress & 0xFFFF); aramAddress = (ps->ea + Bytes2Nibbles((u32)pARAMStart)); addr.endAddressHi = (u16)(aramAddress >> 16); addr.endAddressLo = (u16)(aramAddress & 0xFFFF); aramAddress = (ps->ca + Bytes2Nibbles((u32)pARAMStart)); addr.currentAddressHi = (u16)(aramAddress >> 16); addr.currentAddressLo = (u16)(aramAddress & 0xFFFF); #else pMRAMStart = OSCachedToPhysical(GetDSPADPCMDataAddress(pDSPADPCMData)); // Support for looping if (addr.loopFlag) { adpcmLoop.loop_pred_scale = ps->lps; adpcmLoop.loop_yn1 = ps->lyn1; adpcmLoop.loop_yn2 = ps->lyn2; mramAddress = (ps->sa + Bytes2Nibbles(pMRAMStart)); } else { // The loop address for one-shot samples should be the zero buffer // The "+ 2" is to avoid the frame header mramAddress = Bytes2Nibbles(OSCachedToPhysical(zeroBuffer)) + 2; } addr.loopAddressHi = (u16)(mramAddress >> 16); addr.loopAddressLo = (u16)(mramAddress & 0xFFFF); mramAddress = (ps->ea + Bytes2Nibbles(pMRAMStart)); addr.endAddressHi = (u16)(mramAddress >> 16); addr.endAddressLo = (u16)(mramAddress & 0xFFFF); mramAddress = (ps->ca + Bytes2Nibbles(pMRAMStart)); addr.currentAddressHi = (u16)(mramAddress >> 16); addr.currentAddressLo = (u16)(mramAddress & 0xFFFF); #endif // Fill AXPBADPCM structure adpcm.a[0][0] = ps->coef[0]; adpcm.a[0][1] = ps->coef[1]; adpcm.a[1][0] = ps->coef[2]; adpcm.a[1][1] = ps->coef[3]; adpcm.a[2][0] = ps->coef[4]; adpcm.a[2][1] = ps->coef[5]; adpcm.a[3][0] = ps->coef[6]; adpcm.a[3][1] = ps->coef[7]; adpcm.a[4][0] = ps->coef[8]; adpcm.a[4][1] = ps->coef[9]; adpcm.a[5][0] = ps->coef[10]; adpcm.a[5][1] = ps->coef[11]; adpcm.a[6][0] = ps->coef[12]; adpcm.a[6][1] = ps->coef[13]; adpcm.a[7][0] = ps->coef[14]; adpcm.a[7][1] = ps->coef[15]; adpcm.gain = ps->gain; adpcm.pred_scale = ps->ps; adpcm.yn1 = ps->yn1; adpcm.yn2 = ps->yn2; // Fill AXPBSRC structure for proper sample rates srcBits = (u32)(0x00010000 * ((f32)ps->sample_rate / AX_IN_SAMPLES_PER_SEC)); src.ratioHi = (u16)(srcBits >> 16); src.ratioLo = (u16)(srcBits & 0xFFFF); src.currentAddressFrac = 0; src.last_samples[0] = 0; src.last_samples[1] = 0; src.last_samples[2] = 0; src.last_samples[3] = 0; // Set voice type AXSetVoiceType(voice, AX_PB_TYPE_NORMAL); // Set Address and ADPCM information from header AXSetVoiceAddr(voice, &addr); AXSetVoiceAdpcm(voice, &adpcm); AXSetVoiceAdpcmLoop(voice, &adpcmLoop); // Set simple volumes AXSetVoiceMix(voice, &g_mix); AXSetVoiceVe(voice, &g_ve); // Set sample rate AXSetVoiceSrcType(voice, AX_SRC_TYPE_LINEAR); AXSetVoiceSrc(voice, &src); return voice; } /*---------------------------------------------------------------------------* Name: LoadFileIntoRam Description: Loads a file into memory. Memory is allocated. Arguments: path File to load into main memory Returns: pointer to file in main memory or NULL if not opened *---------------------------------------------------------------------------*/ static void * LoadFileIntoRam(char *path) { DVDFileInfo handle; u32 round_length; s32 read_length; void *buffer; // Open File if (!DVDOpen(path, &handle)) { OSReport("WARNING! Failed to open %s\n", path); return NULL; } // Make sure file length is not 0 if (DVDGetLength(&handle) == 0) { OSReport("WARNING! File length is 0\n"); return NULL; } round_length = OSRoundUp32B(DVDGetLength(&handle)); #ifndef HOLLYWOOD_REV buffer = OSAlloc(round_length); #else buffer = MEMAllocFromExpHeapEx(hExpHeap, round_length, 32); #endif // Make sure we got a buffer if (buffer == NULL) { OSReport("WARNING! Unable to allocate buffer\n"); return NULL; } // Read Files read_length = DVDRead(&handle, buffer, (s32)(round_length), 0); // Make sure we read the file correctly if (read_length <= 0) { OSReport("WARNING! File lenght is wrong\n"); return NULL; } return buffer; } /*---------------------------------------------------------------------------* Name: PrintIntro Description: Prints Intro to debug output Arguments: none Returns: none *---------------------------------------------------------------------------*/ static void PrintIntro(void) { char button[256]; u32 i; OSReport("\n\n****************************************************\n"); OSReport(" AXSimple - Plays DSPADPCM.exe files\n"); OSReport("****************************************************\n"); for (i = 0; i < NUM_BUTTON_MAPS; i++) { switch (ButtonMap[i][0]) { case PAD_BUTTON_A: sprintf(button, "A Button"); break; case PAD_BUTTON_B: sprintf(button, "B Button"); break; case PAD_BUTTON_X: sprintf(button, "X Button"); break; case PAD_BUTTON_Y: sprintf(button, "Y Button"); break; case PAD_TRIGGER_Z: sprintf(button, "Z Button"); break; case PAD_TRIGGER_L: sprintf(button, "L Button"); break; case PAD_TRIGGER_R: sprintf(button, "R Button"); break; case PAD_BUTTON_DOWN: sprintf(button, "+Control Pad Down"); break; case PAD_BUTTON_UP: sprintf(button, "+Control Pad Up"); break; case PAD_BUTTON_LEFT: sprintf(button, "+Control Pad Left"); break; case PAD_BUTTON_RIGHT: sprintf(button, "+Control Pad Right"); break; } OSReport("%s => %s\n", button, SampleFiles[ButtonMap[i][1]]); } OSReport("Start/Pause => Stop all sounds\n"); OSReport("****************************************************\n"); }