/*						sound.c	*/
/* play sound files on SPARCstation 10			*/
/* ------------------------------------------------------------	*/
/* (C)opyright by Claus Assmann					*/
/*  Institut fuer Informatik und praktische Mathematik		*/
/*  Preusserstr. 1 - 9						*/
/*  D 24105 Kiel						*/
/*  EMail: ca@informatik.uni-kiel.de				*/
/* ------------------------------------------------------------	*/
/* please send bug reports, enhancemants etc to above address	*/
/* ------------------------------------------------------------	*/
/*
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both the copyright notice and this permission notice and warranty
disclaimer appear in supporting documentation.

I disclaim all warranties with regard to this software, including
all implied warranties of merchantability and fitness.  In no event
shall I be liable for any special, indirect or consequential
damages or any damages whatsoever resulting from loss of use, data or
profits, whether in an action of contract, negligence or other
tortious action, arising out of or in connection with the use or
performance of this software.
*/


#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <unistd.h>

#include <stropts.h>
#include <sys/ioctl.h>

#include <sun/audioio.h>


#define	Error		(void) fprintf
#define FALSE	0
#define TRUE	1

#define AUDIO_SUCCESS	0


/* Local variables */
char *prog;
char prog_desc[] = "Play an audio file";
char prog_opts[] = "Viv:d:s:?";		/* getopt() flags */

char		*Stdin = "stdin";
unsigned char	buf[1024 * 64];		/* size should depend on sample_rate */


#define ABS(x)	((x) >= 0 ? (x) : (0-(x)))
#define	MAX_GAIN		(100)		/* maximum gain */

/*
 * This defines the tolerable sample rate error as a ratio between the
 * sample rates of the audio data and the audio device.
 */
#define	SAMPLE_RATE_THRESHOLD	(0.1)

typedef struct {
       unsigned int	sample_rate;
       unsigned int	encoding;
       unsigned int	precision;
       unsigned int	channels;
       } audio_formats_t;

/* ------------------------------------------------------------	*/
/* table of supported audio formats				*/
audio_formats_t audio_formats[] = {
        8000,	AUDIO_ENCODING_ULAW,	8,	1
,	8000,	AUDIO_ENCODING_ALAW,	8,	1
,	8000,	AUDIO_ENCODING_LINEAR,	16,	1
,	9600,	AUDIO_ENCODING_LINEAR,	16,	1
,	9600,	AUDIO_ENCODING_LINEAR,	16,	2
,	11025,	AUDIO_ENCODING_LINEAR,	16,	1
,	11025,	AUDIO_ENCODING_LINEAR,	16,	2
,	16000,	AUDIO_ENCODING_LINEAR,	16,	1
,	16000,	AUDIO_ENCODING_LINEAR,	16,	2
,	18900,	AUDIO_ENCODING_LINEAR,	16,	1
,	18900,	AUDIO_ENCODING_LINEAR,	16,	2
,	22050,	AUDIO_ENCODING_LINEAR,	16,	1
,	22050,	AUDIO_ENCODING_LINEAR,	16,	2
,	32000,	AUDIO_ENCODING_LINEAR,	16,	1
,	32000,	AUDIO_ENCODING_LINEAR,	16,	2
,	37800,	AUDIO_ENCODING_LINEAR,	16,	1
,	37800,	AUDIO_ENCODING_LINEAR,	16,	2
,	44100,	AUDIO_ENCODING_LINEAR,	16,	1
,	44100,	AUDIO_ENCODING_LINEAR,	16,	2
,	48000,	AUDIO_ENCODING_LINEAR,	16,	1
,	48000,	AUDIO_ENCODING_LINEAR,	16,	2
};
#define AUDIO_FORMATS	(sizeof(audio_formats) / sizeof(audio_formats[0]))

#ifndef AUDIO_ENCODING_FLOAT
#define AUDIO_ENCODING_FLOAT	(-1)
#endif

unsigned	Volume = ~0;		/* output volume */
double		Savevol;		/* saved volume level */
int		Verbose = FALSE;	/* verbose messages */
int		Immediate = FALSE;	/* don't hang waiting for device */
char		*Audio_dev = "/dev/audio";

int		Audio_fd = -1;		/* file descriptor for audio device */
audio_info_t	Dev_hdr;		/* audio header for device */
char		*Ifile;			/* current filename */
audio_formats_t	File_hdr;		/* audio header for file */

unsigned int	sample_rate,encoding,precision,channels;


/* Global variables */
extern int getopt();
extern int optind;
extern char *optarg;


