From: pwolfe@kai.com (Patrick Wolfe)
Subject: how to get cdreader working under irix 4.0.5
Summary: irix 4.0.5 upgrade changes permissions of /dev/scsi/sc0d4l0
Keywords: cd, irix
Organization: Kuck and Associates, Champaign IL
Date: Sun, 27 Sep 1992 16:20:13 GMT

I have been told that the upgrade to Irix 4.0.5 changes permissions in the
/dev/scsi directory, which is causing my "cdreader" program to stop
functioning.  The fix for this is to become the superuser and run:

	chmod 666 /dev/scsi/sc0d4l0

Also, if you just upgraded from Irix 4.0.1, you may need to recompile cdreader
to get rid of a workaround for a bug in the cdaudio library.

It's not too big, so I'll just include the last version (1.5.1) of cdreader
here.  I haven't got an Indigo on my desk anymore (sigh - those *were* the
"good old days"), so the program hasn't changed since around March, and isn't
likely to change again soon.

IMPORTANT!  If you are running Irix 4.0.1, you'll have to edit the Makefile,
and uncomment the -DIRIX401_CDREADDA_BUG definition.

	Patrick Wolfe (pwolfe@kai.com)
	System Manager, Kuck and Associates

-- cut here --- cut here --- cut here --- cut here --- cut here --- cut here --
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  Readme cdreader.c Makefile cdreader.icon cdreader.1
# Wrapped by pwolfe@kailand on Sun Sep 27 11:08:21 1992
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'Readme' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Readme'\"
else
echo shar: Extracting \"'Readme'\" \(8914 characters\)
sed "s/^X//" >'Readme' <<'END_OF_FILE'
XQuick start instructions:
X- Run "make".  If the link fails because you don't have shared X11/Motif
X  libraries installed, edit Makefile and re-run "make".
X- Test it by running "./cdreader".
X- Run "make install" to install cdreader in /usr/local/bin, the icon in
X  /usr/lib/images, and the manpage in /usr/catman/local/man1.
X
X------------------------------------------------------------------------------
X			WHAT IS CDREADER?
X
XCdreader plays an audio compact disc on Silicon Graphics workstations with an
XAudio Processor (the SGI Indigo, for example).  Cdreader compiles only under
Xrelease 4.0.1 (or later) of the Irix operating system, and you must have the
XX11, Motif and cdrom development packages installed (to compile, not to run).
X
XVersion 1.5.1 fixes a bug in shuffle play mode.
XVersion 1.5 adds a scrollbar to control the speaker/headphone volume.
X
XThis is NOT just like SGI's "cdplayer" program, which simply commands the CD
Xdrive to play an audio CD, and sits back to watch.  In that case, the digital
XCD data is converted to analog inside the CDrom drive, and the analog audio
Xoutput is sent to the headphone jack on the front and the audio out jacks on
Xthe back of the drive.  Without headphones or a stereo, you can't hear anything.
XEven with them, the sound is very quiet, and somewhat distorted.
X
XCdreader uses SGI's libcdaudio routines to read the digital data directly from
Xthe CD, and sends it to the Audio Processor for conversion to analog audio
Xdata.  You can listen on the Indigo's monophonic speaker, or even better, buy a
Xcable with a stereo mini-plug on one end, and two phono plugs on the other, and
Xconnect from your Indigo's lineout jack to your stereo amplifier's cd-in (or
Xtape-in) jacks.  Enjoy!
X
XWARNING!  If you were just using SGI's "cdplayer" program, turn down the volume
Xbefore starting "cdreader" or you'll risk damaging your equipment (and ears)!
X
XCdreader will probably only work with an SGI's cdrom drive.  The program will
Xtell you if your cdrom drive doesn't support reading audio over the SCSI bus.
XIt uses about 5-6% of the cpu, and 4% of the SCSI bus bandwidth.  Use of other
Xprograms which change the audio processor's parameters is discouraged (but sure
Xcan make you laugh).
X
XThe schedctl() system call in cdreader will (silently) fail unless the program
Xis installed setuid root, or your kernel configuration is modified to allow
Xnon-root users to use non-degrading priorities.  See the SCHEDCTL(2) and
XAUTOCONFIG(1M) manpages, and the Irix Programming Guide Vol 2, section 14.2.3
Xfor complete details.  That's what I did, and it's pretty easy!  Basically,
Xbecome superuser, edit /usr/sysgen/master.d/disp, find the variable
X"ndpri_hilim", change it's value to "NDPHIMAX", run "autoconfig" and reboot.
X
XSome people think the delay is caused by disk I/O, but believe me it's not.
XThat's what I thought originally, but Mark Callow of SGI corrected me, and
Xput me onto the fixes.
X
XI wrote cdreader on a 16 Mb SGI Indigo running Irix 4.0.1.
X
X        Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
X
X------------------------------------------------------------------------------
X			RELEASE NOTES:
X
XVERSION 1.5.1:
X
XFixed a bug in the "shuffle" mode, and improved the random-ness of the play
Xlist generator.  The bug was that cdreader always stopped after playing the
Xlast track on the disc, but in shuffle mode, the last track on the disc was
Xrarely the last track on the play list.
X
XVERSION 1.5:
X
XAdded horizontal scrollbar to control the Indigo speaker/headphone volume.
X
XVERSION 1.4:
X
XDistributed to comp.sources.misc.
X
XAdded shuffle, tracks and info buttons.  Shuffle plays the whole disc, but in
Xrandom order.  Tracks displays the list and length of tracks, like "cdinfo"
Xdid.  Info displays some information about the program and programmer.  Also,
Xupdated the online help screen to be more useful.
X
XAlso, reorganized code so that the program "should" run on any sgi irix 4.0.1
Xsystem, whether you have a cd drive and audio processor or not.  Of course, it
Xwon't do you any good, but those folks interested in using this program as an
Xexample for X11/Motif programming won't mind too much.
X
XPut all of the commonly used messages and words in defines, for easy
Xcustomization and translation to foreign languages.
X
XFixed next and prev buttons to work in stopped mode, for selecting which track
Xto start playing on.
X
XDiscovered what happens when you have the highest priority task on your system
Xgo into a cpu loop.  I know, I know, I shouldn't put bugs in my programs.  It's
Xunproductive.
X
XVERSION 1.3:
X
XSports an X11/Motif user interface.  Much nicer.  Also displays program status
Xin a window above the buttons.  Not distributed.
X
XVERSION 1.2:
X
XSports an X11 user interface (using Athena Widgets).  Much nicer, but not
Xperfect.  I like being to pause it without killing it.  Not distributed.
X
XCdreader now has a nice Motif style user interface.  I removed all of the debug
Xcode and command line options, since mostly people just want to play cd's from
Xstart to end, possibly pausing at random points.  At least, that's all I do.
XSorry, no "scan" buttons.  How much did you pay for this program anyway?
X
XVERSION 1.1:
X
XThis version was posted to comp.sys.sgi.
X
XCdreader has been improved in that it buffers up enough audio data to eliminate
Xpractically all pauses in the music that occurred previously.  I was informed
Xthat these were apparently due to cpu conflicts during large amounts of
Xgraphics scrolling.
X
XVERSION 1.0:
X
XThis version was uploaded to uunet's ~ftp/tmp directory via anonymous ftp
X(don't bother checking, it's not there anymore) and announced in comp.sys.sgi.
X
XIt was my first (barely) working version of this program.
X
X------------------------------------------------------------------------------
X			MISC NOTES:
X
XThere is a minor error in Irix 4.0.1's cdaudio.h include file which causes
Xwarning messages complaining about an extra comma.  Ignore them, or fix the
Xinclude file.  Also, some manpages for CD routines are incorrect, or contain
Xtypos.  These are all supposed to be fixed in the next Irix release.
X
XWhile working on cdreader, I discovered a problem in the SGI CD audio library
Xroutine CDreadda().  See comments in the source code.  I've been told that this
Xis fixed in the next Irix release, so after you upgrade, be sure to remove the
X-DIRIX401_CDREADDA_BUG from the Makefile and recompile.
X
XThere is a 2-4 second delay for anything to happen (play, pause, stop).
XThat's because of the huge audio buffer being used.
X
XA couple advantages of my cdreader over SGI's cdplayer program:
X
X- There is a noticible clicking noise coming out of my cdrom drive's audio
X  jacks (where the audio output comes when running SGI's cdplayer).  This is
X  not the clicking that some people describe when the audio pauses due to cpu
X  scheduling conflicts, it's some distortion, apparently from the cdrom drive
X  motor.  I don't know if this problem is common to all cdrom drives, but it
X  sure is annoying.  Cdreader's output is the DAC jacks on the back of the
X  Indigo, and doesn't have this problem.
X
X- When using SGI's cdplayer, the volume coming out of the audio jacks on the
X  back of the SGI CDrom drive is very low.  You really have to jack up the
X  volume on your amp higher than normal.
X
X- The CD doesn't spin while in "stopped" or "paused" modes.
X
XTHANK YOU SGI, for making such wonderful libraries available.  They make
Xmy Indigo an extremely fun machine to use.
X
X        Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
X        System Programmer/Operations Manager, Kuck & Associates
X
X
X------------------------------------------------------------------------------
X			LEGAL JUNK:
X
X/*
X * Original Author:  Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
X *
X * This software is Copyright (c) 1992 by Patrick J. Wolfe.
X *
X * Permission is hereby granted to copy, distribute or otherwise 
X * use any part of this package as long as you do not try to make 
X * money from it or pretend that you wrote it.  This copyright 
X * notice must be maintained in any copy made.
X *
X * Use of this software constitutes acceptance for use in an AS IS 
X * condition. There are NO warranties with regard to this software.  
X * In no event shall the author be liable for any damages whatsoever 
X * arising out of or in connection with the use or performance of this 
X * software.  Any use of this software is at the user's own risk.
X *
X * If you make modifications to this software that you feel 
X * increases it usefulness for the rest of the community, please 
X * email the changes, enhancements, bug fixes as well as any and 
X * all ideas to me. This software is going to be maintained and 
X * enhanced as deemed necessary by the community.
X *              
X *              Patrick J. Wolfe
X *              uunet!kailand!pwolfe
X *              pwolfe@kai.com
X */
X------------------------------------------------------------------------------
END_OF_FILE
if test 8914 -ne `wc -c <'Readme'`; then
    echo shar: \"'Readme'\" unpacked with wrong size!
fi
# end of 'Readme'
fi
if test -f 'cdreader.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cdreader.c'\"
else
echo shar: Extracting \"'cdreader.c'\" \(37755 characters\)
sed "s/^X//" >'cdreader.c' <<'END_OF_FILE'
X/*
X *	cdreader - Plays an audio CD through the SGI Audio Processor.
X *	You can play CDs through the monophonic speaker on your SGI Indigo,
X *	or you can connect an amp to the line-out jacks for some
X *	REAL quality sound.
X *
X *	Original Author:  Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
X *	This software is Copyright (c) 1992 by Patrick J. Wolfe.
X *	See the end of this file for the complete copyright notice.
X */
X
X#define VERSION "1.5.1"
X
X#include <stdio.h>
X#include <stdlib.h>
X#include <sys/types.h>
X	/* for errno */
X#include <errno.h>
X	/* for signal() */
X#include <signal.h>
X	/* for getopt() */
X#include <getopt.h>
X	/* for [ls]rand48() */
X#include <math.h>
X	/* for time() */
X#include <time.h>
X	/* for CD*() routines */
X#include <cdaudio.h>
X	/* for AL*() routines */
X#include <audio.h>
X	/* for schedctl() */
X#include <limits.h>
X#include <sys/prctl.h>
X#include <sys/schedctl.h>
X	/* for X11/Motif interface */
X#include <X11/Intrinsic.h>
X#include <Xm/Xm.h>
X#include <Xm/CascadeB.h>
X#include <Xm/Frame.h>
X#include <Xm/MainW.h>
X#include <Xm/PushB.h>
X#include <Xm/PushBG.h>
X#include <Xm/RowColumn.h>
X#include <Xm/MessageB.h>
X#include <Xm/Form.h>
X#include <Xm/ScrollBar.h>
X
X
X#ifdef WATCH_ABUF
X	/* for setitimer */
X#include <sys/time.h>
Xint monitor_interval = 15;	/* number of seconds between abufs left reports */
Xint show_bufsleft = 0;		/* flag to indicate that it's time to report */
Xint total_samps = 0;		/* number of samples the audio buffer will hold */
X#endif /* WATCH_ABUF */
X
X#define MAX_VALUE(a,b) (a > b ? a : b)
X
X/*
X *	Most of the common messages are here, for easy customization and
X *	translation for foreign languages.  We're not ALL ignorant bastards.
X */
X
X	/* button labels */
X#define PLAY_NAME "Play"
X#define PREV_NAME "Prev"
X#define PAUSE_NAME "Pause"
X#define NEXT_NAME "Next"
X#define SHUFFLE_NAME "Shuffle"
X#define TRACKS_NAME "Tracks"
X#define STOP_NAME "Stop"
X#define EJECT_NAME "Eject"
X#define QUIT_NAME "Quit"
X#define INFO_NAME "Info"
X#define HELP_NAME "Help"
X
X	/* status messages */
Xchar *Playing_Msg = "Playing Track";
Xchar *Stopped_Msg = "Stopped";
Xchar *Paused_Msg = "Paused at Track";
Xchar *Total_Msg = "Total time on disk";
Xchar *Track_Word = "Track";
Xchar *Length_Word = "Length";
X
X	/* error messages */
Xchar *DISC_NOT_READY = "Disc Not Ready";
Xchar *CANNOT_READ_SCSI = "\
XYour cheapo CDrom drive does NOT support reading audio data\n\
Xacross the SCSI bus.  You should have bought one from SGI!";
Xchar *TOO_MANY_TRACKS = "Too many audio tracks on this disc!\nrecompile with larger MAX_TRACK_INFO.";
Xchar *CDOPEN_FAILED = "CDopen failed\nCannot Open CDrom device";
Xchar *CDGETSTATUS_FAILED = "CDgetstatus failed\nCannot get status of CDrom device";
Xchar *CDGETTRACKINFO_FAILED = "CDgettrackinfo failed";
Xchar *CDSEEKTRACK_FAILED = "CDseektrack failed\nCannot find track";
Xchar *CDEJECT_FAILED = "cannot eject disc - not stopped";
X
X
X
X#define SAMPLES_PER_FRAME	(CDDA_DATASIZE/2)
X
X/* valid states for main process loop control */
X#define STOPPED		0	/* we are stopped */
X#define PAUSED		1	/* we were playing, and are now paused */
X#define PLAYING		2	/* we are playing music */
X#define STARTING	3	/* start playing at beginning of the disk */
X#define CONTINUE	4	/* start playing where you left off */
X#define STOPPING	5	/* we were playing, and are about to stop */
X#define PAUSING		6	/* we were playing, and are about to pause */
X#define START_TRACK	9	/* start playing at the beginning of the current track */
X
X/* maximum number of tracks to store info for */
X/* I *have* seen up to 28 tracks on a single CD, but none longer */
X/* someone told me of a sound effects cd that has over 100 tracks - sigh */
X#define MAX_TRACK_INFO	128
X
X	/* audio stuff */
XALport audio_port = NULL;
Xlong audio_param_buf[6];
X
X	/* cd stuff */
XCDPLAYER *cd_device = NULL;
XCDPARSER *cd_parser = NULL;
XCDFRAME *cd_buffer = NULL;
Xstruct cdinformation {
X	short length_min;			/* track length */
X	short length_sec;
X	} track_info[MAX_TRACK_INFO];
X
Xint first_track = 0;				/* first track on the disk */
Xint last_track = 0;				/* index into track_info for entry AFTER last one we have info for */
Xint current_track = -1;				/* number of track currently playing */
Xint total_min = 0;				/* minutes part of total running time on current disk */
Xint total_sec = 0;				/* second part of total running time on current disk */
Xint play_index = -1;				/* index into playlist for shuffle mode, -1 means that shuffle is not on */
Xint play_list[MAX_TRACK_INFO];			/* order of tracks to play */
Xint cd_readsize = 12;				/* the normal number of frames to read at one time (CDbestreadsize fills this in) */
Xint cd_init_readsize = 200;			/* initial number of frames to read at the beginning of the disc */
Xint status = STOPPING;				/* main process loop status */
X
X	/* X11 stuff */
XXtAppContext	appcon;
XWidget	main_window,
X	label,
X	volume_scrollbar,
X	play_button,
X	prev_button,
X	pause_button,
X	next_button,
X	shuffle_button,
X	tracks_button,
X	info_button,
X	stop_button,
X	eject_button;
X
Xstatic XmStringCharSet charset = (XmStringCharSet) XmSTRING_DEFAULT_CHARSET;
X
Xvoid volume_callback();
Xstatic XtCallbackRec volume_callback_list[] = {
X	{ volume_callback, NULL},
X	{ NULL, NULL}
X	};
X
X
XDisplay_Warning (message)
Xchar *message;
X{
XWidget button;
XWidget warning_box;
XXmString title_string = NULL;
XXmString message_string = NULL;
XArg args[4];
Xregister int n;
X
Xmessage_string = XmStringCreateLtoR (message, charset);
Xtitle_string = XmStringCreateLtoR ("Cdreader Warning!", charset);
X
Xn = 0;
XXtSetArg (args[n], XmNdialogTitle, title_string); n++;
XXtSetArg (args[n], XmNmessageString, message_string); n++;
Xwarning_box = XmCreateWarningDialog (main_window, "warning", args, n);
Xbutton = XmMessageBoxGetChild (warning_box, XmDIALOG_CANCEL_BUTTON);
XXtUnmanageChild (button);
Xbutton = XmMessageBoxGetChild (warning_box, XmDIALOG_HELP_BUTTON);
XXtUnmanageChild (button);
Xif (title_string) XtFree (title_string);
Xif (message_string) XtFree (message_string);
XXtManageChild (warning_box);
X}
X
X
Xset_message (message)
Xchar *message;
X{
XXmString label_string;
XArg	args[30];
Xint ctr;
X
Xctr = 0;
Xlabel_string = XmStringCreate (message, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
XXtSetValues (label, args, ctr);
XXmStringFree (label_string);
X}
X
X
Xint
Xget_track_info ()
X{
XCDSTATUS cd_status;
XCDTRACKINFO cd_info;
X
Xif ((cd_device == NULL) && ((cd_device = CDopen(0, "r")) == NULL)) {
X	Display_Warning (CDOPEN_FAILED);
X	return (1);
X	}
X
X/* display disk info */
Xif (CDgetstatus(cd_device, &cd_status) == 0) {
X	status = STOPPING;
X	Display_Warning (CDGETSTATUS_FAILED);
X	return (1);
X	}
X/* don't start unless it's ready */
Xif (cd_status.state != CD_READY) {
X	status = STOPPING;
X	Display_Warning (DISC_NOT_READY);
X	return (1);
X	}
Xif (! cd_status.scsi_audio) {
X	status = STOPPING;
X	Display_Warning (CANNOT_READ_SCSI);
X	return (1);
X	}
Xtotal_min = cd_status.total_min;
Xtotal_sec = cd_status.total_sec;
X
X/* get track information */
Xfor (first_track = last_track = cd_status.first;
X  (last_track <= cd_status.last) && (last_track < MAX_TRACK_INFO);
X  last_track++) {
X	if (CDgettrackinfo(cd_device, last_track, &cd_info) == 0) {
X		status = STOPPING;
X		Display_Warning (CDGETTRACKINFO_FAILED);
X		return (1);
X		}
X	track_info[last_track].length_min = cd_info.total_min;
X	track_info[last_track].length_sec = cd_info.total_sec;
X	}
Xif (last_track >= MAX_TRACK_INFO) {
X	Display_Warning (TOO_MANY_TRACKS);
X	return (1);
X	}
X
X/* find the best number of frames to read at a time */
Xcd_readsize = CDbestreadsize (cd_device);
X
Xreturn (0);
X}
X
X
X/* called as signal handler, and when the quit button is pressed */
Xvoid
Xquit_pgm ()
X{
XCDdeleteparser(cd_parser);	/* free parser memory */
Xif (cd_device != NULL) {
X	CDclose(cd_device);		/* close CD player port */
X	}
Xif (audio_port != NULL) {
X	ALcloseport(audio_port);	/* close audio port */
X	}
Xexit (0);
X}
X
X
X/* called by pressing the EJECT button */
Xvoid
Xeject_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xif (status == STOPPED) {
X	if ((cd_device == NULL) && ((cd_device = CDopen(0, "r")) == NULL)) {
X		Display_Warning (CDOPEN_FAILED);
X		}
X	else	{
X		CDeject(cd_device);
X		CDclose(cd_device);
X		cd_device = NULL;
X		}
X	}
Xelse	{
X	Display_Warning (CDEJECT_FAILED);
X	}
X}
X
X
X/* called by pressing the STOP button */
Xvoid
Xstop_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xplay_index = -1;
Xstatus = STOPPING;
X}
X
X
X/* called by pressing the PAUSE button */
Xvoid
Xpause_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xif (status == PLAYING) {
X	status = PAUSING;
X	}
X}
X
X
X/* called by pressing the PLAY button */
Xvoid
Xplay_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xif (status == STOPPED) {
X	status = STARTING;
X	}
Xelse if (status == PAUSED) {
X	status = CONTINUE;
X	}
X}
X
X
X/* called by pressing the PREV button */
Xvoid
Xprev_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xchar message[64];
X
Xif ((cd_device == NULL) && get_track_info()) {		/* opens cd_device and gets track_info[] */
X	status = STOPPING;
X	return;
X	}
X
Xif ((status == PLAYING) || (status == PAUSED) || (status == STOPPED)) {
X	if (play_index == -1) {
X		current_track--;
X		if (current_track < first_track) {
X			current_track = first_track;
X			}
X		}
X	else	{ /* shuffling */
X		play_index--;
X		if (play_index < first_track) {
X			play_index = first_track;
X			}
X		current_track = play_list[play_index];
X		}
X
X	if (CDseektrack(cd_device, current_track) == -1) {
X		(void) sprintf (message, "%s %d", CDSEEKTRACK_FAILED, current_track);
X		Display_Warning (message);
X		status = STOPPING;
X		}
X	else if (status == PLAYING) {
X		status = CONTINUE;
X		}
X	else	{
X		status = PAUSING;
X		}
X	}
X}
X
X
X/* called by pressing the NEXT button */
Xvoid
Xnext_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xchar message[64];
X
Xif ((cd_device == NULL) && get_track_info()) {		/* opens cd_device and gets track_info[] */
X	status = STOPPING;
X	return;
X	}
X
Xif ((status == PLAYING) || (status == PAUSED) || (status == STOPPED)) {
X	if (play_index < 0) {
X		current_track++;
X		if (current_track >= last_track) {
X			current_track = first_track;
X			status = STOPPING;
X			return;
X			}
X		}
X	else	{	/* shuffling */
X		play_index++;
X		if (play_index >= last_track) {
X			current_track = first_track;
X			status = STOPPING;
X			return;
X			}
X		current_track = play_list[play_index];
X		}
X
X	if (CDseektrack(cd_device, current_track) == -1) {
X		(void) sprintf (message, "%s %d", CDSEEKTRACK_FAILED, current_track);
X		Display_Warning (message);
X		status = STOPPING;
X		}
X	else if (status == PLAYING) {
X		status = CONTINUE;
X		}
X	else	{
X		status = PAUSING;
X		}
X	}
X}
X
X
X/* called by pressing the SHUFFLE button
X * plays all tracks on the disk in a random order
X */
Xvoid
Xshuffle_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xdouble jdb;
Xint i, j, m, d = 1;
Xint played[MAX_TRACK_INFO];
X
Xif (get_track_info()) {		/* opens cd_device and gets track_info[] */
X	status = STOPPING;
X	return;
X	}
X
Xm = last_track - first_track;
X
X/* initialize played array to -1's, indicating track hasn't been selected to play yet */
Xfor (i = first_track; i < last_track; i++) {
X	played[i] = 0;
X	}
X
X/* set the seed for our random numbers */
Xsrand48 (time(0));
X
Xfor (i = first_track; i < last_track; i++) {
X	jdb = drand48(); /* returns between 0 and 1 */
X	jdb = jdb * m;
X	j = (int) jdb;
X	j++;
X	for (; played[j]; j += d) {
X		if (j < first_track) {
X			j = last_track - 1;
X			}
X		else if (j >= last_track) {
X			j = first_track;
X			}
X		}
X	d = d * -1;	/* switch direction */
X	play_list[i] = j;
X	played[j] = 1;
X	}
X
Xplay_index = first_track;
Xcurrent_track = play_list[play_index];
Xstatus = START_TRACK;
X}
X
X
X/* called by pressing the TRACKS button */
X/* show info about the entire disk */
Xvoid
Xtracks_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
XWidget button;
XWidget message_box;
XXmString title_string = NULL;
XXmString message_string = NULL;
XXmString button_string = NULL;
XArg args[4];
Xregister int n;
Xint track;
Xchar message[4096];
Xchar line[256];
X
Xif ((status == STOPPED) && get_track_info() ) {
X	return;	/* if stopped, try to re-read the track info - might be a new disc */
X	}
Xif ((status != PLAYING) && (status != PAUSED) && (status != STOPPED)) {
X	return;		/* ignore this button during odd status changes */
X	}
X(void) sprintf (message, "%s = %02d:%02d\n\n", Total_Msg, total_min, total_sec);
X
X/* show info about each track */
Xfor (track = first_track; track < last_track; track++) {
X	(void) sprintf (line, "\t%s %2d:  %s %02d:%02d\n", Track_Word, track, Length_Word,
X		track_info[track].length_min, track_info[track].length_sec);
X	(void) strcat (message, line);
X	}
X
Xtitle_string = XmStringCreateLtoR ("Cdreader Track List", charset);
Xbutton_string = XmStringCreateLtoR ("Close", charset);
Xmessage_string = XmStringCreateLtoR (message, charset);
X
Xn = 0;
XXtSetArg (args[n], XmNdialogTitle, title_string); n++;
XXtSetArg (args[n], XmNokLabelString, button_string); n++;
XXtSetArg (args[n], XmNmessageString, message_string); n++;
Xmessage_box = XmCreateMessageDialog (main_window, "tracks", args, n);
Xbutton = XmMessageBoxGetChild (message_box, XmDIALOG_CANCEL_BUTTON);
XXtUnmanageChild (button);
Xbutton = XmMessageBoxGetChild (message_box, XmDIALOG_HELP_BUTTON);
XXtUnmanageChild (button);
Xif (title_string) XtFree (title_string);
Xif (button_string) XtFree (button_string);
Xif (message_string) XtFree (message_string);
XXtManageChild (message_box);
X}
X
X
X/* called by pressing the INFO button */
Xvoid
Xinfo_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
XWidget button;
XWidget message_box;
Xchar message[2048];
XXmString title_string = NULL;
XXmString message_string = NULL;
XXmString button_string = NULL;
XArg args[4];
Xregister int n;
X
X(void) sprintf (message, "\
XCdreader V%s plays an audio compact disc loaded in an SGI cdrom drive through\n\
Xthe Audio Processor.\n\
X\n\
XCdreader uses the libcdaudio routines to read the digital data directly from\n\
Xthe cdrom drive, and plays it through the Audio Processor.  You can listen on\n\
Xthe Indigo's speaker, or even better, buy a cable with a stereo mini-plug on\n\
Xone end, and two phono plugs on the other, and connect from your Indigo's\n\
Xline out jack to your stereo amplifier's cd in or tape in jacks.\n\
X\n\
XCdreader will probably only work with an SGI's cdrom drive and under release\n\
X4.0.1 (or later) of the Irix operating system.  The program will tell you if\n\
Xyour cdrom drive doesn't support reading audio over the SCSI bus.  It uses\n\
Xabout 5-6%% of the cpu, and 4%% of the SCSI bus bandwidth.  Use of other\n\
Xprograms which change the audio processor's parameters is discouraged (but\n\
Xsure can make you chuckle).\n\
X\n\
XOriginal Author:\n\
X\tPatrick Wolfe\n\
X\tSystem Programmer/Operations Manager\n\
X\tKuck & Associates\n\
X\t1906 Fox Drive\n\
X\tChampaign, IL 61820\n\
X\tInternet:  pwolfe@kai.com\n\
X\tUUCP:      uunet!kailand!pwolfe\n\
X\tvoice:     (217) 356-2288\n\
X\tFAX:       (217) 356-5199\n\
Xcopyright (c) 1992 Patrick J. Wolfe\n", VERSION);
X
Xmessage_string = XmStringCreateLtoR (message, charset);
Xbutton_string = XmStringCreateLtoR ("Close", charset);
Xtitle_string = XmStringCreateLtoR ("Cdreader Information", charset);
X
Xn = 0;
XXtSetArg (args[n], XmNdialogTitle, title_string); n++;
XXtSetArg (args[n], XmNokLabelString, button_string); n++;
XXtSetArg (args[n], XmNmessageString, message_string); n++;
Xmessage_box = XmCreateMessageDialog (w, "credit", args, n);
Xbutton = XmMessageBoxGetChild (message_box, XmDIALOG_CANCEL_BUTTON);
XXtUnmanageChild (button);
Xbutton = XmMessageBoxGetChild (message_box, XmDIALOG_HELP_BUTTON);
XXtUnmanageChild (button);
Xif (title_string) XtFree (title_string);
Xif (button_string) XtFree (button_string);
Xif (message_string) XtFree (message_string);
XXtManageChild (message_box);
X}
X
X
X/* called by pressing the HELP button */
Xvoid
Xhelp_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
XWidget button;
XWidget message_box;
Xchar message[2048];
XXmString title_string = NULL;
XXmString message_string = NULL;
XXmString button_string = NULL;
XArg args[4];
Xregister int n;
X
X(void) sprintf (message, "\
X\tCdreader V%s - Copyright (c) 1992 by Patrick J. Wolfe\n\n\
XCdreader plays an audio compact disc loaded in an SGI cdrom drive through\n\
Xthe Audio Processor.\n\n\
XTo get started, load an audio CD in the cd caddy, and insert the caddy into \n\
Xyour SGI cdrom drive, with the clear side of the caddy facing up.  Wait until\n\
Xthe little yellow light stops blinking, and press the Play button.\n\n\
XThe buttons function as follows:\n\
X    Play    - plays an audio disk\n\
X    Shuffle - plays the whole disk in a random order\n\
X    Stop    - stops playback\n\
X    Pause   - suspends playback - press Play to continue\n\
X    Next    - selects the next track\n\
X    Prev    - selects the previous track\n\
X    Eject   - ejects the disk from the cdrom drive\n\
X    Tracks  - displays a table of tracks and their lengths\n\n\
XThe buttons are context sensitive, for example, the eject button won't function\n\
Xunless the program is in the \"Stopped\" state.  Buttons are are not active will\n\
Xappear dimly shaded.\n\n\
XNear the top of the window is a menu bar.  On the bar are three buttons:\n\
X    Quit    - terminates the program\n\
X    Info    - give the author some credit!\n\
X    Help    - uh, you're looking at it\n\n\
XThe scrollbar at the bottom controls the Audio Processor speaker/headphone volume.\n\n\
X    Enjoy!\n\
X", VERSION);
X
Xmessage_string = XmStringCreateLtoR (message, charset);
Xbutton_string = XmStringCreateLtoR ("Close", charset);
Xtitle_string = XmStringCreateLtoR ("Cdreader Help", charset);
X
Xn = 0;
XXtSetArg (args[n], XmNdialogTitle, title_string); n++;
XXtSetArg (args[n], XmNokLabelString, button_string); n++;
XXtSetArg (args[n], XmNmessageString, message_string); n++;
Xmessage_box = XmCreateMessageDialog (w, "helpbox", args, n);
Xbutton = XmMessageBoxGetChild (message_box, XmDIALOG_CANCEL_BUTTON);
XXtUnmanageChild (button);
Xbutton = XmMessageBoxGetChild (message_box, XmDIALOG_HELP_BUTTON);
XXtUnmanageChild (button);
Xif (title_string) XtFree (title_string);
Xif (button_string) XtFree (button_string);
Xif (message_string) XtFree (message_string);
XXtManageChild (message_box);
X}
X
X
X/* called by moving the volume scrollbar */
Xvoid
Xvolume_callback (w, client_data, call_data)
XWidget w;
Xcaddr_t client_data;	/* unused */
Xcaddr_t call_data;	/* unused */
X{
Xint volume;
XArg arg;
X
XXtSetArg (arg, XmNvalue, &volume);
XXtGetValues (volume_scrollbar, &arg, 1);
X
X/* set the audio processor output volume */
Xaudio_param_buf[0] = AL_LEFT_SPEAKER_GAIN;
Xaudio_param_buf[2] = AL_RIGHT_SPEAKER_GAIN;
Xaudio_param_buf[1] = audio_param_buf[3] = volume;
XALsetparams (AL_DEFAULT_DEVICE, audio_param_buf, 4);
X}
X
X
X
X/* called only when the program (track) number changes */
Xvoid
Xcd_pnum_callback (arg, type, data)
Xint arg;
XCDDATATYPES type;
Xstruct cdprognum *data;
X{
Xchar message[32];
X
Xif (play_index == -1) {
X	current_track = data->value;
X	(void) sprintf (message, "%s %d - %s %2d:%02d", Playing_Msg, current_track, Length_Word,
X		track_info[current_track].length_min,
X		track_info[current_track].length_sec);
X	set_message (message);
X	}
Xelse if (data->value != current_track) {	/* shuffling and the track changed */
X	play_index++;
X	if (play_index >= last_track) {
X		status = STOPPING;
X		}
X	else	{
X		current_track = play_list[play_index];
X		status = START_TRACK;
X		}
X	}
X}
X
X
X/* called for every frame - data is already byte swapped and de-emphasized */
Xvoid
Xcd_audio_callback (arg, type, data)
Xint arg;
XCDDATATYPES type;
Xvoid *data;
X{
XALwritesamps (audio_port, data, SAMPLES_PER_FRAME);
X}
X
X
Xint
Xinit_audio ()
X{
XALconfig aconfig;
Xint volume, slider_size, increment, page_increment;
X
Xif (audio_port == NULL) {
X	/* initialize the audio port */
X	aconfig = ALnewconfig ();
X
X	/*
X	 * create the maximum size audio buffer we can,
X	 * in another attempt to avoid pauses in the music.
X	 */
X	ALsetqueuesize (aconfig, SAMPLES_PER_FRAME * cd_init_readsize);
X
X#ifdef WATCH_ABUF
X	total_samps = SAMPLES_PER_FRAME * cd_init_readsize;
X	printf ("allocating an audio buffer that can hold %d samples\n", total_samps);
X#endif /* WATCH_ABUF */
X
X	/*
X	 * set the sample to 16 bit width and stereo.  Yes, I know it these are the
X	 * defaults ... TODAY, but I've gotten into trouble before by assuming the
X	 * defaults would never change change.  Besides, it doesn't hurt.
X	 */
X	ALsetwidth (aconfig, AL_SAMPLE_16);
X	ALsetchannels (aconfig, AL_STEREO);
X
X	audio_port = ALopenport ("cdreader", "w", aconfig);
X
X	/* free audio port config buffer immediately */
X	ALfreeconfig(aconfig);
X
X	if (audio_port == NULL) {
X		Display_Warning ("Could not open a port to the Audio Processor!");
X		return (1);
X		}
X	}
X
X/* set audio port output sampling rate to 44.1 kHz */
Xaudio_param_buf[0] = AL_OUTPUT_RATE;
Xaudio_param_buf[1] = AL_RATE_44100;
XALsetparams (AL_DEFAULT_DEVICE, audio_param_buf, 2);
X
X/* get current scrollbar values */
XXmScrollBarGetValues (volume_scrollbar, &volume,
X	&slider_size, &increment, &page_increment);
X
X/* set the volume scrollbar to the current volume */
Xaudio_param_buf[0] = AL_LEFT_SPEAKER_GAIN;
Xaudio_param_buf[2] = AL_RIGHT_SPEAKER_GAIN;
XALgetparams (AL_DEFAULT_DEVICE, audio_param_buf, 4);
Xvolume = MAX_VALUE(audio_param_buf[1], audio_param_buf[3]);
X
X/* set the scrollbar to the current value */
XXmScrollBarSetValues (volume_scrollbar, volume,
X	slider_size, increment, page_increment, 0);
X
Xreturn (0);
X}
X
X
Xint
Xinit_cd ()
X{
X/* allocate a buffer to read CD data into */
Xif (cd_buffer == (CDFRAME *) NULL) {
X	cd_buffer = (CDFRAME *) malloc (cd_init_readsize * CDDA_BLOCKSIZE);
X	if (cd_buffer == (CDFRAME *) NULL) {
X		Display_Warning ("cannot allocate enough memory for a cd digital data buffer");
X		return (1);
X		}
X	}
X
X/* create parser structure */
Xif (cd_parser == NULL) {
X	cd_parser = CDcreateparser ();
X	if (cd_parser == NULL) {
X		Display_Warning ("SERIOUS ERROR!\nCDcreateparser failed");
X		return (2);
X		}
X
X	/* initialize parser structure */
X	CDresetparser(cd_parser);
X
X	/* define callback routines for CDparseframe() */
X	CDsetcallback (cd_parser, cd_audio, cd_audio_callback, 0);
X	CDsetcallback (cd_parser, cd_pnum, cd_pnum_callback, 0);
X	}
Xreturn (0);
X}
X
X
Xvoid
Xinit_motif (argc, argv)
Xint argc;
Xchar **argv;
X{
XDisplay *display;
XWidget	app_shell, menu_bar, cascade, frame, form, row_column;
XXmString label_string;
XArg args[30];
Xint ctr;
X
XXtToolkitInitialize ();
Xappcon = XtCreateApplicationContext ();
Xdisplay = XtOpenDisplay (appcon, NULL, "cdreader", "Cdreader", NULL, 0, &argc, argv);
Xif (!display) {
X	XtWarning ("cdreader: Can't open your X display, exiting...");
X	exit (0);
X	}
X
Xapp_shell = XtAppCreateShell ("cdreader", "Cdreader", applicationShellWidgetClass, display, NULL, 0);
X
X/* XtGetApplicationResources (app_shell, &AppData, resources, XtNumber(resources), NULL, 0); */
X
Xctr = 0;
Xmain_window = XmCreateMainWindow (app_shell, "main1", args, ctr);
XXtManageChild (main_window);
X
Xctr = 0;
Xmenu_bar = XmCreateMenuBar (main_window, "menu_bar", args, ctr);
XXtManageChild (menu_bar);
X
Xctr = 0;
Xcascade = XmCreateCascadeButton (menu_bar, "Quit", args, ctr);
XXtManageChild (cascade);
XXtAddCallback (cascade, XmNactivateCallback, quit_pgm, NULL);
X
Xctr = 0;
Xcascade = XmCreateCascadeButton (menu_bar, "Info", args, ctr);
XXtManageChild (cascade);
XXtAddCallback (cascade, XmNactivateCallback, info_callback, NULL);
X
Xctr = 0;
Xcascade = XmCreateCascadeButton (menu_bar, "Help", args, ctr);
XXtManageChild (cascade);
XXtAddCallback (cascade, XmNactivateCallback, help_callback, NULL);
X
X/* let the menu bar know who's the help button around here */
Xctr = 0;
XXtSetArg (args[ctr], XmNmenuHelpWidget, cascade); ctr++;
XXtSetValues (menu_bar, args, ctr);
X
X/* create form to hold everything */
Xctr = 0;
Xform = XmCreateForm (main_window, "form", args, ctr);
XXtManageChild (form);
X
X/* create label gadget inside frame */
Xctr = 0;
Xlabel_string = XmStringCreate ("Stopped", charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
XXtSetArg (args[ctr], XmNwidth, 230); ctr++;
XXtSetArg (args[ctr], XmNheight, 35); ctr++;
XXtSetArg (args[ctr], XmNrecomputeSize, False); ctr++;
X/* set location inside the form */
XXtSetArg (args[ctr], XmNleftAttachment, XmATTACH_FORM); ctr++;
XXtSetArg (args[ctr], XmNrightAttachment, XmATTACH_FORM); ctr++;
XXtSetArg (args[ctr], XmNtopAttachment, XmATTACH_FORM); ctr++;
Xlabel = XmCreateLabelGadget (form, "label", args, ctr);
XXtManageChild (label);
X
X/* create frame mainwindow */
Xctr = 0;
XXtSetArg (args[ctr], XmNmarginWidth, 2); ctr++;
XXtSetArg (args[ctr], XmNmarginHeight, 2); ctr++;
XXtSetArg (args[ctr], XmNshadowThickness, 1); ctr++;
XXtSetArg (args[ctr], XmNshadowType, XmSHADOW_OUT); ctr++;
X/* set location inside the form */
XXtSetArg (args[ctr], XmNleftAttachment, XmATTACH_FORM); ctr++;
XXtSetArg (args[ctr], XmNrightAttachment, XmATTACH_FORM); ctr++;
XXtSetArg (args[ctr], XmNtopAttachment, XmATTACH_WIDGET); ctr++;
XXtSetArg (args[ctr], XmNtopWidget, label); ctr++;
Xframe = XmCreateFrame (form, "frame", args, ctr);
XXtManageChild (frame);
X
X/* create rowcolumn in frame to manage buttons */
Xctr = 0;
XXtSetArg (args[ctr], XmNpacking, XmPACK_COLUMN); ctr++;
XXtSetArg (args[ctr], XmNnumColumns, 4); ctr++;
Xrow_column = XmCreateRowColumn (frame, "row_column", args, ctr);
XXtManageChild (row_column);
X
X/* create buttons by column */
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (PLAY_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xplay_button = XmCreatePushButtonGadget (row_column, PLAY_NAME, args, ctr);
XXtManageChild (play_button);
XXtAddCallback (play_button, XmNarmCallback, play_callback, NULL);
XXmStringFree (label_string);
X
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (PREV_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xprev_button = XmCreatePushButtonGadget (row_column, PREV_NAME, args, ctr);
XXtManageChild (prev_button);
XXtAddCallback (prev_button, XmNarmCallback, prev_callback, NULL);
XXmStringFree (label_string);
X
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (PAUSE_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xpause_button = XmCreatePushButtonGadget (row_column, PAUSE_NAME, args, ctr);
XXtManageChild (pause_button);
XXtAddCallback (pause_button, XmNarmCallback, pause_callback, NULL);
XXmStringFree (label_string);
X
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (NEXT_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xnext_button = XmCreatePushButtonGadget (row_column, NEXT_NAME, args, ctr);
XXtManageChild (next_button);
XXtAddCallback (next_button, XmNarmCallback, next_callback, NULL);
XXmStringFree (label_string);
X
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (SHUFFLE_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xshuffle_button = XmCreatePushButtonGadget (row_column, SHUFFLE_NAME, args, ctr);
XXtManageChild (shuffle_button);
XXtAddCallback (shuffle_button, XmNarmCallback, shuffle_callback, NULL);
XXmStringFree (label_string);
X
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (TRACKS_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xtracks_button = XmCreatePushButtonGadget (row_column, TRACKS_NAME, args, ctr);
XXtManageChild (tracks_button);
XXtAddCallback (tracks_button, XmNarmCallback, tracks_callback, NULL);
XXmStringFree (label_string);
X
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (STOP_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xstop_button = XmCreatePushButtonGadget (row_column, STOP_NAME, args, ctr);
XXtManageChild (stop_button);
XXtAddCallback (stop_button, XmNarmCallback, stop_callback, NULL);
XXmStringFree (label_string);
X
Xctr = 0;
Xlabel_string = XmStringCreateLtoR (EJECT_NAME, charset);
XXtSetArg (args[ctr], XmNlabelString, label_string); ctr++;
Xeject_button = XmCreatePushButtonGadget (row_column, EJECT_NAME, args, ctr);
XXtManageChild (eject_button);
XXtAddCallback (eject_button, XmNarmCallback, eject_callback, NULL);
XXmStringFree (label_string);
X
X/* get the current volume */
Xaudio_param_buf[0] = AL_LEFT_SPEAKER_GAIN;
Xaudio_param_buf[2] = AL_RIGHT_SPEAKER_GAIN;
XALgetparams (AL_DEFAULT_DEVICE, audio_param_buf, 4);
X
X/* create scrollbar */
Xctr = 0;
XXtSetArg (args[ctr], XmNheight, 20); ctr++;
XXtSetArg (args[ctr], XmNorientation, XmHORIZONTAL); ctr++;
XXtSetArg (args[ctr], XmNprocessingDirection, XmMAX_ON_RIGHT); ctr++;
XXtSetArg (args[ctr], XmNmaximum, 255); ctr++;
XXtSetArg (args[ctr], XmNvalue, MAX_VALUE(audio_param_buf[1], audio_param_buf[3])); ctr++;
XXtSetArg (args[ctr], XmNvalueChangedCallback, volume_callback_list); ctr++;
X/* set location inside the form */
XXtSetArg (args[ctr], XmNleftAttachment, XmATTACH_FORM); ctr++;
XXtSetArg (args[ctr], XmNrightAttachment, XmATTACH_FORM); ctr++;
XXtSetArg (args[ctr], XmNtopAttachment, XmATTACH_WIDGET); ctr++;
XXtSetArg (args[ctr], XmNtopWidget, frame); ctr++;
XXtSetArg (args[ctr], XmNbottomAttachment, XmATTACH_FORM); ctr++;
Xvolume_scrollbar = XmCreateScrollBar (form, "scrollbar", args, ctr);
XXtManageChild (volume_scrollbar);
X
X/* set MainWindow areas */
XXmMainWindowSetAreas (main_window, menu_bar, NULL, NULL, NULL, form);
X
X/* put them all on the screen */
XXtRealizeWidget (app_shell);
X}
X
X
X#ifdef WATCH_ABUF
Xvoid
Xheartbeat ()
X{
Xshow_bufsleft = 1;
Xsignal (SIGALRM, heartbeat);
X}
X#endif /* WATCH_ABUF */
X
X
Xmain (argc, argv)
Xint argc;
Xchar *argv[];
X{
X#ifdef WATCH_ABUF
Xstruct itimerval itbuf;
X#endif /* WATCH_ABUF */
XXEvent an_event;
Xint frame_ctr, frames_to_play;
Xchar message[64];
X
X/*
X * Try to increase the scheduling priority of this process.  This should help
X * reduce pauses in the music, due to cpu scheduling conflicts by making
X * cdreader one of the highest priority processes in the system.
X *
X * The schedctl() system call will fail (silently) unless the program is installed
X * setuid to root, or the kernel is reconfigured to allow mere mortals to use
X * non-degrading priorities.  See the SCHEDCTL(2) and AUTOCONFIG(1M) manpages,
X * and the Irix Programming Guide Vol 2, section 14.2.3 for complete details.
X *
X * That's what I did, and it's pretty easy!  Basically, * become superuser,
X * edit /usr/sysgen/master.d/disp, find the variable "ndpri_hilim", change it's
X * value to "NDPHIMAX", run "autoconfig" and reboot.
X *
X * We don't care if it fails, probably the user doesn't have permission.
X *
X *	WARNING!  when testing new code, DON'T do this!  (this is
X *	the voice of experience talking, so listen up!)  If you get
X *	into an infinite loop, your system will LOCK UP tight!
X *	(because cdreader will be the highest priority process,
X *	and you won't be able to kill it)
X *	
X */
X#ifndef TESTING
X(void) schedctl (NDPRI, 0, NDPHIMAX+1);
X#endif
X
X/* lose any setuid priviledges assigned to allow schedctl to succeed */
X(void) seteuid (getuid());
X
X(void) signal (SIGHUP, quit_pgm);	/* should really trap everything, right? */
X(void) signal (SIGINT, quit_pgm);
X(void) signal (SIGQUIT, quit_pgm);
X(void) signal (SIGTERM, quit_pgm);
X
Xinit_motif (argc, argv);	/* initialize the X11/Motif user interface */
X
X/* main process loop */
Xfor (;;) {
X	switch (status) {
X
X	case PAUSED:
X	case STOPPED:
X		XtAppNextEvent (appcon, &an_event);
X		XtDispatchEvent (&an_event);
X		break;
X
X	case PAUSING:
X		(void) sprintf (message, "%s %d", Paused_Msg, current_track);
X		set_message (message);
X
X		XtSetSensitive (play_button, True);
X		XtSetSensitive (stop_button, True);
X		XtSetSensitive (pause_button, False);
X		XtSetSensitive (shuffle_button, False);
X		XtSetSensitive (eject_button, False);
X
X#ifdef WATCH_ABUF
X		/* turn off monitor timer */
X		signal (SIGALRM, SIG_IGN);
X		itbuf.it_value.tv_sec = itbuf.it_interval.tv_sec = 0;
X		itbuf.it_value.tv_usec = itbuf.it_interval.tv_usec = 0;
X		if (setitimer (ITIMER_REAL, &itbuf, (struct itimerval *) 0) == -1) {
X			perror ("setitimer");
X			return;
X			}
X#endif /* WATCH_ABUF */
X
X		status = PAUSED;
X		break;
X
X	case STOPPING:
X		set_message (Stopped_Msg);
X		current_track = 1;
X		if (cd_device != NULL) {
X			CDclose (cd_device);
X			cd_device = NULL;
X			}
X
X		XtSetSensitive (play_button, True);
X		XtSetSensitive (shuffle_button, True);
X		XtSetSensitive (eject_button, True);
X		XtSetSensitive (pause_button, False);
X		XtSetSensitive (stop_button, False);
X
X#ifdef WATCH_ABUF
X		/* turn off monitor timer */
X		signal (SIGALRM, SIG_IGN);
X		itbuf.it_value.tv_sec = itbuf.it_interval.tv_sec = 0;
X		itbuf.it_value.tv_usec = itbuf.it_interval.tv_usec = 0;
X		if (setitimer (ITIMER_REAL, &itbuf, (struct itimerval *) 0) == -1) {
X			perror ("setitimer");
X			return;
X			}
X#endif /* WATCH_ABUF */
X
X		status = STOPPED;
X		break;
X
X	case STARTING:
X		if (get_track_info()) {		/* opens cd_device and gets track_info[] */
X			status = STOPPING;
X			break;
X			}
X		current_track = first_track;	/* play whole disk from the start */
X		play_index = -1;
X
X	case START_TRACK:
X		if (CDseektrack(cd_device, current_track) == -1) {
X			(void) sprintf (message, "%s %d", CDSEEKTRACK_FAILED, current_track);
X			Display_Warning (message);
X			status = STOPPING;
X			break;
X			}
X		/* FALLTHROUGH */
X
X	case CONTINUE:
X		if (init_audio() || init_cd()) {	/* initialize audio port and cd structures */
X			status = STOPPING;
X			break;
X			}
X
X		(void) sprintf (message, "%s %d - %s %2d:%02d", Playing_Msg, current_track, Length_Word,
X			track_info[current_track].length_min,
X			track_info[current_track].length_sec);
X		set_message (message);
X
X		while (XtAppPending (appcon)) {		/* process any pending X events */
X			XtAppNextEvent (appcon, &an_event);
X			XtDispatchEvent (&an_event);
X			}
X
X		XtSetSensitive (pause_button, True);
X		XtSetSensitive (stop_button, True);
X		XtSetSensitive (play_button, False);
X		XtSetSensitive (shuffle_button, False);
X		XtSetSensitive (eject_button, False);
X
X#ifdef WATCH_ABUF
X		show_bufsleft = 0;
X		signal (SIGALRM, heartbeat);
X		itbuf.it_value.tv_sec = itbuf.it_interval.tv_sec = monitor_interval;
X		itbuf.it_value.tv_usec = itbuf.it_interval.tv_usec = 0;
X		if (setitimer (ITIMER_REAL, &itbuf, (struct itimerval *) 0) == -1) {
X			perror ("setitimer");
X			return;
X			}
X#endif /* WATCH_ABUF */
X
X		/*
X		 * read enough cd data to fill the audio buffer, to help
X		 * cover any later pauses due to cpu scheduling.
X		 * Be aware that we might still have some audio data left over
X		 * from a previous pause.
X		 */
X		frames_to_play = CDreadda (cd_device, &cd_buffer[0], cd_init_readsize);
X
X		/* process any pending X events */
X		while (XtAppPending (appcon)) {
X			XtAppNextEvent (appcon, &an_event);
X			XtDispatchEvent (&an_event);
X			}
X
X		if (frames_to_play < 1) {
X			status = STOPPING;
X			}
X		else	{
X			status = PLAYING;
X			}
X		break;
X
X	case PLAYING:
X
X#ifdef IRIX401_CDREADDA_BUG
X		/*
X		 *	BUG!  The manpage for CDreadda() says it returns the number
X		 *	of frames read.  In reality, it returns the number of bytes read
X		 *	into the buffer.  Divide by CDDA_BLOCKSIZE to get the number of frames.
X		 *
X		 *	I'd prefer it to work like the manpage says, return the number of frames.
X		 *	This is supposed to be fixed in the next release of Irix (after 4.0.1).
X		 */
X		frames_to_play = frames_to_play / CDDA_BLOCKSIZE;
X#endif /* IRIX401_CDREADDA_BUG */
X
X		/* process CD data */
X		for (frame_ctr = 0; frame_ctr < frames_to_play; frame_ctr++) {
X			CDparseframe (cd_parser, &cd_buffer[frame_ctr]);
X			}
X
X		/* process any pending X events */
X		while (XtAppPending (appcon)) {
X			XtAppNextEvent (appcon, &an_event);
X			XtDispatchEvent (&an_event);
X			}
X
X		/* still playing?  read more CD data */
X		if (status == PLAYING) {
X			frames_to_play = CDreadda (cd_device, &cd_buffer[0], cd_readsize);
X			if (frames_to_play < 1) {	/* end of disc */
X				if ((play_index > -1) && (play_index < last_track)) {	/* shuffling - play next track in the list */
X					play_index++;
X					current_track = play_list[play_index];
X					status = START_TRACK;
X					}
X				else	{	/* end of disc - stop */
X					status = STOPPING;
X					}
X				}
X			}
X
X#ifdef WATCH_ABUF
X		if (show_bufsleft) {
X			show_bufsleft = 0;
X			printf ("abufs left = %d\n", total_samps - ALgetfilled(audio_port));
X			}
X#endif /* WATCH_ABUF */
X
X		/* process any pending X events */
X		while (XtAppPending (appcon)) {
X			XtAppNextEvent (appcon, &an_event);
X			XtDispatchEvent (&an_event);
X			}
X		break;
X
X	default:
X		Display_Warning ("Cdreader has become horribly confused.\nYou should quit and start the program over");
X		status = STOPPING;
X		}
X	}
X
X/* NOTREACHED */
X}
X
X/*
X * Original Author:  Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
X *
X * This software is Copyright (c) 1992 by Patrick J. Wolfe.
X *
X * Permission is hereby granted to copy, distribute or otherwise 
X * use any part of this package as long as you do not try to make 
X * money from it or pretend that you wrote it.  This copyright 
X * notice must be maintained in any copy made.
X *
X * Use of this software constitutes acceptance for use in an AS IS 
X * condition. There are NO warranties with regard to this software.  
X * In no event shall the author be liable for any damages whatsoever 
X * arising out of or in connection with the use or performance of this 
X * software.  Any use of this software is at the user's own risk.
X *
X * If you make modifications to this software that you feel 
X * increases it usefulness for the rest of the community, please 
X * email the changes, enhancements, bug fixes as well as any and 
X * all ideas to me. This software is going to be maintained and 
X * enhanced as deemed necessary by the community.
X *              
X *              Patrick J. Wolfe
X *              uunet!kailand!pwolfe
X *              pwolfe@kai.com
X */
END_OF_FILE
if test 37755 -ne `wc -c <'cdreader.c'`; then
    echo shar: \"'cdreader.c'\" unpacked with wrong size!
fi
# end of 'cdreader.c'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(3353 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X#	Makefile for cdreader
X
X#	IRIX401_CDREADDA_BUG - workaround for bug in CDreadda() routine
X#		as distributed in Irix 4.0.1, and fixed in 4.0.2.
X#		See cdreader.c for details.
X#DEFS = -DIRIX401_CDREADDA_BUG
X
X# where the program will be installed
XBIN = /usr/local/bin
X
X# where the icon will be installed
XICONDIR = /usr/lib/images
X
X# where the manpage will be installed
XMANDIR = /usr/catman/local/man1
X
XCFLAGS = -O -s $(DEFS)
X#CFLAGS = -g -DTESTING $(DEFS)
X
X# SGI CD Audio library routines
XCDLIBS = -lcdaudio -lds
X
X# SGI Audio Processor library routines
XAUDIOLIBS = -laudio
X
X# X11/Motif window libraries
X#	The shared libraries make for a smaller executable, and are PREFERRED,
X#	but then the program will only run on systems that have them installed
X
X# shared libraries make an executable that's only 168 Kb!
XXLIBS = -lXm_s -lXt_s -lX11_s -lPW
X
X# non shared libraries make an executable that's over 1 Mb tall!
X#XLIBS = -lXm -lXt -lX11 -lPW
X
XLIBS = $(XLIBS) $(CDLIBS) $(AUDIOLIBS) -lc_s
XSHARFILES = Readme cdreader.c Makefile cdreader.icon cdreader.1
XTARFILES = README.1st Readme cdreader.c Makefile cdreader.icon cdreader.1 cdreader cdreader.shared_libs
X
Xall: cdreader
X
Xcdreader: cdreader.o
X	rm -f cdreader
X	$(CC) -o cdreader $(CFLAGS) cdreader.o $(LIBS)
X
Xcdreader.1nr: cdreader.1
X	nroff -man cdreader.1 > cdreader.1nr
X
Xinstall: $(BIN)/cdreader $(MANDIR)/cdreader.1 $(ICONDIR)/cdreader.icon
X
X$(BIN)/cdreader: cdreader
X	rm -f $(BIN)/cdreader
X	cp cdreader $(BIN)
X	chmod 755 $(BIN)/cdreader
X	ls -l $(BIN)/cdreader
X
X$(MANDIR)/cdreader.1: cdreader.1
X	rm -f $(MANDIR)/cdreader.1
X	cp cdreader.1 $(MANDIR)/cdreader.1
X	chmod 644 $(MANDIR)/cdreader.1
X	ls -l $(MANDIR)/cdreader.1
X
X$(ICONDIR)/cdreader.icon: cdreader.icon
X	rm -f $(ICONDIR)/cdreader.icon
X	cp cdreader.icon $(ICONDIR)/cdreader.icon
X	chmod 644 $(ICONDIR)/cdreader.icon
X	ls -l $(ICONDIR)/cdreader.icon
X
Xdeinstall:
X	rm -f $(BIN)/cdreader
X
Xlint:
X	lint -uvxz cdreader.c
X
Xshar: $(SHARFILES)
X	rm -f cdreader.shar
X	shar $(SHARFILES) > cdreader.shar
X	ls -l cdreader.shar
X
Xtar: $(TARFILES)
X	rm -f cdreader.tar*
X	tar cvf cdreader.tar $(TARFILES)
X	compress cdreader.tar
X	ls -l cdreader.tar*
X
Xclean:
X	rm -f a.out core mklog cdreader cdreader.shared_libs cdreader.shar *.o
X
X#
X# Original Author:  Patrick Wolfe  (pwolfe@kai.com, uunet!kailand!pwolfe)
X#
X# This software is Copyright (c) 1992 by Patrick J. Wolfe.
X#
X# Permission is hereby granted to copy, distribute or otherwise 
X# use any part of this package as long as you do not try to make 
X# money from it or pretend that you wrote it.  This copyright 
X# notice must be maintained in any copy made.
X#
X# Use of this software constitutes acceptance for use in an AS IS 
X# condition. There are NO warranties with regard to this software.  
X# In no event shall the author be liable for any damages whatsoever 
X# arising out of or in connection with the use or performance of this 
X# software.  Any use of this software is at the user's own risk.
X#
X# If you make modifications to this software that you feel 
X# increases it usefulness for the rest of the community, please 
X# email the changes, enhancements, bug fixes as well as any and 
X# all ideas to me. This software is going to be maintained and 
X# enhanced as deemed necessary by the community.
X#              
X#              Patrick J. Wolfe
X#              uunet!kailand!pwolfe
X#              pwolfe@kai.com
X#
END_OF_FILE
if test 3353 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'cdreader.icon' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cdreader.icon'\"
else
echo shar: Extracting \"'cdreader.icon'\" \(3086 characters\)
sed "s/^X//" >'cdreader.icon' <<'END_OF_FILE'
X#define cdreader_width 60
X#define cdreader_height 60
Xstatic char cdreader_bits[] = {
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x1e, 0xc0, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xf8, 0x7f, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0xfe, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x3f, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0xfe, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x07, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0xfe, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0xee, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x0e, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0xf8, 0x0f, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x1f,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x3f, 0x0e, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0xff, 0x7f, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00,
X   0x00, 0xc0, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff,
X   0x0f, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00,
X   0x00, 0xe0, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff,
X   0x0f, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00,
X   0x00, 0x80, 0xff, 0xff, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f,
X   0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x3f, 0x0e, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0xfc, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x0f,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
X   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
END_OF_FILE
if test 3086 -ne `wc -c <'cdreader.icon'`; then
    echo shar: \"'cdreader.icon'\" unpacked with wrong size!
fi
# end of 'cdreader.icon'
fi
if test -f 'cdreader.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'cdreader.1'\"
else
echo shar: Extracting \"'cdreader.1'\" \(2357 characters\)
sed "s/^X//" >'cdreader.1' <<'END_OF_FILE'
X...
X.TH CDREADER 1 "V1.5.1" "Usenet Software" "Usenet software"
X
X.SH NAME
Xcdreader \- play an audio compact disc through the audio processor
X
X.SH SYNOPSIS
X.B cdreader
X
X.SH DESCRIPTION
X.B Cdreader
Xplays an audio compact disc through the audio processor. 
X
XIt opens a window with several buttons, whose function is similar to those on a
Xaudio CD player.  Above the buttons is a message display, where the state of
Xthe program is displayed.  When playing music, the track number and length
Xappear here.  Below the buttons is a horizontal scrollbar, which is used to
Xcontrol the speaker/headphone volume of the audio processor.
X
X.B Cdreader
Xperforms much better when installed setuid root, or if you
Xreconfigure your Irix operating system to allow non-root
Xusers to use non-degrading priorities.  See the
X.I schedctl(2)
Xand
X.I autoconfig(1M)
Xmanpages, and the
X.I Irix Programming Guide Vol 2
Xsection 14.2.3 for complete details (It's easy).
X
XThere is a 2-4 second delay for anything to happen (Play, Pause, Stop).  This
Xis because of the large audio buffer being used.  It takes time to fill it up
Xand empty it out.  Other than that, and the weird things the buttons do
Xoccationally, everything works fine.
X
X.SH "BUTTONS"
XSeveral buttons appear in the main
X.B Cdreader
Xwindow.  They are:
X
X.TP
X.I Play
XIf stopped, begins playing the first track of an audio CD.
XIf paused, continues where you left off.
X.TP
X.I Pause
XSuspends audio playback.  Press Play to continue.
X.TP
X.I Shuffle
XBegins playing all the tracks on the CD, but in a random order.
X.TP
X.I Stop
XStops audio playback.  Resets the current location to the
Xfirst track of the CD.
X.TP
X.I Prev
Xswitches to the beginning of the previous track.
X.TP
X.I Next
Xswitches to the beginning of the next track.  If you press
XNext while on the last track, playback stops.
X.TP
X.I Tracks
Xdisplays a list of tracks and their lengths.  Sorry, but the
Xnames of the songs is NOT encoded on the CD.  Bummer.
X.TP
X.I Eject
XCauses the CD caddy to be ejected from the CDrom drive.
X.TP
X.I Quit
Xcloses the window and terminates the cdreader program.
X.TP
X.I Info
Xdisplays information about the program and programmer.
X.TP
X.I Help
Xdisplays an online help window
X
X.SH "SEE ALSO"
Xcdplayer(1), apanel(1), cdaudio(3), cdframe(4)
X
X.SH "AUTHOR"
X.B Cdreader
Xis Copyright (c) 1992 by Patrick J. Wolfe,
X(pwolfe@kai.com, uunet!kailand!pwolfe).
END_OF_FILE
if test 2357 -ne `wc -c <'cdreader.1'`; then
    echo shar: \"'cdreader.1'\" unpacked with wrong size!
fi
# end of 'cdreader.1'
fi
echo shar: End of shell archive.
exit 0
-- 

	Patrick Wolfe (pwolfe@kai.com)
	System Manager, Kuck and Associates