/*
 * W E X T R A C T
 *
 *  Extract a specified time interval from an audio file in .wav format
 *
 *  Note that this works ONLY for uncompressed waveform files
 *       with one data chunk
 *       and no other stuff.
 *  However, it should work for any combination of
 *           sampling rate, number of channels, and bits-per-sample.
 *
 *  Usage: wextract -i inputfile -o outputfile -s starttime -e endtime
 *
 *  Mark Liberman <myl@cis.upenn.edu> 11/30/2006
 *
 *    Tested on Linux (Redhat Enterprise 4) and Ubuntu (Edgy Eft)
 *      and on Windows (Cygwin under XP)
 *
 * Copyright (C) 2006  Mark Y. Liberman
 * 
 * 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.
 * 
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <stdio.h>
#include <stdlib.h>

/* 
 * Information about .wav header lay-out
 *        taken from http://www.borg.com/~jglatt/tech/wave.htm 
 */


typedef struct {
  /* RiffChunk */
  unsigned char riffID[4]; /* 'RIFF' */
  long RchunkSize;  /* filesize - 8 */
  unsigned char waveID[4]; /* 'WAVE' */

  /* FormatChunk */
  unsigned char FchunkID[4]; /* 'fmt ' */
  long FchunkSize;
  short wFormatTag;
  unsigned short wChannels;
  unsigned long dwSamplesPerSec;
  unsigned long dwAvgBytesPerSec;
  unsigned short wBlockAlign;
  unsigned short wBitsPerSample;

  /* Note: there may be additional fields here, if wFormatTag is not = 1 */
  /* DataChunk  -- we assume only one */
  unsigned char DchunkID[4]; /* 'data' */
  long DchunkSize; /* filesize - 44 */
} WavHeader;

/* Note:
 * If there is one FormatChunk and one DataChunk
 *    and the file is uncompressed waveform samples
 *    (regardless of the bits-per-sample and number of channels),
 *    then the header (in the sense of stuff before the speech data)
 *    should contain a total of 44 bytes,
 *    as described above...
 */    

WavHeader Inheader, Outheader;
main(ac,av)
     int ac;
     char **av;
{
  double starttime = 0.0, endtime = 0.0, fileseconds;
  long int firstbyte, lastbyte, totalbytes, filesize, ret;
  char *infile=NULL, *outfile=NULL;
  unsigned char *data;
  FILE *infd, *outfd;
  int c, nread;
  extern char *optarg;

  while((c=getopt(ac,av,"i:o:s:e:"))!=EOF)
    switch(c){
    case 'i':
      infile = optarg;
      break;
    case 'o':
      outfile = optarg;
      break;
    case 's':
      starttime = atof(optarg);
      break;
    case 'e':
      endtime = atof(optarg);
      break;
    default:
      fprintf(stderr,
	      "Usage: %s\n -i infile -o outfile -s starttime -e endtime",
	      av[0]);
      exit(2);
    }
  infd = fopen(infile, "r");
  if(infd==NULL){
    fprintf(stderr,"Can't open %s for reading\n", infile);
    exit(2);
  }
  outfd = fopen(outfile, "w");
  if(outfd==NULL){
    fprintf(stderr,"Can't open %s for writing\n", outfile);
    exit(2);
  }
  nread = fread(&Inheader, 44, 1, infd);
  if(nread != 1){
    fprintf(stderr, "Failed to read header from %s\n", infile);
    exit(2);
  }
  if(starttime == 0.0 && endtime == 0.0){
    fprintf(stderr,
	    "Usage: %s\n -i infile -o outfile -s starttime -e endtime",
	    av[0]);
    exit(2);
  }

  Outheader = Inheader;

  fileseconds = (double)(Inheader.DchunkSize)/(double)Inheader.dwAvgBytesPerSec;
  if (endtime == 0.0) endtime = fileseconds;

  if((starttime < 0.0) || 
    (starttime >= endtime) || 
    (endtime > fileseconds)){
    fprintf(stderr, 
      "Given file size of %g seconds, start = %g and end = %g not feasible\n",
	    fileseconds, starttime, endtime);
    exit(2);
  }
  firstbyte = 44 + (int)(starttime*(Inheader.dwAvgBytesPerSec));
  totalbytes = (int)((endtime-starttime)*(Inheader.dwAvgBytesPerSec));
  totalbytes += totalbytes%(Inheader.wBlockAlign);

  if(fseek(infd, firstbyte, SEEK_SET)){
    fprintf(stderr, "Couldn't seek to byte %d in %s\n", firstbyte,infile);
    exit(2);
  }

  Outheader.RchunkSize = totalbytes + 44 - 8;
  Outheader.DchunkSize = totalbytes;

  ret = fwrite(&Outheader, 44, 1, outfd);

  data = malloc(totalbytes);
  ret = fread(data, 1, totalbytes, infd);
  ret = fwrite(data, 1, totalbytes, outfd);

  fclose(infd); fclose(outfd);
  
}
