/***************************************************************************
                          wavutil.c - description
                             -------------------
    begin                : Wed Mar 09 2005
    copyright            : (C) 2005 by Carsten Siebholz
    email                : c.siebholz@t-online.de

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <getopt.h>
#include <math.h>

#include "wavutil.h"

#define MAX_OUTFILES              100

#define DEFAULT_BASEFILENAME  "Split"
#define DEFAULT_BEGINGAP          100
#define DEFAULT_ENDGAP            400
#define DEFAULT_GAPLENGTH        3000
#define DEFAULT_THRESHOLD           8

#define RC_INPUT_FILENAME_MISSING               10
#define RC_VERSION_DISPLAYED                    11
#define RC_USAGE_DISPLAYED                      12
#define RC_INVALID_PARAMETER                    13
#define RC_CANNOT_HANDLE_COMPRESSED_FILES       20
#define RC_CANNOT_HANDLE_MORE_THAN_2_CHANNELS   21
#define RC_DATA_CHUNK_BEFORE_FMT_CHUNK          22
#define RC_FREAD_ERROR                          23
#define RC_INVALID_START_END_FRAMES             24
#define RC_READ_UNKNOWN_DATA                    25
#define RC_ALLOC_ERROR                          26

#define TRACE_MSG_SEARCH_LOUD_SILENT        1
#define TRACE_MSG_FIRST_LOUD                2
#define TRACE_MSG_FOUND_LOUD_SILENT         3
#define TRACE_MSG_SILENT_NOT_LONG_ENOUGH    4
#define TRACE_MSG_LOUD_SAMPLE               5
#define TRACE_MSG_SILENT_SAMPLE             6
#define TRACE_MSG_SILENCE_FOUND             7
#define TRACE_MSG_LAST_LOUD                 8



char*   progname        = NULL;

char*   pszOutFileBase  = NULL;
DWORD   BeginGap        = DEFAULT_BEGINGAP;
DWORD   EndGap          = DEFAULT_ENDGAP;
DWORD   GapLength       = DEFAULT_GAPLENGTH; /* 1/1000 sec of silence indicates gap */
DWORD   ThresPerMille   = DEFAULT_THRESHOLD; /* low volume indicative of silence
                                                in 1/1000 (per mill) */
DWORD   Threshold       = 0;

BOOL    bAnalyze   = FALSE;
BOOL    bScanBegin = FALSE;
BOOL    bScanEnd   = FALSE;
BOOL    bScanSplit = FALSE;

int     iDebug     = 0;


long MAX(long a, long b) {
  if(a>b) {
    return a;
  } else {
    return b;
  }
}
long MIN(long a, long b) {
  if(a<b) {
    return a;
  } else {
    return b;
  }
}



/*************************************************************************
 * parseArgs
 */
