/*---------------------------------------------------------------------------* Project: Revolution WPAD AX demo File: wpad_axdemo.c Copyright (C)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: wpad_axdemo.c,v $ Revision 1.18.6.1 2008/08/27 04:50:20 tojo Modified the error handling of controller data. Revision 1.18 2007/07/10 12:14:46 tojo (none) Revision 1.17 2007/05/10 04:17:34 aka Revised to use AXRmtGetSamplesLeft(). Revision 1.16 2007/04/23 09:16:08 tojo (none) Revision 1.15 2007/02/13 04:35:17 tojo Changed the button assignment of MUTE/MUTE OFF. Revision 1.14 2007/02/09 07:22:07 tojo Modifed to follow the guideline. Revision 1.13 2006/11/21 08:38:15 aka Removed the zero buffer. Revision 1.12 2006/10/23 02:16:23 aka Changed from AXInit() to AXInitSpecifyMem(). Changed from MIXInit() to MIXInitSpecifyMem(). Changed from SYNInit() to SYNInitSpecifyMem(). Revision 1.11 2006/10/23 01:17:30 tojo Called WPADSetConnectCallback before initialization complete. Revision 1.10 2006/10/11 03:03:50 aka Revised AXInit(), MIXInit() and SYNInit(). Revision 1.9 2006/09/19 06:35:07 tojo (none) Revision 1.8 2006/09/18 12:13:54 tojo (none) Revision 1.7 2006/08/15 08:46:04 tojo (none) Revision 1.6 2006/08/11 09:33:13 tojo (none) Revision 1.5 2006/08/10 00:41:21 aka Changed comments. Revision 1.4 2006/08/03 13:32:55 tojo Removed old apis. Revision 1.3 2006/07/24 02:01:07 aka Changed in relation to modification of AX. Revision 1.2 2006/07/21 03:00:11 tojo Implemented gui. Revision 1.1 2006/07/19 09:31:24 tojo Initial check in. $NoKeywords: $ *---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* This program plays audio streaming on Wii remote controller using AX. The audio streaming is encoded by WENC in runtime. *---------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include // User defines #define NUM_SAMPLES 8 #define NUM_BUTTON_MAPS 8 // 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/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] = { {WPAD_BUTTON_A, 0}, {WPAD_BUTTON_B, 1}, {WPAD_BUTTON_1, 2}, {WPAD_BUTTON_2, 3}, {WPAD_BUTTON_DOWN, 4}, {WPAD_BUTTON_UP, 5}, {WPAD_BUTTON_LEFT, 6}, {WPAD_BUTTON_RIGHT, 7} }; // 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 AXPBMIX g_mix = { 0x0000, // volume left 0x0000, // volume ramp left 0x0000, // 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 }; AXPBVE g_ve = { 0x8000, // volume at start of frame, 0x8000 = 1.0 0 // signed per sample delta (160 samples per frame) }; AXPBRMTMIX g_rmix = { 0x0000, // volume main0 0x0000, // volume ramp main0 0x0000, // volume aux0 0x0000, // volume ramp aux0 0x0000, // volume main1 0x0000, // volume ramp main1 0x0000, // volume aux1 0x0000, // volume ramp aux1 0x0000, // volume main2 0x0000, // volume ramp main2 0x0000, // volume aux2 0x0000, // volume ramp aux2 0x0000, // volume main3 0x0000, // volume ramp main3 0x0000, // volume aux3 0x0000 // volume ramp aux3 }; // User Structures to keep track of data typedef struct { void *mramAddr; } SampleInfo; 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 // Exp Heap static MEMHeapHandle hExpHeap; // 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))) // Sample Info static SampleInfo Samples[NUM_SAMPLES]; // AX Voice info static VoiceInfo Voices[AX_MAX_VOICES]; // function Prototypes static void * LoadFileIntoRam ( char *path ); static void AudioFrameCallback ( void ); static AXVPB* AquireVoiceADPCM ( s32 chan, void *pDSPADPCMData ); static void LoadSamples ( void ); static void PlaySample ( s32 chan, SampleInfo *sample ); static void VoiceCallback ( void * voiceIn ); #define SAMPLES_PER_AUDIO_FRAME 96 #define BYTES_PER_AUDIO_FRAME 384 static s16 SoundBuffer[2][SAMPLES_PER_AUDIO_FRAME * 2] ATTRIBUTE_ALIGN(32); static s32 SoundBufferIndex = 0; static AIDCallback OldAIDCallback = NULL; static s16* LastAudioBuffer = NULL; static s16* CurAudioBuffer = NULL; #define GM_WT "/axdemo/synth/gm16adpcm.wt" #define GM_PCM "/axdemo/synth/gm16adpcm.pcm" #define MIDI_FILE "/axdemo/midi/2nd_time.mid" // allocator functions for WPAD void* myAlloc ( u32 size ); u8 myFree ( void *ptr ); static void ConnectCallback ( s32 chan, s32 reason ); static void ExtensionCallback ( s32 chan, s32 result ); static void SpeakerCallback ( s32 chan, s32 result ); static void SpeakerOnCallback ( s32 chna, s32 result ); static void SpeakerOffCallback ( s32 chan, s32 result ); static void MuteOnCallback ( s32 chan, s32 result ); static void MuteOffCallback ( s32 chan, s32 result ); static void UpdateSpeaker ( OSAlarm *alarm, OSContext *context ); #define SAMPLES_PER_AUDIO_PACKET 40 #define AUDIO_PACKET_MAX_LEN 20 // Speaker Status typedef struct SpeakerInfo { u8 active; BOOL muteReq; WENCInfo encInfo; BOOL first; BOOL last; } SpeakerInfo; // Periodic Alarms for audio streaming static OSAlarm SpeakerAlarm; // Audio buffers static s16 AudioBuffer[WPAD_MAX_CONTROLLERS][SAMPLES_PER_AUDIO_PACKET]; static void initialize ( void ); static void RenderOperation ( void ); static void RenderControllerStatus ( void ); typedef union Status { WPADStatus cr; WPADFSStatus fs; WPADCLStatus cl; } Status; typedef struct ContInfo { u32 type; s32 status; u16 keepBtnMap; u16 button; Status currStat; Status prevStat; SpeakerInfo Speakers; } ContInfo; static ContInfo info[WPAD_MAX_CONTROLLERS]; /*---------------------------------------------------------------------------* * Name : main * Description : main * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ void main( void ) { s32 i; s32 chan; void *arenaMem2Lo; void *arenaMem2Hi; SEQSEQUENCE Sequence; u8 *Wavetable; u8 *Pcm; u8 *MidiFile; void *axBuffer; void *mixBuffer; void *synBuffer; // Initialize DEMO initialize(); // initialize Exp Heap on MEM2 arenaMem2Lo = OSGetMEM2ArenaLo(); arenaMem2Hi = OSGetMEM2ArenaHi(); hExpHeap = MEMCreateExpHeap(arenaMem2Lo, (u32)arenaMem2Hi - (u32) arenaMem2Lo); ASSERT(hExpHeap != NULL); // Initialize Audio axBuffer = OSAlloc(AXGetMemorySize(AX_MAX_VOICES)); mixBuffer = MEMAllocFromExpHeap(hExpHeap, MIXGetMemorySize(AX_MAX_VOICES)); synBuffer = MEMAllocFromExpHeap(hExpHeap, SYNGetMemorySize(AX_MAX_VOICES)); AIInit(NULL); AXInitSpecifyMem(AX_MAX_VOICES, axBuffer); MIXInitSpecifyMem(mixBuffer); SYNInitSpecifyMem(synBuffer); SEQInit(); // Load Voice data into MRAM LoadSamples(); Wavetable = LoadFileIntoRam(GM_WT); Pcm = LoadFileIntoRam(GM_PCM); MidiFile = LoadFileIntoRam(MIDI_FILE); // Register Callback with AX for audio processing AXRegisterCallback(&AudioFrameCallback); SEQAddSequence( &Sequence, MidiFile, Wavetable, Pcm, NULL, 16, 15, 1 ); SEQSetState(&Sequence, SEQ_STATE_RUNLOOPED); // Initialize WPAD WPADRegisterAllocator(myAlloc, myFree); WPADInit(); for(i=0;i= 0) { WPADSetDataFormat(chan, WPAD_FMT_CORE); } } /*---------------------------------------------------------------------------* * Name : * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ void *myAlloc( u32 size ) { void *ptr; ptr = MEMAllocFromExpHeap(hExpHeap, size); ASSERTMSG(ptr, "Memory allocation failed\n"); return(ptr); } /*---------------------------------------------------------------------------* * Name : * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ u8 myFree( void *ptr ) { MEMFreeToExpHeap(hExpHeap, ptr); return(1); } /*---------------------------------------------------------------------------* Name: LoadSamples Description: Loads ADPCM files into Main Memory (Header + Data) and ARAM (Data) Arguments: none Returns: none *---------------------------------------------------------------------------*/ static void LoadSamples( void ) { u32 i; // 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; } } } /*---------------------------------------------------------------------------* 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( s32 chan, SampleInfo *sample ) { AXVPB *voice; if (sample->mramAddr == NULL) { OSReport("WARNING! Sample not loaded!\n"); return; } // Aquire Voice and start voice = AquireVoiceADPCM(chan, sample->mramAddr); 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; } } // run the sequencer SEQRunAudioFrame(); // run the synth SYNRunAudioFrame(); // tell the mixer to update settings MIXUpdateSettings(); } /*---------------------------------------------------------------------------* 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 *---------------------------------------------------------------------------*/ static AXVPB* AquireVoiceADPCM( s32 chan, void *pDSPADPCMData ) { DSPADPCM *ps = (DSPADPCM*)pDSPADPCMData; AXPBADDR addr; AXPBADPCM adpcm; AXPBSRC src; AXPBADPCMLOOP adpcmLoop; AXVPB* voice; u32 srcBits; u32 mramAddress; u32 pMRAMStart; // 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 folowing addresses are in nibbles addr.loopFlag = ps->loop_flag; addr.format = ps->format; 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)); 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); // 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 controller speaker AXSetVoiceRmtOn(voice, AX_PB_REMOTE_ON); memset(&g_rmix, 0, sizeof(AXPBRMTMIX)); switch(chan) { case WPAD_CHAN0: g_rmix.vMain0 = 0x8000; // chan0 main g_rmix.vAux0 = 0x8000; // chan0 aux break; case WPAD_CHAN1: g_rmix.vMain1 = 0x8000; // chan1 main g_rmix.vAux1 = 0x8000; // chan1 aux break; case WPAD_CHAN2: g_rmix.vMain2 = 0x8000; // chan2 main g_rmix.vAux2 = 0x8000; // chan2 aux break; case WPAD_CHAN3: g_rmix.vMain3 = 0x8000; // chan3 main g_rmix.vAux3 = 0x8000; // chan3 aux break; } AXSetVoiceRmtMix(voice, &g_rmix); // 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)); buffer = MEMAllocFromExpHeapEx(hExpHeap, round_length, 32); // 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; } static const s16 HEIGHT = 10; /*---------------------------------------------------------------------------* * Name : RenderOperation * Description : * Arguments : * Returns : None. *---------------------------------------------------------------------------*/ static void RenderOperation( void ) { s16 y = 96; char button[256]; u32 i; for (i = 0; i < NUM_BUTTON_MAPS; i++) { switch (ButtonMap[i][0]) { case WPAD_BUTTON_A: sprintf(button, "A "); break; case WPAD_BUTTON_B: sprintf(button, "B "); break; case WPAD_BUTTON_1: sprintf(button, "1 "); break; case WPAD_BUTTON_2: sprintf(button, "2 "); break; case WPAD_BUTTON_DOWN: sprintf(button, "Down "); break; case WPAD_BUTTON_UP: sprintf(button, "Up "); break; case WPAD_BUTTON_LEFT: sprintf(button, "Left "); break; case WPAD_BUTTON_RIGHT: sprintf(button, "Right"); break; } DEMOPrintf( 16, y+=HEIGHT, 0, "%s : %s", button, SampleFiles[ButtonMap[i][1]]); } DEMOPrintf( 16, y+=HEIGHT, 0, "Home : Stop all sounds"); DEMOPrintf( 16, y+=HEIGHT, 0, "Plus : Mute On"); DEMOPrintf( 16, y+=HEIGHT, 0, "Minus : Mute Off"); } /*---------------------------------------------------------------------------* * Name : RenderControllerStatus * Description : * Arguments : * Returns : None. *---------------------------------------------------------------------------*/ static void RenderControllerStatus( void ) { s16 y = 32; int chan; DEMOPrintf( 150, y, 0, "speaker"); DEMOPrintf( 250, y, 0, "volume"); for(chan=0; chan