/*****************************************************************
play list of audiofiles

$Id: playlist.c,v 1.25 2003/01/19 23:17:09 purnhage Exp $

Source file: playlist.c

(c) Heiko Purnhagen 1998-2003
e-mail: purnhage@tnt.uni-hannover.de
WWW:    http://www.tnt.uni-hannover.de/~purnhage/

This program is available under the terms of the GNU General Public
License. See file COPYING for further details. All usual disclaimers
apply!

Authors:
BE    Bernd Edler, Uni Hannover <edler@tnt.uni-hannover.de>
HP    Heiko Purnhagen, Uni Hannover <purnhage@tnt.uni-hannover.de>

Changes:
30-jul-98   BE      first version
16-sep-98   HP      now using austream and playrec
21-sep-98   HP      include code from plax.c
30-dec-98   HP      CVS
07-jan-99   HP      AFsp-v4r1
08-jan-99   HP      audio device selection
17-jan-99   HP      ...
22-jan-99   HP      drain stdin before prompting "enter grading(s)"
                    improved ^C handling in interactive mode
26-jan-99   HP      improved output file format evaluation
27-jan-99   HP      fix rawFormat
28-jan-99   HP      debug output on stderr
17-feb-99   HP      fixed pause < 0 bug
21-feb-99   HP      fixed pause 0 bug / maxNumSamp
17-may-99   HP      improved output file format detection
04-nov-99   HP/BE   added -df
11-nov-99   HP      added fabs() #include <math.h>
14-nov-99   HP      removed fileno()
19-nov-99   HP      added -bp
04-sep-00   HP      test for "file not found" to avoid abort in AFopenRead()
**********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <signal.h>
#include <ctype.h>

#ifndef WIN32
#include <termios.h>		/* for drain() ... */
#endif
#include <fcntl.h>
#ifdef MSVC
#include <io.h> /* open(), close(), write(), read() */
#else
#include <unistd.h> /* open(), close(), write(), read() */
#endif

#include <libtsp.h>		/* AFsp audio file library */
#include <libtsp/AFpar.h>	/* AFsp audio file library - definitions */

#include "common.h"		/* common module */
#include "cmdline.h"		/* command line module */
#include "austream.h"		/* audio i/o streams */
#include "playrec.h"		/* audio play & record */
#include "rateconv.h"		/* audio sample rate conversion */


/* ---------- declarations ---------- */

#define PROGVER "playlist V0.7.2 19-Nov-1999"
#define CVSID "$Id: playlist.c,v 1.25 2003/01/19 23:17:09 purnhage Exp $"
#ifndef DISTVER		/* set in Makefile */
#define DISTVER "<unknown>"
#endif

#ifndef MSVC
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif

#define STRLEN (255)

#define MAX_FS (48000.0)
#define BUF_TIME (1.)		/* length of buffer in seconds */

#define AUDIO_PATH_ENV "PLAYLISTPATH"
#define AUDIO_EXT ".au"
#define LIST_EXT ".lis"
#define TEST_EXT ".tst"
#define RAWENV "RAWAUDIOFILE"
#define RAWDEFAULT "integer16,0,48000.,native,1,1.0"

#ifdef FW_SUN
/* only AFsp-V3R2 available: map AFsp-V3R2 to AFsp-v4r1 */
#define AFsetNHpar AFsetNH
#define FTW_AU (FW_SUN/256)
#define FTW_WAVE (FW_WAVE/256)
#define FTW_AIFF_C (FW_AIFF_C/256)
#define FTW_NH_EB (FW_NH_EB/256)
#endif


/* ---------- variables ---------- */

/* command line module */

static char *progName = "<no program name>";
static char *outFileName;
static int outFileNameUsed;
static int outNumChan;
static int outNumChanUsed;
static float outFSample;
static int outFSampleUsed;
static char *playDevice;
static int playDeviceUsed;
static float maxFsDev;
static float gain;
static char *listFileName;
static int listFileNameUsed;
static int noAudio;
static int interactive;
static char *audioPath;
static int audioPathUsed;
static char *audioExt;
static char *listExt;
static char *testExt;
static char *rawFormat;
static int rawFormatUsed;
static float bufTime;
static float playBufTime;
static int ht;
static float al;
static float fc;
static float fd;
static int debugLevel;
static int *varArgIdx;

