/*
 * Copyright: GNU Public License 2 applies
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * CDDA2WAV (C) Heiko Eissfeldt heiko@colossus.escape.de
 *
 * last changes:
 *   18.12.93 - first version,	OK
 *   01.01.94 - generalized & clean up HE
 *   10.06.94 - first linux version HE
 *   12.06.94 - wav header alignment problem fixed HE
 *   12.08.94 - open the cdrom device O_RDONLY makes more sense :-)
 *		no more floating point math
 *		change to sector size 2352 which is more common
 *		sub-q-channel information per kernel ioctl requested
 *		doesn't work as well as before
 *		some new options (-max -i)
 *
 * TODO: 
 *       support pipes (with raw format?)
 *       test program with multimedia cds (have none)
 *
 *       
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>

#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/cdrom.h>

#include "uti.h"
#include "wav.h"	/* wav file header structures */
#include "interface.h"

#ifdef	ECHO_TO_SOUNDCARD
#include <linux/soundcard.h>
#endif

unsigned char dev_name [200] = CD_DEVICE;		/* device name */

static unsigned char g_track=0xff, g_index=0xff;		/* current track, index */
static unsigned char g_minute=0xff, g_seconds=0xff;	/* curr. minute, second */
static unsigned char g_frame=0xff;			/* current frame */

static FILE *audio = NULL;

static WAVEHDR waveHdr;
static unsigned long nBytesDone = 0;

long cd_fd=-1;

static int no_file = 0;

#ifdef	ECHO_TO_SOUNDCARD
static long soundcard_fd;
static int echo = 0;
#endif

/* define size-related entries in wave header, update and close file */
static void CloseAll (void)
{
#ifdef	ECHO_TO_SOUNDCARD
  if (echo) {
      close(soundcard_fd);
  }
#endif
  if (audio) {
      fseek(audio, 0L, SEEK_SET);

      waveHdr.chkRiff.dwSize = nBytesDone + sizeof(WAVEHDR) - sizeof(CHUNKHDR) ;
      waveHdr.chkData.dwSize = nBytesDone;

      fwrite (&waveHdr, sizeof (waveHdr), 1, audio);

      fclose (audio);
      audio = NULL;
  }
  if (cd_fd >= 0) {
      EnableCdda (FALSE);
      close(cd_fd);
      cd_fd = -1;
  }
}


/* report a fatal error, clean up and exit */
void FatalError (char *szMessage, ...)
{
  vprintf (szMessage, (char *)(&szMessage + 1));
  CloseAll();
  exit (1);
}


/* open the wav output file and prepare the wav header. */
static void OpenAudio (char *fname, unsigned long rate, long nBitsPerSample, 
		       long channels)
{
  audio = fopen (fname, "wb");
  if (!audio) FatalError ("Could not open %s\n", fname);

  /* special wish from a.s. change owner to real uid */
  if (getuid() != geteuid())
    fchown(fileno(audio), getuid(), getgid());

  waveHdr.chkRiff.ckid = FOURCC_RIFF;
  waveHdr.fccWave = FOURCC_WAVE;
  waveHdr.chkFmt.ckid = FOURCC_FMT;
  waveHdr.chkFmt.dwSize = sizeof (PCMWAVEFORMAT);
  waveHdr.wFormatTag = WAVE_FORMAT_PCM;
  waveHdr.nChannels = channels;
  waveHdr.nSamplesPerSec = rate;
  waveHdr.nBlockAlign = channels * ((nBitsPerSample + 7) / 8);
  waveHdr.nAvgBytesPerSec = waveHdr.nBlockAlign * waveHdr.nSamplesPerSec;
  waveHdr.wBitsPerSample = nBitsPerSample;
  waveHdr.chkData.ckid = FOURCC_DATA;

  fwrite (&waveHdr, sizeof (waveHdr), 1, audio);
}

static long Remainder;
static long quiet = 0;
static long verbose = 0;

/* print the track currently read */
static void UpdateTrackData (short p_num)
{
  if (!quiet) { printf ("\ntrack: %.2d, ", p_num); fflush(stdout); }
  g_track = (char)p_num;
}


