/*---------------------------------------------------------------------------* Project: AX Compressor Demo File: compressor.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: compressor.c,v $ Revision 1.9 2006/03/06 09:59:03 kawaset Eliminated warnings. Revision 1.8 02/21/2006 01:04:15 mitu modified am.h path. Revision 1.7 02/20/2006 04:13:07 mitu changed include path from dolphin/ to revolution/. Revision 1.6 02/02/2006 07:30:11 aka Modified using MEM functions instead of OSAlloc()/OSFree(). Revision 1.5 02/01/2006 07:47:40 aka Added #ifndef(#ifdef) HOLLYWOOD_REV - #else - #endif. Revision 1.4 2006/01/31 08:07:05 aka Added cast from u32 to u8* in relation to changing API around ARAM. Revision 1.3 2006/01/27 04:54:59 ekwon Corrected "\%" escape sequence warning (replaced with "%%"). Revision 1.2 11/08/2005 02:55:02 aka Changed suiting to Revolution's audio spec. Revision 1.1 11/04/2005 05:01:39 aka Imported from dolphin tree. 1 1/11/02 3:43p Eugene Demonstration of AX compressor. $NoKeywords: $ *---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* * Includes *---------------------------------------------------------------------------*/ #include #include #include #include #include #include #ifndef HOLLYWOOD_REV #include #else #include #include #endif #include "comp_sp.h" /*---------------------------------------------------------------------------* * SP data *---------------------------------------------------------------------------*/ #define SPT_FILE "/AXDEMO/compressor/comp_sp.spt" #define SPD_FILE "/AXDEMO/compressor/comp_sp.spd" static SPSoundTable *sp_table; #ifdef HOLLYWOOD_REV static u8 *sp_data; #endif #ifndef HOLLYWOOD_REV /*---------------------------------------------------------------------------* * ARAM initialization *---------------------------------------------------------------------------*/ // Use AR allocator to divide ARAM into 3 blocks #define MAX_ARAM_BLOCKS 3 // Give a whopping 8MB of ARAM to audio! #define AUDIO_BLOCK_SIZE_BYTES (8*1024*1024) static u32 aramZeroBase; static u32 aramUserBase; static u32 aramMemArray[MAX_ARAM_BLOCKS]; // transfer buffer for ARAM audio manager (AM) #define XFER_BUFFER_SIZE_BYTES (16*1024) u8 xfer_buffer[XFER_BUFFER_SIZE_BYTES] ATTRIBUTE_ALIGN(32); #else /*---------------------------------------------------------------------------* * Exp Heap *---------------------------------------------------------------------------*/ static MEMHeapHandle hExpHeap; /*---------------------------------------------------------------------------* * Zero Buffer *---------------------------------------------------------------------------*/ #define ZEROBUFFER_BYTES 256 #endif /*---------------------------------------------------------------------------* * AX Profiling *---------------------------------------------------------------------------*/ // store up to 8 frames, just to be safe #define NUM_AX_PROFILE_FRAMES 8 static AXPROFILE ax_profile[NUM_AX_PROFILE_FRAMES]; /*---------------------------------------------------------------------------* * Application-layer voice abstraction *---------------------------------------------------------------------------*/ #define MAX_DEMO_VOICES 64 typedef struct { AXVPB *ax_voice; SPSoundEntry *sp_entry; } DEMO_VOICE; DEMO_VOICE demo_voice[MAX_DEMO_VOICES]; // Checks SP entry 'type' to see if the voice is looped or not #define mISLOOPED(x) ((x->type)&0x1) // flag to track state of compressor! static u32 compressor_mode = AX_COMPRESSOR_ON; /*---------------------------------------------------------------------------* * DEMO-AVX: for snooping output of DSP for clipping *---------------------------------------------------------------------------*/ #define AVX_BUFFER_SIZE_WORDS 160 s16 __left_channel [AVX_BUFFER_SIZE_WORDS] ATTRIBUTE_ALIGN(32); s16 __right_channel [AVX_BUFFER_SIZE_WORDS] ATTRIBUTE_ALIGN(32); static u32 clip_tick_left; // for animating the clip detector static u32 clip_tick_right; // for animating the clip detector static u32 clip_left; // for counting number of clips, just for fun static u32 clip_right; // for counting number of clips, just for fun /*---------------------------------------------------------------------------* * Alarms and such for test processes *---------------------------------------------------------------------------*/ #define PING_PERIOD 100 // in milliseconds static OSAlarm PingAlarm; static u32 ping_counter; /*---------------------------------------------------------------------------* * Prototypes *---------------------------------------------------------------------------*/ // for user interface static void MNU_play_click (DEMOWinMenuInfo *menu, u32 item); static void MNU_play_hum (DEMOWinMenuInfo *menu, u32 item, u32 *result); static void MNU_play_ping (DEMOWinMenuInfo *menu, u32 item, u32 *result); static void MNU_play_voice (DEMOWinMenuInfo *menu, u32 item, u32 *result); static void MNU_stop_sfx (DEMOWinMenuInfo *menu, u32 item, u32 *result); static void MNU_toggle_comp (DEMOWinMenuInfo *menu, u32 item, u32 *result); static void MNU_start_ping_test (DEMOWinMenuInfo *menu, u32 item, u32 *result); static void MNU_start_voice_test (DEMOWinMenuInfo *menu, u32 item, u32 *result); // for voice abstraction layer static DEMO_VOICE *get_demo_voice (void); static void init_demo_voices (void); static void ax_demo_callback (void); static void ax_drop_voice_callback (void *p); static void play_sfx (u32 sfx); static void stop_all_sfx (void); static void ax_profile_update (DEMOWinInfo *window); // test infrastructure static void ping_test_refresh (DEMOWinInfo *handle); static void ping_alarm_handler (OSAlarm *alarm, OSContext *context); static void voice_alarm_handler (OSAlarm *alarm, OSContext *context); static void do_test (u32 sfx); /*---------------------------------------------------------------------------* * UI Stuff *---------------------------------------------------------------------------*/ DEMOWinInfo *DebugWin; // debug messages DEMOWinInfo *ProfileWin; // AX state information DEMOWinMenuItem MenuItem[] = { { "Tests", DEMOWIN_ITM_SEPARATOR, NULL, NULL }, { " Ping test", DEMOWIN_ITM_NONE, MNU_start_ping_test, NULL }, { " Voice test", DEMOWIN_ITM_NONE, MNU_start_voice_test, NULL }, { " ", DEMOWIN_ITM_SEPARATOR, NULL, NULL }, { "Manual AX Controls", DEMOWIN_ITM_SEPARATOR, NULL, NULL }, { " Toggle Compressor", DEMOWIN_ITM_NONE, MNU_toggle_comp, NULL }, { " Play Looping Hum", DEMOWIN_ITM_NONE, MNU_play_hum, NULL }, { " Play Ping", DEMOWIN_ITM_NONE, MNU_play_ping, NULL }, { " Play Voice", DEMOWIN_ITM_NONE, MNU_play_voice, NULL }, { " Stop All Voices", DEMOWIN_ITM_NONE, MNU_stop_sfx, NULL }, { " ", DEMOWIN_ITM_SEPARATOR, NULL, NULL }, { "", DEMOWIN_ITM_TERMINATOR, NULL, NULL } }; DEMOWinMenuInfo Menu = { "AX Compressor Demo", // title NULL, // window handle MenuItem, // list of menu items 12, // max num of items to display at a time DEMOWIN_MNU_NONE, // attribute flags // user callbacks NULL, // callback for menu open event MNU_play_click, // callback for cursor move event NULL, // callback for item select event NULL, // callback for cancel event // private members 0, 0, 0, 0, 0 }; DEMOWinMenuInfo *MenuPtr; /*===========================================================================* * F U N C T I O N D E F I N I T I O N S *===========================================================================*/ /*---------------------------------------------------------------------------* * Name : ax_profile_updatek() * Description : refresh callback for AX profile window * Arguments : * Returns : *---------------------------------------------------------------------------*/ static void ax_profile_update(DEMOWinInfo *window) { BOOL old; u32 i; u32 cpuCycles; u32 userCycles; u32 axCycles; u32 voices; u32 maxCpuCycles =0; u32 maxUserCycles=0; u32 maxAxCycles =0; u32 maxVoices =0; old = OSDisableInterrupts(); i = AXGetProfile(); if (i) { // up to 4 audio frames can complete within a 60Hz video frame // so spin thru the accumulated audio frame profiles and find the peak values while (i) { i--; cpuCycles = (u32)(ax_profile[i].axFrameEnd - ax_profile[i].axFrameStart); userCycles = (u32)(ax_profile[i].userCallbackEnd - ax_profile[i].userCallbackStart); axCycles = cpuCycles - userCycles; voices = ax_profile[i].axNumVoices; // find peak values over the last i audio frames if (cpuCycles > maxCpuCycles) maxCpuCycles = cpuCycles; if (userCycles > maxUserCycles) maxUserCycles = userCycles; if (axCycles > maxAxCycles) maxAxCycles = axCycles; if (voices > maxVoices) maxVoices = voices; } OSRestoreInterrupts(old); DEMOWinPrintfXY(window, 0, 2, "Total CPU : %5.2f%%", (f32)OSTicksToNanoseconds(maxCpuCycles) / 50000); DEMOWinPrintfXY(window, 0, 4, "User : %5.2f%%", (f32)OSTicksToNanoseconds(maxUserCycles) / 50000); DEMOWinPrintfXY(window, 0, 5, "AX : %5.2f%%", (f32)OSTicksToNanoseconds(maxAxCycles) / 50000); DEMOWinPrintfXY(window, 0, 7, "Voices : %5d", maxVoices); DEMOWinPrintfXY(window, 0, 9, "Compressor: %s", (compressor_mode ? "ON " : "OFF")); } OSRestoreInterrupts(old); } // end profile_update() /*---------------------------------------------------------------------------* * Name : MNU_play_click() * Description : Callback for menu system, plays 'click' for cursor movement * Arguments : * Returns : *---------------------------------------------------------------------------*/ static void MNU_play_click(DEMOWinMenuInfo *menu, u32 item) { #pragma unused(menu) #pragma unused(item) play_sfx(SFX_MENU); } // end MNU_play_click() /*---------------------------------------------------------------------------* * Name : * Description : * Arguments : * Returns : *---------------------------------------------------------------------------*/ static void MNU_play_hum(DEMOWinMenuInfo *menu, u32 item, u32 *result) { #pragma unused(menu, item, result) play_sfx(SFX_HUM_LOOPED); } // end MNU_play_sfx() /*---------------------------------------------------------------------------* * Name : * Description : * Arguments : * Returns : *---------------------------------------------------------------------------*/ static void MNU_play_ping(DEMOWinMenuInfo *menu, u32 item, u32 *result) { #pragma unused(menu, item, result) play_sfx(SFX_PING); } // end MNU_play_sfx() /*---------------------------------------------------------------------------* * Name : * Description : * Arguments : * Returns : *---------------------------------------------------------------------------*/ static void MNU_play_voice(DEMOWinMenuInfo *menu, u32 item, u32 *result) { #pragma unused(menu, item, result) play_sfx(SFX_VOICE_NGC_MAN); } // end MNU_play_sfx() /*---------------------------------------------------------------------------* * Name : MNU_stop_sfx() * Description : Stops all voices. Note that voices are freed by the AX user * callback on the next frame. * Arguments : * Returns : *---------------------------------------------------------------------------*/ static void MNU_stop_sfx(DEMOWinMenuInfo *menu, u32 item, u32 *result) { #pragma unused(menu) #pragma unused(item) #pragma unused(result) stop_all_sfx(); } // end MNU_stop_sfx() /*---------------------------------------------------------------------------* * Name : MNU_toggle_comp() * Description : Toggles the AX compressor. * Arguments : * Returns : *---------------------------------------------------------------------------*/ static void MNU_toggle_comp(DEMOWinMenuInfo *menu, u32 item, u32 *result) { #pragma unused(menu) #pragma unused(item) #pragma unused(result) BOOL old; old = OSDisableInterrupts(); compressor_mode ^= 1; AXSetCompressor(compressor_mode); OSRestoreInterrupts(old); } // end MNU_stop_sfx() /*---------------------------------------------------------------------------* * Name : * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ static void init_demo_voices() { u32 i; for (i=0; ipb.state)) { MIXReleaseChannel(demo_voice[i].ax_voice); AXFreeVoice(demo_voice[i].ax_voice); demo_voice[i].ax_voice = NULL; } } } } // end ax_demo_callback() /*---------------------------------------------------------------------------* * Name : ax_drop_voice_callback() * Description : Invoked by AX when a voice has been forciby dropped. * Must delete references to the voice from our abstraction layer * and release the associated MIXer channel. * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ static void ax_drop_voice_callback(void *p) { u32 i; OSReport("Voice dropped!\n"); // search for abstracted voice associated with low-level AX voice. for (i=0; iax_voice = AXAcquireVoice(15, ax_drop_voice_callback, 0); if (v->ax_voice) { v->sp_entry = SPGetSoundEntry(sp_table, sfx); SPPrepareSound(v->sp_entry, v->ax_voice, (v->sp_entry)->sampleRate); MIXInitChannel(v->ax_voice, 0, 0, -960, -960, -960, 64, 127, 0); AXSetVoiceState(v->ax_voice, AX_PB_STATE_RUN); OSRestoreInterrupts(old); } else { OSRestoreInterrupts(old); DEMOWinLogPrintf(DebugWin, "SFX: AX Voice allocation failed.\n"); } } else { OSRestoreInterrupts(old); DEMOWinLogPrintf(DebugWin, "(No free voices in abstraction layer)\n"); } } // end play_sfx() /*---------------------------------------------------------------------------* * Name : ping_alarm_handler() * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ static void ping_alarm_handler(OSAlarm *alarm, OSContext *context) { #pragma unused(alarm, context) // last four counts, play a ping if (ping_counter > 7 ) { play_sfx(SFX_PING); } // for the first eight counts, stay silent ping_counter = (ping_counter + 1) % 12; } /*---------------------------------------------------------------------------* * Name : voice_alarm_handler() * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ static void voice_alarm_handler(OSAlarm *alarm, OSContext *context) { #pragma unused(alarm, context) // play the voice for just one count if (ping_counter == 19 ) { play_sfx(SFX_VOICE_NGC_MAN); } // for the remaining counts, stay silent ping_counter = (ping_counter + 1) % 25; } /*---------------------------------------------------------------------------* * Name : ping_test_refresh() * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ static void ping_test_refresh(DEMOWinInfo *handle) { DEMOWinPrintfXY(handle, 0, 3, "Compressor: %s", (compressor_mode ? "ON " : "OFF")); DEMOWinPrintfXY(handle, 0, 5, "Clip Left : %s", ((clip_tick_left > 0) && (clip_tick_left < 19)) ? "CLIP" : " "); DEMOWinPrintfXY(handle, 0, 6, "Clip Right: %s", ((clip_tick_right > 0) && (clip_tick_right < 19)) ? "CLIP" : " "); } // end of ping_test_refresh() /*---------------------------------------------------------------------------* * Name : MNU_start_ping_test() * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ static void MNU_start_ping_test(DEMOWinMenuInfo *menu, u32 item, u32 *result) { #pragma unused(menu, item, result) do_test(SFX_PING); } /*---------------------------------------------------------------------------* * Name : NNU_start_voice_test() * Description : * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ static void MNU_start_voice_test(DEMOWinMenuInfo *menu, u32 item, u32 *result) { #pragma unused(menu, item, result) do_test(SFX_VOICE_NGC_MAN); } /*---------------------------------------------------------------------------* * Name : do_test() * Description : * Arguments : sound effect with which to induce clipping. * Returns : None. *---------------------------------------------------------------------------*/ static void do_test(u32 sfx) { DEMOWinInfo *handle; DEMOWinPadInfo pad; BOOL old; OSTime now; u32 num_samples = 0; u32 start_index = 0; u32 end_index = 0; u32 index = 0; u32 i; // stop all voices, just in case stop_all_sfx(); handle = DEMOWinCreateWindow(150, 90, 540, 180, "Compressor Test", 0, ping_test_refresh); DEMOWinOpenWindow(handle); DEMOWinPrintfXY(handle, 0, 0, "A: Toggle Compressor"); DEMOWinPrintfXY(handle, 0, 1, "B: Exit"); // debounce DEMOWinPadInit(&pad); DEMOBeforeRender(); DEMOWinRefresh(); DEMODoneRender(); DEMOWinPadRead(&pad); DEMOBeforeRender(); DEMOWinRefresh(); DEMODoneRender(); DEMOWinPadRead(&pad); // start low-freq hum play_sfx(SFX_HUM_LOOPED); // start periodic ping ping_counter = 0; OSCreateAlarm(&PingAlarm); now = OSGetTime(); if (SFX_PING == sfx) { // set alarm using "ping" handler OSSetPeriodicAlarm(&PingAlarm, now, OSMillisecondsToTicks(PING_PERIOD), ping_alarm_handler); } else if (SFX_VOICE_NGC_MAN) { // set alarm using "voice" handler OSSetPeriodicAlarm(&PingAlarm, now, OSMillisecondsToTicks(PING_PERIOD), voice_alarm_handler); } else { OSHalt("Unknown sound effect reference for test!\n"); } // reset clip-o-meter clip_left = 0; clip_right = 0; clip_tick_left = 0; clip_tick_right = 0; while (1) { if (pad.changed_button[0] & PAD_BUTTON_B) { break; } if (pad.changed_button[0] & PAD_BUTTON_A) { old = OSDisableInterrupts(); compressor_mode ^= 1; AXSetCompressor(compressor_mode); OSRestoreInterrupts(old); } // Clip detection num_samples = DEMOAVXRefreshBuffer(&start_index, &end_index); index = start_index; for (i=0; i 32766) || (__right_channel[index] < -32766)) { clip_left++; // count number of clips, just for fun clip_tick_left = 20; } if ((__right_channel[index] > 32766) || (__right_channel[index] < -32766)) { clip_right++; // count number of clips, just for fun clip_tick_right = 20; } index = (index + 1) % AVX_BUFFER_SIZE_WORDS; } DEMOBeforeRender(); DEMOWinRefresh(); DEMODoneRender(); DEMOWinPadRead(&pad); // decrement clip-o-meter animation ticks clip_tick_right = (clip_tick_right ? (clip_tick_right - 1) : 0); clip_tick_left = (clip_tick_left ? (clip_tick_left - 1) : 0); } stop_all_sfx(); OSRestoreInterrupts(old); OSCancelAlarm(&PingAlarm); DEMOWinCloseWindow(handle); DEMOWinDestroyWindow(handle); DEMOWinLogPrintf(DebugWin, "Left : clipped %d times.\n", clip_left); DEMOWinLogPrintf(DebugWin, "Right: clipped %d times.\n", clip_right); DEMOWinLogPrintf(DebugWin, "\n"); } // end do_test() #ifdef HOLLYWOOD_REV /*---------------------------------------------------------------------------* *---------------------------------------------------------------------------*/ 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; } #endif /*---------------------------------------------------------------------------* * Name : main() * Description : Hold on to your seatbelts! * Arguments : None. * Returns : None. *---------------------------------------------------------------------------*/ void main(void) { #ifdef HOLLYWOOD_REV void *arenaMem2Lo; void *arenaMem2Hi; u8 *zeroBuffer; #endif // initialize system DEMOInit(NULL); DEMOWinInit(); #ifndef HOLLYWOOD_REV // initialize ARAM w/ stack allocator ARInit(aramMemArray, MAX_ARAM_BLOCKS); ARQInit(); #else arenaMem2Lo = OSGetMEM2ArenaLo(); arenaMem2Hi = OSGetMEM2ArenaHi(); hExpHeap = MEMCreateExpHeap(arenaMem2Lo, (u32)arenaMem2Hi - (u32) arenaMem2Lo); #endif // initialize AI subsystem AIInit(NULL); // initialize AX audio system and MIXer application AXInit(); MIXInit(); // turn on compressor! AXSetCompressor(compressor_mode); #ifndef HOLLYWOOD_REV // ----------------------------------------------------------- // Initialize ARAM audio manager (AM) // ----------------------------------------------------------- // get a block from the AR ARAM allocator aramUserBase = ARAlloc(AUDIO_BLOCK_SIZE_BYTES); // initialize AM with the block AMInit(aramUserBase, AUDIO_BLOCK_SIZE_BYTES); // retrieve start of zero buffer, as created by AM aramZeroBase = AMGetZeroBuffer(); #endif // ----------------------------------------------------------- // Load SP data! // ----------------------------------------------------------- #ifndef HOLLYWOOD_REV // Retrieve sound table sp_table = (SPSoundTable *)AMLoadFile(SPT_FILE, NULL); // Load sound effects into ARAM aramUserBase = AMPushBuffered(SPD_FILE, (void *)xfer_buffer, XFER_BUFFER_SIZE_BYTES); #else // Load sound table sp_table = LoadFileIntoRam(SPT_FILE); // Load sound effects sp_data = LoadFileIntoRam(SPD_FILE); #endif #ifdef HOLLYWOOD_REV // ----------------------------------------------------------- // Prepare Zero Buffer // ----------------------------------------------------------- zeroBuffer = MEMAllocFromExpHeapEx(hExpHeap, ZEROBUFFER_BYTES, 8); memset(zeroBuffer, 0, ZEROBUFFER_BYTES); DCFlushRange(zeroBuffer, ZEROBUFFER_BYTES); #endif // ----------------------------------------------------------- // initialize sound table! // ----------------------------------------------------------- #ifndef HOLLYWOOD_REV SPInitSoundTable(sp_table, (u8*)aramUserBase, (u8*)aramZeroBase); #else SPInitSoundTable(sp_table, sp_data, zeroBuffer); #endif // ----------------------------------------------------------- // Initialize demo voice abstraction layer // ----------------------------------------------------------- init_demo_voices(); AXRegisterCallback(ax_demo_callback); // initialize profiling for AX AXInitProfile(ax_profile, NUM_AX_PROFILE_FRAMES); // ----------------------------------------------------------- // Initialize DEMO-AVX functions. // This allows us to snoop a copy of the contents of the DSP // DSP output before it is consumed by the DAC. We want this // data so we can search for clipping. // ----------------------------------------------------------- DEMOAVXInit(__left_channel, __right_channel, AVX_BUFFER_SIZE_WORDS); // ----------------------------------------------------------- // Invoke menu system! // ----------------------------------------------------------- MenuPtr = DEMOWinCreateMenuWindow(&Menu, 20, 100); DebugWin = DEMOWinCreateWindow((u16)(MenuPtr->handle->x2+10), 20, 620, 440, "Debug", 1024, NULL); ProfileWin = DEMOWinCreateWindow((u16)(MenuPtr->handle->x1), (u16)(MenuPtr->handle->y2+10), (u16)(MenuPtr->handle->x2), (u16)(MenuPtr->handle->y2+160), "AX Status", 0, ax_profile_update); DEMOWinOpenWindow(DebugWin); DEMOWinOpenWindow(ProfileWin); DEMOWinLogPrintf(DebugWin, "-------------------------------\n"); DEMOWinLogPrintf(DebugWin, "AX Compressor Demo\n"); DEMOWinLogPrintf(DebugWin, "-------------------------------\n"); while (1) { DEMOWinMenu(MenuPtr); } } // end main()