/*
 * $Id: read_cdda.c,v 1.13 1997/10/14 08:46:39 jim Exp $
 * Read audio track from a CD using CDDA
 * 
 * Written by Jim Mintha (mintha@geog.ubc.ca)
 * Large portions are borrowed from the Workman 
 * sources written by Steven Grimm (koreth@hyperion.com)
 *
 * $Log: read_cdda.c,v $
 * Revision 1.13  1997/10/14 08:46:39  jim
 * make sure we clean up on exit
 *
 * Revision 1.12  1997/10/14 07:32:31  jim
 * Added -D dev, fixed getopt struct, updated help
 *
 * Revision 1.11  1997/10/10 09:22:56  jim
 * type
 *
 * Revision 1.10  1997/10/10 09:09:44  jim
 * added patches from "Alexander V. Panasyuk" <panasyuk@cfauvcs5.harvard.edu>
 *
 * Revision 1.9  1997/10/02 16:57:01  jim
 * (int) stdout isn't correct, 1 is better
 *
 * Revision 1.8  1997/09/10 00:17:07  jim
 * lots of changes to add jitter control, clean things up
 * reset block size when done, better output, etc.
 *
 * Revision 1.7  1997/08/31 21:36:18  jim
 * added changes from Andreas Karrer for output to stdout, changed
 * options a bit.
 *
 * Revision 1.6  1996/06/04 23:00:45  jim
 * Patches from Hans Werner Strube (strube@physik3.gwdg.de)
 * to open volmgt device if normal open fails.  To allow
 * output to stdout (- filename) and don't call cdda_init if
 * just checking drive type.
 *
 * Revision 1.5  1996/01/18 14:05:49  jim
 * Added ability to query drive type
 * moved some stuff back from header file
 *
 * Revision 1.4  1996/01/10 10:05:33  jim
 * Fixed display on song sectors
 *
 * Revision 1.3  1996/01/10 09:53:22  jim
 * mistake in checking starting time
 *
 * Revision 1.2  1996/01/10 09:44:01  jim
 * Added -q to help
 *
 * Revision 1.1  1996/01/10 09:11:32  jim
 * Initial revision
 *
 */

/*
 * Notes: (this is my understanding of things)
 * 
 * One CDDA block is 2368 bytes each block contains:
 * 588 frames and 16bytes for Q data.
 * Each frame is 4 bytes (presumably 2 16bit values)
 * 588 * 4 = 2352 + 16 = 2368
 * Each frame is 1/44100 th. of a second (44100 Hz)
 * therefore each block is 1/75 th. of a second
 *
 * You can also read without the Q data in which case
 * each block is only 2352 bytes long.
 */

#include "read_cdda.h"
#include "version.h"

/* disable X window stuff in my general utilities */
NO_X_WIN

/* Global variables - yuk */

/*
 * This is the fastest way to convert from BCD to 8-bit.
 */