/* print the index currently read */
static void UpdateIndexData (short p_num)
{
  if (!quiet) { printf ("index: %.2d\n", p_num); fflush(stdout); }
  g_index = (char)p_num;
}


/* print the time of track currently read */
static void UpdateTimeData (short p_min, short p_sec, short p_frm)
{
  if (!quiet) { printf ("time: %.2d:%.2d.%.2d\r", p_min, p_sec, p_frm); 
		fflush(stdout); }
  g_minute = (char)p_min;
  g_seconds = (char)p_sec;
  g_frame = (char)p_frm;
}

static void AnalyzeQchannel ( unsigned frame )
{
    subq_chnl *sub_ch;

    if (trackindex_disp) {
	sub_ch = ReadSubQ(GET_POSITIONDATA,0);
	/* analyze sub Q-channel data */
	if (sub_ch->track != g_track ||
	    sub_ch->index != g_index) {
	    UpdateTrackData (sub_ch->track);
	    UpdateIndexData (sub_ch->index);
	}
    }
    UpdateTimeData (frame / (60*75), 
		    (frame % (60*75)) / 75, 
		    frame % 75);
}

static long waitforsignal = 0;	/* flag to wait for any audio response */

static unsigned char *bufferWav;

/* convert cdda data to required output format */
static long SaveBuffer (unsigned long rate, long nBitsPerSample, long channels, 
                 long SecsToDo, unsigned long *BytesDone, long TotalSecs)
{
  unsigned short i;

  unsigned long BytesToDo = ( SecsToDo > nsectors ? nsectors : SecsToDo ) * CD_FRAMESIZE_RAW;
  unsigned short undersampling = 44100 / rate;
  int sh_bits = 16 - nBitsPerSample;

  char *pSrc = ( char * )bufferCdda;
  char *pSrcStop = pSrc + BytesToDo;
  unsigned char *pDst = ( unsigned char * )bufferWav;
  unsigned char *pStart = pDst;
  static int any_signal = 0;

  /* optimize the case of no conversion */
  if ( undersampling == 1 && nBitsPerSample == 16 && channels == 2) {
      /* output format is the original cdda format 
       * just forward the buffer */
      
      if ( waitforsignal && !any_signal ) {
	  while (!*pStart++);		/* scan for first signal */
	  /* for efficiency, no end check is done! */
	  if ( (char *)--pStart < pSrcStop ) {
	      /* first non null amplitude is found in buffer */
	      any_signal = 1;
	  } else pStart = pDst;	/* restore pStart */
      }
      pDst = pSrcStop;		/* set pDst to end */
  } else {
    while ( pSrc < pSrcStop ) {
	long lsum;	/* accumulate amplitudes in long ints */
	long rsum;
	  
	lsum = 0;
	rsum = 0;
	for ( i=undersampling; i; i-- ) {
	    /* LSB l, MSB l, LSB r, MSB r, ... */
	    lsum += *( ( signed short * )pSrc )++;	/* left channel */
	    rsum += *( ( signed short * )pSrc )++;	/* right channel */
	}
	lsum /= (signed) undersampling;
	rsum /= (signed) undersampling;
	
	if ( channels == 1 ) {
	    short sum;       /* mono section */
	    sum = ( lsum + rsum ) >> (sh_bits + 1);
	    if ( sh_bits == 8 ) {
		if ( ( unsigned char ) sum != 0 ) {
		    if ( !any_signal ) pStart = pDst;
		    any_signal = 1;
		}
		*pDst++ = ( unsigned char ) sum + ( 1 << 7 );	/* ok */
	    } else {
		if ( sum != 0 ) {
		    if ( !any_signal ) pStart = pDst;
		    any_signal = 1;
		}
		*( ( short * )pDst )++ = sum; /* ok */
	    }
	} else {
	    /* stereo section */
	    lsum >>= sh_bits;
	    rsum >>= sh_bits;
	    if ( sh_bits == 8 ) {
		if ( ( short ) lsum != 0 || 
		     ( short ) rsum != 0 ) {
		    if ( !any_signal ) pStart = pDst;
		    any_signal = 1;
		}
		*pDst++ = ( unsigned char )( short ) lsum + ( 1 << 7 );
		*pDst++ = ( unsigned char )( short ) rsum + ( 1 << 7 ); /* ok */
	    } else {
		if ( ( short ) lsum != 0 || 
		     ( short ) rsum != 0) {
		    if ( !any_signal ) pStart = pDst;
		    any_signal = 1;
		}
		*( ( short * )pDst )++ = ( short ) lsum;
		*( ( short * )pDst )++ = ( short ) rsum; /* ok */
	    }
	}
    } /* while */
  } /* if */

  if ( !quiet && 
       ((TotalSecs-SecsToDo) % 75L) > Remainder )
      AnalyzeQchannel(TotalSecs-SecsToDo);
  if ( !waitforsignal ) pStart = (unsigned char *)bufferWav;

  if ( !waitforsignal || any_signal ) {
    long retval = 0;
    unsigned long outlen = pDst - pStart;
#ifdef	ECHO_TO_SOUNDCARD
    if (echo) write(soundcard_fd, pStart, outlen);
#endif
    if ( no_file || 
	!( retval = fwrite ( pStart, outlen, 1, audio ) != 1 ) )
	*BytesDone += outlen;
    return retval;
  } else return 0;
}

