/*---------------------------------------------------------------------------* Project: Revolution PMIC simple demo File: audio.c Copyright (C)2008 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: audio.c,v $ Revision 1.4 2008/08/06 01:39:14 carlmu Added graphic demo. Revision 1.3 2008/08/04 23:51:51 carlmu Added quit function for audio. Revision 1.2 2008/04/23 00:09:19 aka Modified an argument of PMICChangeRate(). Revision 1.1 2008/01/22 02:50:04 aka initial check-in. $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include "audio.h" /*---------------------------------------------------------------------------* definitions *---------------------------------------------------------------------------*/ #define USE_HEADPHONE #undef USE_HEADPHONE #define USE_64TAP_SRC //#undef USE_64TAP_SRC // audio data #define AUDIO_GM_WT "/axdemo/synth/gm16adpcm.wt" #define AUDIO_GM_PCM "/axdemo/synth/gm16adpcm.pcm" #define AUDIO_MIDI_FILE "/axdemo/midi/candy.mid" // audio processing unit #define AUDIO_SAMPLES_PER_FRAME (32 * 3) // 32KHz x 3msec #define AUDIO_BYTES_PER_FRAME (AUDIO_SAMPLES_PER_FRAME * 2 * 2) // for voice processing #define STACK_SIZE (256 * 1024) // 256KB #define THREAD_PRIORITY 20 #define SRC_TAPS 64 #define SRC_BUFF_SAMPLES (SRC_TAPS + 16 * 3) // taps + 16KHz x 3msec #define PROC_BUFF_SAMPLES (32000 * 10) // 32KHz * 10sec typedef struct { s32 samples; // buffer size s32 top; // write ptr s32 buttom; // read ptr s16* buffer; // <- mono } PROCInfo; #define PROC_VOICE_GAIN 2 // 2bit SLA = + 12dB /*---------------------------------------------------------------------------* variables *---------------------------------------------------------------------------*/ // for AXs static u8* axBuff; static u8* mixBuff; static u8* synBuff; static u8* waveTbl; static u8* pcmData; static u8* midiData; static SEQSEQUENCE sequence; // for AI static s16* aiBuff[2]; static AIDCallback old_aiCb; // for ISO-OUT static s16* isoBuff; // for voice processing static OSThread procThread; static u8 procStack[STACK_SIZE]; static OSThreadQueue procWait; static s16* srcBuff; static s16 srcCoef[2][SRC_TAPS] = { { -23, 10, -11, 12, -10, 7, -1, -6, 18, -31, 49, -69, 94, -120, 149, -177, 208, -235, 262, -281, 296, -299, 292, -267, 223, -149, 37, 140, -426, 962, -2313, 14176, 5461, -2586, 1754, -1326, 1049, -842, 678, -540, 424, -322, 236, -162, 101, -49, 9, 23, -45, 62, -70, 75, -74, 72, -65, 58, -49, 41, -32, 25, -18, 13, -12, 8, }, { 8, -12, 13, -18, 25, -32, 41, -49, 58, -65, 72, -74, 75, -70, 62, -45, 23, 9, -49, 101, -162, 236, -322, 424, -540, 678, -842, 1049, -1326, 1754, -2586, 5461, 14176, -2313, 962, -426, 140, 37, -149, 223, -267, 292, -299, 296, -281, 262, -235, 208, -177, 149, -120, 94, -69, 49, -31, 18, -6, -1, 7, -10, 12, -11, 10, -23 } }; static PROCInfo procInfo; // for voice playback static BOOL playVoice = FALSE; /*---------------------------------------------------------------------------* funcs *---------------------------------------------------------------------------*/ static void audioCb ( void ); static u8* loadFile ( char *path, MEMHeapHandle* heap ); static void aiCb ( void ); static void* voiceProc ( void *param ); static s32 fillVoice ( s16* buffer, s32 samples); /*---------------------------------------------------------------------------* Name: AUDIOInit Description: initialize audio. Arguments: heap for mem allocation. Returns: TRUE/FALSE *---------------------------------------------------------------------------*/ BOOL AUDIOInit(MEMHeapHandle* heap, MicFunc procFunc) { BOOL old; // // init AXs. // axBuff = MEMAllocFromExpHeapEx(*heap, AXGetMemorySize (AX_MAX_VOICES), 32); mixBuff = MEMAllocFromExpHeapEx(*heap, MIXGetMemorySize(AX_MAX_VOICES), 32); synBuff = MEMAllocFromExpHeapEx(*heap, SYNGetMemorySize(AX_MAX_VOICES), 32); if (!axBuff || !mixBuff || !synBuff) { return FALSE; } AIInit(NULL); // initialize AI AXInitSpecifyMem(AX_MAX_VOICES, axBuff); // initialize AX MIXInitSpecifyMem(mixBuff); // initialize mixer SYNInitSpecifyMem(synBuff); // initialize synthesizer SEQInit(); // initialize sequencer AXRegisterCallback(audioCb); // // play MIDI. // waveTbl = loadFile(AUDIO_GM_WT, heap); pcmData = loadFile(AUDIO_GM_PCM, heap); midiData = loadFile(AUDIO_MIDI_FILE, heap); if (!waveTbl || !pcmData || !midiData) { return FALSE; } SEQAddSequence(&sequence, midiData, waveTbl, pcmData, NULL, 16, 15, 1); SEQSetState (&sequence, SEQ_STATE_RUNLOOPED); SEQSetVolume(&sequence, -120); // // create voice processing thread. // srcBuff = MEMAllocFromExpHeapEx(*heap, SRC_BUFF_SAMPLES * sizeof(s16), 32); memset(srcBuff, 0, SRC_BUFF_SAMPLES * sizeof(s16)); procInfo.samples = PROC_BUFF_SAMPLES; procInfo.top = 0; procInfo.buttom = 0; procInfo.buffer = MEMAllocFromExpHeapEx(*heap, PROC_BUFF_SAMPLES * sizeof(s16), 32); OSInitThreadQueue(&procWait); if (procFunc == NULL) { procFunc = voiceProc; } OSCreateThread(&procThread, procFunc, NULL, procStack + STACK_SIZE, STACK_SIZE, THREAD_PRIORITY, OS_THREAD_ATTR_DETACH); OSResumeThread(&procThread); // // snatch AI callback. // aiBuff[0]= MEMAllocFromExpHeapEx(*heap, AUDIO_BYTES_PER_FRAME, 32); aiBuff[1]= MEMAllocFromExpHeapEx(*heap, AUDIO_BYTES_PER_FRAME, 32); isoBuff = MEMAllocFromExpHeapEx(*heap, (AUDIO_SAMPLES_PER_FRAME / 2) * sizeof(s16) * 2, 32); // 16KHz * s16 * stereo old = OSDisableInterrupts(); old_aiCb = AIRegisterDMACallback(aiCb); OSRestoreInterrupts(old); return TRUE; } /*---------------------------------------------------------------------------* Name: AUDIOQuit Description: shut down audio system. Arguments: none. Returns: none. *---------------------------------------------------------------------------*/ void AUDIOQuit(void) { SEQQuit(); SYNQuit(); MIXQuit(); AXQuit(); } /*---------------------------------------------------------------------------* Name: AUDIOStartPlay Description: start playing P-Mic's data. Arguments: none. Returns: none. *---------------------------------------------------------------------------*/ void AUDIOStartPlay(void) { playVoice = TRUE; } /*---------------------------------------------------------------------------* Name: AUDIOStopPlay Description: stop playing P-Mic's data. Arguments: none. Returns: none. *---------------------------------------------------------------------------*/ void AUDIOStopPlay(void) { playVoice = FALSE; procInfo.top = 0; procInfo.buttom = 0; } /*---------------------------------------------------------------------------* Name: AUDIOSleepThread Description: Used by (external) pmic processing function, since the threadqueue is not public. Arguments: none. Returns: none. *---------------------------------------------------------------------------*/ void AUDIOSleepThread(void) { OSSleepThread(&procWait); } /*---------------------------------------------------------------------------* *---------------------------------------------------------------------------* *---------------------------------------------------------------------------* *---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* Name: audioCb Description: callback for AX apps. Arguments: none. Returns: none. *---------------------------------------------------------------------------*/ static void audioCb(void) { SEQRunAudioFrame(); SYNRunAudioFrame(); MIXUpdateSettings(); } /*---------------------------------------------------------------------------* Name: loadFile Description: load file from DVD. Arguments: path file name with path info. heap for mem allocation. Returns: data address. *---------------------------------------------------------------------------*/ static u8* loadFile(char *path, MEMHeapHandle* heap) { DVDFileInfo handle; u32 round_length; s32 read_length; void* buffer; if (!DVDOpen(path, &handle)) { return NULL; } if (DVDGetLength(&handle) == 0) { return NULL; } round_length = OSRoundUp32B(DVDGetLength(&handle)); buffer = MEMAllocFromExpHeapEx(*heap, round_length, 32); if (buffer == NULL) { return NULL; } read_length = DVDRead(&handle, buffer, (s32)(round_length), 0); if (read_length <= 0) { return NULL; } return buffer; } /*---------------------------------------------------------------------------* Name: aiCb Description: new AI callback func. Arguments: none. Returns: none. *---------------------------------------------------------------------------*/ static void aiCb(void) { static s16* last_buff = NULL; static s32 buff_ptr = 0; static BOOL init = TRUE; s16* curr_buff; s32 retval; // hand over AI buffer address. curr_buff = last_buff; // call old AI callback func. old_aiCb(); // get AI buffer address set in the old AI callback func. last_buff = (s16 *)OSPhysicalToCached(AIGetDMAStartAddr()); // set new AI buffer. AIInitDMA((u32)aiBuff[buff_ptr], AUDIO_BYTES_PER_FRAME); // fill the new AI buffer. if (playVoice) { // fill P-Mic's data to the new AI buffer. if (!fillVoice(aiBuff[buff_ptr], AUDIO_BYTES_PER_FRAME / 2)) { playVoice = FALSE; } } else { if (curr_buff) { DCInvalidateRange(curr_buff, AUDIO_BYTES_PER_FRAME); // to make sure memcpy(aiBuff[buff_ptr], curr_buff, AUDIO_BYTES_PER_FRAME); } else { memset(aiBuff[buff_ptr], 0, AUDIO_BYTES_PER_FRAME); } } // send final AI data in the new AI buffer to P-Mic (ISO-OUT). #ifdef USE_64TAP_SRC // retval is -1 if PMIC lib is not initialized retval = PMICChangeRate(aiBuff[buff_ptr], AUDIO_BYTES_PER_FRAME / 2, AI_SAMPLERATE_32KHZ, isoBuff, init); #else { extern BOOL PMICIsUp(); s16 *pIn = aiBuff[buff_ptr]; s16 *pOut = isoBuff; u32 i; for(i=0; i16Khz } if (PMICIsUp()) retval = AUDIO_SAMPLES_PER_FRAME; else retval = -1; } #endif if (retval > 0) { #ifdef USE_HEADPHONE memset(isoBuff, 0, (u32)(retval * sizeof(s16))); #endif PMICWrite(isoBuff, retval); init = FALSE; } // finish touching the new AI buffer. DCFlushRange(aiBuff[buff_ptr], AUDIO_BYTES_PER_FRAME); buff_ptr ^= 1; // report to voice processing thread. OSWakeupThread(&procWait); } /*---------------------------------------------------------------------------* Name: voiceProc Description: convert P-Mic's data from 16KHz to 32KHz. Arguments: param no use. Returns: none. *---------------------------------------------------------------------------*/ static void* voiceProc(void *param) { #pragma unused(param) s32 wptr; s32 limits; s16* buffer; s32 writables; s32 needs; s32 reads; s32 ii, jj; s32 acc_1st, acc_2nd; s16* inptr; while (1) { AUDIOSleepThread(); wptr = procInfo.top; limits = procInfo.samples; buffer = procInfo.buffer; // check space of output buffer. // # don't overwrite here <- P-Mic lib may overwrite... writables = (limits - wptr) & ~0x1; if (writables <= 0) { continue; } // get P-Mic's data (16KHz). needs = writables >> 1; if (needs >= SRC_BUFF_SAMPLES - (SRC_TAPS - 1)) { needs = SRC_BUFF_SAMPLES - (SRC_TAPS - 1); } reads = PMICRead(&srcBuff[SRC_TAPS - 1], needs); if (reads <= 0) { continue; } // do SRC from 16KHz to 32KHz. for (ii = 0; ii < reads; ii++) { inptr = &srcBuff[ii]; #ifdef USE_64TAP_SRC acc_1st = 0; acc_2nd = 0; for (jj = 0; jj < SRC_TAPS; jj++) { acc_1st += 2 * srcCoef[0][jj] * *inptr; acc_2nd += 2 * srcCoef[1][jj] * *inptr++; } acc_1st = (acc_1st + 0x4000) >> 15; acc_2nd = (acc_2nd + 0x4000) >> 15; if (acc_1st > 32767) { acc_1st = 32767; } else if (acc_1st < -32768) { acc_1st = -32768; } if (acc_2nd > 32767) { acc_2nd = 32767; } else if (acc_2nd < -32768) { acc_2nd = -32768; } #else acc_1st = *inptr++; acc_2nd = (acc_1st + (*inptr)) >> 1; #endif buffer[wptr++] = (s16)acc_1st; buffer[wptr++] = (s16)acc_2nd; } // move histories. for (jj = 0; jj < SRC_TAPS - 1; jj++) { srcBuff[jj] = srcBuff[ii + jj]; } procInfo.top = wptr; } } /*---------------------------------------------------------------------------* Name: fillVoice Description: fill P-Mic's data (already converted to 32KHz). Arguments: buffer output buffer. samples samples to output. Returns: num of outputs. *---------------------------------------------------------------------------*/ static s32 fillVoice(s16* buffer, s32 samples) { s32 needs; s32 rptr; s32 lefts; s16* data; s32 outputs; s32 ii; s32 stmp32; needs = samples >> 1; // stereo -> mono rptr = procInfo.buttom; lefts = procInfo.top - rptr; data = procInfo.buffer; outputs = needs > lefts? lefts: needs; for (ii = 0; ii < outputs; ii++) { stmp32 = (s32)data[rptr++] << PROC_VOICE_GAIN; if (stmp32 > 32767) { stmp32 = 32767; } else if (stmp32 < -32768) { stmp32 = -32768; } *buffer++ = (s16)stmp32; // Rch *buffer++ = (s16)stmp32; // Lch } for (; ii < needs; ii++) { *buffer++ = 0; // Rch *buffer++ = 0; // Lch } procInfo.buttom = rptr; return outputs << 1; // mono -> stereo }