1 /*---------------------------------------------------------------------------*
2   Project:  SP Demo application
3   File:     spdemo.c
4 
5   Copyright (C)1998-2006 Nintendo  All Rights Reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law.  They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12 
13   $Log: spdemo.c,v $
14   Revision 1.8  2006/03/06 09:59:03  kawaset
15   Eliminated warnings.
16 
17   Revision 1.7  02/21/2006 01:04:31  mitu
18   modified am.h path.
19 
20   Revision 1.6  02/20/2006 04:13:12  mitu
21   changed include path from dolphin/ to revolution/.
22 
23   Revision 1.5  02/02/2006 08:17:04  aka
24   Modified using MEM functions instead of OSAlloc()/OSFree().
25 
26   Revision 1.4  02/01/2006 08:30:07  aka
27   Added #ifndef(#ifdef) HOLLYWOOD_REV - #else - #endif.
28 
29   Revision 1.3  2006/01/27 04:55:47  ekwon
30   Corrected "\%" escape sequence warning (replaced with "%%").
31 
32   Revision 1.2  11/08/2005 03:01:25  aka
33   Changed suiting to Revolution's audio spec.
34 
35   Revision 1.1  11/04/2005 05:02:22  aka
36   Copyright 2001 Nintendo.  All rights reserved.
37 
38     1     9/05/01 8:09p Eugene
39     Demonstration of SP and AM libraries. Uses AX!
40     created
41 
42   $NoKeywords: $
43  *---------------------------------------------------------------------------*/
44 
45 /*---------------------------------------------------------------------------*
46  * Includes
47  *---------------------------------------------------------------------------*/
48 
49 #include <demo.h>
50 #include <demo/DEMOWin.h>
51 #include <revolution.h>
52 #include <revolution/mix.h>
53 #include <revolution/sp.h>
54 #ifndef HOLLYWOOD_REV
55 #include <dolphin/am.h>
56 #else
57 #include <revolution/mem.h>
58 #include <string.h>
59 #endif
60 
61 #include "spdemo.h"
62 
63 /*---------------------------------------------------------------------------*
64  * SP data
65  *---------------------------------------------------------------------------*/
66 
67 #define SPT_FILE "/SPDEMO/spdemo.spt"
68 #define SPD_FILE "/SPDEMO/spdemo.spd"
69 
70 static SPSoundTable *sp_table;
71 #ifdef HOLLYWOOD_REV
72 static u8           *sp_data;
73 #endif
74 
75 #ifndef HOLLYWOOD_REV
76  /*---------------------------------------------------------------------------*
77  * ARAM initialization
78  *---------------------------------------------------------------------------*/
79 
80 // Use AR allocator to divide ARAM into 3 blocks
81 #define MAX_ARAM_BLOCKS  3
82 
83 // Give a whopping 8MB of ARAM to audio!
84 #define AUDIO_BLOCK_SIZE_BYTES (8*1024*1024)
85 
86 
87 static u32  aramZeroBase;
88 static u32  aramUserBase;
89 static u32  aramMemArray[MAX_ARAM_BLOCKS];
90 
91 
92 // transfer buffer for ARAM audio manager (AM)
93 #define XFER_BUFFER_SIZE_BYTES (16*1024)
94 
95 u8 xfer_buffer[XFER_BUFFER_SIZE_BYTES] ATTRIBUTE_ALIGN(32);
96 
97 #else
98 /*---------------------------------------------------------------------------*
99  * Exp Heap
100  *---------------------------------------------------------------------------*/
101 
102 static MEMHeapHandle hExpHeap;
103 
104 /*---------------------------------------------------------------------------*
105  * Zero Buffer
106  *---------------------------------------------------------------------------*/
107 
108 #define ZEROBUFFER_BYTES 256
109 #endif
110 
111 /*---------------------------------------------------------------------------*
112  * AX Profiling
113  *---------------------------------------------------------------------------*/
114 
115 // store up to 8 frames, just to be safe
116 #define NUM_AX_PROFILE_FRAMES 8
117 
118 static AXPROFILE        ax_profile[NUM_AX_PROFILE_FRAMES];
119 
120 /*---------------------------------------------------------------------------*
121  * Application-layer voice abstraction
122  *---------------------------------------------------------------------------*/
123 
124 #define MAX_DEMO_VOICES  64
125 
126 typedef struct
127 {
128     AXVPB *ax_voice;
129     SPSoundEntry *sp_entry;
130 
131 } DEMO_VOICE;
132 
133 DEMO_VOICE demo_voice[MAX_DEMO_VOICES];
134 
135 // Checks SP entry 'type' to see if the voice is looped or not
136 #define mISLOOPED(x) ((x->type)&0x1)
137 
138 
139 /*---------------------------------------------------------------------------*
140  * Prototypes
141  *---------------------------------------------------------------------------*/
142 
143 static DEMO_VOICE  *get_demo_voice          (void);
144 static void         init_demo_voices        (void);
145 static void         ax_demo_callback        (void);
146 static void         ax_drop_voice_callback  (void *p);
147 static void         play_sfx                (u32 sfx);
148 
149 // for UI menus
150 static void         MNU_play_click          (DEMOWinMenuInfo *menu, u32 item);
151 static void         MNU_play_sfx            (DEMOWinMenuInfo *menu, u32 item, u32 *result);
152 static void         MNU_stop_sfx            (DEMOWinMenuInfo *menu, u32 item, u32 *result);
153 static void         MNU_stop_looping        (DEMOWinMenuInfo *menu, u32 item, u32 *result);
154 static void         ax_profile_update       (DEMOWinInfo *window);
155 
156 
157 /*---------------------------------------------------------------------------*
158  * UI Stuff
159  *---------------------------------------------------------------------------*/
160 
161 DEMOWinInfo *DebugWin;
162 DEMOWinInfo *ProfileWin;
163 
164 DEMOWinMenuItem MenuItem[] =
165 {
166     { "Sound Effect",           DEMOWIN_ITM_SEPARATOR,  NULL,             NULL },
167     { "  Noisy Drum",           DEMOWIN_ITM_NONE,       MNU_play_sfx,     NULL },
168     { "  Gunshot",              DEMOWIN_ITM_NONE,       MNU_play_sfx,     NULL },
169     { "  Voice-Man",            DEMOWIN_ITM_NONE,       MNU_play_sfx,     NULL },
170     { "  Voice-Woman",          DEMOWIN_ITM_NONE,       MNU_play_sfx,     NULL },
171     { "  Looping Strings",      DEMOWIN_ITM_NONE,       MNU_play_sfx,     NULL },
172     { " ",                      DEMOWIN_ITM_SEPARATOR,  NULL,             NULL },
173     { "Voice Control",          DEMOWIN_ITM_SEPARATOR,  NULL,             NULL },
174     { "  Stop All",             DEMOWIN_ITM_NONE,       MNU_stop_sfx,     NULL },
175     { "  Stop All Looping",     DEMOWIN_ITM_NONE,       MNU_stop_looping, NULL },
176     { " ",                      DEMOWIN_ITM_SEPARATOR,  NULL,             NULL },
177     { "",                       DEMOWIN_ITM_TERMINATOR, NULL,             NULL }
178 };
179 
180 DEMOWinMenuInfo Menu =
181 {
182     "AX Sound Pipeline Demo",   // title
183     NULL,                       // window handle
184     MenuItem,                   // list of menu items
185     10,                         // max num of items to display at a time
186     DEMOWIN_MNU_NONE,           // attribute flags
187 
188     // user callbacks
189     NULL,                       // callback for menu open event
190     MNU_play_click,             // callback for cursor move event
191     NULL,                       // callback for item select event
192     NULL,                       // callback for cancel event
193 
194     // private members
195     0, 0, 0, 0, 0
196 };
197 
198 DEMOWinMenuInfo *MenuPtr;
199 
200 /*===========================================================================*
201  *                   F U N C T I O N    D E F I N I T I O N S
202  *===========================================================================*/
203 
204 /*---------------------------------------------------------------------------*
205  * Name        : ax_profile_updatek()
206  * Description : refresh callback for AX profile window
207  * Arguments   :
208  * Returns     :
209  *---------------------------------------------------------------------------*/
210 
ax_profile_update(DEMOWinInfo * window)211 static void ax_profile_update(DEMOWinInfo *window)
212 {
213 
214     BOOL old;
215 
216     u32 i;
217 
218     u32 cpuCycles;
219     u32 userCycles;
220     u32 axCycles;
221     u32 voices;
222 
223     u32 maxCpuCycles =0;
224     u32 maxUserCycles=0;
225     u32 maxAxCycles  =0;
226     u32 maxVoices    =0;
227 
228         old = OSDisableInterrupts();
229 
230         i = AXGetProfile();
231 
232         if (i)
233         {
234             // up to 4 audio frames can complete within a 60Hz video frame
235             // so spin thru the accumulated audio frame profiles and find the peak values
236             while (i)
237             {
238                 i--;
239 
240                 cpuCycles   = (u32)(ax_profile[i].axFrameEnd      - ax_profile[i].axFrameStart);
241                 userCycles  = (u32)(ax_profile[i].userCallbackEnd - ax_profile[i].userCallbackStart);
242                 axCycles    = cpuCycles - userCycles;
243                 voices      = ax_profile[i].axNumVoices;
244 
245                 // find peak values over the last i audio frames
246                 if (cpuCycles > maxCpuCycles)     maxCpuCycles    = cpuCycles;
247                 if (userCycles > maxUserCycles)   maxUserCycles   = userCycles;
248                 if (axCycles > maxAxCycles)       maxAxCycles     = axCycles;
249                 if (voices > maxVoices)           maxVoices       = voices;
250 
251             }
252             OSRestoreInterrupts(old);
253 
254             DEMOWinPrintfXY(window, 0, 2, "Total CPU : %5.2f%%", (f32)OSTicksToNanoseconds(maxCpuCycles) / 50000);
255             DEMOWinPrintfXY(window, 0, 4, "User      : %5.2f%%", (f32)OSTicksToNanoseconds(maxUserCycles) / 50000);
256             DEMOWinPrintfXY(window, 0, 5, "AX        : %5.2f%%", (f32)OSTicksToNanoseconds(maxAxCycles) / 50000);
257             DEMOWinPrintfXY(window, 0, 7, "Voices    : %5d",    maxVoices);
258 
259         }
260 
261         OSRestoreInterrupts(old);
262 
263 } // end profile_update()
264 
265 /*---------------------------------------------------------------------------*
266  * Name        : MNU_play_click()
267  * Description : Callback for menu system, plays 'click' for cursor movement
268  * Arguments   :
269  * Returns     :
270  *---------------------------------------------------------------------------*/
271 
MNU_play_click(DEMOWinMenuInfo * menu,u32 item)272 static void MNU_play_click(DEMOWinMenuInfo *menu, u32 item)
273 {
274 
275 #pragma unused(menu)
276 #pragma unused(item)
277 
278     play_sfx(SFX_MENU);
279 
280 
281 } // end MNU_play_click()
282 
283 
284 /*---------------------------------------------------------------------------*
285  * Name        : MNU_play_sfx()
286  * Description : Play sound effect selected from menu.
287  * Arguments   :
288  * Returns     :
289  *---------------------------------------------------------------------------*/
290 
MNU_play_sfx(DEMOWinMenuInfo * menu,u32 item,u32 * result)291 static void MNU_play_sfx(DEMOWinMenuInfo *menu, u32 item, u32 *result)
292 {
293 
294 #pragma unused(menu)
295 #pragma unused(result)
296 
297     play_sfx(item);
298 
299 } // end MNU_play_sfx()
300 
301 
302 
303 /*---------------------------------------------------------------------------*
304  * Name        : MNU_stop_sfx()
305  * Description : Stops all voices. Note that voices are freed by the AX user
306  *               callback on the next frame.
307  * Arguments   :
308  * Returns     :
309  *---------------------------------------------------------------------------*/
310 
MNU_stop_sfx(DEMOWinMenuInfo * menu,u32 item,u32 * result)311 static void MNU_stop_sfx(DEMOWinMenuInfo *menu, u32 item, u32 *result)
312 {
313 #pragma unused(menu)
314 #pragma unused(item)
315 #pragma unused(result)
316 
317     u32    i;
318     BOOL old;
319 
320         old = OSDisableInterrupts();
321 
322         for (i=0; i<MAX_DEMO_VOICES; i++)
323         {
324             if (demo_voice[i].ax_voice)
325             {
326                 AXSetVoiceState(demo_voice[i].ax_voice, AX_PB_STATE_STOP);
327             }
328         }
329 
330         OSRestoreInterrupts(old);
331 
332 } // end MNU_stop_sfx()
333 
334 /*---------------------------------------------------------------------------*
335  * Name        : MNU_stop_looping()
336  * Description : Stops looped sfx only. Note that voices are freed by the
337  *               AX user callback. Note also that SPPrepareEnd() is used,
338  *               so the sound effect will play to the end (beyond the loop)
339  *               and THEN get stopped.
340  * Arguments   :
341  * Returns     :
342  *---------------------------------------------------------------------------*/
343 
MNU_stop_looping(DEMOWinMenuInfo * menu,u32 item,u32 * result)344 static void MNU_stop_looping(DEMOWinMenuInfo *menu, u32 item, u32 *result)
345 {
346 #pragma unused(menu)
347 #pragma unused(item)
348 #pragma unused(result)
349 
350     u32    i;
351     BOOL old;
352 
353         old = OSDisableInterrupts();
354 
355         for (i=0; i<MAX_DEMO_VOICES; i++)
356         {
357             if ( (demo_voice[i].ax_voice) && mISLOOPED(demo_voice[i].sp_entry) )
358             {
359                 SPPrepareEnd(demo_voice[i].sp_entry, demo_voice[i].ax_voice);
360             }
361         }
362 
363         OSRestoreInterrupts(old);
364 
365 } // end MNU_stop_looping()
366 
367 
368 
369 /*---------------------------------------------------------------------------*
370  * Name        :
371  * Description :
372  * Arguments   : None.
373  * Returns     : None.
374  *---------------------------------------------------------------------------*/
init_demo_voices()375 static void init_demo_voices()
376 {
377 
378     u32 i;
379 
380         for (i=0; i<MAX_DEMO_VOICES; i++)
381         {
382             demo_voice[i].ax_voice = NULL;
383             demo_voice[i].sp_entry = NULL;
384         }
385 
386 } // end init_demo_voices()
387 
388 
389 /*---------------------------------------------------------------------------*
390  * Name        :
391  * Description :
392  * Arguments   : None.
393  * Returns     : None.
394  *---------------------------------------------------------------------------*/
get_demo_voice()395 static DEMO_VOICE *get_demo_voice()
396 {
397 
398     u32 i;
399 
400         i=0;
401         while (i < MAX_DEMO_VOICES)
402         {
403 
404             if (NULL == demo_voice[i].ax_voice)
405             {
406                 return(&demo_voice[i]);
407             }
408             i++;
409         }
410 
411         return(NULL);
412 
413 }  // end get_demo_voice()
414 
415 /*---------------------------------------------------------------------------*
416  * Name        :
417  * Description :
418  * Arguments   : None.
419  * Returns     : None.
420  *---------------------------------------------------------------------------*/
421 
ax_demo_callback(void)422 static void ax_demo_callback(void)
423 {
424 
425     u32 i;
426 
427         for (i=0; i<MAX_DEMO_VOICES; i++)
428         {
429             if (demo_voice[i].ax_voice)
430             {
431                 if ( AX_PB_STATE_STOP == ((demo_voice[i].ax_voice)->pb.state))
432                 {
433                     MIXReleaseChannel(demo_voice[i].ax_voice);
434                     AXFreeVoice(demo_voice[i].ax_voice);
435                     demo_voice[i].ax_voice = NULL;
436                 }
437             }
438         }
439 
440 } // end ax_demo_callback()
441 
442 /*---------------------------------------------------------------------------*
443  * Name        : ax_drop_voice_callback()
444  * Description : Invoked by AX when a voice has been forciby dropped.
445  *               Must delete references to the voice from our abstraction layer
446  *               and release the associated MIXer channel.
447  * Arguments   : None.
448  * Returns     : None.
449  *---------------------------------------------------------------------------*/
450 
ax_drop_voice_callback(void * p)451 static void ax_drop_voice_callback(void *p)
452 {
453 
454     u32 i;
455 
456         // search for abstracted voice associated with low-level AX voice.
457         for (i=0; i<MAX_DEMO_VOICES; i++)
458         {
459             // found it!
460             if  ( (AXVPB *)(p) == demo_voice[i].ax_voice)
461             {
462                 // release mixer channel, delete reference to AX voice (and SP entry, just for neatness)
463                 MIXReleaseChannel(demo_voice[i].ax_voice);
464                 demo_voice[i].ax_voice = NULL;
465                 demo_voice[i].sp_entry = NULL;
466                 break;
467             }
468         }
469 
470         // freak out if the voice doesn't exist in our voice abstraction list
471         ASSERTMSG(i != MAX_DEMO_VOICES, "AXVoiceCallback: unknown voice reference!\n");
472 
473 } // end ax_demo_callback()
474 
475 /*---------------------------------------------------------------------------*
476  * Name        : play_sfx()
477  * Description :
478  * Arguments   : None.
479  * Returns     : None.
480  *---------------------------------------------------------------------------*/
481 
play_sfx(u32 sfx)482 static void play_sfx(u32 sfx)
483 {
484 
485     DEMO_VOICE *v;
486     BOOL old;
487 
488 
489         old = OSDisableInterrupts();
490 
491         v = get_demo_voice();
492         if (v)
493         {
494 
495             v->ax_voice = AXAcquireVoice(15, ax_drop_voice_callback, 0);
496             if (v->ax_voice)
497             {
498 
499                 v->sp_entry = SPGetSoundEntry(sp_table, sfx);
500 
501                 SPPrepareSound(v->sp_entry, v->ax_voice, (v->sp_entry)->sampleRate);
502 
503                 MIXInitChannel(v->ax_voice, 0, 0, -960, -960, -960, 64, 127, 0);
504                 AXSetVoiceState(v->ax_voice, AX_PB_STATE_RUN);
505 
506                 OSRestoreInterrupts(old);
507 
508             }
509             else
510             {
511                 OSRestoreInterrupts(old);
512                 DEMOWinLogPrintf(DebugWin, "SFX: AX Voice allocation failed.\n");
513             }
514 
515         }
516         else
517         {
518             OSRestoreInterrupts(old);
519             DEMOWinLogPrintf(DebugWin, "(No free voices in abstraction layer)\n");
520         }
521 
522 } // end play_sfx()
523 
524 #ifdef HOLLYWOOD_REV
525 /*---------------------------------------------------------------------------*
526  *---------------------------------------------------------------------------*/
LoadFileIntoRam(char * path)527 static void* LoadFileIntoRam(char *path)
528 {
529     DVDFileInfo handle;
530     u32         round_length;
531     s32         read_length;
532     void        *buffer;
533 
534     // Open File
535     if (!DVDOpen(path, &handle))
536     {
537         OSReport("WARNING! Failed to open %s\n", path);
538         return NULL;
539     }
540 
541     // Make sure file length is not 0
542     if (DVDGetLength(&handle) == 0)
543     {
544         OSReport("WARNING! File length is 0\n");
545         return NULL;
546     }
547 
548     round_length = OSRoundUp32B(DVDGetLength(&handle));
549     buffer       = MEMAllocFromExpHeapEx(hExpHeap, round_length,  32);
550 
551     // Make sure we got a buffer
552     if (buffer == NULL)
553     {
554         OSReport("WARNING! Unable to allocate buffer\n");
555         return NULL;
556     }
557 
558     // Read Files
559     read_length = DVDRead(&handle, buffer, (s32)(round_length), 0);
560 
561     // Make sure we read the file correctly
562     if (read_length <= 0)
563     {
564         OSReport("WARNING! File lenght is wrong\n");
565         return NULL;
566     }
567 
568     return buffer;
569 }
570 #endif
571 
572 /*---------------------------------------------------------------------------*
573  * Name        : main()
574  * Description : Hold on to your seatbelts!
575  * Arguments   : None.
576  * Returns     : None.
577  *---------------------------------------------------------------------------*/
main(void)578 void main(void)
579 {
580 #ifdef HOLLYWOOD_REV
581     void       *arenaMem2Lo;
582     void       *arenaMem2Hi;
583     u8         *zeroBuffer;
584 #endif
585 
586     // initialize system
587     DEMOInit(NULL);
588     DEMOWinInit();
589 
590 #ifndef HOLLYWOOD_REV
591     // initialize ARAM w/ stack allocator
592     ARInit(aramMemArray, MAX_ARAM_BLOCKS);
593     ARQInit();
594 #else
595     // initialize Exp Heap on MEM2
596     arenaMem2Lo = OSGetMEM2ArenaLo();
597     arenaMem2Hi = OSGetMEM2ArenaHi();
598     hExpHeap    = MEMCreateExpHeap(arenaMem2Lo, (u32)arenaMem2Hi - (u32) arenaMem2Lo);
599 #endif
600 
601     // initialize AI subsystem
602     AIInit(NULL);
603 
604     // initialize AX audio system and MIXer application
605     AXInit();
606     MIXInit();
607 
608 #ifndef HOLLYWOOD_REV
609     // -----------------------------------------------------------
610     // Initialize ARAM audio manager (AM)
611     // -----------------------------------------------------------
612 
613     // get a block from the AR ARAM allocator
614     aramUserBase = ARAlloc(AUDIO_BLOCK_SIZE_BYTES);
615 
616     // initialize AM with the block
617     AMInit(aramUserBase, AUDIO_BLOCK_SIZE_BYTES);
618 
619     // retrieve start of zero buffer, as created by AM
620     aramZeroBase = AMGetZeroBuffer();
621 #endif
622 
623     // -----------------------------------------------------------
624     // Load SP data!
625     // -----------------------------------------------------------
626 #ifndef HOLLYWOOD_REV
627     // Retrieve sound table
628     sp_table = (SPSoundTable *)AMLoadFile(SPT_FILE, NULL);
629 
630     // Load sound effects into ARAM
631     aramUserBase = AMPushBuffered(SPD_FILE, (void *)xfer_buffer, XFER_BUFFER_SIZE_BYTES);
632 #else
633     // Load sound table
634     sp_table = LoadFileIntoRam(SPT_FILE);
635 
636     // Load sound effects
637     sp_data  = LoadFileIntoRam(SPD_FILE);
638 #endif
639 
640 #ifdef HOLLYWOOD_REV
641     // -----------------------------------------------------------
642     // Prepare Zero Buffer
643     // -----------------------------------------------------------
644     zeroBuffer = MEMAllocFromExpHeapEx(hExpHeap, ZEROBUFFER_BYTES, 8);
645     memset(zeroBuffer, 0, ZEROBUFFER_BYTES);
646     DCFlushRange(zeroBuffer, ZEROBUFFER_BYTES);
647 #endif
648 
649     // -----------------------------------------------------------
650     // initialize sound table!
651     // -----------------------------------------------------------
652 #ifndef HOLLYWOOD_REV
653     SPInitSoundTable(sp_table, (u8*)aramUserBase, (u8*)aramZeroBase);
654 #else
655     SPInitSoundTable(sp_table, sp_data, zeroBuffer);
656 #endif
657 
658     // -----------------------------------------------------------
659     // Initialize demo voice abstraction layer
660     // -----------------------------------------------------------
661     init_demo_voices();
662     AXRegisterCallback(ax_demo_callback);
663 
664     // initialize profiling for AX
665     AXInitProfile(ax_profile, NUM_AX_PROFILE_FRAMES);
666 
667     // -----------------------------------------------------------
668     // Invoke menu system!
669     // -----------------------------------------------------------
670 
671     MenuPtr    = DEMOWinCreateMenuWindow(&Menu, 20, 100);
672     DebugWin   = DEMOWinCreateWindow((u16)(MenuPtr->handle->x2+10), 20, 620, 440, "Debug", 1024, NULL);
673     ProfileWin = DEMOWinCreateWindow((u16)(MenuPtr->handle->x1), (u16)(MenuPtr->handle->y2+10), (u16)(MenuPtr->handle->x2), (u16)(MenuPtr->handle->y2+160), "AX Profile", 0, ax_profile_update);
674 
675     DEMOWinOpenWindow(DebugWin);
676     DEMOWinOpenWindow(ProfileWin);
677 
678     DEMOWinLogPrintf(DebugWin, "-------------------------------\n");
679     DEMOWinLogPrintf(DebugWin, "AX Sound Pipeline Demo!\n");
680     DEMOWinLogPrintf(DebugWin, "-------------------------------\n");
681 
682     while (1)
683     {
684 
685         DEMOWinMenu(MenuPtr);
686 
687     }
688 
689 } // end main()
690