static unsigned tracks = 0;

static long GetStartSector ( long p_track )
{
  long i;

  for (i = 0; i < tracks; i++) {
    if (g_toc [i].bTrack == p_track) {
      unsigned long dw = g_toc [i].dwStartSector;
      if (g_toc [i].bFlags & CDROM_DATA_TRACK)
	return -1;
      return dw;
    }
  }

  return -1;
}


static long GetEndSector ( long p_track )
{
  long i;

  for ( i = 1; i <= tracks; i++ ) {
    if ( g_toc [i-1].bTrack == p_track ) {
      unsigned long dw = g_toc [i].dwStartSector;
      if ( g_toc [i].bFlags & CDROM_DATA_TRACK )
	return -1;
      return dw-1;
    }
  }

  return -1;
}

static long FirstTrack ( void )
{
  long i;
  
  for ( i = 0; i < tracks; i++ ) {
    if ( g_toc [i].bTrack != CDROM_LEADOUT &&
	!( g_toc [i].bFlags & CDROM_DATA_TRACK ) )
      return g_toc [i].bTrack;
  }
  return 0;
}

#if	1
void usleep( unsigned long x ) {}

static int GetIndexOfSector( unsigned sec )
{
    subq_chnl *sub_ch;

    ReadCdRom( sec, 1 ); 

#if	1
/* this usleep statement will cause wrong data results!!! */
usleep(1);
#endif

    sub_ch = ReadSubQ(GET_POSITIONDATA,0);

    return sub_ch->index;
}

static int retrieveInd( unsigned sec )
{
    int Index;
    Index = GetIndexOfSector( sec );
    /* look up for required sector */

    /* Sector not found. get Index and register */

    /* Sector found. return Index */
    return Index;
}

static void ForgetAllSectors( void )
{
}

static int binary_search(int searchInd, unsigned Start, unsigned End)
{
      int l = Start; int r = End; int x;
      int ind;
      while ( r >= l ) {
	  x = ( l + r ) / 2;
	  /* try to avoid seeking */
	  ind = retrieveInd(x);
usleep(650000);
	  if ( searchInd == ind ) {
	      break;
	  } else
	      if ( searchInd < ind ) r = x - 1;
	      else	     	     l = x + 1;
      }
      if ( r >= l ) {
        /* Index found. Now find the first position of this index */
	/* l=LastPos	x=found		r=NextPos */
        r = x;
	while ( l < r-1 ) {
	  x = ( l + r ) / 2;
	  /* try to avoid seeking */
	  ind = retrieveInd(x);
usleep(650000);
	  if ( searchInd == ind ) {
	      r = x;
	  } else {
	      l = x;
	  }
        }
	return r;
      }

      return -1;
}