static CmdLinePara paraList[] = {
  {&varArgIdx,NULL,"<playlist entries>"},
  {NULL,NULL,NULL}
};

static CmdLineSwitch switchList[] = {
  {"h",NULL,NULL,NULL,NULL,"print help"},
  {"o",&outFileName,"%s",NULL,&outFileNameUsed,
   "output file name (- = stdout) (dflt: audio device)"},
  {"c",&outNumChan,"%d",NULL,&outNumChanUsed,
   "output: number of channels (dflt: as 1st input file)"},
  {"s",&outFSample,"%f",NULL,&outFSampleUsed,
   "output: sampling frequency [Hz] (dflt: as 1st input file)"},
  {"pd",&playDevice,"%s",NULL,&playDeviceUsed,
   "play: audio device name (dflt: default device)"},
  {"df",&maxFsDev,"%f","0.01",NULL,
   "max. tolerated relative sampling frequency deviation\n\b"},
  {"g",&gain,"%f","1.0",NULL,
   "amplitude gain"},
  {"l",&listFileName,"%s",NULL,&listFileNameUsed,
   "playlist file name"},
  {"n",&noAudio,NULL,NULL,NULL,
   "no audio output"},
  {"t",&interactive,NULL,NULL,NULL,
   "interactive listening test\n"
   "(grading file: playlist file name + YYYYMMDDHHMMSS)\n"
   "[ \"enter grading(s)\" prompt: 'r' to repeat          ]\n"
   "[                            'x' to seek to trial # ]\n"
   "[                            'quit' to quit         ]\n"
   "[ '^C' to abort playback and goto next audio file   ]"},
  {"p",&audioPath,"%s",NULL,&audioPathUsed,
   "audio file path (dflt: $" AUDIO_PATH_ENV ")"},
  {"e",&audioExt,"%s",AUDIO_EXT,NULL,
   "audio file extension"},
  {"el",&listExt,"%s",LIST_EXT,NULL,
   "playlist file extension"},
  {"et",&testExt,"%s",TEST_EXT,NULL,
   "test file extension"},
  {"f",&rawFormat,"%s",NULL,&rawFormatUsed,
   "audio file format for raw files\n"
   "(dflt: $" RAWENV " / \"" RAWDEFAULT "\")"},
  {"b",&bufTime,"%f","0.2",NULL,
   "buffer time [sec] (if no sample rate conv.)"},
  {"bp",&playBufTime,"%f","0.2",NULL,
   "play buffer size [sec] (0=max.)"},
  {"ht",&ht,"%d","-1",NULL,
   "sample rate conv.: num taps\n"
   "(-1 = auto)"},
  {"al",&al,"%f","-1.0",NULL,
   "sample rate conv.: alpha for Kaiser window\n"
   "(-1 = auto)"},
  {"fc",&fc,"%f","-1.0",NULL,
   "sample rate conv.: 6 dB cutoff freq / input bandwidth\n"
   "(-1 = auto)"},
  {"fd",&fd,"%f","-1.0",NULL,
   "sample rate conv.: 100 dB cutoff freq / input bandwidth\n"
   "(-1 = auto)"},
  {"d",&debugLevel,"%d","0",NULL,
   "debug level (on stderr) (0=off 1=basic 2=info file 3=full)\n"
   "(+4*audioDbgLvl +16*playrecDbgLvl +64*rateconvDbgLvl)\n\b"},
  {NULL,NULL,NULL,NULL,NULL,NULL}
};

static int stop = 0;

typedef char HMSHstr[12];


/* ---------- local functions ---------- */

void stopproc (int sig)
{
  stop = 1;
}


int isfmtstr (char *filename, char *fmtstr)
/* isfmtstr returns true if filename has extension fmtstr */
{
  int i;

  i = strlen(filename)-strlen(fmtstr);
  if (i<0)
    return 0;
  filename += i;
  while (*filename) {
    if (tolower(*filename) != *fmtstr)
      return 0;
    filename++;
    fmtstr++;
  }
  return 1;
}


void f2hmsh (float f, HMSHstr hmsh)
{
  long l;

  l = (long)(f*100+.5);
  hmsh[ 0] = '0'+((l/3600000)%10);
  hmsh[ 1] = '0'+((l/ 360000)%10);
  hmsh[ 2] = ':';
  hmsh[ 3] = '0'+((l/  60000)% 6);
  hmsh[ 4] = '0'+((l/   6000)%10);
  hmsh[ 5] = ':';
  hmsh[ 6] = '0'+((l/   1000)% 6);
  hmsh[ 7] = '0'+((l/    100)%10);
  hmsh[ 8] = '.';
  hmsh[ 9] = '0'+((l/     10)%10);
  hmsh[10] = '0'+((l/      1)%10);
  hmsh[11] = '\0';
}