usage()
{
       Error(stderr, "%s -- usage:\n\t%s ", prog_desc, prog);
       Error(stderr, "\t[-V] [-v #] file ...\nwhere:\n");
       Error(stderr, "\t-V\tPrint verbose warning messages\n");
       Error(stderr, "\t-v #\tSet output volume (0 - %d)\n", MAX_GAIN);
       Error(stderr, "\tfile\tList of files to play\n");
/*
       Error(stderr, "\t[-iV] [-v #] [-d dev] [file ...]\nwhere:\n");
       Error(stderr, "\t-i\tDon't hang if audio device is busy\n");
       Error(stderr, "\t-V\tPrint verbose warning messages\n");
       Error(stderr, "\t-v #\tSet output volume (0 - %d)\n", MAX_GAIN);
       Error(stderr, "\t-d dev\tSpecify audio device (default: /dev/audio)\n");
       Error(stderr, "\t-s sample_rate\tsample rate (default: %d)\n",sample_rate);
       Error(stderr, "\tfile\tList of files to play\n");
       Error(stderr, "\t\tIf no files specified, read stdin\n");
*/
       exit(1);
}

void
sigint()
{
       /* flush output queues before exiting */
       if (Audio_fd >= 0) {
/*
              (void) audio_flush_play(Audio_fd);
              if (Volume != ~0)
                     (void) audio_set_play_gain(Audio_fd, &Savevol);
*/
              (void) close(Audio_fd);			/* close output */
       }
       exit(1);
}

/* ------------------------------------------------------------	*/
/* Parse an unsigned integer					*/
int parse_unsigned(str, dst, flag)
 char             *str;
 unsigned *dst;
 char             *flag;
{
 char             x;

 if (sscanf(str, "%u%c", dst, &x) != 1) {
   Error(stderr, "%s: invalid value for %s\n", prog, flag);
   return (1);
 }
 return (0);
}

/* ------------------------------------------------------------	*/
/* is the combination supported ?				*/
int supported(sample_rate,encoding,precision,channels)
 unsigned int	sample_rate,encoding,precision,channels;
 {
  int i,res;

  res = FALSE;
  i   = 0;
  while (!res && i < AUDIO_FORMATS) {
    res = sample_rate	== audio_formats[i].sample_rate
       && encoding	== audio_formats[i].encoding
       && precision	== audio_formats[i].precision
       && channels	== audio_formats[i].channels;
    ++i;
  }
  return(res);
 }

/* ------------------------------------------------------------	*/
/* can the combination be supported ?				*/
int adopt(sample_rate,encoding,precision,channels)
 unsigned int	*sample_rate,*encoding,*precision,*channels;
 {
  double	derivation;
  int		i,res;

  res = FALSE;
  i   = 0;
  while (!res && i < AUDIO_FORMATS) {
    if (res = *encoding	== audio_formats[i].encoding
       && *precision	== audio_formats[i].precision
       && *channels	== audio_formats[i].channels) {
      derivation = ABS(1.0 - (double)(*sample_rate) / (double)audio_formats[i].sample_rate);
      if (res = (derivation <= SAMPLE_RATE_THRESHOLD)) {
        *sample_rate = audio_formats[i].sample_rate;
      }
    }
    ++i;
  }
  return(res);
 }

/* ------------------------------------------------------------	*/
/* read header from ifd, set File_hdr				*/
int audio_read_filehdr(ifd, File_hdr)
 int			ifd;
 audio_formats_t	*File_hdr;
 {
  char	buf[64];
  int	cnt;
  long type,channel,hertz;
  unsigned int	e,p;

  File_hdr->sample_rate	= 0;
  File_hdr->encoding	= 0;
  File_hdr->precision	= 0;
  File_hdr->channels	= 0;
  if ((cnt = read(ifd, (char *)buf, sizeof (buf))) >= 0) {
    if (!strncmp(buf,".snd",4)) {	/* .snd file ?	*/
      /* ja: restliche daten lesen	*/
      type    = *((long *)(buf + 12));
      channel = *((long *)(buf + 20));
      hertz   = *((long *)(buf + 16));
      switch(type) {
        case 1: e = AUDIO_ENCODING_ULAW;	p = 8;	break;
        case 2: e = AUDIO_ENCODING_LINEAR;	p = 8;	break;
        case 3: e = AUDIO_ENCODING_LINEAR;	p = 16;	break;
        case 4: e = AUDIO_ENCODING_LINEAR;	p = 24;	break;
        case 5: e = AUDIO_ENCODING_LINEAR;	p = 32;	break;
        case 6: e = AUDIO_ENCODING_FLOAT;	p = 32;	break;
        case 7: e = AUDIO_ENCODING_FLOAT;	p = 64;	break;
        default: e = AUDIO_ENCODING_LINEAR; p = 16; break;
      }
      File_hdr->sample_rate	= hertz;
      File_hdr->encoding	= e;
      File_hdr->precision	= p;
      File_hdr->channels	= channel;
     }
    else {
    }
   }
  else {
  }
  (void)lseek(ifd,0L,SEEK_SET);

 }