/* experimental code */
/* search for indices (audio mode required) */
static unsigned ScanIndices( unsigned track, unsigned index )
{
  /* scan for indices. */
  /* look at last sector of track. */
  /* when the index is not equal 1 scan by bipartition 
   * for offsets of all indices */

  int i; 
  int starttrack, endtrack;
  int startindex, endindex;

  int j;
  int LastIndex=0; 
  int StartSector;
  unsigned retval = 0;

  if (!quiet && !verbose) printf("seeking index start ...");
  if (index != 1) {
    starttrack = track; endtrack = track;
  } else {
    starttrack = 1; endtrack = tracks;
  }
  for (i = starttrack; i <= endtrack; i++) {
    if ( verbose ) { 
	printf( "index scan: %d...\r", i ); 
	fflush (stdout);
    }
    if ( g_toc [i-1].bFlags & CDROM_DATA_TRACK )
	continue;/* skip nonaudio tracks */
    LastIndex = retrieveInd(GetEndSector(i)-1);
#if	0
    if (index > LastIndex) {
      fprintf(stderr,"index %d not found\n",index);
      return 0;
    }
#endif
    StartSector = GetStartSector(i);
    if (verbose && LastIndex > 1)
	printf("track %d index table:\n", i);
    
    if (index != 1) {
	startindex = index; endindex = index;
    } else {
	startindex = 2; endindex = LastIndex;
    }
    for (j = startindex; j <= endindex; j++) {
      unsigned IndexOffset;

      /* this track has indices */
      /* do a binary search */
      if (verbose) printf("seek offset...\r"); fflush(stdout);
      IndexOffset = binary_search(j, StartSector, GetEndSector(i));
      if ( IndexOffset == -1 ) {
	  fprintf(stderr, " index %d not found\n",j);
	  return 0;
      } else {
	  if (verbose) printf(" index %d at offset %u\n",j,
		 IndexOffset-(unsigned)GetStartSector(i));
	  StartSector = IndexOffset;
	  if (track == i && index == j) {
	     retval = IndexOffset-(unsigned)GetStartSector(i);
	  }
      }
    }
    ForgetAllSectors();
  }
  if (!quiet && !verbose) printf("done\n");
  return retval;
}
#else
void ScanIndices( void ){}
#endif