void dellast (char *c)
{
  while (*c && *(c+1))
    c++;;
  *c = '\0';
}


#ifndef WIN32
void drain(int fn)
{
  struct termios termOld;
  struct termios termNew;
  int oldFL;
  char c;

  /* set stream attributes to ~canon ~echo ndelay */
  /* fn = fileno(stream); */
  if (tcgetattr(fn,&termOld))
    exit(-1);
  termNew = termOld;
  termNew.c_lflag &= ~ICANON;
  termNew.c_lflag &= ~ECHO;
  termNew.c_cc[VMIN] = 1;
  termNew.c_cc[VTIME] = 0;
  if (tcsetattr(fn,TCSANOW,&termNew))
    exit(-1);
  oldFL=fcntl(fn,F_GETFL,0);
  fcntl(fn,F_SETFL,fcntl(fn,F_GETFL,0)|O_NDELAY);

  /* drain all pending input ... */
  while (read(fn,&c,1)>0);

  /* restore stream attributes */
  fcntl(fn,F_SETFL,oldFL);
  if (tcsetattr(fn,TCSANOW,&termOld))
    exit(-1);
}
#endif


/* ---------- main ---------- */

int main (int argc, char *argv[])
{
  int listIdx;
  int result;
  int RCdebugLevel;

  FILE *list;
  FILE *test;
  AFILE *audio;
  FILE *tf;

  AFILE *outaf;
  AuStream *outas;
  PRStream *outpr;
  RCBuf *rc;

  typedef enum {AUDEV, AUSTREAM, AUFILE} autype;
  autype outtype;

  char fullListFileName[STRLEN];
  char fullTestFileName[STRLEN];
  char listStr[STRLEN];
  char audioFileName[STRLEN];
  long numSample;
  long numChan;
  float fSample;

  time_t curTime;
  char dateTime[STRLEN];

  char inStr[STRLEN];
  char tmpStr[STRLEN];
  char trialStr[STRLEN];
  char keyStr[STRLEN];

  float totTime;
  HMSHstr hmsh;
  int numEntry;
  int numAudio;
  int numTrial;
  int numGrad;
  int minNumChan,maxNumChan;
  float minFSample,maxFSample;
  float dur;

  long trialStart;

  long maxBufLen,bufLen,convLen,bufTrans;
  long inBufUsed,outBufUsed,tmpBufUsed;
  long numClip,totClip,totClipOld;
  short *bufS;
  float *bufF;
  short *outBufS;
  float *outBufF;
  long i,tmp;

  long inNumSamp;
  long outNumSamp;
  long totNumSamp;
  int devNumChan;
  float devFSample;
  long maxNumSamp;
  int fmt;
  int fmti;
  struct {
    char *str;
    int fmt;
  } fmtstr[] = {
    {"au",FTW_AU*256},
    {"snd",FTW_AU*256},
    {"wav",FTW_WAVE*256},
    {"wave",FTW_WAVE*256},
    {"aif",FTW_AIFF_C*256},
    {"aiff",FTW_AIFF_C*256},
    {"aifc",FTW_AIFF_C*256},
    {"raw",FTW_NH_EB*256},	/* no header big-endian */
    {NULL,-1}
  };

  
  /* evaluate command line  */
  CmdLineInit(0);
  result = CmdLineEval(argc,argv,paraList,switchList,1,&progName);
  if (result) {
    if (result==1) {
      fprintf(stderr,
	      "%s: %s\n"
	      "%s\n",
	      progName,PROGVER "   " DISTVER,CVSID);
      CmdLineHelp(progName,paraList,switchList,stderr);
      fprintf(stderr,
	      "playlist entry format:\n"
	      "  comment:           ;comment\n"
	      "  raw file format:   @integer16,0,48000.,native,1,1.0\n"
	      "  trial n:           !n\n"
	      "  keys:              *key1 key2 key3 \n"
	      "  audio file:        path/file.au\n"
	      "  ask grading(s):    ?question\n"
	      "  n second pause:    #n\n"
	      "\n");
      exit (1);
    }
    else
      CommonExit(1,"command line error (\"-h\" for help)");
  }
  AuInit((debugLevel>>2)&3);
  PRInit((debugLevel>>4)&3);
  RCdebugLevel = (debugLevel>>6)&3;
  debugLevel &= 3;

  if (debugLevel)
    fprintf(stderr,"%s: %s\n",progName,PROGVER "   " DISTVER);
  if (debugLevel>1) {
    fprintf(stderr,"%s\n",CVSID);
    fprintf(stderr,"debugLevel=%d\n",debugLevel);
  }

  if (!playDeviceUsed)
    playDevice = NULL;

  if (interactive && !listFileNameUsed)
    CommonExit(1,"playlist file required for interactive test");
  if (interactive && outFileNameUsed)
    CommonExit(1,"no output file permitted for interactive test");
  if (listFileNameUsed && varArgIdx[0] >= 0)
    CommonExit(1,"either playlist file or playlist entries on command line");

  curTime = time((time_t*)NULL);
  strftime(dateTime,STRLEN,"%Y%m%d%H%M%S",gmtime(&curTime));

  if (debugLevel)
    fprintf(stderr,"test id:           %s\n",dateTime);

  if (listFileNameUsed) {
    if (ComposeFileName(listFileName,0,NULL,listExt,fullListFileName,STRLEN))
      CommonExit(1,"composed playlist file name too long");
  }
  if (interactive) {
    if (ComposeFileName(dateTime,0,NULL,testExt,listStr,STRLEN))
      CommonExit(1,"composed test file name too long");
    if (ComposeFileName(listFileNameUsed?listFileName:"",
			1,NULL,listStr,fullTestFileName,STRLEN))
      CommonExit(1,"composed test file name too long");
  }

  if (debugLevel) {
    if (listFileNameUsed)
      fprintf(stderr,
	      "playlist file:     %s\n",fullListFileName);
    if (interactive)
      fprintf(stderr,
	      "test file:         %s\n",fullTestFileName);
  }

  if (!audioPathUsed)
    audioPath = getenv(AUDIO_PATH_ENV);

  if (debugLevel>2)
    fprintf(stderr,
	    "audio file path:   %s\n"
	    "audio file ext:    %s\n"
	    "playlist file ext: %s\n"
	    "test file ext:     %s\n",
	    (audioPath)?audioPath:"<none>",audioExt,listExt,testExt);


  /* check all audio files */
  if (rawFormatUsed)
    AFsetNHpar(rawFormat);
  else {
    if (getenv(RAWENV))
      AFsetNHpar("$" RAWENV);
    else
      AFsetNHpar(RAWDEFAULT);
  }
  if (listFileNameUsed) {
    if (!(list=fopen(fullListFileName,"r")))
      CommonExit(1,"can not open list file %s",fullListFileName);
  }
  else
    listIdx = -1;
  totTime = 0;
  numEntry = 0;
  numAudio = 0;
  numTrial = 0;
  numGrad = 0;
  minNumChan = -1;
  maxNumChan = 0;
  while (1) {
    if (listFileNameUsed) {
      if (!feof(list) && fgets(listStr,STRLEN,list))
	dellast(listStr);
      else
	*listStr = '\0';
    }
    else {
      listIdx++;
      if (varArgIdx[listIdx] >= 0)
	strncpy(listStr,argv[varArgIdx[listIdx]],STRLEN);
      else
	*listStr = '\0';
    }
    listStr[STRLEN-1] = '\0';
    if (!*listStr)
      break;
    f2hmsh(totTime,hmsh);
    if (debugLevel>1)
      fprintf(stderr,"%s   %s",hmsh,listStr);
    if (*listStr==';') {
    }
    else if (*listStr=='@') {
      AFsetNHpar(listStr+1);
    }
    else if (*listStr=='!') {
      numTrial++;
    }
    else if (*listStr=='*') {
    }
    else if (*listStr=='?') {
      numGrad++;
    }
    else if (*listStr=='#') {
      dur = (float)atof(listStr+1);
      if (dur<0) {
	if (debugLevel>1)
	  fprintf(stderr,"   (t=%6.2f)\n",0.0);
	CommonWarning("pause duration < 0 (%g)",dur);
	dur = 0;
      }
      else
	if (debugLevel>1)
	  fprintf(stderr,"   (t=%6.2f)",dur);
      totTime += dur;
    }
    else if (*listStr) {
      /* open audio file */
      if (ComposeFileName(listStr,0,audioPath,audioExt,audioFileName,STRLEN))
	CommonExit(1,"composed audio file name too long");
      if (debugLevel>2)
	fprintf(stderr," \n");
      if(!(tf=fopen(audioFileName,"rb")) ||
	 /* file not found, AFopenRead() would exit ... */
	 (fclose(tf), !(audio=AFopenRead(audioFileName,&numSample,
					 &numChan,&fSample,
					 (debugLevel>2)?stderr:(FILE*)NULL))))
	CommonWarning("can not open audio file %s",audioFileName);
      AFclose(audio);
      dur = numSample/numChan/fSample;
      if (debugLevel>1)
	fprintf(stderr,"   (c=%ld f=%7.1f t=%6.2f)",numChan,fSample,dur);
      totTime += dur;
      numAudio++;
      if (minNumChan == -1) {
	minNumChan = maxNumChan = numChan;
	minFSample = maxFSample = fSample;
      }
      else {
	if (minNumChan > numChan)
	  minNumChan = numChan;
	if (maxNumChan < numChan)
	  maxNumChan = numChan;
	if (minFSample > fSample)
	  minFSample = fSample;
	if (maxFSample < fSample)
	  maxFSample = fSample;
      }
    }
    if (debugLevel>1)
      fprintf(stderr," \n");
    numEntry ++;
  }	/* while (1) */
  if (listFileNameUsed)
    fclose(list);
  f2hmsh(totTime,hmsh);
  if (debugLevel>1)
    fprintf(stderr,"%s   <end of playlist file>\n",hmsh);
  if (debugLevel)
    fprintf(stderr,
	    "num entries:       %d\n"
	    "num audio files:   %d\n"
	    "num trials:        %d\n"
	    "num gradings:      %d\n"
	    "min num chan:      %d\n"
	    "max num chan:      %d\n"
	    "min sample freq:   %7.1f\n"
	    "max sample freq:   %7.1f\n"
	    "total duration:    %s\n",
	    numEntry,numAudio,numTrial,numGrad,minNumChan,maxNumChan,
	    minFSample,maxFSample,hmsh);


  if (noAudio) {
    CmdLineEvalFree(paraList);
    if (debugLevel>1)
      fprintf(stderr,"%s: %s\n",progName,stop?"aborted":"finished");
    exit (0);
  }


  /* prepare to play playlist ... */
  if (interactive) {
    if ((test=fopen(fullTestFileName,"r")))
      CommonExit(1,"test file %s already exists",fullTestFileName);
    if (!(test=fopen(fullTestFileName,"w")))
      CommonExit(1,"can not open test file %s",fullTestFileName);

    fflush(stderr);
    do {
      printf("enter your name & age:   ");
      fgets(inStr,STRLEN,stdin);
      dellast(inStr);
    } while (!*inStr);
    do {
      strcpy(tmpStr,inStr);
      printf("<ret> if OK or re-enter: ");
      fgets(inStr,STRLEN,stdin);
      dellast(inStr);
    } while (*inStr);
    fprintf(test,"playlist+ID+subject: %s %s %s\n",
	    fullListFileName,dateTime,tmpStr);
    fflush(test);
  }

  maxBufLen = (long)(BUF_TIME*MAX_FS*2);
  if(!(bufF = (float *)malloc(maxBufLen*sizeof(float))))
    CommonExit(1,"failed to allocate bufF");
  if(!(bufS = (short *)malloc(maxBufLen*sizeof(short))))
    CommonExit(1,"failed to allocate bufS");
  outBufS = bufS;

  /* enable local ^C handler */
#ifndef WIN32
  signal(SIGINT,stopproc);
#endif

  /* open audio output */
  if (maxNumChan > 2)
    CommonExit(-1,"only mono/stereo supported");
  if (!outNumChanUsed) {
    if (minNumChan!=maxNumChan)
      CommonExit(-1,"output: number of channels not obvious, use -c <x>");
    else
      outNumChan = minNumChan;
  }
  if (!outFSampleUsed) {
    if (minFSample!=maxFSample)
      CommonExit(-1,"output: sampling frequency not obvious, use -s <x>");
    else
      outFSample = minFSample;
  }

  if (outFileNameUsed) {
    if (strcmp(outFileName,"-")==0) {
      outtype = AUSTREAM;
      if (!(outas=AuOpenWrite(outFileName,outNumChan,outFSample)))
	CommonExit(1,"can not open output \"%s\"",outFileName);
    }
    else {
      outtype = AUFILE;
      fmti = 0;
      while (fmtstr[fmti].str && !isfmtstr(outFileName,fmtstr[fmti].str))
	fmti++;
      if (fmtstr[fmti].str)
	fmt = FD_INT16 + fmtstr[fmti].fmt;
      else
	CommonExit(1,"unkown output audio format");
      if (!(outaf=AFopenWrite(outFileName,fmt,outNumChan,outFSample,
			      (debugLevel>1)?stderr:(FILE*)NULL)))
	CommonExit(1,"can not open output \"%s\"",outFileName);
      }
  }
  else {
    outtype = AUDEV;
    devNumChan = outNumChan;
    devFSample = outFSample;
    if (!(outpr=PROpenWrite(playDevice,&devNumChan,&devFSample)))
      CommonExit(1,"can not open output audio device");
    /* check whether sampling frequencies are similar enough */
    if (fabs(devFSample-outFSample) < maxFsDev*outFSample)
      devFSample = outFSample;
    if (devNumChan != outNumChan || devFSample != outFSample)
      CommonWarning("can not configure output audio device as requested");
    if (playBufTime>0)
      PRWriteBuffer(outpr,(long)(devNumChan*devFSample*playBufTime+.5));
  }
  if (debugLevel)
    fprintf(stderr,"output=%s outNumChan=%d outFSample=%.1f\n",
	    (outtype==AUDEV)?"audiodevice":outFileName,outNumChan,outFSample);
  totClip = 0;
  totNumSamp = 0;

  
  /* play all audio files */
  if (rawFormatUsed)
    AFsetNHpar(rawFormat);
  else {
    if (getenv(RAWENV))
      AFsetNHpar("$" RAWENV);
    else
      AFsetNHpar(RAWDEFAULT);
  }
  if (listFileNameUsed) {
    if (!(list=fopen(fullListFileName,"r")))
      CommonExit(1,"can not open list file %s",fullListFileName);
  }
  else
    listIdx = -1;
  totTime = 0;
  *trialStr = '\0';
  *keyStr = '\0';
  trialStart = 0;
  while (!stop) {
    if (listFileNameUsed) {
      if (!feof(list) && fgets(listStr,STRLEN,list))
	dellast(listStr);
      else
	*listStr = '\0';
    }
    else {
      listIdx++;
      if (varArgIdx[listIdx] >= 0)
	strncpy(listStr,argv[varArgIdx[listIdx]],STRLEN);
      else
	*listStr = '\0';
    }
    listStr[STRLEN-1] = '\0';
    if (!*listStr)
      break;
    f2hmsh(totTime,hmsh);
    if (debugLevel>1)
      fprintf(stderr,"%s   %s",hmsh,listStr);

    fflush(stderr);
    if (*listStr==';') {
      if (interactive)
	printf("%s\n",listStr+1);
    }
    else if (*listStr=='@') {
      AFsetNHpar(listStr+1);
    }
    else if (*listStr=='!') {
      if (interactive) {
	printf("trial %s\n",listStr+1);
	trialStart = ftell(list);
	strcpy(trialStr,listStr+1);
	*keyStr = '\0';
      }
    }
    else if (*listStr=='*') {
      if (interactive)
	strcpy(keyStr,listStr+1);
    }
    else if (*listStr=='?') {
      if (interactive) {
	printf("%s\n",listStr+1);
	do {
	  printf("enter grading(s) or 'r' to repeat: ");
	  fflush(stdout);
#ifndef WIN32
	  /* drain(stdin); */
	  drain(STDIN_FILENO);   /* 0 in <unistd.h>   HP 991114 */
#endif
	  fgets(inStr,STRLEN,stdin);
	  dellast(inStr);
	} while (!*inStr);
	if (strcmp(inStr,"r") && strcmp(inStr,"x") && strcmp(inStr,"quit"))
	  do {
	    strcpy (tmpStr,inStr);
	    printf("<ret> if OK or re-enter:           ");
	    fflush(stdout);
#ifndef WIN32
            /* drain(stdin); */
	    drain(STDIN_FILENO);   /* 0 in <unistd.h>   HP 991114 */
#endif
	    fgets(inStr,STRLEN,stdin);
	    dellast(inStr);
	  } while (*inStr && strcmp(inStr,"r") && strcmp(inStr,"x") &&
		   strcmp(inStr,"quit"));
	if (!strcmp(inStr,"r"))
	  fseek(list,trialStart,SEEK_SET);
	else if (!strcmp(inStr,"x")) {
	  while (1) {
	    printf("enter trial # or <ret> to restart from beginning: ");
	    fflush(stdout);
#ifndef WIN32
	    /* drain(stdin); */
	    drain(STDIN_FILENO);   /* 0 in <unistd.h>   HP 991114 */
#endif
	    fgets(inStr,STRLEN,stdin);
	    dellast(inStr);
	    rewind(list);
	    if (!*inStr)
	      break;
	    do {
	      if (!feof(list) && fgets(listStr,STRLEN,list))
		dellast(listStr);
	      else
		*listStr = '\0';
	    } while (*listStr && (*listStr!='!' || strcmp(listStr+1,inStr)));
	    if (*listStr) {
	      printf("trial %s\n",listStr+1);
	      trialStart = ftell(list);
	      strcpy(trialStr,listStr+1);
	      break;
	    }
	    else
	      printf("trial %s not found!\n",inStr);
	  }
	}
	else if (!strcmp(inStr,"quit"))
	  stop = -1;
	else {
	  fprintf(test,"%s %s %s %s\n",dateTime,trialStr,keyStr,tmpStr);
	  fflush(test);
	}
      }
    }
    else if (*listStr=='#') {
      dur = (float)atof(listStr+1);
      if (dur<0) {
	if (debugLevel>1)
	  fprintf(stderr,"   (t=%6.2f)\n",0.0);
	CommonWarning("pause duration < 0 (%g)",dur);
	dur = 0;
      }
      else
	if (debugLevel>1)
	  fprintf(stderr,"   (t=%6.2f)",dur);

      /* write silence */
      bufLen = (long)(outNumChan*outFSample*bufTime+.5);
      bufLen = max(bufLen,outNumChan);
      bufLen = (bufLen/outNumChan)*outNumChan;
      bufLen = min(bufLen,maxBufLen);
      if (debugLevel>2)
	fprintf(stderr,"\nbufLen=%ld maxBufLen=%ld",bufLen,maxBufLen);
      inNumSamp = 0;
      outNumSamp = 0;
      maxNumSamp = (long)(outNumChan*outFSample*dur+.5);
      do {
	outBufUsed = maxNumSamp-inNumSamp;
	if (outBufUsed>bufLen)
	  outBufUsed = bufLen;
	inNumSamp += outBufUsed;
	for (i=0; i<outBufUsed; i++)
	  bufF[i] = bufS[i] = 0;
	switch (outtype) {
	case AUSTREAM:
	  AuWriteData(outas,bufS,outBufUsed);
	  break;
	case AUFILE:
	  AFwriteData(outaf,bufF,outBufUsed);
	  break;
	case AUDEV:
	  PRWriteData(outpr,bufS,outBufUsed);
	  break;
	}
	outNumSamp += outBufUsed;
      } while (!stop && outBufUsed);
      totNumSamp += outNumSamp;
      totTime += dur;
    }
    else if (*listStr) {
      /* open audio file */
      if (ComposeFileName(listStr,0,audioPath,audioExt,audioFileName,STRLEN))
	CommonExit(1,"composed audio file name too long");
      if (debugLevel>2)
	fprintf(stderr," \n");
      if(!(tf=fopen(audioFileName,"rb")) ||
	 /* file not found, AFopenRead() would exit ... */
	 (fclose(tf), !(audio=AFopenRead(audioFileName,&numSample,
					 &numChan,&fSample,
					 (debugLevel>2)?stderr:(FILE*)NULL))))
	CommonExit(1,"can not open audio file %s",audioFileName);
      dur = numSample/numChan/fSample;
      if (debugLevel>1)
	fprintf(stderr,"   (c=%ld f=%7.1f t=%6.2f)",numChan,fSample,dur);

      /* copy samples */
      bufLen = (long)(numChan*fSample*bufTime+.5);
      bufLen = max(bufLen,numChan);
      bufLen = (bufLen/numChan)*numChan;
      rc = NULL;
      if (fSample!=outFSample) {
	if (!(rc=RateConvInit(RCdebugLevel,
			      (double)outFSample/(double)fSample,0,
			      min(numChan,outNumChan),ht,al,fc,fd,
			      &convLen)))
	  CommonExit(1,"can not init sample rate conversion");
	bufLen = convLen;
	if (numChan==2 && outNumChan==1)
	  bufLen *= 2;
      }
      bufLen = min(bufLen,maxBufLen);
      if (debugLevel>2)
	fprintf(stderr,"\nbufLen=%ld maxBufLen=%ld",bufLen,maxBufLen);

      totClipOld = totClip;
      inNumSamp = 0;
      outNumSamp = 0;
      do {
	bufTrans = bufLen;

	inBufUsed = AFreadData(audio,inNumSamp,bufF,bufTrans);
	inNumSamp += inBufUsed;

	outBufUsed = inBufUsed;
	outBufF = bufF;

	if (numChan==2 && outNumChan==1) {
	  outBufUsed = inBufUsed/2;
	  for (i=0; i<outBufUsed; i++)
	    bufF[i] = (bufF[2*i]+bufF[2*i+1])/2;
	}
	if (rc) {
	  while (outBufUsed<convLen)
	    bufF[outBufUsed++] = 0;
	  outBufUsed = RateConv(rc,bufF,outBufUsed,&outBufF);
	  tmpBufUsed = (long)(inNumSamp/numChan/fSample*
			      outFSample*outNumChan)-outNumSamp;
	  if (outNumChan==2) {
	    if (numChan==1)
	      tmpBufUsed /= 2;
	    else
	      tmpBufUsed = (tmpBufUsed/outNumChan)*outNumChan;
	  }
	  if (tmpBufUsed<outBufUsed)
	    outBufUsed = tmpBufUsed;
	}
	if (numChan==1 && outNumChan==2) {
	  outBufUsed *= 2;
	  for (i=outBufUsed-1; i>=0; i--)
	    bufF[i] = outBufF[i/2];
	  outBufF = bufF;
	}

	if (outBufUsed>maxBufLen)
	  CommonExit(-1,"output buffer size troubles");
	numClip = 0;
	for (i=0; i<outBufUsed; i++) {
	  tmp = ((long)(gain*outBufF[i]+32768.5))-32768;
	  if (tmp>32767) {
	    tmp = 32767;
	    numClip++;
	  }
	  if (tmp<-32768) {
	    tmp = -32768;
	    numClip++;
	  }
	  outBufS[i] = (short)tmp;
	}
	totClip += numClip;
	
	switch (outtype) {
	case AUSTREAM:
	  AuWriteData(outas,outBufS,outBufUsed);
	  break;
	case AUFILE:
	  AFwriteData(outaf,outBufF,outBufUsed);
	  break;
	case AUDEV:
	  PRWriteData(outpr,outBufS,outBufUsed);
	  break;
	}
	outNumSamp += outBufUsed;

      } while (!stop && inBufUsed);
      
      if (rc)
	RateConvFree(rc);
      
      if (totClip-totClipOld)
	CommonWarning("%ld samples clipped!",totClip);
    
      AFclose(audio);
      totNumSamp += outNumSamp;
      totTime += dur;
    }	/*  else if (*listStr) */

    if (debugLevel>1)
      fprintf(stderr," \n");

    if (interactive && stop==1) {
      /* re-enable local ^C handler */
      stop = 0;
#ifndef WIN32
      signal(SIGINT,stopproc);
#endif
    }
  }	/* while (!stop) */
  if (listFileNameUsed)
    fclose(list);
  f2hmsh(totTime,hmsh);
  if (debugLevel>1)
    fprintf(stderr,"%s   <end of playlist file>\n",hmsh);


  /* close audio output */
    switch (outtype) {
    case AUSTREAM:
      AuClose(outas);
      break;
    case AUFILE:
      AFclose(outaf);
      break;
    case AUDEV:
      PRClose(outpr);
      break;
    }

    if (debugLevel)
      fprintf(stderr,
	      "out: %ld samples (%.3f sec)\n",
	      totNumSamp,totNumSamp/(outNumChan*outFSample));
    if (totClip)
      CommonWarning("%ld samples clipped!",totClip);

  if (interactive)
    fclose(test);

  CmdLineEvalFree(paraList);

  if (debugLevel>1)
    fprintf(stderr,"%s: %s\n",progName,stop?"aborted":"finished");

  exit (0);
}

/* end of playlist.c */