/*
 * Play a list of audio files.
 */
main(argc, argv)
       int		argc;
       char		**argv;
{
       int		i;
       int		cnt;
       int		err;
       int		ifd;
       double		vol;
       struct stat	st;
       struct sigvec	vec;

       prog		= argv[0];		/* save program initiation name */
       sample_rate	= audio_formats[0].sample_rate;
       encoding		= audio_formats[0].encoding;
       precision	= audio_formats[0].precision;
       channels		= audio_formats[0].channels;

       err = 0;
       while ((i = getopt(argc, argv, prog_opts)) != EOF) switch (i) {
       case 'v':
        if (parse_unsigned(optarg, &Volume, "-v")) {
              err++;
        } else if (Volume > MAX_GAIN) {
              Error(stderr, "%s: invalid value for -v\n", prog);
              err++;
        }
        break;
       case 's':
        if (parse_unsigned(optarg, &sample_rate, "-s")) {
              err++;
        }
        break;
       case 'd':
        Audio_dev = optarg;
        break;
       case 'V':
        Verbose = TRUE;
        break;
       case 'i':
        Immediate = TRUE;
        break;
       case '?':
        usage();
/*NOTREACHED*/
       }
       if (err > 0)
         exit(1);

       argc -= optind;		/* update arg pointers */
       argv += optind;

       /* Validate and open the audio device */
       err = stat(Audio_dev, &st);
       if (err < 0) {
              Error(stderr, "%s: cannot stat ", prog);
              perror(Audio_dev);
              exit(1);
       }
       if (!S_ISCHR(st.st_mode)) {
              Error(stderr, "%s: %s is not an audio device\n", prog,
                  Audio_dev);
              exit(1);
       }

       /* Try it quickly, first */
       Audio_fd = open(Audio_dev, O_WRONLY | O_NDELAY);
       if ((Audio_fd < 0) && (errno == EBUSY)) {
         if (Immediate) {
              Error(stderr, "%s: %s is busy\n", prog, Audio_dev);
              exit(1);
         }
         if (Verbose) {
              Error(stderr, "%s: waiting for %s...", prog, Audio_dev);
              (void) fflush(stderr);
         }
         /* Now hang until it's open */
         Audio_fd = open(Audio_dev, O_WRONLY);
         if (Verbose)
              Error(stderr, "%s\n", (Audio_fd < 0) ? "" : "open");
       }
       if (Audio_fd < 0) {
              Error(stderr, "%s: error opening ", prog);
              perror(Audio_dev);
              exit(1);
       }

       /* Get the device output encoding configuration */
       if (ioctl(Audio_fd, AUDIO_GETINFO, &Dev_hdr) != AUDIO_SUCCESS) {
              Error(stderr, "%s: %s is not an audio device\n",
                  prog, Audio_dev);
              exit(1);
       }

#ifndef SOLARIS2
       /* Get the device configuration */
       if (ioctl(Audio_fd, AUDIO_GETDEV, &i) != AUDIO_SUCCESS) {
              Error(stderr, "%s: %s is not an appropriate audio device\n",
                  prog, Audio_dev);
              exit(1);
       }
       if (i != AUDIO_DEV_SPEAKERBOX) {
              Error(stderr, "%s: %s is not an audio speaker box\n",
                  prog, Audio_dev);
              exit(1);
       }
#endif

#ifdef UnBenutzt
        if (!supported(sample_rate,encoding,precision,channels)) {
              Error(stderr, "%s: audio format not supported\n",
                  prog);
              exit(1);
        }
        Dev_hdr.play.sample_rate	= sample_rate;
        Dev_hdr.play.precision		= precision;
        Dev_hdr.play.encoding		= encoding;
        Dev_hdr.play.channels		= channels;
       if ((err = ioctl(Audio_fd, AUDIO_SETINFO, &Dev_hdr)) != AUDIO_SUCCESS) {
              Error(stderr, "%s: ioctl failed: %d\n",
                  prog, err);
              exit(1);
       }
#endif

       /* If -v flag, set the output volume now */
       if (Volume != ~0) {
              vol = (double) Volume / (double) MAX_GAIN;
#ifdef UnBenutzt
              (void) audio_get_play_gain(Audio_fd, &Savevol);
              err = audio_set_play_gain(Audio_fd, &vol);
              if (err != AUDIO_SUCCESS) {
              Error(stderr,
                  "%s: could not set output volume for %s\n",
                  prog, Audio_dev);
              exit(1);
              }
#endif
       }

       /* Set up SIGINT handler to flush output */
       vec.sv_handler = sigint;
       vec.sv_mask = 0;
       vec.sv_flags = 0;
       (void) sigvec(SIGINT, &vec, (struct sigvec *)NULL);


       if (argc <= 0) {
         Error(stderr,"%s: missing filename\n",prog);
         exit(1);
       } else {
         Ifile = *argv++;
         argc--;
       }

       /* Loop through all filenames */
       do {
         if ((ifd = open(Ifile, O_RDONLY, 0)) < 0) {
           Error(stderr, "%s: cannot open ", prog);
           perror(Ifile);
           goto nextfile;
         }

         err = audio_read_filehdr(ifd, &File_hdr);
         sample_rate	= File_hdr.sample_rate;
         precision	= File_hdr.precision;
         encoding	= File_hdr.encoding;
         channels	= File_hdr.channels;
         if (!supported(sample_rate,encoding,precision,channels)) {
           if (!adopt(&sample_rate,&encoding,&precision,&channels)) {
             Error(stderr, "%s: audio format not supported\n",prog);
             exit(1);
            }
            else {
              if (Verbose) {
               Error(stderr,"%s: WARNING: %s sampled at %d, playing at %d\n",
                         prog, Ifile, File_hdr.sample_rate, sample_rate);
             }
           }
         }
         Dev_hdr.play.sample_rate	= sample_rate;
         Dev_hdr.play.precision		= precision;
         Dev_hdr.play.encoding		= encoding;
         Dev_hdr.play.channels		= channels;
         Dev_hdr.play.gain		= Volume;
         if ((err = ioctl(Audio_fd, AUDIO_SETINFO, &Dev_hdr)) != AUDIO_SUCCESS) {
                Error(stderr, "%s: ioctl failed: %d\n",
                    prog, err);
                exit(1);
         }

         /*
          * At this point, we're all ready to copy the data.
          */
         while ((cnt = read(ifd, (char *)buf, sizeof (buf))) > 0) {
           i = 0;
           while (cnt > 0) {
             /* If input EOF, write an eof marker */
             err = write(Audio_fd, (char *)(buf+i), cnt);
             if (err > 0) {
               cnt -= err;
               i   += err;
             }
           }
         }
         if (cnt == 0) {
             err = write(Audio_fd, (char *)buf, 0);
         }
         if (cnt < 0) {
                Error(stderr, "%s: error reading ", prog);
                perror(Ifile);
         }

closeinput:
         (void) close(ifd);		/* close input file */
nextfile:;
       } while ((argc > 0) && (argc--, (Ifile = *argv++) != NULL));

       /*
        * Though drain is implicit on close(), it's performed here
        * for the sake of completeness, and to ensure that the volume
        * is reset after all output is complete.
        */
#ifdef UnBenutzt
       (void) audio_drain(Audio_fd, FALSE);
       if (Volume != ~0) {
              (void) audio_set_play_gain(Audio_fd, &Savevol);
       }
#endif
       
       (void) close(Audio_fd);			/* close output */
       exit(0);
/*NOTREACHED*/
}

#ifdef UnBenutzt

                     ratio = (double) abs((int)
                         (Dev_hdr.sample_rate - File_hdr.sample_rate)) /
                         (double) File_hdr.sample_rate;
                     if (ratio <= SAMPLE_RATE_THRESHOLD) {
                            if (Verbose) {
                                   Error(stderr,
                         "%s: WARNING: %s sampled at %d, playing at %d\n",
                                       prog, Ifile, File_hdr.sample_rate,
                                       Dev_hdr.sample_rate);
                            }
                            return (TRUE);
                     }
                     Error(stderr, "%s: %s sample rate %d not available\n",
                         prog, Ifile, File_hdr.sample_rate);
                     return (FALSE);
              }
              (void) audio_enc_to_str(&File_hdr, msg);
              Error(stderr, "%s: %s encoding not available: %s\n",
                  prog, Ifile, msg);
#endif

/* end of	sound.c	*/