static void DisplayToc ( void )
{
  unsigned i;
  unsigned long dw;
  unsigned mins;
  unsigned secnds;
  unsigned centi_secnds;	/* hundreds of a second */

  dw = (unsigned long) g_toc[tracks].dwStartSector;
  mins	       =       dw / ( 60*75 );
  secnds       =     ( dw % ( 60*75 ) ) / 75;
  centi_secnds = (4* ( dw %      75   ) +1 ) /3; /* convert from 1/75 to 1/100 */
  /* g_toc [i].bFlags contains two fields:
       bits 7-4 (ADR) : 0 no sub-q-channel information
                      : 1 sub-q-channel contains current position
		      : 2 sub-q-channel contains media catalog number
		      : 3 sub-q-channel contains International Standard
		                                 Recording Code ISRC
		      : other values reserved
       bits 3-0 (Control) :
       bit 3 : when set indicates there are 4 audio channels else 2 channels
       bit 2 : when set indicates this is a data track else an audio track
       bit 1 : when set indicates digital copy is permitted else prohibited
       bit 0 : when set indicates pre-emphasis is present else not present
  */
  if ( verbose ) {
    subq_chnl *sub_ch;

    puts( "track pre-emphasis copy-permitted tracktype channels sub-Q-channel" );
    i = 0;
    while ( i < tracks ) {
      int from;

      from = g_toc [i].bTrack;
      while ( i < tracks && g_toc [i].bFlags == g_toc [i+1].bFlags ) i++;
      if (i >= tracks) i--;
      
      printf("%2d-%2d %12s %14s %18s ",from,g_toc [i].bTrack,
			g_toc [i].bFlags & 1 ? "  " : "no",
			g_toc [i].bFlags & 2 ? "  " : "no",
			g_toc [i].bFlags & 4 ? "data          " : 
			g_toc [i].bFlags & 8 ? "audio        4" :
	                                          "audio        2"
                       );
      switch ( g_toc [i].bFlags >> 4 ) {
        case 0:  puts( "no data" ); break;
	case 1:  puts( "position" ); break;
	case 2:  puts( "media catalog" ); break;
	case 3:  puts( "ISRC" ); break;
	default: printf( "invalid 0x%2x\n", g_toc [i].bFlags >> 4 );
      }
      i++;
    }
    /* get and display Media Catalog Number ( one per disc ) */
    sub_ch = ReadSubQ(GET_CATALOGNUMBER,0);
    if (sub_ch && ((subq_catalog *)sub_ch->data)->mc_valid && !quiet) {
	printf ("media catalog number: "
		"%.1X%.1X%.1X%.1X%.1X %.1X%.1X%.1X%.1X%.1X%.1X %.1X%.1X%.1X%.1X\n",
		((subq_catalog *)sub_ch->data)->media_catalog_number [0],
		((subq_catalog *)sub_ch->data)->media_catalog_number [1],
		((subq_catalog *)sub_ch->data)->media_catalog_number [2],
		((subq_catalog *)sub_ch->data)->media_catalog_number [3],
		((subq_catalog *)sub_ch->data)->media_catalog_number [4],
		((subq_catalog *)sub_ch->data)->media_catalog_number [5],
		((subq_catalog *)sub_ch->data)->media_catalog_number [6],
		((subq_catalog *)sub_ch->data)->media_catalog_number [7],
		((subq_catalog *)sub_ch->data)->media_catalog_number [8],
		((subq_catalog *)sub_ch->data)->media_catalog_number [9],
		((subq_catalog *)sub_ch->data)->media_catalog_number [10],
		((subq_catalog *)sub_ch->data)->media_catalog_number [11],
		((subq_catalog *)sub_ch->data)->media_catalog_number [12],
		((subq_catalog *)sub_ch->data)->media_catalog_number [13],
		((subq_catalog *)sub_ch->data)->media_catalog_number [14]
		); 
	fflush(stdout); 
    }
    /* get and display Track International Standard Recording Codes 
       (for each track) */
    for ( i = 0; i < tracks; i++ ) {
	sub_ch = ReadSubQ(GET_TRACK_ISRC,i+1);
	if (sub_ch && ((subq_track_isrc *)sub_ch->data)->tc_valid && !quiet) {
	  printf ("International Standard Recording Code for track %d: "
		"%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X\n", i+1,
		((subq_track_isrc *)sub_ch->data)->track_isrc [2],
		((subq_track_isrc *)sub_ch->data)->track_isrc [3],
		((subq_track_isrc *)sub_ch->data)->track_isrc [4],
		((subq_track_isrc *)sub_ch->data)->track_isrc [5],
		((subq_track_isrc *)sub_ch->data)->track_isrc [6],
		((subq_track_isrc *)sub_ch->data)->track_isrc [7],
		((subq_track_isrc *)sub_ch->data)->track_isrc [8],
		((subq_track_isrc *)sub_ch->data)->track_isrc [9],
		((subq_track_isrc *)sub_ch->data)->track_isrc [10],
		((subq_track_isrc *)sub_ch->data)->track_isrc [11],
		((subq_track_isrc *)sub_ch->data)->track_isrc [12],
		((subq_track_isrc *)sub_ch->data)->track_isrc [13],
		((subq_track_isrc *)sub_ch->data)->track_isrc [14]
		); 
	  fflush(stdout); 
	}
    }
    puts("");
  }

  printf ( "Table of Contents: total tracks:%u, (total time %u:%02u.%02u)\n",
          tracks, mins, secnds, centi_secnds );
  for ( i = 0; i < tracks; i++ ) {
    if ( g_toc [i].bFlags & CDROM_DATA_TRACK ) continue;/* skip nonaudio tracks */
    if ( g_toc [i].bTrack <= MAXTRK ) {
      dw = (unsigned long) (g_toc[i+1].dwStartSector - g_toc[i].dwStartSector);
      mins         =         dw / ( 60*75 );
      secnds       =       ( dw % ( 60*75 )) / 75;
      centi_secnds = ( 4 * ( dw %      75 ) + 1 ) / 3;
      printf ( " %2u.(%2u:%02u.%02u)", g_toc [i].bTrack,mins,secnds,centi_secnds );
    }
    if ( i < tracks-1 )
       if ( (i+1) % 5 == 0 ) puts( "" );
       else putchar ( ',' );
  }
  puts ("");
}

