#include <stdio.h>
#include <sys/soundcard.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <strings.h>
#include <errno.h>
#include <signal.h> /* Just for fun we use POSIX-compliant signals */
#include </dev/null> /* Spot the irony there :-) */

#define AUDIODEVICE "/dev/dsp"
#define DEFAULTSPEED 22050
#define DEFAULTFMT AFMT_S16_LE
#define CHUNKSIZE 512

char buf[CHUNKSIZE]; /* I rely on sizeof(char)==1. I know, this is bad :-( */
char *p;

int audiod,ind;
int speed_req,speed;
int format_req,format;
char stopit;
int chunkread,chunkwritten,chunkleft;

void inthand(int whatever)
{
  stopit = 1;
}

#define FNAMEBUFLENGTH 50
char fname_buf[FNAMEBUFLENGTH];
char *fname(int fmt)
{
  switch (fmt) {
  case AFMT_U8: strcpy(fname_buf,"u8"); break;
  case AFMT_S16_LE: strcpy(fname_buf,"s16-le"); break;
  case AFMT_MU_LAW: strcpy(fname_buf,"mu-law"); break;
  case AFMT_A_LAW: strcpy(fname_buf,"a-law"); break;
  default: strcpy(fname_buf,"Unknown"); break;
  }
  return fname_buf;
}

void main(int argc,char **argv)
{
#define FILENAMEMAXLENGTH 257
  char filename[FILENAMEMAXLENGTH];
  int i;
  char gotfn;
  struct sigaction myaction;
  speed_req=DEFAULTSPEED;
  format_req=DEFAULTFMT;
  gotfn=0;
  for (i=1;i<argc;i++) {
    if ((strcmp(argv[i],"-h")==0)||(strcmp(argv[i],"--help")==0)) {
      fprintf(stderr,"Usage: %s [OPTION]... [FILE]\n",argv[0]);
      fprintf(stderr,"  -f, --format FMT    set file format\n");
      fprintf(stderr,"  -h, --help          print this message to stderr and exit\n");
      fprintf(stderr,"  -s, --speed SPEED   set sample rate\n");
      fprintf(stderr,"  -v, --version       print version number to stderr and exit\n");
      fprintf(stderr,"Legal formats are:\n");
      fprintf(stderr,"  u8                  unsigned 8-bit\n");
      fprintf(stderr,"  s16, s16-le         signed 16-bit, little-endian\n");
      fprintf(stderr,"  mu-law\n");
      fprintf(stderr,"  a-law\n");
      exit(0);
    } else if ((strcmp(argv[i],"-v")==0)||(strcmp(argv[i],"--version")==0)) {
      fprintf(stderr,"Version -infinity.42.31415\n");
      fprintf(stderr,"This program is in the public domain\n");
      exit(0);
    } else if ((strcmp(argv[i],"-s")==0)||(strcmp(argv[i],"--speed")==0)) {
      if ((++i>=argc)||(argv[i][0]=='-')) {
        fprintf(stderr,"Speed expected as argument %d.\n",i);
        exit(1);
      }
      if (sscanf(argv[i],"%d",&speed_req)!=1) {
        fprintf(stderr,"Error parsing argument %d.\n",i);
        exit(1);
      }
    } else if ((strcmp(argv[i],"-f")==0)||(strcmp(argv[i],"--format")==0)) {
      if ((++i>=argc)||(argv[i][0]=='-')) {
        fprintf(stderr,"Format expected as argument %d.\n",i);
        exit(1);
      }
      if ((strcmp(argv[i],"u8")==0))
        format_req=AFMT_U8;
      else if ((strcmp(argv[i],"s16")==0)||(strcmp(argv[i],"s16-le")==0))
        format_req=AFMT_S16_LE;
      else if ((strcmp(argv[i],"mu-law")==0))
        format_req=AFMT_MU_LAW;
      else if ((strcmp(argv[i],"a-law")==0))
        format_req=AFMT_A_LAW;
      else {
        fprintf(stderr,"Unknown format %s.\n",argv[i]);
        exit(1);
      }
    } else if ((argv[i][0]=='-')&&(argv[i][1]!=0)) {
      fprintf(stderr,"Unkown option %s.\n",argv[i]);
      exit(1);
    } else {
      if (!gotfn) {
        strncpy(filename,argv[i],FILENAMEMAXLENGTH-1);
        filename[FILENAMEMAXLENGTH-1]=0;
        gotfn=1;
      } else {
        fprintf(stderr,"Only one file name, please.\n");
        exit(1);
      }
    }
  }
  if (!gotfn) {
    printf("Please enter input file name: ");
    fgets(filename,FILENAMEMAXLENGTH,stdin);
    i=strlen(filename);
    if (filename[i-1]=='\n') filename[i-1]=0;
  }
  if (strcmp(filename,"-")==0) ind=0;
  else if ((ind=open(filename,O_RDONLY))==-1) {
    perror("Opening input file");
    exit(1);
  }
  if ((audiod=open(AUDIODEVICE,O_WRONLY))==-1) {
    perror("Opening audio device");
    exit(1);
  }
  if (ioctl(audiod,SNDCTL_DSP_RESET)) {
    perror("Could not reset audio device");
    exit(1);
  }
  fprintf(stderr,"Initialization successfully completed.\n");
  fprintf(stderr,"Setting device speed to %d.\n",speed_req);
  speed=speed_req;
  if (ioctl(audiod,SNDCTL_DSP_SPEED,&speed)) {
    perror("Could not set speed");
    exit(1);
  }
  fprintf(stderr,"Speed set to %d.\n",speed);
  fprintf(stderr,"Setting device format to %s.\n",fname(format_req));
  format=format_req;
  if (ioctl(audiod,SNDCTL_DSP_SETFMT,&format)) {
    perror("Could not set format");
    exit(1);
  }
  fprintf(stderr,"Format set to %s.\n",fname(format));
  myaction.sa_handler = inthand;
  myaction.sa_mask = SIGINT|SIGQUIT|SIGTERM;
  myaction.sa_flags = 0;
  stopit=0;
  if (sigaction(SIGINT,&myaction,NULL)) { perror("Seting signal handler"); exit(1); }
  if (sigaction(SIGQUIT,&myaction,NULL)) { perror("Seting signal handler"); exit(1); }
  if (sigaction(SIGTERM,&myaction,NULL)) { perror("Seting signal handler"); exit(1); }
  fprintf(stderr,"Playing: press ^C to stop.\n");
  while (!stopit) {
    if ((chunkread=read(ind,&buf,CHUNKSIZE))==-1) {
      if (errno==EINTR) chunkread=0;
      else {
        perror("Reading data");
        exit(1);
      }
    }
    if (stopit) break;
    if (!chunkread) break; /* EOF */
    p=buf;
    chunkleft=chunkread;
    do {
      if ((chunkwritten=write(audiod,p,chunkleft))==-1) {
        if (errno==EINTR) chunkwritten=0;
        else {
          perror("Writing data");
          exit(1);
        }
      }
      p+=chunkwritten;
      chunkleft-=chunkwritten;
      if (stopit) break;
    } while (chunkleft);
    if (stopit) break;
  }
  fprintf(stderr,"Exiting.\n");
  if (close(ind)) { perror("Closing output file"); exit(1); }
  if (close(audiod)) { perror("Closing audio device"); exit(1); }
  fprintf(stderr,"All was sucessful!\n");
  exit(0);
}