int parseArgs (int argc, char *argv[], WAVFile* I) {
  int iRc = 0;
  int c;
  int iVersion = 0;
  int iUsage = 0;

  static struct option long_options[] = {
    {"output"       , 1, 0, 0},
    {"analyze"      , 0, 0, 0},
    {"begin"        , 0, 0, 0},
    {"begin-gap"    , 1, 0, 0},
    {"end"          , 0, 0, 0},
    {"end-gap"      , 1, 0, 0},
    {"split"        , 0, 0, 0},
    {"gap-length"   , 1, 0, 0},
    {"threshold"    , 1, 0, 0},
    {"debug"        , 0, 0, 0},
    {"version"      , 0, 0, 0},
    {"help"         , 0, 0, 0},
    {0, 0, 0, 0}
  };

  while((1) && (!iUsage)) {
    int option_index = 0;

    c = getopt_long(argc, argv, "o:abB:eE:sg:t:DVh", long_options, &option_index);
    if(c == -1)
      break;

    switch(c) {
    case 'o' :       /* output file base filename */
      pszOutFileBase = (char *) strdup(optarg);
      break;
    case 'a':        /* analyze */
      bAnalyze = TRUE;
      break;
    case 'b':        /* begin */
      bScanBegin = TRUE;
      break;
    case 'B' :       /* begin-gap */
      BeginGap = (DWORD)atoi(optarg);
      break;
    case 'e':        /* end */
      bScanEnd = TRUE;
      break;
    case 'E' :       /* end-gap */
      EndGap = (DWORD)atoi(optarg);
      break;
    case 's':        /* split */
      bScanSplit = TRUE;
      break;
    case 'g' :       /* gap-length */
      GapLength = (DWORD)atoi(optarg);
      break;
    case 't' :       /* silance threshold */
      ThresPerMille = (DWORD)atoi(optarg);
      break;
    case 'D':        /* debug */
      iDebug++;
      break;
    case 'V':        /* version */
      iVersion++;
      break;
    case 'h':        /* help */
      iUsage++;
      break;

    case 0 :
      switch(option_index) {
      case 0 :       /* output file base filename */
        pszOutFileBase = (char *) strdup(optarg);
        break;
      case 1 :       /* analyze */
        bAnalyze = TRUE;
        break;
      case 2 :       /* begin */
        bScanBegin = TRUE;
        break;
      case 3 :       /* begin-gap */
        BeginGap = (DWORD)atoi(optarg);
        break;
      case 4 :       /* end */
        bScanEnd = TRUE;
        break;
      case 5 :       /* end-gap */
        EndGap = (DWORD)atoi(optarg);
        break;
      case 6 :       /* split */
        bScanSplit = TRUE;
        break;
      case 7 :       /* gap-length */
        GapLength = (DWORD)atoi(optarg);
        break;
      case 8 :       /* silance threshold */
        ThresPerMille = (DWORD)atoi(optarg);
        break;
      case 9 :       /* debug */
        iDebug++;
        break;
      case 10 :      /* version */
        iVersion++;
        break;
      default :
        iUsage++;
        break;
      }
      break;
    default :
      iUsage++;
      break;
    }
  }

  /* read input filename */
  if((optind<argc) && !iUsage && !iVersion) {
    I->filename = (char *) strdup(argv[optind]);
  }


  if(!iRc && iVersion) {
    fprintf(stderr, "%s\n", VERSION);
    iRc = RC_VERSION_DISPLAYED;
  }

  if(!iRc && iUsage) {
    fprintf(stderr, "%s v%s\n", progname, VERSION);
    fprintf(stderr, "Tool to detect silence in WAV files,\n");
    fprintf(stderr, "     to remove silence at the beginning and at the end and\n");
    fprintf(stderr, "     to split to multiple WAV files if there are gaps in the WAV file.\n");
    fprintf(stderr, "(C) 2005 Carsten Siebholz <c.siebholz@t-online.de>\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "Usage: %s [OPTION]... FILE\n", progname);
    fprintf(stderr, "  -o, --output=BASEFILE  output file base filename.\n");
    fprintf(stderr, "                         The output files will have an additional number\n");
    fprintf(stderr, "                         and the extension .wav:\n");
    fprintf(stderr, "                          %s_01.wav ... %s_99.wav\n", DEFAULT_BASEFILENAME, DEFAULT_BASEFILENAME);
    fprintf(stderr, "                         (default: the basename of the input file)\n");
    fprintf(stderr, "  -a, --analyze          just analyze the input FILE\n");
    fprintf(stderr, "                         (does not create any output files)\n");
    fprintf(stderr, "  -b, --begin            remove silence at the beginning\n");
    fprintf(stderr, "  -B, --begin-gap=TIME   this parameter specifies a time to start the track\n");
    fprintf(stderr, "                         before the first 'loud' sample (in 1/1000 sec)\n");
    fprintf(stderr, "                         (default: %d)\n", DEFAULT_BEGINGAP);
    fprintf(stderr, "  -e, --end              remove silence at the end\n");
    fprintf(stderr, "  -E, --end-gap=TIME     this parameter specifies a time to end the track\n");
    fprintf(stderr, "                         after the last 'loud' sample (in 1/1000 sec)\n");
    fprintf(stderr, "                         (default: %d)\n", DEFAULT_ENDGAP);
    fprintf(stderr, "  -s, --split            split XXX\n");
    fprintf(stderr, "  -g, --gap-length=TIME  minimum gap length in 1/1000 seconds\n");
    fprintf(stderr, "                         (default: %d)\n", DEFAULT_GAPLENGTH);
    fprintf(stderr, "  -t, --threshold=VAL    threshold for silence in 1/1000 of the full amplitude\n");
    fprintf(stderr, "                         (16bit: +32768...-32768  8bit: +127...-128)\n");
    fprintf(stderr, "                         (default: %d)\n", DEFAULT_THRESHOLD);
    fprintf(stderr, "  -D, --debug            enable additional debug output\n");
    fprintf(stderr, "  -V, --version          display version information and exit\n");
    fprintf(stderr, "  -h, --help             display this help and exit\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "Report bugs, comments, ideas to <c.siebholz@t-online.de>\n");
    fprintf(stderr, "http://www.htpc-forum.de/csiebholz/\n");
    iRc = RC_USAGE_DISPLAYED;
  }

  if(!iRc && !I->filename) {
    fprintf(stderr, "ERROR. You have to specify an input file !!!\n");
    iRc = RC_INPUT_FILENAME_MISSING;
  }

  if(!iRc && (BeginGap > GapLength)) {
    fprintf(stderr, "ERROR. 'begin-gap' must be lower than 'gap-length' !!!\n");
    iRc = RC_INVALID_PARAMETER;
  }

  if(!iRc && (EndGap > GapLength)) {
    fprintf(stderr, "ERROR. 'end-gap' must be lower than 'gap-length' !!!\n");
    iRc = RC_INVALID_PARAMETER;
  }

  return iRc;
}


/*************************************************************************
 * frameToTimeString
 */
char* frameToTimeString (WAVFile* I, DWORD f) {
  static char   szTime[15+1];   /* for time string HH:MM:SS.MMMMMM */
  double dTime;
  DWORD  h,m,s,y;

  if(I->f_samplesPerSec == 0) {
    sprintf(szTime, "--:--:--.------");
  } else {
    dTime = ((double)f / (double)I->f_samplesPerSec);
    h = (DWORD)floor(dTime / 3600.0);
    dTime = dTime - (double)h * 3600.0;
    m = (DWORD)floor(dTime / 60.0);
    dTime = dTime - (double)m *   60.0;
    s = (DWORD)floor(dTime);
    dTime = dTime - (double)s;
    y = (DWORD)(dTime * 1000000.0);

    sprintf(szTime, "%02d:%02d:%02d.%06d", h, m, s, y);
  }
  return szTime;
}


/*************************************************************************
 * parseInputWav
 */
int parseInputWav (WAVFile* I) {
  int iRc = 0;

  chunkHeader ch;
  long  lReadPos;
  char* junk = NULL;


  do {
    lReadPos = ftell(I->f);
    if(iDebug>1) {
      fprintf(stderr, "\t[%08lX]: ", lReadPos);
    }
    if(fread((void *)&ch, sizeof(chunkHeader), 1, I->f) == 1) {
      if(iDebug>1) {
        fprintf(stderr, "%02X%02X%02X%02X ",
                (BYTE)(ch.id    ) & 0xff,
                (BYTE)(ch.id>>8 ) & 0xff,
                (BYTE)(ch.id>>16) & 0xff,
                (BYTE)(ch.id>>24) & 0xff);
        fprintf(stderr, "%02X%02X%02X%02X  ",
                (BYTE)(ch.chunkSize    ) & 0xff,
                (BYTE)(ch.chunkSize>>8 ) & 0xff,
                (BYTE)(ch.chunkSize>>16) & 0xff,
                (BYTE)(ch.chunkSize>>24) & 0xff);
        fprintf(stderr, "%c%c%c%c ",
                (((BYTE)(ch.id    ) & 0xff) > 0x1f) ? (BYTE)(ch.id    ) & 0xff:'.',
                (((BYTE)(ch.id>>8 ) & 0xff) > 0x1f) ? (BYTE)(ch.id>>8 ) & 0xff:'.',
                (((BYTE)(ch.id>>16) & 0xff) > 0x1f) ? (BYTE)(ch.id>>16) & 0xff:'.',
                (((BYTE)(ch.id>>24) & 0xff) > 0x1f) ? (BYTE)(ch.id>>24) & 0xff:'.');
        fprintf(stderr, "%c%c%c%c\n",
                (((BYTE)(ch.chunkSize    ) & 0xff) > 0x1f) ? (BYTE)(ch.chunkSize    ) & 0xff:'.',
                (((BYTE)(ch.chunkSize>>8 ) & 0xff) > 0x1f) ? (BYTE)(ch.chunkSize>>8 ) & 0xff:'.',
                (((BYTE)(ch.chunkSize>>16) & 0xff) > 0x1f) ? (BYTE)(ch.chunkSize>>16) & 0xff:'.',
                (((BYTE)(ch.chunkSize>>24) & 0xff) > 0x1f) ? (BYTE)(ch.chunkSize>>24) & 0xff:'.');
      }

      switch(ch.id) {
      case (ID_RIFF) :
        if(iDebug>1) {
          fprintf(stderr, "\tFound RIFF chunk. Size:%d (=0x%08X)\n", ch.chunkSize, ch.chunkSize);
        }
        /* read RIFF Format 'WAVE' */
/*      fread(&(rc.type), sizeof(rc.type), 1, I->f); */
        fseek(I->f, 4, SEEK_CUR);
        break;

      case (ID_FMT) :
        if(iDebug>1) {
          fprintf(stderr, "\tFound FMT  chunk. Size:%d (=0x%08X)\n", ch.chunkSize, ch.chunkSize);
        }
        if(fread(&I->f_formatTag, sizeof(I->f_formatTag), 1, I->f) != 1) {
          iRc = RC_FREAD_ERROR;
          break;
        }
        if(I->f_formatTag != 1) { /* cannot handle compression */
          fprintf(stderr, "Cannot handle compressed files\n");
          iRc = RC_CANNOT_HANDLE_COMPRESSED_FILES;
          break;
        }

        if(fread(&I->f_channels, sizeof(I->f_channels), 1, I->f) != 1) {
          iRc = RC_FREAD_ERROR;
          break;
        }
        if(I->f_channels > 2) { /* cannot handle above stereo */
          fprintf(stderr, "Cannot do more than 2 channels\n");
          iRc = RC_CANNOT_HANDLE_MORE_THAN_2_CHANNELS;
          break;
        }

        if(fread(&I->f_samplesPerSec,  sizeof(I->f_samplesPerSec),  1, I->f) != 1) {
          iRc = RC_FREAD_ERROR;
          break;
        }

        if(fread(&I->f_avgBytesPerSec, sizeof(I->f_avgBytesPerSec), 1, I->f) != 1) {
          iRc = RC_FREAD_ERROR;
          break;
        }

        if(fread(&I->f_blockAlign,     sizeof(I->f_blockAlign),     1, I->f) != 1) {
          iRc = RC_FREAD_ERROR;
          break;
        }

        if(fread(&I->f_bitsPerSample,  sizeof(I->f_bitsPerSample),  1, I->f) != 1) {
          iRc = RC_FREAD_ERROR;
          break;
        }

        break;

      case (ID_DATA) :
        if(iDebug>1) {
          fprintf(stderr, "\tFound DATA chunk. Size:%d (=0x%08X)\n", ch.chunkSize, ch.chunkSize);
        }
        if(I->f_blockAlign == 0) {
          fprintf(stderr, "Got DATA chunk before FMT chunk\n");
          iRc = RC_DATA_CHUNK_BEFORE_FMT_CHUNK;
          break;
        }
        I->d_start = ftell(I->f);
        I->d_end = I->d_start + ch.chunkSize;
        I->o_curFrame = 0;
        I->o_anzFrames = ch.chunkSize / I->f_blockAlign;
        fprintf(stdout, "TOT %10d frames (%s)\n",
                I->o_anzFrames,
                frameToTimeString(I, I->o_anzFrames));
        fseek(I->f, ch.chunkSize, SEEK_CUR);
        break;

      case (ID_LIST) :
        if(iDebug>1) {
          fprintf(stderr, "\tFound LIST chunk. Size:%d (=0x%08X)\n", ch.chunkSize, ch.chunkSize);
        }
        junk = malloc(ch.chunkSize);
        if(!junk) {
          iRc = RC_ALLOC_ERROR;
        } else {
          if(fread(junk, ch.chunkSize, 1, I->f) != 1) {
            iRc = RC_FREAD_ERROR;
          }
        }
        break;

      default :
        if(iDebug>1) {
          fprintf(stderr, "\tUnknown data\n");
        }
        fseek(I->f, lReadPos+1, SEEK_SET);
        break;
      }
    } else {
      if(iDebug>1) {
        fprintf(stderr, "eof or err\n");
      }
    }

  } while(!feof(I->f) && !iRc);

  return iRc;
}



/*************************************************************************
 * saveOutputWave
 */
int saveOutputWave(WAVFile* I, DWORD start, DWORD end, int* piPartNr) {
  int iRc = 0;

  FILE* OutFile = NULL;
  char* OutFN   = NULL;

  RIFFChunk rc;
  FMTChunk  fc;
  chunkHeader ch;
  DWORD cf;
  DWORD dw;

  if(iDebug>0) {
    fprintf(stderr, "Creating WAV file (%d-%d)\n", start, end);
  }

  if(end>start) {
    asprintf( &OutFN, "%s_%02d.wav", pszOutFileBase, *piPartNr );
    if(bAnalyze==FALSE) {

      OutFile = fopen(OutFN, "wb");
      if(OutFile) {

        memset(&rc, 0, sizeof(RIFFChunk));
        memset(&fc, 0, sizeof(FMTChunk));
        memset(&ch, 0, sizeof(chunkHeader));

        rc.header.id = ID_RIFF;
        rc.header.chunkSize = 4 +                   /* 'WAVE'            */
                              sizeof(FMTChunk) +    /* FMT chunk         */
                              sizeof(chunkHeader) + /* DATA chunk header */
                              (end-start+1)*I->f_blockAlign;
        memcpy(&rc.type, "WAVE" , 4);
        fwrite(&rc, sizeof(rc), 1, OutFile);

        fc.header.id = ID_FMT;
        fc.header.chunkSize = sizeof(FMTChunk) - sizeof(chunkHeader); /* 16 */
        fc.formatTag        = I->f_formatTag;
        fc.channels         = I->f_channels;
        fc.samplesPerSec    = I->f_samplesPerSec;
        fc.avgBytesPerSec   = I->f_avgBytesPerSec;
        fc.blockAlign       = I->f_blockAlign;
        fc.bitsPerSample    = I->f_bitsPerSample;
        fwrite(&fc, sizeof(fc), 1, OutFile);

        ch.id = ID_DATA;
        ch.chunkSize = (end-start+1)*I->f_blockAlign;
        fwrite(&ch, sizeof(ch), 1, OutFile);

        /* write data */
        fseek(I->f, I->d_start + (start * I->f_blockAlign), SEEK_SET);
        for(cf=start; cf<=end; cf++) {
          if(fread(&dw, I->f_blockAlign, 1, I->f) != 1) {
            iRc = RC_FREAD_ERROR;
            break;
          }
          fwrite(&dw, I->f_blockAlign, 1, OutFile);
        }

        fclose(OutFile);
      }
    }


    fprintf(stdout, "T%02d %10d - %10d ", *piPartNr, start, end);
    fprintf(stdout, "(%s-",  frameToTimeString(I, start));
    fprintf(stdout, "%s) ", frameToTimeString(I, end));
    fprintf(stdout, "L:%s ", frameToTimeString(I, end-start));
    fprintf(stdout, "F:%s\n", OutFN);
    (*piPartNr)++;
    free(OutFN);
  } else {
    iRc = RC_INVALID_START_END_FRAMES;
  }

  return iRc;
}



/*************************************************************************
 * SampleType
 */
SAMPLETYPE SampleType(WAVFile* I, void* f) {
  SAMPLETYPE type = SILENT;
  WORD c;

  /* check for silence */
  for(c=0; c<I->f_channels; c++) {
    if(I->f_bitsPerSample == 8) {
/*    fprintf(stderr, "  Frame %08ld / %d : %02X  ",                       */
/*            I->o_curFrame, c+1, abs( *((unsigned char*)f + c) - 0x80 ) );*/
      if(abs( *((BYTE*)f + c) - 0x80 ) >= Threshold) {
        type = LOUD;
        break;
      }
    } else {
/*    fprintf(stderr, "  Frame %08ld / %d : %04X  ",               */
/*            I->o_curFrame, c+1, abs( *((signed short*)f + c) ) );*/
      if(abs( *((SWORD*)f + c) ) >= Threshold) {
        type = LOUD;
        break;
      }
    }
  }

  return type;
}


/*************************************************************************
 * searchSilence
 */
int searchSilence(WAVFile* I) {
  int iRc = 0;

  BYTE  ubSample[2];
  SWORD swSample[2];
  void* sample;
  size_t frameSize  = (size_t)I->f_blockAlign;
  int   compMethod  = 0;
  BOOL  bFound;

  DWORD startFrame  = 0;
  DWORD endFrame    = I->o_anzFrames-1;
  DWORD startPart   = 0;
  DWORD endPart     = 0;
  int   iPartNr     = 1;
  static BYTE lastMsg = 0;


  if(I->f_bitsPerSample == 8) {
    sample = (void *) ubSample;
    compMethod = 1;
  } else {
    sample = (void *) swSample;
    compMethod = 2;
  }


  fseek(I->f, I->d_start, SEEK_SET);
  I->o_curFrame = 0;


  /* find first 'loud' frame */
  fprintf(stderr, "Scanning for silence at the beginning...\n");
  bFound = FALSE;
  while( !feof(I->f) && (I->o_curFrame < I->o_anzFrames) && bFound==FALSE) {
    /* read a sample */
    if(fread(sample, frameSize, 1, I->f) != 1) {
      return RC_FREAD_ERROR;
    }

    if(SampleType(I, sample)==LOUD) {
      startFrame = I->o_curFrame;
      bFound = TRUE;
    }
    I->o_curFrame++;
  }
  fprintf(stdout, "BEG %10d frames (%s) with silence at the beginning\n",
          startFrame,
          frameToTimeString(I, startFrame));
  if(startFrame>0) {
    if(!bScanBegin==TRUE && bAnalyze==FALSE) {
      startFrame = 0;
      fprintf(stderr, "Do not remove silence at the beginning. ('-b' parameter is missing)\n");
    }
  }


  /* find last 'loud' frame */
  fprintf(stderr, "Scanning for silence at the end...\n");
  while( !feof(I->f) && (I->o_curFrame < I->o_anzFrames) ) {
    /* read a sample */
    if(fread(sample, frameSize, 1, I->f) != 1) {
      return RC_FREAD_ERROR;
    }

    if(SampleType(I, sample)==LOUD) {
      endFrame = I->o_curFrame;
    }
    I->o_curFrame++;
  }

  fprintf(stdout, "END %10d frames (%s) with silence at the end\n",
          I->o_anzFrames-1-endFrame,
          frameToTimeString(I, I->o_anzFrames-1-endFrame));
  if(endFrame<I->o_anzFrames-1) {
    if(!bScanEnd==TRUE && bAnalyze==FALSE) {
      endFrame = I->o_anzFrames-1;
      fprintf(stderr, "Do not remove silence at the end.       ('-e' parameter is missing)\n");
    }
  }



  if(bScanSplit==TRUE || bAnalyze==TRUE) {
    fprintf(stderr, "Scanning frames (%d-%d) for silence to split...\n", startFrame, endFrame);


    /* scan (startFrame...endFrame) for silence to split */
    I->o_curFrame = startFrame;
    fseek(I->f, I->d_start + (I->o_curFrame * I->f_blockAlign), SEEK_SET);

    while( !feof(I->f) && (I->o_curFrame < endFrame) ) {

      startPart = 0;
      endPart = 0;

      /* search for -_ (loud->silent) transition */
      if(iDebug>1 && lastMsg!=TRACE_MSG_SEARCH_LOUD_SILENT) {
        lastMsg = TRACE_MSG_SEARCH_LOUD_SILENT;
        fprintf(stderr, "\t[%010d]: search for 'loud'->'silent' transition\n", I->o_curFrame);
      }

      /*  get next 'loud' sample */
      while( !feof(I->f) && (I->o_curFrame < endFrame) ) {
        /* read a sample */
        if(fread(sample, frameSize, 1, I->f) != 1) {
          return RC_FREAD_ERROR;
        }
        I->o_curFrame++;

        if(SampleType(I, sample)==LOUD) {
          startPart = I->o_curFrame-1;
          if(iDebug>1 && lastMsg!=TRACE_MSG_FIRST_LOUD) {
            lastMsg = TRACE_MSG_FIRST_LOUD;
            fprintf(stderr, "\t[%010d]:   LOUD    first sample\n", startPart);
          }
          break;
        }
      }
      /*  get next 'silent' sample */
      while( !feof(I->f) && (I->o_curFrame < endFrame) ) {
        /* read a sample */
        if(fread(sample, frameSize, 1, I->f) != 1) {
          return RC_FREAD_ERROR;
        }
        I->o_curFrame++;

        if(SampleType(I, sample)==SILENT) {

          if(!endPart) {
            /* remember the last 'loud' sample */
            if(iDebug>2 && lastMsg!=TRACE_MSG_FOUND_LOUD_SILENT) {
              lastMsg = TRACE_MSG_FOUND_LOUD_SILENT;
              fprintf(stderr, "\t[%010d]:   SILENT  'loud'->'silent' transition found. Checking if silence is long enough...\n",
                      I->o_curFrame-1);
            }
            endPart = I->o_curFrame-2;
          } else {
            /* check if the 'silence' is long enough */
            if(I->o_curFrame-1 - endPart > (GapLength * I->f_samplesPerSec) / 1000) {
              if(iDebug>1 && lastMsg!=TRACE_MSG_SILENCE_FOUND) {
                lastMsg = TRACE_MSG_SILENCE_FOUND;
                fprintf(stderr, "\t[%010d]:   SILENT  Found silence longer than %d.%03d seconds.\n",
                        I->o_curFrame-1,
                        GapLength/1000, GapLength%1000);
              }

              iRc = saveOutputWave(I,
                                   (DWORD)MAX((long)startPart - (long)(BeginGap * I->f_samplesPerSec)/1000, (long)0),
                                   (DWORD)MIN((long)endPart   + (long)(EndGap   * I->f_samplesPerSec)/1000, (long)I->o_anzFrames-1),
                                   &iPartNr);
              break;
            } else {
              if(iDebug>2 && lastMsg!=TRACE_MSG_SILENT_NOT_LONG_ENOUGH) {
                lastMsg = TRACE_MSG_SILENT_NOT_LONG_ENOUGH;
                fprintf(stderr, "\t[%010d]:   SILENT  silence is not long enough yet...\n",
                        I->o_curFrame-1);
              }
            }
          }
        } else {
          /* 'loud' sample */
          if(iDebug>2 && lastMsg!=TRACE_MSG_LOUD_SAMPLE) {
            lastMsg = TRACE_MSG_LOUD_SAMPLE;
            fprintf(stderr, "\t[%010d]:   LOUD\n", I->o_curFrame-1);
          }
          endPart = 0;
        }
      }
    }

    /* and save the rest... */
    if(iDebug>1 && lastMsg!=TRACE_MSG_LAST_LOUD) {
      lastMsg = TRACE_MSG_LAST_LOUD;
      fprintf(stderr, "\t[%010d]:   LOUD    last sample => save the rest...\n",
              I->o_curFrame);
    }

    iRc = saveOutputWave(I,
                         (DWORD)MAX((long)startPart  - (long)(BeginGap * I->f_samplesPerSec)/1000, (long)0),
                         (DWORD)MIN((long)endFrame   + (long)(EndGap   * I->f_samplesPerSec)/1000, (long)I->o_anzFrames-1),
                         &iPartNr);

  } else {
    /* no '-s' and no '-a' */
    iRc = saveOutputWave(I,
                         (DWORD)MAX((long)startFrame - (long)(BeginGap * I->f_samplesPerSec)/1000, (long)0),
                         (DWORD)MIN((long)endFrame   + (long)(EndGap   * I->f_samplesPerSec)/1000, (long)I->o_anzFrames-1),
                         &iPartNr);
  }

  return iRc;
}




/*************************************************************************
 * main
 */
int main(int argc, char *argv[]) {
  int iRc = 0;
  char* pszT1;
  char* pszT2;

  WAVFile InFile;
/*WAVFile OutFiles[MAX_OUTFILES]; */


  /* parse command line parameters */
  (progname = strchr(argv[0], '/')) ? progname++ : (progname = argv[0]);


  memset(&InFile, 0, sizeof(WAVFile));

  if((iRc = parseArgs(argc, argv, &InFile))!=0) {
    if(iDebug>0) {
      if((iRc != RC_VERSION_DISPLAYED) &&
         (iRc != RC_USAGE_DISPLAYED)) {
        fprintf(stderr, "ERROR in parseArgs(): rc=%d\n", iRc);
      }
    }
    return iRc;
  }


  /* let's open the input stream */
  if(InFile.filename) {
    InFile.f = fopen(InFile.filename, "rb");

    if(!pszOutFileBase) {   /* use default   INPUT_00.wav */
      pszT1 = strrchr(InFile.filename, '/');
      if(pszT1) {
        pszT1++;
      } else {
        pszT1 = InFile.filename;
      }
      pszOutFileBase = strdup(pszT1);
      pszT2 = strstr(pszOutFileBase, ".wav");
      if(pszT2) *pszT2 = 0;
    }


    fprintf(stderr, "\n");
    fprintf(stderr, "Input                           : %s\n", InFile.filename);
    fprintf(stderr, "Output base                     : %s\n", pszOutFileBase);
    fprintf(stderr, "Analyze only                    : %s\n", (bAnalyze  ==TRUE) ? "YES":"NO");
    fprintf(stderr, "Remove silence at the beginning : %s\n", (bScanBegin==TRUE) ? "YES":"NO");
    fprintf(stderr, "Begin-gap                       : %d.%03d sec\n", BeginGap/1000, BeginGap%1000);
    fprintf(stderr, "Remove silence at the end       : %s\n", (bScanEnd  ==TRUE) ? "YES":"NO");
    fprintf(stderr, "End-gap                         : %d.%03d sec\n", EndGap/1000, EndGap%1000);
    fprintf(stderr, "Split into multiple files       : %s\n", (bScanSplit==TRUE) ? "YES":"NO");
    fprintf(stderr, "Threshold in 1/1000             : %d\n", ThresPerMille);
    fprintf(stderr, "Minimal length of silence       : %d.%03d sec\n", GapLength/1000, GapLength%1000);
    fprintf(stderr, "\n");


    if(InFile.f) {
      if((iRc = parseInputWav(&InFile))!=0) {
        if(iDebug>0) {
          fprintf(stderr, "ERROR in parseInputWav(): rc=%d\n", iRc);
        }
      } else {
        if(iDebug>0) {
          fprintf(stderr, "\tSamples/Sec  = %d\n", InFile.f_samplesPerSec);
          fprintf(stderr, "\tBytes/Sec    = %d\n", InFile.f_avgBytesPerSec);
          fprintf(stderr, "\tFrameSize    = %d\n", InFile.f_blockAlign);
          fprintf(stderr, "\tBits/Sample  = %d\n", InFile.f_bitsPerSample);
          fprintf(stderr, "\tTotal Frames = %d\n", InFile.o_anzFrames);
        }

        if(InFile.f_bitsPerSample == 8) {
          Threshold = (0x80 * ThresPerMille + 500) / 1000;
        } else {
          Threshold = (0x8000 * ThresPerMille + 500) / 1000;
        }


        if((iRc = searchSilence(&InFile))!=0) {
          if(iDebug>0) {
            fprintf(stderr, "ERROR in searchSilence(): rc=%d\n", iRc);
          }
        }
      }
      fclose(InFile.f);
    }

    if(pszOutFileBase) free(pszOutFileBase);
  }

  return iRc;
}

/*
ToDo:
=====
  - LIST Chunk
  - add frames/time in trace outputs
  - log trace outputs only, if output has changed

*/