static void usage( void )
{
	fprintf( stderr, "cdda2wav [-c chans] [-s] [-m] [-b bits] [-r rate] [-t track] [-o offset]\n" );
	fprintf( stderr, "         [-D device] [-I interface] [-max] [-v] [-i] [-e] [-n] [wavfile.wav]\n"
		         "         Version %s\n",VERSION );
	fprintf( stderr, "cdda2wav copies parts from audio cd's directly to WAV files.\n" );
	fprintf( stderr, "It requires a supported cdrom drive to work.\n" );
	fprintf( stderr, "options: -c channels : set 1 for mono, or 2 for stereo recording.\n" );
	fprintf( stderr, "         -D device   : set the cdrom device.\n" );
	fprintf( stderr, "         -I interface: specify the interface for cdrom access.\n" );
	fprintf( stderr, "                     : (generic_scsi or cooked_ioctl or scsi_ioctl).\n" );
	fprintf( stderr, "         -s          : set to stereo recording.\n" );
	fprintf( stderr, "         -m          : set to mono recording.\n" );
	fprintf( stderr, "         -max        : set to maximum quality (stereo/16-bit/44.1 KHz).\n" );
	fprintf( stderr, "         -b bits     : set bits per sample per channel (8, 12 or 16 bits).\n" );
	fprintf( stderr, "         -r rate     : set rate in samples per second. Possible values are:\n" );
	fprintf( stderr, "            44100,22050,14700,11025,7350,3675,3150,1575,900 Hertz.\n" );
	fprintf( stderr, "         -t track    : select start track.\n" );
	fprintf( stderr, "         -i index    : select start index.\n" );
	fprintf( stderr, "         -o offset   : start 'offset' sectors behind start track/index.\n" );
	fprintf( stderr, "                       one sector equivalents 1/75 second.\n" );
	fprintf( stderr, "         -d duration : set recording time in seconds or 0 for whole track.\n" );
	fprintf( stderr, "         -w          : wait for audio signal, then start recording.\n" );
	fprintf( stderr, "         -e          : echo audio data to sound device SOUND_DEV.\n" );
	fprintf( stderr, "         -v          : print all information on current cd.\n" );
	fprintf( stderr, "         -n          : no file operation.\n" );
	fprintf( stderr, "         -q          : quiet operation, no screen output.\n" );
	fprintf( stderr, "defaults: mono, 16 bit, 22050 Hz, track 1, no offset, 16 seconds, 'audio.wav'\n"
                  "          don't wait for signal, not quiet, not verbose, use /dev/sr0\n" );
  exit( 1 );
}

