home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume28 / cdreader / part01 / cdreader.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-03-15  |  33.9 KB  |  1,198 lines

  1. /*
  2.  *    cdreader - Plays an audio CD through the SGI Audio Processor.
  3.  *    You can play CDs through the monophonic speaker on your SGI Indigo,
  4.  *    or you can connect an amp to the line-out jacks for some
  5.  *    REAL quality sound.
  6.  *
  7.  *    Original Author:  Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
  8.  *    This software is Copyright (c) 1992 by Patrick J. Wolfe.
  9.  *    See the end of this file for the complete copyright notice.
  10.  */
  11.  
  12. #define VERSION "1.4"
  13.  
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <sys/types.h>
  17.     /* for errno */
  18. #include <errno.h>
  19.     /* for signal() */
  20. #include <signal.h>
  21.     /* for getopt() */
  22. #include <getopt.h>
  23.     /* for CD*() routines */
  24. #include <cdaudio.h>
  25.     /* for AL*() routines */
  26. #include <audio.h>
  27.     /* for schedctl() */
  28. #include <limits.h>
  29. #include <sys/prctl.h>
  30. #include <sys/schedctl.h>
  31.     /* for X11/Motif interface */
  32. #include <X11/Intrinsic.h>
  33. #include <Xm/Xm.h>
  34. #include <Xm/CascadeB.h>
  35. #include <Xm/Frame.h>
  36. #include <Xm/MainW.h>
  37. #include <Xm/PushB.h>
  38. #include <Xm/PushBG.h>
  39. #include <Xm/RowColumn.h>
  40. #include <Xm/MessageB.h>
  41. #include <Xm/Form.h>
  42.  
  43. #ifdef WATCH_ABUF
  44.     /* for setitimer */
  45. #include <sys/time.h>
  46. int monitor_interval = 15;    /* number of seconds between abufs left reports */
  47. int show_bufsleft = 0;        /* flag to indicate that it's time to report */
  48. int total_samps = 0;        /* number of samples the audio buffer will hold */
  49. #endif /* WATCH_ABUF */
  50.  
  51. /*
  52.  *    Most of the common messages are here, for easy customization and
  53.  *    translation for foreign languages.  We're not ALL ignorant bastards.
  54.  */
  55.  
  56.     /* button labels */
  57. #define PLAY_NAME "Play"
  58. #define PREV_NAME "Prev"
  59. #define PAUSE_NAME "Pause"
  60. #define NEXT_NAME "Next"
  61. #define SHUFFLE_NAME "Shuffle"
  62. #define TRACKS_NAME "Tracks"
  63. #define STOP_NAME "Stop"
  64. #define EJECT_NAME "Eject"
  65. #define QUIT_NAME "Quit"
  66. #define INFO_NAME "Info"
  67. #define HELP_NAME "Help"
  68.  
  69.     /* status messages */
  70. char *Playing_Msg = "Playing Track";
  71. char *Stopped_Msg = "Stopped";
  72. char *Paused_Msg = "Paused at Track";
  73. char *Total_Msg = "Total time on disk";
  74. char *Track_Word = "Track";
  75. char *Length_Word = "Length";
  76.  
  77.     /* error messages */
  78. char *DISC_NOT_READY = "Disc Not Ready";
  79. char *CANNOT_READ_SCSI = "\
  80. Your cheapo CDrom drive does NOT support reading audio data\n\
  81. across the SCSI bus.  You should have bought one from SGI!";
  82. char *TOO_MANY_TRACKS = "Too many audio tracks on this disc!\nrecompile with larger MAX_TRACK_INFO.";
  83. char *CDOPEN_FAILED = "CDopen failed\nCannot Open CDrom device";
  84. char *CDGETSTATUS_FAILED = "CDgetstatus failed\nCannot get status of CDrom device";
  85. char *CDGETTRACKINFO_FAILED = "CDgettrackinfo failed";
  86. char *CDSEEKTRACK_FAILED = "CDseektrack failed\nCannot find track";
  87. char *CDEJECT_FAILED = "cannot eject disc - not stopped";
  88.  
  89.  
  90.  
  91. #define SAMPLES_PER_FRAME    (CDDA_DATASIZE/2)
  92.  
  93. /* valid states for main process loop control */
  94. #define STOPPED        0    /* we are stopped */
  95. #define PAUSED        1    /* we were playing, and are now paused */
  96. #define PLAYING        2    /* we are playing music */
  97. #define STARTING    3    /* start playing at beginning of the disk */
  98. #define CONTINUE    4    /* start playing where you left off */
  99. #define STOPPING    5    /* we were playing, and are about to stop */
  100. #define PAUSING        6    /* we were playing, and are about to pause */
  101. #define START_TRACK    9    /* start playing at the beginning of the current track */
  102.  
  103. /* maximum number of tracks to store info for */
  104. /* I *have* seen up to 28 tracks on a single CD, but none longer */
  105. #define MAX_TRACK_INFO    64
  106.  
  107.     /* audio stuff */
  108. ALport audio_port = NULL;
  109. CDPLAYER *cd_device = NULL;
  110. CDPARSER *cd_parser = NULL;
  111. CDFRAME *cd_buffer = NULL;
  112.  
  113.     /* cd stuff */
  114. struct cdinformation {
  115.     short length_min;            /* track length */
  116.     short length_sec;
  117.     } track_info[MAX_TRACK_INFO];
  118. int first_track = 0;                /* first track on the disk */
  119. int last_track = 0;                /* index into track_info for entry AFTER last one we have info for */
  120. int current_track = -1;                /* number of track currently playing */
  121. int total_min = 0;                /* minutes part of total running time on current disk */
  122. int total_sec = 0;                /* second part of total running time on current disk */
  123. int play_index = -1;                /* index into playlist for shuffle mode, -1 means that shuffle is not on */
  124. int play_list[MAX_TRACK_INFO];            /* order of tracks to play */
  125. int cd_readsize = 12;                /* the normal number of frames to read at one time (CDbestreadsize fills this in) */
  126. int cd_init_readsize = 200;            /* initial number of frames to read at the beginning of the disc */
  127. int status = STOPPING;                /* main process loop status */
  128.  
  129.     /* X11 stuff */
  130. XtAppContext    appcon;
  131. Widget    main_window,
  132.     label,
  133.     play_button,
  134.     prev_button,
  135.     pause_button,
  136.     next_button,
  137.     shuffle_button,
  138.     tracks_button,
  139.     info_button,
  140.     stop_button,
  141.     eject_button;
  142. static XmStringCharSet charset = (XmStringCharSet) XmSTRING_DEFAULT_CHARSET;
  143.  
  144.  
  145. Display_Warning (message)
  146. char *message;
  147. {
  148. Widget button;
  149. Widget warning_box;
  150. XmString title_string = NULL;
  151. XmString message_string = NULL;
  152. Arg args[4];
  153. register int n;
  154.  
  155. message_string = XmStringCreateLtoR (message, charset);
  156. title_string = XmStringCreateLtoR ("Cdreader Warning!", charset);
  157.  
  158. n = 0;
  159. XtSetArg (args[n], XmNdialogTitle, title_string); n++;
  160. XtSetArg (args[n], XmNmessageString, message_string); n++;
  161. warning_box = XmCreateWarningDialog (main_window, "warning", args, n);
  162. button = XmMessageBoxGetChild (warning_box, XmDIALOG_CANCEL_BUTTON);
  163. XtUnmanageChild (button);
  164. button = XmMessageBoxGetChild (warning_box, XmDIALOG_HELP_BUTTON);
  165. XtUnmanageChild (button);
  166. if (title_string) XtFree (title_string);
  167. if (message_string) XtFree (message_string);
  168. XtManageChild (warning_box);
  169. }
  170.  
  171.  
  172. set_message (message)
  173. char *message;
  174. {
  175. XmString label_string;
  176. Arg    args[30];
  177. int ctr;
  178.  
  179. ctr = 0;
  180. label_string = XmStringCreate (message, charset);
  181. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  182. XtSetValues (label, args, ctr);
  183. XmStringFree (label_string);
  184. }
  185.  
  186.  
  187. int
  188. get_track_info ()
  189. {
  190. CDSTATUS cd_status;
  191. CDTRACKINFO cd_info;
  192.  
  193. if ((cd_device == NULL) && ((cd_device = CDopen(0, "r")) == NULL)) {
  194.     Display_Warning (CDOPEN_FAILED);
  195.     return (1);
  196.     }
  197.  
  198. /* display disk info */
  199. if (CDgetstatus(cd_device, &cd_status) == 0) {
  200.     status = STOPPING;
  201.     Display_Warning (CDGETSTATUS_FAILED);
  202.     return (1);
  203.     }
  204. /* don't start unless it's ready */
  205. if (cd_status.state != CD_READY) {
  206.     status = STOPPING;
  207.     Display_Warning (DISC_NOT_READY);
  208.     return (1);
  209.     }
  210. if (! cd_status.scsi_audio) {
  211.     status = STOPPING;
  212.     Display_Warning (CANNOT_READ_SCSI);
  213.     return (1);
  214.     }
  215. total_min = cd_status.total_min;
  216. total_sec = cd_status.total_sec;
  217.  
  218. /* get track information */
  219. for (first_track = last_track = cd_status.first;
  220.   (last_track <= cd_status.last) && (last_track < MAX_TRACK_INFO);
  221.   last_track++) {
  222.     if (CDgettrackinfo(cd_device, last_track, &cd_info) == 0) {
  223.         status = STOPPING;
  224.         Display_Warning (CDGETTRACKINFO_FAILED);
  225.         return (1);
  226.         }
  227.     track_info[last_track].length_min = cd_info.total_min;
  228.     track_info[last_track].length_sec = cd_info.total_sec;
  229.     }
  230. if (last_track >= MAX_TRACK_INFO) {
  231.     Display_Warning (TOO_MANY_TRACKS);
  232.     return (1);
  233.     }
  234.  
  235. /* find the best number of frames to read at a time */
  236. cd_readsize = CDbestreadsize (cd_device);
  237.  
  238. return (0);
  239. }
  240.  
  241.  
  242. /* called as signal handler, and when the quit button is pressed */
  243. void
  244. quit_pgm ()
  245. {
  246. CDdeleteparser(cd_parser);    /* free parser memory */
  247. if (cd_device != NULL) {
  248.     CDclose(cd_device);        /* close CD player port */
  249.     }
  250. if (audio_port != NULL) {
  251.     ALcloseport(audio_port);    /* close audio port */
  252.     }
  253. exit (0);
  254. }
  255.  
  256.  
  257. /* called by pressing the EJECT button */
  258. void
  259. eject_callback (w, client_data, call_data)
  260. Widget w;
  261. caddr_t client_data;    /* unused */
  262. caddr_t call_data;    /* unused */
  263. {
  264. if (status == STOPPED) {
  265.     if ((cd_device == NULL) && ((cd_device = CDopen(0, "r")) == NULL)) {
  266.         Display_Warning (CDOPEN_FAILED);
  267.         }
  268.     else    {
  269.         CDeject(cd_device);
  270.         CDclose(cd_device);
  271.         cd_device = NULL;
  272.         }
  273.     }
  274. else    {
  275.     Display_Warning (CDEJECT_FAILED);
  276.     }
  277. }
  278.  
  279.  
  280. /* called by pressing the STOP button */
  281. void
  282. stop_callback (w, client_data, call_data)
  283. Widget w;
  284. caddr_t client_data;    /* unused */
  285. caddr_t call_data;    /* unused */
  286. {
  287. play_index = -1;
  288. status = STOPPING;
  289. }
  290.  
  291.  
  292. /* called by pressing the PAUSE button */
  293. void
  294. pause_callback (w, client_data, call_data)
  295. Widget w;
  296. caddr_t client_data;    /* unused */
  297. caddr_t call_data;    /* unused */
  298. {
  299. if (status == PLAYING) {
  300.     status = PAUSING;
  301.     }
  302. }
  303.  
  304.  
  305. /* called by pressing the PLAY button */
  306. void
  307. play_callback (w, client_data, call_data)
  308. Widget w;
  309. caddr_t client_data;    /* unused */
  310. caddr_t call_data;    /* unused */
  311. {
  312. if (status == STOPPED) {
  313.     status = STARTING;
  314.     }
  315. else if (status == PAUSED) {
  316.     status = CONTINUE;
  317.     }
  318. }
  319.  
  320.  
  321. /* called by pressing the PREV button */
  322. void
  323. prev_callback (w, client_data, call_data)
  324. Widget w;
  325. caddr_t client_data;    /* unused */
  326. caddr_t call_data;    /* unused */
  327. {
  328. char message[64];
  329.  
  330. if ((cd_device == NULL) && get_track_info()) {        /* opens cd_device and gets track_info[] */
  331.     status = STOPPING;
  332.     return;
  333.     }
  334.  
  335. if ((status == PLAYING) || (status == PAUSED) || (status == STOPPED)) {
  336.     if (play_index == -1) {
  337.         current_track--;
  338.         if (current_track < first_track) {
  339.             current_track = first_track;
  340.             }
  341.         }
  342.     else    { /* shuffling */
  343.         play_index--;
  344.         if (play_index < first_track) {
  345.             play_index = first_track;
  346.             }
  347.         current_track = play_list[play_index];
  348.         }
  349.  
  350.     if (CDseektrack(cd_device, current_track) == -1) {
  351.         (void) sprintf (message, "%s %d", CDSEEKTRACK_FAILED, current_track);
  352.         Display_Warning (message);
  353.         status = STOPPING;
  354.         }
  355.     else if (status == PLAYING) {
  356.         status = CONTINUE;
  357.         }
  358.     else    {
  359.         status = PAUSING;
  360.         }
  361.     }
  362. }
  363.  
  364.  
  365. /* called by pressing the NEXT button */
  366. void
  367. next_callback (w, client_data, call_data)
  368. Widget w;
  369. caddr_t client_data;    /* unused */
  370. caddr_t call_data;    /* unused */
  371. {
  372. char message[64];
  373.  
  374. if ((cd_device == NULL) && get_track_info()) {        /* opens cd_device and gets track_info[] */
  375.     status = STOPPING;
  376.     return;
  377.     }
  378.  
  379. if ((status == PLAYING) || (status == PAUSED) || (status == STOPPED)) {
  380.     if (play_index < 0) {
  381.         current_track++;
  382.         if (current_track >= last_track) {
  383.             current_track = first_track;
  384.             status = STOPPING;
  385.             return;
  386.             }
  387.         }
  388.     else    {    /* shuffling */
  389.         play_index++;
  390.         if (play_index >= last_track) {
  391.             current_track = first_track;
  392.             status = STOPPING;
  393.             return;
  394.             }
  395.         current_track = play_list[play_index];
  396.         }
  397.  
  398.     if (CDseektrack(cd_device, current_track) == -1) {
  399.         (void) sprintf (message, "%s %d", CDSEEKTRACK_FAILED, current_track);
  400.         Display_Warning (message);
  401.         status = STOPPING;
  402.         }
  403.     else if (status == PLAYING) {
  404.         status = CONTINUE;
  405.         }
  406.     else    {
  407.         status = PAUSING;
  408.         }
  409.     }
  410. }
  411.  
  412.  
  413. /* called by pressing the SHUFFLE button
  414.  * plays all tracks on the disk in a random order
  415.  */
  416. void
  417. shuffle_callback (w, client_data, call_data)
  418. Widget w;
  419. caddr_t client_data;    /* unused */
  420. caddr_t call_data;    /* unused */
  421. {
  422. double jdb;
  423. int i, j, m, d = 1;
  424. int played[MAX_TRACK_INFO];
  425.  
  426. if (get_track_info()) {        /* opens cd_device and gets track_info[] */
  427.     status = STOPPING;
  428.     return;
  429.     }
  430.  
  431. m = last_track - 1;
  432.  
  433. /* initialize played array to -1's, indicating track hasn't been selected to play yet */
  434. for (i = first_track; i < last_track; i++) {
  435.     played[i] = 0;
  436.     }
  437.  
  438. /* seed is our process id */
  439. srand ((u_int) getpid());
  440.  
  441. for (i = first_track; i < last_track; i++) {
  442.     j = rand( );
  443.     jdb = (double)j / (double)(RAND_MAX + 1);
  444.     j = (int)(jdb * m) + 1;
  445.     for (; played[j]; j += d) {
  446.         if (j < first_track) {
  447.             j = last_track;
  448.             }
  449.         else if (j >= last_track) {
  450.             j = first_track;
  451.             }
  452.         }
  453.     d = d * -1;    /* switch direction */
  454.     play_list[i] = j;
  455.     played[j] = 1;
  456.     }
  457.  
  458. play_index = first_track;
  459. current_track = play_list[play_index];
  460. status = START_TRACK;
  461. }
  462.  
  463.  
  464. /* called by pressing the TRACKS button */
  465. /* show info about the entire disk */
  466. void
  467. tracks_callback (w, client_data, call_data)
  468. Widget w;
  469. caddr_t client_data;    /* unused */
  470. caddr_t call_data;    /* unused */
  471. {
  472. Widget button;
  473. Widget message_box;
  474. XmString title_string = NULL;
  475. XmString message_string = NULL;
  476. XmString button_string = NULL;
  477. Arg args[4];
  478. register int n;
  479. int track;
  480. char message[4096];
  481. char line[256];
  482.  
  483. if ((status == STOPPED) && get_track_info() ) {
  484.     return;    /* if stopped, try to re-read the track info - might be a new disc */
  485.     }
  486. if ((status != PLAYING) && (status != PAUSED) && (status != STOPPED)) {
  487.     return;        /* ignore this button during odd status changes */
  488.     }
  489. (void) sprintf (message, "%s = %02d:%02d\n\n", Total_Msg, total_min, total_sec);
  490.  
  491. /* show info about each track */
  492. for (track = first_track; track < last_track; track++) {
  493.     (void) sprintf (line, "\t%s %2d:  %s %02d:%02d\n", Track_Word, track, Length_Word,
  494.         track_info[track].length_min, track_info[track].length_sec);
  495.     (void) strcat (message, line);
  496.     }
  497.  
  498. title_string = XmStringCreateLtoR ("Cdreader Track List", charset);
  499. button_string = XmStringCreateLtoR ("Close", charset);
  500. message_string = XmStringCreateLtoR (message, charset);
  501.  
  502. n = 0;
  503. XtSetArg (args[n], XmNdialogTitle, title_string); n++;
  504. XtSetArg (args[n], XmNokLabelString, button_string); n++;
  505. XtSetArg (args[n], XmNmessageString, message_string); n++;
  506. message_box = XmCreateMessageDialog (main_window, "tracks", args, n);
  507. button = XmMessageBoxGetChild (message_box, XmDIALOG_CANCEL_BUTTON);
  508. XtUnmanageChild (button);
  509. button = XmMessageBoxGetChild (message_box, XmDIALOG_HELP_BUTTON);
  510. XtUnmanageChild (button);
  511. if (title_string) XtFree (title_string);
  512. if (button_string) XtFree (button_string);
  513. if (message_string) XtFree (message_string);
  514. XtManageChild (message_box);
  515. }
  516.  
  517.  
  518. /* called by pressing the INFO button */
  519. void
  520. info_callback (w, client_data, call_data)
  521. Widget w;
  522. caddr_t client_data;    /* unused */
  523. caddr_t call_data;    /* unused */
  524. {
  525. Widget button;
  526. Widget message_box;
  527. char message[2048];
  528. XmString title_string = NULL;
  529. XmString message_string = NULL;
  530. XmString button_string = NULL;
  531. Arg args[4];
  532. register int n;
  533.  
  534. (void) sprintf (message, "\
  535. Cdreader V%s plays an audio compact disc loaded in an SGI cdrom drive through\n\
  536. the Audio Processor.\n\
  537. \n\
  538. Cdreader uses the libcdaudio routines to read the digital data directly from\n\
  539. the cdrom drive, and plays it through the Audio Processor.  You can listen on\n\
  540. the Indigo's speaker, or even better, buy a cable with a stereo mini-plug on\n\
  541. one end, and two phono plugs on the other, and connect from your Indigo's\n\
  542. line out jack to your stereo amplifier's cd in or tape in jacks.\n\
  543. \n\
  544. Cdreader will probably only work with an SGI's cdrom drive and under release\n\
  545. 4.0.1 (or later) of the Irix operating system.  The program will tell you if\n\
  546. your cdrom drive doesn't support reading audio over the SCSI bus.  It uses\n\
  547. about 5-6%% of the cpu, and 4%% of the SCSI bus bandwidth.  Use of other\n\
  548. programs which change the audio processor's parameters is discouraged (but\n\
  549. sure can make you chuckle).\n\
  550. \n\
  551. Original Author:\n\
  552. \tPatrick Wolfe\n\
  553. \tSystem Programmer/Operations Manager\n\
  554. \tKuck & Associates\n\
  555. \t1906 Fox Drive\n\
  556. \tChampaign, IL 61820\n\
  557. \tInternet:  pwolfe@kai.com\n\
  558. \tUUCP:      uunet!kailand!pwolfe\n\
  559. \tvoice:     (217) 356-2288\n\
  560. \tFAX:       (217) 356-5199\n\
  561. copyright (c) 1992 Patrick J. Wolfe\n", VERSION);
  562.  
  563. message_string = XmStringCreateLtoR (message, charset);
  564. button_string = XmStringCreateLtoR ("Close", charset);
  565. title_string = XmStringCreateLtoR ("Cdreader Information", charset);
  566.  
  567. n = 0;
  568. XtSetArg (args[n], XmNdialogTitle, title_string); n++;
  569. XtSetArg (args[n], XmNokLabelString, button_string); n++;
  570. XtSetArg (args[n], XmNmessageString, message_string); n++;
  571. message_box = XmCreateMessageDialog (w, "credit", args, n);
  572. button = XmMessageBoxGetChild (message_box, XmDIALOG_CANCEL_BUTTON);
  573. XtUnmanageChild (button);
  574. button = XmMessageBoxGetChild (message_box, XmDIALOG_HELP_BUTTON);
  575. XtUnmanageChild (button);
  576. if (title_string) XtFree (title_string);
  577. if (button_string) XtFree (button_string);
  578. if (message_string) XtFree (message_string);
  579. XtManageChild (message_box);
  580. }
  581.  
  582.  
  583. /* called by pressing the HELP button */
  584. void
  585. help_callback (w, client_data, call_data)
  586. Widget w;
  587. caddr_t client_data;    /* unused */
  588. caddr_t call_data;    /* unused */
  589. {
  590. Widget button;
  591. Widget message_box;
  592. char message[2048];
  593. XmString title_string = NULL;
  594. XmString message_string = NULL;
  595. XmString button_string = NULL;
  596. Arg args[4];
  597. register int n;
  598.  
  599. (void) sprintf (message, "\
  600. Cdreader V%s plays an audio compact disc loaded in an SGI cdrom drive through\n\
  601. the Audio Processor.\n\n\
  602. To get started, load an audio CD in the caddy, and insert it into your cd drive\n\
  603. with the clear side of the caddy facing up.\n\n\
  604. The buttons function as follows:\n\
  605. \tPlay    - plays an audio disk\n\
  606. \tShuffle - plays the whole disk in a random order\n\
  607. \tStop    - stops playback\n\
  608. \tPause   - suspends playback - press Play to continue\n\
  609. \tNext    - selects the next track\n\
  610. \tPrev    - selects the previous track\n\
  611. \tTracks  - displays a table of tracks and their lengths\n\n\
  612. \tEject   - ejects the disk from the cdrom drive\n\
  613. The buttons are context sensitive, for example, the eject button won't function\n\
  614. unless the program is in the \"Stopped\" state.  Buttons are are not active will\n\
  615. appear dimly shaded.\n\n\
  616. Near the top of the window is a menu bar.  On the bar are three buttons:\n\
  617. \tQuit    - terminates the program\n\
  618. \tInfo    - give the author some credit!\n\
  619. \tHelp    - uh, you're looking at it\n\
  620. ", VERSION);
  621.  
  622. message_string = XmStringCreateLtoR (message, charset);
  623. button_string = XmStringCreateLtoR ("Close", charset);
  624. title_string = XmStringCreateLtoR ("Cdreader Help", charset);
  625.  
  626. n = 0;
  627. XtSetArg (args[n], XmNdialogTitle, title_string); n++;
  628. XtSetArg (args[n], XmNokLabelString, button_string); n++;
  629. XtSetArg (args[n], XmNmessageString, message_string); n++;
  630. message_box = XmCreateMessageDialog (w, "helpbox", args, n);
  631. button = XmMessageBoxGetChild (message_box, XmDIALOG_CANCEL_BUTTON);
  632. XtUnmanageChild (button);
  633. button = XmMessageBoxGetChild (message_box, XmDIALOG_HELP_BUTTON);
  634. XtUnmanageChild (button);
  635. if (title_string) XtFree (title_string);
  636. if (button_string) XtFree (button_string);
  637. if (message_string) XtFree (message_string);
  638. XtManageChild (message_box);
  639. }
  640.  
  641.  
  642. /* called only when the program (track) number changes */
  643. void
  644. cd_pnum_callback (arg, type, data)
  645. int arg;
  646. CDDATATYPES type;
  647. struct cdprognum *data;
  648. {
  649. char message[32];
  650.  
  651. if (play_index == -1) {
  652.     current_track = data->value;
  653.     (void) sprintf (message, "%s %d - %s %2d:%02d", Playing_Msg, current_track, Length_Word,
  654.         track_info[current_track].length_min,
  655.         track_info[current_track].length_sec);
  656.     set_message (message);
  657.     }
  658. else if (data->value != current_track) {    /* shuffling and the track changed */
  659.     play_index++;
  660.     if (play_index >= last_track) {
  661.         status = STOPPING;
  662.         }
  663.     else    {
  664.         current_track = play_list[play_index];
  665.         status = START_TRACK;
  666.         }
  667.     }
  668. }
  669.  
  670.  
  671. /* called for every frame - data is already byte swapped and de-emphasized */
  672. void
  673. cd_audio_callback (arg, type, data)
  674. int arg;
  675. CDDATATYPES type;
  676. void *data;
  677. {
  678. ALwritesamps (audio_port, data, SAMPLES_PER_FRAME);
  679. }
  680.  
  681.  
  682. int
  683. init_audio ()
  684. {
  685. ALconfig aconfig;
  686. long pvbuf[6];
  687.  
  688. if (audio_port == NULL) {
  689.     /* initialize the audio port */
  690.     aconfig = ALnewconfig ();
  691.  
  692.     /*
  693.      * create the maximum size audio buffer we can,
  694.      * in another attempt to avoid pauses in the music.
  695.      */
  696.     ALsetqueuesize (aconfig, SAMPLES_PER_FRAME * cd_init_readsize);
  697.  
  698. #ifdef WATCH_ABUF
  699.     total_samps = SAMPLES_PER_FRAME * cd_init_readsize;
  700.     printf ("allocating an audio buffer that can hold %d samples\n", total_samps);
  701. #endif /* WATCH_ABUF */
  702.  
  703.     /*
  704.      * set the sample to 16 bit width and stereo.  Yes, I know it these are the
  705.      * defaults ... TODAY, but I've gotten into trouble before by assuming the
  706.      * defaults would never change change.  Besides, it doesn't hurt.
  707.      */
  708.     ALsetwidth (aconfig, AL_SAMPLE_16);
  709.     ALsetchannels (aconfig, AL_STEREO);
  710.  
  711.     audio_port = ALopenport ("cdreader", "w", aconfig);
  712.  
  713.     /* free audio port config buffer immediately */
  714.     ALfreeconfig(aconfig);
  715.  
  716.     if (audio_port == NULL) {
  717.         Display_Warning ("Could not open a port to the Audio Processor!");
  718.         return (1);
  719.         }
  720.  
  721.     /* set audio port output sampling rate to 44.1 kHz */
  722.     pvbuf[0] = AL_OUTPUT_RATE;
  723.     pvbuf[1] = AL_RATE_44100;
  724.     ALsetparams (AL_DEFAULT_DEVICE, pvbuf, 2);
  725.     }
  726. return (0);
  727. }
  728.  
  729.  
  730. int
  731. init_cd ()
  732. {
  733. /* allocate a buffer to read CD data into */
  734. if (cd_buffer == (CDFRAME *) NULL) {
  735.     cd_buffer = (CDFRAME *) malloc (cd_init_readsize * CDDA_BLOCKSIZE);
  736.     if (cd_buffer == (CDFRAME *) NULL) {
  737.         Display_Warning ("cannot allocate enough memory for a cd digital data buffer");
  738.         return (1);
  739.         }
  740.     }
  741.  
  742. /* create parser structure */
  743. if (cd_parser == NULL) {
  744.     cd_parser = CDcreateparser ();
  745.     if (cd_parser == NULL) {
  746.         Display_Warning ("SERIOUS ERROR!\nCDcreateparser failed");
  747.         return (1);
  748.         }
  749.  
  750.     /* initialize parser structure */
  751.     CDresetparser(cd_parser);
  752.  
  753.     /* define callback routines for CDparseframe() */
  754.     CDsetcallback (cd_parser, cd_audio, cd_audio_callback, 0);
  755.     CDsetcallback (cd_parser, cd_pnum, cd_pnum_callback, 0);
  756.     }
  757. return (0);
  758. }
  759.  
  760.  
  761. void
  762. init_motif (argc, argv)
  763. int argc;
  764. char **argv;
  765. {
  766. Display *display;
  767. Widget    app_shell, menu_bar, cascade, frame, form, row_column;
  768. XmString label_string;
  769. Arg args[30];
  770. int ctr;
  771.  
  772. XtToolkitInitialize ();
  773. appcon = XtCreateApplicationContext ();
  774. display = XtOpenDisplay (appcon, NULL, "cdreader", "Cdreader", NULL, 0, &argc, argv);
  775. if (!display) {
  776.     XtWarning ("cdreader: Can't open your X display, exiting...");
  777.     exit (0);
  778.     }
  779.  
  780. app_shell = XtAppCreateShell ("cdreader", "Cdreader", applicationShellWidgetClass, display, NULL, 0);
  781.  
  782. /* XtGetApplicationResources (app_shell, &AppData, resources, XtNumber(resources), NULL, 0); */
  783.  
  784. ctr = 0;
  785. main_window = XmCreateMainWindow (app_shell, "main1", args, ctr);
  786. XtManageChild (main_window);
  787.  
  788. ctr = 0;
  789. menu_bar = XmCreateMenuBar (main_window, "menu_bar", args, ctr);
  790. XtManageChild (menu_bar);
  791.  
  792. ctr = 0;
  793. cascade = XmCreateCascadeButton (menu_bar, "Quit", args, ctr);
  794. XtManageChild (cascade);
  795. XtAddCallback (cascade, XmNactivateCallback, quit_pgm, NULL);
  796.  
  797. ctr = 0;
  798. cascade = XmCreateCascadeButton (menu_bar, "Info", args, ctr);
  799. XtManageChild (cascade);
  800. XtAddCallback (cascade, XmNactivateCallback, info_callback, NULL);
  801.  
  802. ctr = 0;
  803. cascade = XmCreateCascadeButton (menu_bar, "Help", args, ctr);
  804. XtManageChild (cascade);
  805. XtAddCallback (cascade, XmNactivateCallback, help_callback, NULL);
  806.  
  807. /* let the menu bar know who's the help button around here */
  808. ctr = 0;
  809. XtSetArg (args[ctr], XmNmenuHelpWidget, cascade); ctr++;
  810. XtSetValues (menu_bar, args, ctr);
  811.  
  812. /* create form to hold everything */
  813. ctr = 0;
  814. form = XmCreateForm (main_window, "form", args, ctr);
  815. XtManageChild (form);
  816.  
  817. /* create label gadget inside frame */
  818. ctr = 0;
  819. label_string = XmStringCreate ("Stopped", charset);
  820. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  821. XtSetArg (args[ctr], XmNwidth, 230); ctr++;
  822. XtSetArg (args[ctr], XmNheight, 35); ctr++;
  823. XtSetArg (args[ctr], XmNrecomputeSize, False); ctr++;
  824. XtSetArg (args[ctr], XmNleftAttachment, XmATTACH_FORM); ctr++;
  825. XtSetArg (args[ctr], XmNrightAttachment, XmATTACH_FORM); ctr++;
  826. XtSetArg (args[ctr], XmNtopAttachment, XmATTACH_FORM); ctr++;
  827. label = XmCreateLabelGadget (form, "label", args, ctr);
  828. XtManageChild (label);
  829.  
  830. /* create frame mainwindow */
  831. ctr = 0;
  832. XtSetArg (args[ctr], XmNmarginWidth, 2); ctr++;
  833. XtSetArg (args[ctr], XmNmarginHeight, 2); ctr++;
  834. XtSetArg (args[ctr], XmNshadowThickness, 1); ctr++;
  835. XtSetArg (args[ctr], XmNshadowType, XmSHADOW_OUT); ctr++;
  836. XtSetArg (args[ctr], XmNleftAttachment, XmATTACH_FORM); ctr++;
  837. XtSetArg (args[ctr], XmNrightAttachment, XmATTACH_FORM); ctr++;
  838. XtSetArg (args[ctr], XmNtopAttachment, XmATTACH_WIDGET); ctr++;
  839. XtSetArg (args[ctr], XmNtopWidget, label); ctr++;
  840. XtSetArg (args[ctr], XmNbottomAttachment, XmATTACH_FORM); ctr++;
  841. frame = XmCreateFrame (form, "frame", args, ctr);
  842. XtManageChild (frame);
  843.  
  844. /* create rowcolumn in frame to manage buttons */
  845. ctr = 0;
  846. XtSetArg (args[ctr], XmNpacking, XmPACK_COLUMN); ctr++;
  847. XtSetArg (args[ctr], XmNnumColumns, 4); ctr++;
  848. row_column = XmCreateRowColumn (frame, "row_column", args, ctr);
  849. XtManageChild (row_column);
  850.  
  851. /* create buttons by column */
  852. ctr = 0;
  853. label_string = XmStringCreateLtoR (PLAY_NAME, charset);
  854. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  855. play_button = XmCreatePushButtonGadget (row_column, PLAY_NAME, args, ctr);
  856. XtManageChild (play_button);
  857. XtAddCallback (play_button, XmNarmCallback, play_callback, NULL);
  858. XmStringFree (label_string);
  859.  
  860. ctr = 0;
  861. label_string = XmStringCreateLtoR (PREV_NAME, charset);
  862. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  863. prev_button = XmCreatePushButtonGadget (row_column, PREV_NAME, args, ctr);
  864. XtManageChild (prev_button);
  865. XtAddCallback (prev_button, XmNarmCallback, prev_callback, NULL);
  866. XmStringFree (label_string);
  867.  
  868. ctr = 0;
  869. label_string = XmStringCreateLtoR (PAUSE_NAME, charset);
  870. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  871. pause_button = XmCreatePushButtonGadget (row_column, PAUSE_NAME, args, ctr);
  872. XtManageChild (pause_button);
  873. XtAddCallback (pause_button, XmNarmCallback, pause_callback, NULL);
  874. XmStringFree (label_string);
  875.  
  876. ctr = 0;
  877. label_string = XmStringCreateLtoR (NEXT_NAME, charset);
  878. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  879. next_button = XmCreatePushButtonGadget (row_column, NEXT_NAME, args, ctr);
  880. XtManageChild (next_button);
  881. XtAddCallback (next_button, XmNarmCallback, next_callback, NULL);
  882. XmStringFree (label_string);
  883.  
  884. ctr = 0;
  885. label_string = XmStringCreateLtoR (SHUFFLE_NAME, charset);
  886. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  887. shuffle_button = XmCreatePushButtonGadget (row_column, SHUFFLE_NAME, args, ctr);
  888. XtManageChild (shuffle_button);
  889. XtAddCallback (shuffle_button, XmNarmCallback, shuffle_callback, NULL);
  890. XmStringFree (label_string);
  891.  
  892. ctr = 0;
  893. label_string = XmStringCreateLtoR (TRACKS_NAME, charset);
  894. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  895. tracks_button = XmCreatePushButtonGadget (row_column, TRACKS_NAME, args, ctr);
  896. XtManageChild (tracks_button);
  897. XtAddCallback (tracks_button, XmNarmCallback, tracks_callback, NULL);
  898. XmStringFree (label_string);
  899.  
  900. ctr = 0;
  901. label_string = XmStringCreateLtoR (STOP_NAME, charset);
  902. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  903. stop_button = XmCreatePushButtonGadget (row_column, STOP_NAME, args, ctr);
  904. XtManageChild (stop_button);
  905. XtAddCallback (stop_button, XmNarmCallback, stop_callback, NULL);
  906. XmStringFree (label_string);
  907.  
  908. ctr = 0;
  909. label_string = XmStringCreateLtoR (EJECT_NAME, charset);
  910. XtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
  911. eject_button = XmCreatePushButtonGadget (row_column, EJECT_NAME, args, ctr);
  912. XtManageChild (eject_button);
  913. XtAddCallback (eject_button, XmNarmCallback, eject_callback, NULL);
  914. XmStringFree (label_string);
  915.  
  916. /* set MainWindow areas */
  917. XmMainWindowSetAreas (main_window, menu_bar, NULL, NULL, NULL, form);
  918.  
  919. /* put them all on the screen */
  920. XtRealizeWidget (app_shell);
  921. }
  922.  
  923.  
  924. #ifdef WATCH_ABUF
  925. void
  926. heartbeat ()
  927. {
  928. show_bufsleft = 1;
  929. signal (SIGALRM, heartbeat);
  930. }
  931. #endif /* WATCH_ABUF */
  932.  
  933.  
  934. main (argc, argv)
  935. int argc;
  936. char *argv[];
  937. {
  938. #ifdef WATCH_ABUF
  939. struct itimerval itbuf;
  940. #endif /* WATCH_ABUF */
  941. XEvent an_event;
  942. int frame_ctr, frames_to_play;
  943. char message[64];
  944.  
  945. /*
  946.  * Try to increase the scheduling priority of this process.  This should help
  947.  * reduce pauses in the music, due to cpu scheduling conflicts by making
  948.  * cdreader one of the highest priority processes in the system.
  949.  *
  950.  * The schedctl() system call will fail (silently) unless the program is installed
  951.  * setuid to root, or the kernel is reconfigured to allow mere mortals to use
  952.  * non-degrading priorities.  See the SCHEDCTL(2) and AUTOCONFIG(1M) manpages,
  953.  * and the Irix Programming Guide Vol 2, section 14.2.3 for complete details.
  954.  *
  955.  * That's what I did, and it's pretty easy!  Basically, * become superuser,
  956.  * edit /usr/sysgen/master.d/disp, find the variable "ndpri_hilim", change it's
  957.  * value to "NDPHIMAX", run "autoconfig" and reboot.
  958.  *
  959.  * We don't care if it fails, probably the user doesn't have permission.
  960.  *
  961.  *    WARNING!  when testing new code, DON'T do this!  (this is
  962.  *    the voice of experience talking, so listen up!)  If you get
  963.  *    into an infinite loop, your system will LOCK UP tight!
  964.  *    (because cdreader will be the highest priority process,
  965.  *    and you won't be able to kill it)
  966.  *    
  967.  */
  968. #ifndef TESTING
  969. (void) schedctl (NDPRI, 0, NDPHIMAX+1);
  970. #endif
  971.  
  972. /* lose any setuid priviledges assigned to allow schedctl to succeed */
  973. (void) seteuid (getuid());
  974.  
  975. (void) signal (SIGHUP, quit_pgm);    /* should really trap everything, right? */
  976. (void) signal (SIGINT, quit_pgm);
  977. (void) signal (SIGQUIT, quit_pgm);
  978. (void) signal (SIGTERM, quit_pgm);
  979.  
  980. init_motif (argc, argv);    /* initialize the X11/Motif user interface */
  981.  
  982. /* main process loop */
  983. for (;;) {
  984.     switch (status) {
  985.  
  986.     case PAUSED:
  987.     case STOPPED:
  988.         XtAppNextEvent (appcon, &an_event);
  989.         XtDispatchEvent (&an_event);
  990.         break;
  991.  
  992.     case PAUSING:
  993.         (void) sprintf (message, "%s %d", Paused_Msg, current_track);
  994.         set_message (message);
  995.  
  996.         XtSetSensitive (play_button, True);
  997.         XtSetSensitive (stop_button, True);
  998.         XtSetSensitive (pause_button, False);
  999.         XtSetSensitive (shuffle_button, False);
  1000.         XtSetSensitive (eject_button, False);
  1001.  
  1002. #ifdef WATCH_ABUF
  1003.         /* turn off monitor timer */
  1004.         signal (SIGALRM, SIG_IGN);
  1005.         itbuf.it_value.tv_sec = itbuf.it_interval.tv_sec = 0;
  1006.         itbuf.it_value.tv_usec = itbuf.it_interval.tv_usec = 0;
  1007.         if (setitimer (ITIMER_REAL, &itbuf, (struct itimerval *) 0) == -1) {
  1008.             perror ("setitimer");
  1009.             return;
  1010.             }
  1011. #endif /* WATCH_ABUF */
  1012.  
  1013.         status = PAUSED;
  1014.         break;
  1015.  
  1016.     case STOPPING:
  1017.         set_message (Stopped_Msg);
  1018.         current_track = 1;
  1019.         if (cd_device != NULL) {
  1020.             CDclose (cd_device);
  1021.             cd_device = NULL;
  1022.             }
  1023.  
  1024.         XtSetSensitive (play_button, True);
  1025.         XtSetSensitive (shuffle_button, True);
  1026.         XtSetSensitive (eject_button, True);
  1027.         XtSetSensitive (pause_button, False);
  1028.         XtSetSensitive (stop_button, False);
  1029.  
  1030. #ifdef WATCH_ABUF
  1031.         /* turn off monitor timer */
  1032.         signal (SIGALRM, SIG_IGN);
  1033.         itbuf.it_value.tv_sec = itbuf.it_interval.tv_sec = 0;
  1034.         itbuf.it_value.tv_usec = itbuf.it_interval.tv_usec = 0;
  1035.         if (setitimer (ITIMER_REAL, &itbuf, (struct itimerval *) 0) == -1) {
  1036.             perror ("setitimer");
  1037.             return;
  1038.             }
  1039. #endif /* WATCH_ABUF */
  1040.  
  1041.         status = STOPPED;
  1042.         break;
  1043.  
  1044.     case STARTING:
  1045.         if (get_track_info()) {        /* opens cd_device and gets track_info[] */
  1046.             status = STOPPING;
  1047.             break;
  1048.             }
  1049.         current_track = first_track;    /* play whole disk from the start */
  1050.         play_index = -1;
  1051.  
  1052.     case START_TRACK:
  1053.         if (CDseektrack(cd_device, current_track) == -1) {
  1054.             (void) sprintf (message, "%s %d", CDSEEKTRACK_FAILED, current_track);
  1055.             Display_Warning (message);
  1056.             status = STOPPING;
  1057.             break;
  1058.             }
  1059.         /* FALLTHROUGH */
  1060.  
  1061.     case CONTINUE:
  1062.         if (init_audio() || init_cd()) {    /* initialize audio port and cd structures */
  1063.             status = STOPPING;
  1064.             break;
  1065.             }
  1066.  
  1067.         (void) sprintf (message, "%s %d - %s %2d:%02d", Playing_Msg, current_track, Length_Word,
  1068.             track_info[current_track].length_min,
  1069.             track_info[current_track].length_sec);
  1070.         set_message (message);
  1071.  
  1072.         while (XtAppPending (appcon)) {        /* process any pending X events */
  1073.             XtAppNextEvent (appcon, &an_event);
  1074.             XtDispatchEvent (&an_event);
  1075.             }
  1076.  
  1077.         XtSetSensitive (pause_button, True);
  1078.         XtSetSensitive (stop_button, True);
  1079.         XtSetSensitive (play_button, False);
  1080.         XtSetSensitive (shuffle_button, False);
  1081.         XtSetSensitive (eject_button, False);
  1082.  
  1083. #ifdef WATCH_ABUF
  1084.         show_bufsleft = 0;
  1085.         signal (SIGALRM, heartbeat);
  1086.         itbuf.it_value.tv_sec = itbuf.it_interval.tv_sec = monitor_interval;
  1087.         itbuf.it_value.tv_usec = itbuf.it_interval.tv_usec = 0;
  1088.         if (setitimer (ITIMER_REAL, &itbuf, (struct itimerval *) 0) == -1) {
  1089.             perror ("setitimer");
  1090.             return;
  1091.             }
  1092. #endif /* WATCH_ABUF */
  1093.  
  1094.         /*
  1095.          * read enough cd data to fill the audio buffer, to help
  1096.          * cover any later pauses due to cpu scheduling.
  1097.          * Be aware that we might still have some audio data left over
  1098.          * from a previous pause.
  1099.          */
  1100.         frames_to_play = CDreadda (cd_device, &cd_buffer[0], cd_init_readsize);
  1101.  
  1102.         /* process any pending X events */
  1103.         while (XtAppPending (appcon)) {
  1104.             XtAppNextEvent (appcon, &an_event);
  1105.             XtDispatchEvent (&an_event);
  1106.             }
  1107.  
  1108.         if (frames_to_play < 1) {
  1109.             status = STOPPING;
  1110.             }
  1111.         else    {
  1112.             status = PLAYING;
  1113.             }
  1114.         break;
  1115.  
  1116.     case PLAYING:
  1117.  
  1118. #ifdef IRIX401_CDREADDA_BUG
  1119.         /*
  1120.          *    BUG!  The manpage for CDreadda() says it returns the number
  1121.          *    of frames read.  In reality, it returns the number of bytes read
  1122.          *    into the buffer.  Divide by CDDA_BLOCKSIZE to get the number of frames.
  1123.          *
  1124.          *    I'd prefer it to work like the manpage says, return the number of frames.
  1125.          *    This is supposed to be fixed in the next release of Irix (after 4.0.1).
  1126.          */
  1127.         frames_to_play = frames_to_play / CDDA_BLOCKSIZE;
  1128. #endif /* IRIX401_CDREADDA_BUG */
  1129.  
  1130.         /* process CD data */
  1131.         for (frame_ctr = 0; frame_ctr < frames_to_play; frame_ctr++) {
  1132.             CDparseframe (cd_parser, &cd_buffer[frame_ctr]);
  1133.             }
  1134.  
  1135.         /* process any pending X events */
  1136.         while (XtAppPending (appcon)) {
  1137.             XtAppNextEvent (appcon, &an_event);
  1138.             XtDispatchEvent (&an_event);
  1139.             }
  1140.  
  1141.         /* still playing?  read more CD data */
  1142.         if (status == PLAYING) {
  1143.             frames_to_play = CDreadda (cd_device, &cd_buffer[0], cd_readsize);
  1144.             if (frames_to_play < 1) {    /* end of disc */
  1145.                 status = STOPPING;
  1146.                 }
  1147.             }
  1148.  
  1149. #ifdef WATCH_ABUF
  1150.         if (show_bufsleft) {
  1151.             show_bufsleft = 0;
  1152.             printf ("abufs left = %d\n", total_samps - ALgetfilled(audio_port));
  1153.             }
  1154. #endif /* WATCH_ABUF */
  1155.  
  1156.         /* process any pending X events */
  1157.         while (XtAppPending (appcon)) {
  1158.             XtAppNextEvent (appcon, &an_event);
  1159.             XtDispatchEvent (&an_event);
  1160.             }
  1161.         break;
  1162.  
  1163.     default:
  1164.         Display_Warning ("Cdreader has become horribly confused.\nYou should quit and start the program over");
  1165.         status = STOPPING;
  1166.         }
  1167.     }
  1168.  
  1169. /* NOTREACHED */
  1170. }
  1171.  
  1172. /*
  1173.  * Original Author:  Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
  1174.  *
  1175.  * This software is Copyright (c) 1992 by Patrick J. Wolfe.
  1176.  *
  1177.  * Permission is hereby granted to copy, distribute or otherwise 
  1178.  * use any part of this package as long as you do not try to make 
  1179.  * money from it or pretend that you wrote it.  This copyright 
  1180.  * notice must be maintained in any copy made.
  1181.  *
  1182.  * Use of this software constitutes acceptance for use in an AS IS 
  1183.  * condition. There are NO warranties with regard to this software.  
  1184.  * In no event shall the author be liable for any damages whatsoever 
  1185.  * arising out of or in connection with the use or performance of this 
  1186.  * software.  Any use of this software is at the user's own risk.
  1187.  *
  1188.  * If you make modifications to this software that you feel 
  1189.  * increases it usefulness for the rest of the community, please 
  1190.  * email the changes, enhancements, bug fixes as well as any and 
  1191.  * all ideas to me. This software is going to be maintained and 
  1192.  * enhanced as deemed necessary by the community.
  1193.  *              
  1194.  *              Patrick J. Wolfe
  1195.  *              uunet!kailand!pwolfe
  1196.  *              pwolfe@kai.com
  1197.  */
  1198.