unsigned char unbcd[256] = {
  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  0,0,0,0,0,0,
 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,  0,0,0,0,0,0,
 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,  0,0,0,0,0,0,
 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,  0,0,0,0,0,0,
 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,  0,0,0,0,0,0,
 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,  0,0,0,0,0,0,
 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,  0,0,0,0,0,0,
 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,  0,0,0,0,0,0,
 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,  0,0,0,0,0,0,
 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,  0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

/* where we are and where we want to be */
typedef struct 
{
  int length;
  int start_block;
  int num_blocks;
  int mins,secs,frames;
} Track;

Track tracks[60];

boolean verbose = FALSE;
unsigned char *ulawmap;
int NUM_BLOCKS=150;

int
main(int argc, char **argv)
{
  unsigned char *cdda_buf, *cdda_buf2, *temp_buf, *prev_end;
  long cddabuf_len, to_write, to_reread, jitter = 0, to_read, to_skip, num_read;
  int cd_fd, out_fd, track, fmt = CDR, ch, ctr, want_percent;
  int start_block, num_blocks, current_block, num_tracks;
  int starting_sec = 0, ending_sec = 0, end_block;
  boolean toc = FALSE, swap = FALSE, drv_type = FALSE;
  char *device_name = NULL;
  extern char *optarg;
  extern int optind;
  struct option const longopts[] = 
    {
      /* name, has_arg, *flag, val */
      { "help", 0, 0, 'h'},
      { "start", 1, 0, 's'},
      { "end", 1, 0, 'e'},
      { "au16", 0, 0, 'a'},
      { "au8", 0, 0, '8'},
      { "swap", 0, 0, 'x'},
      { "buffer", 1, 0, 'b'},
      { "verbose", 0, 0, 'v'},
      { "toc", 0, 0, 't'},
      { "drvtype", 0, 0, 'd'},
      { "device", 1, 0, 'D'},
      { "version", 0, 0, 'V'},
      { NULL, 0, NULL, 0}
    };

  while((ch = getopt_long(argc, argv, "hs:e:a8xb:vtdD:V",
			  longopts, (int *) 0)) != -1)
    {
      switch(ch)
	{
	case 'h':   /* help */
	  usage(argv[0]);
	  break;
	case 'v':   /* quiet */
	  verbose = TRUE;
	  break;
	case 't':   /* toc */
	  toc = TRUE;
	  verbose = TRUE;
	  break;
	case 'd':   /* drive type */
	  drv_type = TRUE;
	  break;
	case 'D':   /* device */
	  device_name = optarg;
	  break;
	case 'x':   /* swap */
	  swap = TRUE;
	  break;
	case '8':   /* Sun .au 8KHz */
	  fmt = AU8;
	  break;
	case 'a':   /* Sun .au 44.1KHz */
	  fmt = AU16;
	  break;
	case 'w':   /* WAV format */
	  fmt = WAV;
	  break;
	case 'b':   /* Buffer size in blocks */
	  sscanf(optarg, "%d", &NUM_BLOCKS);
	  if(NUM_BLOCKS < 10 || NUM_BLOCKS > 400)
	    pop_error(ERR_GEN, "Buffer size: %d invalid", NUM_BLOCKS);
	  break;
	case 's':   /* start time */
	  sscanf(optarg, "%d", &starting_sec);
	  if(starting_sec < 0)
	    pop_error(ERR_GEN, "Starting time: %d invalid", starting_sec);
	  break;
	case 'e':   /* end time */
	  sscanf(optarg, "%d", &ending_sec);
	  if(ending_sec < 0)
	    pop_error(ERR_GEN, "Ending time: %d invalid", ending_sec);
	  break;
	case 'V':
	  fprintf(stderr, "%s\n", version_string);
	  fprintf(stderr, "Written by Jim Mintha (mintha@geog.ubc.ca)\n");
	  exit(0);
	  break;
	case '?':
	  usage(argv[0]);
	  break;
	}
    }
  
  if(starting_sec > ending_sec && ending_sec != 0)
    pop_error(ERR_GEN, "Starting time is after ending time");

  if(!toc && !drv_type)
    {
      if(optind + 2 > argc)
	pop_error(ERR_GEN, "Must specify a track and filename\n");
      
      sscanf(argv[optind], "%d", &track);
      if(track <= 0 || track > 99)
	pop_error(ERR_GEN, "Invalid track specified: %d", track);

      if(argv[optind + 1][0] == '-')
	out_fd = 1;
      else
	out_fd = open(argv[optind + 1], O_WRONLY | O_CREAT, 0666);
  
      if(out_fd < 0)
	pop_error(ERR_FILE, "Error opening output file: %s", argv[optind + 1]);

      if(fmt == AU8 || fmt == AU16)
	write_sun_header(out_fd, fmt);
    }
  
  if(drv_type)
    {
      get_drv_type(cd_fd, device_name);
      all_done(cd_fd);
    }
  
  cd_fd = cdda_init(&cdda_buf, &cdda_buf2, &cddabuf_len, device_name);
  num_tracks = read_toc(cd_fd, toc);

  if(toc)
    all_done(cd_fd);

  if(track > num_tracks)
    pop_error(ERR_GEN, "Invalid track specified: %d", track);

  if(verbose)
    fprintf(stderr, "\nWriting track %d to %s\n", track, argv[optind + 1]);

  if(starting_sec * 75 > tracks[track].num_blocks ||
     ending_sec * 75 > tracks[track].num_blocks)
    pop_error(ERR_GEN, "Start/End times too long\n");
  
  start_block = tracks[track].start_block + starting_sec * 75;
  num_blocks = tracks[track].num_blocks - starting_sec * 75;
  end_block = start_block + num_blocks;
  current_block = start_block;
  want_percent = 10;

  if(ending_sec != 0)
    {
      end_block = tracks[track].start_block + ending_sec * 75;
      num_blocks = end_block - num_blocks;
    }
  else
    ending_sec = tracks[track].length;

  if(verbose)
    {
      fprintf(stderr, "Song is sectors: %d to %d\n", tracks[track].start_block, 
	      tracks[track].start_block + tracks[track].num_blocks);
      
      fprintf(stderr, "Recording sectors: %d to %d\n", start_block, end_block);
      fprintf(stderr, "Song start time: %02d:%02d  end time: %02d:%02d\n",
	     starting_sec / 60, starting_sec % 60,
	     ending_sec / 60, ending_sec % 60);
    }

  /*
   * Okay - now we do the actual reading.
   *
   */

  while(current_block < end_block)
    {
      to_reread = REREAD_BLKS;
      to_read = NUM_BLOCKS;
      
      if((current_block - start_block) * 100 / num_blocks >= want_percent && verbose)
	{
	  fprintf(stderr, "%02d%% ", want_percent);
	  while(want_percent <= (current_block - start_block) * 100 / num_blocks)
	    want_percent += 10;
	}
      
	
      if(current_block == start_block)
	to_reread = 0;
      
      if(end_block - (current_block - to_reread) < to_read)
	to_read = end_block - (current_block - to_reread);

      /*
      printf("Current block: %d\n",current_block);
      printf("to_read %d\n",to_read);
      */

      num_read = cdda_read(cd_fd, cdda_buf, to_read, current_block - to_reread);
      if(num_read != to_read)
	pop_error(ERR_GEN, "Error reading CD");

      if(current_block == start_block) 
	{
	  to_skip = 0;
	  to_write = to_read;
	}
      else
	{
	  jitter = calc_jitter(cdda_buf, prev_end, to_read);
	  /*	  printf("jitter %d\n", jitter - (CDDA_BLKSIZE * (REREAD_BLKS - COMPARE_BLKS)));*/

	  to_skip = jitter + CDDA_BLKSIZE * COMPARE_BLKS;
	  to_write = to_read - to_reread;
	  
	  /* don't write more than we read */
	  while(to_skip + to_write * CDDA_BLKSIZE > to_read * CDDA_BLKSIZE)
	    to_write--;
	  
	  /* don't write more than the length of track */
	  if(to_write + current_block > end_block)
	    to_write = end_block - current_block;
	  
	  /* if necessary add a padded block at end */
	  if(to_write == 0)
	    {
	      current_block = end_block - 1;
	      to_write = 1;
	      for(ctr = CDDA_BLKSIZE - (to_skip % CDDA_BLKSIZE); ctr < CDDA_BLKSIZE; ctr++)
		cdda_buf[to_skip + ctr] = 0;
	    }
	}
      
      cdda_write(out_fd, cdda_buf + to_skip, CDDA_BLKSIZE * to_write, swap, fmt);

      prev_end = cdda_buf + to_skip + to_write * CDDA_BLKSIZE;
      temp_buf = cdda_buf;
      cdda_buf = cdda_buf2;
      cdda_buf2 = temp_buf;
      current_block += to_write;
   }

  if(verbose)
    fprintf(stderr, "100%%\nAll Done.\n");

  all_done(cd_fd);
}

/*
 * calculate jitter (see explanation before cdda_read)
 * compare 128 frames or 512 bytes
 */

long
calc_jitter(unsigned char *buf, unsigned char *last, int to_read)
{
  int offset;
  
  for(offset = CDDA_BLKSIZE * (REREAD_BLKS - COMPARE_BLKS);
      offset >= 0; offset -= SAMPLE)
    {
      if(!memcmp(last - CDDA_BLKSIZE * COMPARE_BLKS,
		 buf + offset, CDDA_BLKSIZE * COMPARE_BLKS))
	return offset;
    }
  
  for(offset = CDDA_BLKSIZE * (REREAD_BLKS - COMPARE_BLKS);
      offset < to_read * CDDA_BLKSIZE; offset += SAMPLE)
    {
      if(!memcmp(last - CDDA_BLKSIZE * COMPARE_BLKS,
		 buf + offset, CDDA_BLKSIZE * COMPARE_BLKS))
	return offset;
    }
  
  fprintf(stderr, "Jitter Control Failed!!\n");
  return CDDA_BLKSIZE * (REREAD_BLKS - COMPARE_BLKS);
}

    
/*
 * Initialize CDDA data buffer and open device.
 */

int
cdda_init(unsigned char **bufadr, unsigned char **bufadr2, long *buflen, char *device)
{
  int fd;
  struct cdrom_cdda cdda;
  
  *bufadr = malloc(NUM_BLOCKS * CDDA_BLKSIZE);
  *bufadr2 = malloc(NUM_BLOCKS * CDDA_BLKSIZE);
   
  if(*bufadr == NULL || *bufadr2 == NULL)
    pop_error(ERR_MALLOC, "Malloc of buffer failed");

  *buflen = NUM_BLOCKS * CDDA_BLKSIZE;

  /* open the device */

  if(device != NULL)
    fd = open(device, 0);
  else {
    fd = open("/dev/rdsk/c0t6d0s2", 0);
    if(fd < 0)
      fd = open("/vol/dev/aliases/cdrom0", 0);
  }

  if(fd < 0)
    pop_error(ERR_FILE, "Open of CD-ROM device failed");
  
  cdda.cdda_addr = 200;
  cdda.cdda_length = 1;
  cdda.cdda_data = *bufadr;
  cdda.cdda_subcode = CDROM_DA_NO_SUBCODE;
  
  if(ioctl(fd, CDROMCDDA, &cdda) < 0)
    pop_error(ERR_GEN, "Error initializing CD-ROM DA");
  
  return fd;
}

/*
 * Reads a bunch of data from the cdrom returning the number of
 * bytes read.  To do jitter control we always re-read 7 extra 
 * block from the cd.  Next time around we compare the first
 * block read to the previous last block, adjusting +- bytes
 *
 * eg:
 * first read: (writing out all blocks 1->n)
 * |------||------||------||------||------|
 *   blk 1  blk 2    ...            blk n
 * next read:
 *                                 |------||------||------||------|
 *                                  blk n   blk n+1  ...
 * ideally the last CDDABLKSIZE bytes of the old buffer would match
 * exactly with the first CDDABLKSIZE bytes of the new buffer and we
 * would write out from newbuffer+CDDABLKSIZE.  But because of jitter
 * the usually won't so we write out a little bit more or less.
 */

long
cdda_read(int fd, unsigned char *rawbuf, int to_read, int addr)
{
  struct cdrom_cdda cdda;
  
  cdda.cdda_addr = addr;
  cdda.cdda_length = to_read;
  cdda.cdda_data = rawbuf;
  cdda.cdda_subcode = CDROM_DA_NO_SUBCODE;
  
  if(ioctl(fd, CDROMCDDA, &cdda) < 0)
    {
      if(errno == ENXIO)
	{
	  pop_error(NO_ERR, "CD has been ejected\n");
	  return -1;
	}
      
      if(ioctl(fd, CDROMCDDA, &cdda) < 0)
	{
	  if(ioctl(fd, CDROMCDDA, &cdda) < 0)
	    {
	      if(ioctl(fd, CDROMCDDA, &cdda) < 0)
		{
		  pop_error(NO_ERR, "CDROM CDDA failed\n");
		  return -1;
		}
	    }
	}
    }
  
  return (cdda.cdda_length);
}

/*
 * Write out the data
 */

void
cdda_write(int fd, unsigned char *buf, long len, boolean swap, int fmt)
{
  int blocks = len / CDDA_BLKSIZE, ctr, ctr2;
  int write_len = SAMPLES_PER_BLK * 2 * 2;   /* i.e. 2352 */
  unsigned char *outbuf = NULL, *ptr;

  if(outbuf == NULL)
    outbuf = malloc(CDDA_BLKSIZE + 1);

  for(ctr = 0; ctr < blocks; ctr++)
    {
      ptr = outbuf;
      
      /* if they want swapping then we don't swap - make sense? */
      if(!swap)
	{
	  for(ctr2 = 0; ctr2 < SAMPLES_PER_BLK * 2; ctr2++)
	    {
	      *(ptr+1) = *buf++;
	      *ptr = *buf++;
	      ptr += 2;
	    }
	}

      if(fmt == AU8)
	write_len = convert(outbuf);
      
      write(fd, outbuf, write_len);
    }
}

/*
 * Read the Table of Contents
 */

int
read_toc(int fd, boolean toc)
{
  int ctr, prev_time, start_sec;
  struct cdrom_tochdr hdr;
  struct cdrom_tocentry entry;
  
  if(ioctl(fd, CDROMREADTOCHDR, &hdr) < 0)
    pop_error(ERR_GEN, "Error getting TOC header from CD");
  
  if(ioctl(fd, CDROMSBLKMODE, CDDA_BLKSIZE) < 0)
    fprintf(stderr,  "Unable to set blocksize, continuing anyways...");
  
  if(verbose)
    fprintf(stderr, "Number of tracks %d\n", hdr.cdth_trk1);

  prev_time = 0;
  for(ctr = 1; ctr <= hdr.cdth_trk1; ctr++)
    {
      entry.cdte_track = ctr;
      entry.cdte_format = CDROM_MSF;
      
      if(ioctl(fd, CDROMREADTOCENTRY, &entry) < 0)
	pop_error(ERR_GEN, "Error getting TOC entry from CD");
  
      tracks[ctr].mins = entry.cdte_addr.msf.minute;
      tracks[ctr].secs = entry.cdte_addr.msf.second;
      tracks[ctr].frames = entry.cdte_addr.msf.frame;
      start_sec = entry.cdte_addr.msf.minute * 60 + entry.cdte_addr.msf.second;
      
      entry.cdte_format = CDROM_LBA;
      
      if(ioctl(fd, CDROMREADTOCENTRY, &entry) < 0)
	pop_error(ERR_GEN, "Error getting TOC entry from CD");

      tracks[ctr].start_block = entry.cdte_addr.lba;

      if(ctr != 1)
	{
	  tracks[ctr - 1].num_blocks = tracks[ctr].start_block - tracks[ctr - 1].start_block;
	  tracks[ctr - 1].length =  start_sec - prev_time;
	}
      prev_time = start_sec;
    }
  
  /* get last lba */
  
  entry.cdte_track = CDROM_LEADOUT;
  entry.cdte_format = CDROM_MSF;

  if(ioctl(fd, CDROMREADTOCENTRY, &entry) < 0)
    pop_error(ERR_GEN, "Error getting TOC entry from CD");
    
  start_sec = entry.cdte_addr.msf.minute * 60 + entry.cdte_addr.msf.second;

  entry.cdte_track = CDROM_LEADOUT;
  entry.cdte_format = CDROM_LBA;
  
  if(ioctl(fd, CDROMREADTOCENTRY, &entry) < 0)
    pop_error(ERR_GEN, "Error getting TOC entry from CD");
    
  tracks[ctr - 1].num_blocks = entry.cdte_addr.lba - tracks[ctr - 1].start_block;
  tracks[ctr - 1].length = start_sec - prev_time;

  /* if needed print out info */

  if(verbose)
    {
      for(ctr = 1; ctr <= hdr.cdth_trk1; ctr++)
	{
      	  fprintf(stderr, "Track %02d: Start: %02d:%02d:%02d  Length: %02d:%02d  %3.2f MB  lba: %d/%d\n", ctr,
		  tracks[ctr].mins, tracks[ctr].secs, tracks[ctr].frames,
		  tracks[ctr].length / 60, tracks[ctr].length % 60,
		  (double) tracks[ctr].num_blocks * CDDA_BLKSIZE / (1024 * 1024),
		  tracks[ctr].start_block,
		  tracks[ctr].num_blocks);
	}
    }
  return hdr.cdth_trk1;
}

/*
 *  write_sun_header - Write sun .au fmt type header
 */

void
write_sun_header(int fd, int fmt)
{
  int wrote, val;
  typedef unsigned long	u_32;
  struct auheader {
    u_32 magic;
    u_32 hdr_size;
    u_32 data_size;
    u_32 encoding;
    u_32 sample_rate;
    u_32 channels;
  } hdr;
  
  /* initialize ulaw mappings */
  ulawmap = (unsigned char *) malloc(65536);
  if(ulawmap == NULL)
    pop_error(ERR_MALLOC, "Error mallocing memory for ulawmap");
  for(val = 0; val < 65536; val++)
    ulawmap[val] = linear_to_ulaw(val - 32768);
  ulawmap += 32768;

  hdr.magic = 0x2e736e64;
  hdr.hdr_size = sizeof(hdr) + 28;
  hdr.data_size = (fmt == AU16) ? 16 : 8;
  hdr.encoding = (fmt == AU16) ? 3 : 1;
  hdr.sample_rate = (fmt == AU16) ? 44100 : 8000;
  hdr.channels = (fmt == AU16) ? 2 : 1;
 
  wrote = write(fd, &hdr, sizeof(hdr));
  if(wrote != sizeof(hdr))
    pop_error(ERR_FILE, "Error writing to output file");

  wrote = write(fd, "Recorded from CD by Read_CDDA", 28);
  if(wrote != 28)
    pop_error(ERR_FILE, "Error writing to output file");
}


int
convert(unsigned char *buf)
{
  short *buf16 = (short *) buf;
  int ctr, ctr2, samples;
  int mono_value;
  unsigned char *bufend = buf + SAMPLES_PER_BLK * 4;
    
  for(ctr = 0; buf16 < (short *)(bufend); ctr++)
    {
      samples = (ctr & 1) ? ((ctr % 20) ? 10 : 12) : 12;
      
      if(buf16 + samples > (short *)(bufend))
	samples = ((short *)bufend) - buf16;
     
      mono_value = 0;
      for(ctr2 = 0; ctr2 < samples; ctr2++)
	mono_value += *buf16++;
      mono_value /= samples;
      buf[ctr] = ulawmap[mono_value];
    }
  
  return ctr;
}

/*
 *  usage - Print out some command line help
 */

void
usage(char *prog_name)
{
  fprintf(stderr, "%s: Read audio track from CD to a file\n", prog_name);
  fprintf(stderr, "Written by Jim Mintha (mintha@geog.ubc.ca)\n");
  fprintf(stderr, "Usage: %s [-h] [-a] [-8] [-d] [-t] [-D dev] [-s start] [-e end] track [outfile]\n", prog_name);
  fprintf(stderr, " track           Track number to read\n");
  fprintf(stderr, " outfile         File name for PCM or .au output (\"-\" for stdout)\n");
  fprintf(stderr, " -s  --start     Starting second for the track\n");
  fprintf(stderr, " -e  --end       Ending second for the track\n");
  fprintf(stderr, " -t  --toc       Only print out the Table of Contents\n");
  fprintf(stderr, " -x  --swap      Swap bytes (for x86?)\n");
  fprintf(stderr, " -8  --au8       Output in Sun .au format 8KHz\n");
  fprintf(stderr, " -a  --au16      Output in Sun .au format 44.1KHz\n");
  fprintf(stderr, " -b  --buffer    Size of read-ahead buffer\n");
  fprintf(stderr, " -v  --verbose   Be talkative while running\n");
  fprintf(stderr, " -d  --drvtype   Print CD-ROM model type only\n");
  fprintf(stderr, " -D  --device    Device to read from\n");
  /*  fprintf(stderr, " -W  --whole     Extract all tracks to [outfile].[t#]\n");*/
  fprintf(stderr, " -V  --version   Print program version number only\n\n");
  fprintf(stderr, " Default is to output in CDR format the whole track\n");
  exit(0);
}

/*
 * all-done - Reset the blocksize and finish
 */

void
all_done(int fd)
{
  /* reset block size on cdrom */
  if(ioctl(fd, CDROMSBLKMODE, 512) < 0)
    fprintf(stderr, "Unable to reset blocksize");

  close(fd);
  exit(0);
}


/*
** This routine converts from linear to ulaw.
**
** Craig Reese: IDA/Supercomputing Research Center
** Joe Campbell: Department of Defense
** 29 September 1989
**
** References:
** 1) CCITT Recommendation G.711  (very difficult to follow)
** 2) "A New Digital Technique for Implementation of Any
**     Continuous PCM Companding Law," Villeret, Michel,
**     et al. 1973 IEEE Int. Conf. on Communications, Vol 1,
**     1973, pg. 11.12-11.17
** 3) MIL-STD-188-113,"Interoperability and Performance Standards
**     for Analog-to_Digital Conversion Techniques,"
**     17 February 1987
**
** Input: Signed 16 bit linear sample
** Output: 8 bit ulaw sample
*/
#define ZEROTRAP    /* turn on the trap as per the MIL-STD */
#define BIAS 0x84               /* define the add-in bias for 16 bit samples */
#define CLIP 32635
 
unsigned char
linear_to_ulaw( sample )
int sample;
{
	static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
				   4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
				   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
				   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
				   6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
				   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
	int sign, exponent, mantissa;
	unsigned char ulawbyte;
 
	/* Get the sample into sign-magnitude. */
	sign = (sample >> 8) & 0x80;            /* set aside the sign */
	if ( sign != 0 ) sample = -sample;              /* get magnitude */
	if ( sample > CLIP ) sample = CLIP;             /* clip the magnitude */
 
	/* Convert from 16 bit linear to ulaw. */
	sample = sample + BIAS;
	exponent = exp_lut[( sample >> 7 ) & 0xFF];
	mantissa = ( sample >> ( exponent + 3 ) ) & 0x0F;
	ulawbyte = ~ ( sign | ( exponent << 4 ) | mantissa );
#ifdef ZEROTRAP
	if ( ulawbyte == 0 ) ulawbyte = 0x02;   /* optional CCITT trap */
#endif
 
	return ulawbyte;
}