/* and finally: the MAIN program */
long main ( long argc, char *argv [] )
{
  long args = 0;
  long lSector;
  long lSector_p1;
  long sector_offset = 0;
  long nSectorsToDo = ( DURATION*75 );
  unsigned long SectorBurst;
  double time = DURATION;
  long track = 1;
  long index = 1;
  long	channels = CHANNELS;
  unsigned rate = 44100 / UNDERSAMPLING;
  int bits = BITS_P_S;
  char fname[100] = FILENAME;
  int just_the_toc = 0;
  struct sigaction sigac = { exit, 0, 0, NULL }; 
  char int_name[100] = DEF_INTERFACE;
  int i;

  /* command options parsing */
  /* TODO: use getopt() */
  while ( --argc ) {
    args++;
    /* override device */
    if ( !strncmp( "-D",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	strcpy( dev_name,argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  strcpy( dev_name,argv[args] );
	  continue;
        } else usage();
      }
    }
    /* interface */
    if ( !strncmp( "-I",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	strcpy( int_name,argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  strcpy( int_name,argv[args] );
	  continue;
        } else usage();
      }
    }
    /* channels */
    if ( !strncmp( "-c",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	channels = atoi( argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  channels = atoi( argv[args] );
	  continue;
        } else usage();
      }
    }
    /* bits */
    if ( !strncmp( "-b",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	bits = atoi( argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  bits = atoi( argv[args] );
	  continue;
        } else usage();
      }
    }
    /* rate */
    if ( !strncmp( "-r",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	rate = atoi( argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  rate = atoi( argv[args] );
	  continue;
        } else usage();
      }
    }
    /* start track */
    if ( !strncmp( "-t",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	track = atoi( argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  track = atoi( argv[args] );
	  continue;
	} else usage();
      }
    }
    /* start index */
    if ( !strncmp( "-i",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	index = atoi( argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  index = atoi( argv[args] );
	  continue;
	} else usage();
      }
    }
    /* recording time */
    if ( !strncmp( "-d",argv[args],2 ) ) {
      if ( strlen ( argv[args] ) > 2 ) {
	time = strtod( argv[args]+2, NULL );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  time = strtod( argv[args], NULL );
	  continue;
	} else usage();
      }
    }
    /* sector offset */
    if ( !strncmp( "-o",argv[args],2 ) ) {
      if (strlen ( argv[args] ) > 2 ) {
	sector_offset = atol( argv[args]+2 );
	continue;
      } else {
        args++;
        if ( --argc ) {
	  sector_offset = atol( argv[args] );
	  continue;
	} else usage();
      }
    }
    /* stereo */
    if ( !strncmp( "-s",argv[args],2 ) ) {
      channels = 2;
      continue;
    }
    /* maximum quality */
    if ( !strncmp( "-max",argv[args],4 ) ) {
      channels = 2; bits = 16; rate = 44100;
      continue;
    }
    /* mono */
    if ( !strncmp( "-m",argv[args],2 ) ) {
      channels = 1;
      continue;
    }
    /* wait for signal */
    if ( !strncmp( "-w",argv[args],2 ) ) {
      waitforsignal = 1;
      continue;
    }
    /* echo to sound card */
    if ( !strncmp( "-e",argv[args],2 ) ) {
#ifdef	ECHO_TO_SOUNDCARD
      echo = 1;
#endif
      continue;
    }
    /* don't write to file */
    if ( !strncmp( "-n",argv[args],2 ) ) {
      no_file = 1;
      continue;
    }
    /* verbose. can be overridden by quiet */
    if ( !strncmp( "-v",argv[args],2 ) ) {
      verbose = 1;
      continue;
    }
    /* quiet */
    if ( !strncmp( "-q",argv[args],2 ) ) {
      quiet = 1;
      verbose = 0;
      continue;
    }
    /* other options unknown */
    if ( *argv[args] == '-' ) usage();

    /* filename given */
    strcpy( fname,argv[args] );

    /* should be the last parameter */
    if ( argc > 1 ) usage();
  }

  /* check all parameters */
  if ( channels != 1 && channels != 2 ) {
    fprintf(stderr, "Error: incorrect channel setting: %ld\n",channels);
    usage();
  }
  if ( bits != 8 && bits != 12 && bits != 16 ) {
    fprintf(stderr, "Error: incorrect bits_per_sample setting: %d\n",bits);
    usage();
  }
  if ( rate < 900 || rate > 44100 || 44100 % rate != 0 || 
       2352 % (44100/rate) != 0) {
    fprintf(stderr, "Error: incorrect sample rate setting: %d\n",rate);
    usage();
  }
  if ( track < 1 || track > 99 ) {
    fprintf(stderr, "Error: incorrect track setting: %ld\n",track);
    usage();
  }
  if ( time < 0 ) {
    fprintf(stderr, "Error: incorrect recording time setting: %f\n",time);
    usage();
  }
  if (no_file) fname[0] = '\0';

  if (!strcmp(int_name,"generic_scsi"))
      interface = GENERIC_SCSI;
  else if (!strcmp(int_name,"cooked_ioctl"))
      interface = COOKED_IOCTL;
  else if (!strcmp(int_name,"scsi_ioctl"))
      interface = SCSI_IOCTL;
  else  {
    fprintf(stderr, "Error: incorrect interface setting: %s\n",int_name);
    usage();
  }

  /* setup interface and open cdrom device */
  SetupInterface( int_name );

  /* get table of contents */
  tracks = ReadToc( g_toc );
  if ( !quiet ) DisplayToc ();

  if (just_the_toc) exit(0);

  EnableCdda (TRUE);

  if (index != 1)
    sector_offset += ScanIndices( track, index );
  else if (verbose)
    ScanIndices( track, index );

#if	1
  sigemptyset(&sigac.sa_mask);
  sigac.sa_flags |= SA_RESTART;
  sigaction( SIGINT, &sigac, NULL);
#else
  signal( SIGINT, exit );
#endif
  atexit ( CloseAll );

  if ( !FirstTrack () )
    FatalError ( "This is no audio disk\n" );

  lSector = GetStartSector ( track );
  lSector_p1 = GetEndSector ( track ) + 1;
  if ( lSector < 0 )
    FatalError ( "track %d not found\n", track );

  lSector += sector_offset;
  /* check against end sector of track */
  if ( lSector >= lSector_p1 ) {
    printf( "sector offset exceeds track size (ignored)\n" );
    lSector -= sector_offset;
  }

  if ( time == 0.0 ) {
    /* set time to track time */
    nSectorsToDo = lSector_p1 - lSector;
    time = nSectorsToDo / 75.0;
  } else {
    if ( time > 4440.0 ) {
      /* Prepare the maximum recording duration.
       * It is defined as the biggest amount of
       * adjacent audio sectors beginning with the
       * specified track/index/offset. */

      for (i = track+1; i <= tracks; i++) {
        /* break if a nonaudio track follows */
        if ( g_toc [i-1].bFlags & CDROM_DATA_TRACK ) break;
        lSector_p1 = GetEndSector ( i ) + 1;
      }
      time = (lSector_p1 - lSector) / 75.0;
    }

    /* calculate # of sectors to read */
    nSectorsToDo = (long)(time * 75.0 + 0.5);
  }

#ifdef	ECHO_TO_SOUNDCARD
  if (echo) {
    if ((soundcard_fd=open(SOUND_DEV, O_WRONLY, 0)) == EOF) {
        fprintf(stderr, "Cannot open "SOUND_DEV);
        echo = 0;
    }
    if (echo) { 
	int garbled_rate = rate;
	if (ioctl(soundcard_fd, SOUND_PCM_WRITE_RATE, &garbled_rate) == EOF) {
	    fprintf(stderr, "Cannot set rate %d Hz for "SOUND_DEV"\n",rate);
	    echo = 0;
	}
	if (ioctl(soundcard_fd, SOUND_PCM_WRITE_CHANNELS, &channels) == EOF) {
	    fprintf(stderr, "Cannot set %ld channel(s) for "SOUND_DEV"\n",channels);
	    echo = 0;
	}
	if (ioctl(soundcard_fd, SOUND_PCM_WRITE_BITS, &bits) == EOF) {
	    fprintf(stderr, "Cannot set %d bits/sample for "SOUND_DEV"\n",bits);
	    echo = 0;
	}
    }
  }
#endif

  if ( !quiet ) {
      printf ("recording %.2f seconds %s with %d bits @ %u Hz"
	      ,time ,channels == 1 ? "mono":"stereo", bits, rate);
      if (*fname) printf(" ->'%s'...", fname );
      puts("");
      if ( verbose && !waitforsignal )
	  printf("file size will be %lu bytes.\n",sizeof (waveHdr) + 
	 (nSectorsToDo*CD_FRAMESIZE_RAW/(3-channels)/(3-(bits+7)/8)/(44100/rate))); 
  }

  if ( !no_file ) OpenAudio( fname, rate, bits, channels );

  bufferWav = bufferCdda;
  Remainder = (75 % nsectors)+1;


  /* Everything is setup. Now read cdda sectors and store them in a wav file */
  for (i = nSectorsToDo; i; i -= SectorBurst) {

    /* how many sectors should be read */
    SectorBurst = i > nsectors ? nsectors : i;

    ReadCdRom( lSector, SectorBurst );

    if ( SaveBuffer ( rate, bits, channels, i, &nBytesDone, nSectorsToDo ) ) {
	fprintf ( stderr, "\ndisk space exhausted. Stopped.\n" );
	exit(2);
    }
    lSector += SectorBurst;
  }

  if (!quiet) puts( "" );

  return 0;
}
