 /*************************************************************************/
 /*                                                                       */
 /*                Centre for Speech Technology Research                  */
 /*                     University of Edinburgh, UK                       */
 /*                      Copyright (c) 1995,1996                          */
 /*                        All Rights Reserved.                           */
 /*                                                                       */
 /*  Permission is hereby granted, free of charge, to use and distribute  */
 /*  this software and its documentation without restriction, including   */
 /*  without limitation the rights to use, copy, modify, merge, publish,  */
 /*  distribute, sublicense, and/or sell copies of this work, and to      */
 /*  permit persons to whom this work is furnished to do so, subject to   */
 /*  the following conditions:                                            */
 /*   1. The code must retain the above copyright notice, this list of    */
 /*      conditions and the following disclaimer.                         */
 /*   2. Any modifications must be clearly marked as such.                */
 /*   3. Original authors' names are not deleted.                         */
 /*   4. The authors' names are not used to endorse or promote products   */
 /*      derived from this software without specific prior written        */
 /*      permission.                                                      */
 /*                                                                       */
 /*  THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK        */
 /*  DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING      */
 /*  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT   */
 /*  SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE     */
 /*  FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    */
 /*  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN   */
 /*  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,          */
 /*  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF       */
 /*  THIS SOFTWARE.                                                       */
 /*                                                                       */
 /*************************************************************************/
 /*                                                                       */
 /*             Author : Richard Caley <rjc@cstr.ed.ac.uk>                */
 /* -------------------------------------------------------------------   */
 /* Wave file input and output.                                           */
 /*                                                                       */
 /*************************************************************************/
#include <stdlib.h>
#include "EST_Wave.h"
#include "EST_WaveFile.h"
#include "waveP.h"
#include "EST_cutils.h"
#include "EST_Option.h"
#include "EST_io_aux.h"
#include "stdio.h"
#include "math.h"

void extract(EST_Wave &sig, EST_Option &al);

typedef 
EST_read_status (*standard_load_fn_fp)(EST_TokenStream &ts,
				    short **data, int *nsamp, int *nchan, 
				    int *wsize, 
				    int *srate, 
				    EST_sample_type_t *stype, int *bo,
				    int offset, int length);

typedef 
EST_write_status (*standard_save_fn_fp)(FILE *fp,
				    const short *data,
				    int offset, int nsamp,
				    int nchan, int srate,
				    EST_sample_type_t stype, int bo);
				    
  
static 
EST_read_status load_using(standard_load_fn_fp fn,
			   EST_TokenStream &ts,
				  EST_Wave &wv,
				  int rate,
			          EST_sample_type_t stype, int bo, int nchan,
				  int offset, int length)
{
  short *data;
  int nsamp;
  int wsize;
  int srate = rate;

  EST_read_status status =  (*fn)(ts, 
				  &data, &nsamp, &nchan, 
				  &wsize, 
				  &srate, &stype, &bo,
				  offset, length);

  if (status == read_ok)
    {
      wv.values().set_memory(data, 0, nsamp, nchan, TRUE);
      wv.set_sample_rate(srate);
    }

  return status;
}

static
EST_write_status save_using(standard_save_fn_fp fn,
			    FILE *fp, const EST_Wave wv,
			    EST_sample_type_t stype, int bo)
{
EST_write_status status =  (*fn)(fp,
				wv.values().memory(), 
				0, wv.num_samples(), wv.num_channels(),
				wv.sample_rate(),
				stype, bo);

return status; 
}

EST_read_status EST_WaveFile::load_nist(EST_TokenStream &ts,
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  return load_using(load_wave_nist,
		    ts, wv, rate,
		    stype, bo, nchan,
		    offset, length);
}

EST_write_status EST_WaveFile::save_nist(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
  return save_using(save_wave_nist, fp, wv, stype, bo);
}

EST_read_status EST_WaveFile::load_est(EST_TokenStream &ts,
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
    (void) ts;
    return load_using(load_wave_est,
		    ts, wv, rate,
		    stype, bo, nchan,
		    offset, length);

}

EST_write_status EST_WaveFile::save_est(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
  return save_using(save_wave_est,
		    fp, wv,
		    stype, bo);

}

EST_read_status EST_WaveFile::load_aiff(EST_TokenStream &ts, 
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  return load_using(load_wave_aiff,
		    ts, wv,  rate, 
		    stype, bo, nchan,
		    offset, length);
}

EST_write_status EST_WaveFile::save_aiff(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
  return save_using(save_wave_aiff, fp, wv, stype, bo);
}


EST_read_status EST_WaveFile::load_riff(EST_TokenStream &ts,
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  return load_using(load_wave_riff,
		    ts, wv, rate, 
		    stype, bo, nchan,
		    offset, length);
}

EST_write_status EST_WaveFile::save_riff(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
  return save_using(save_wave_riff, fp, wv, stype, bo);
}


EST_read_status EST_WaveFile::load_esps(EST_TokenStream &ts, 
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  return load_using(load_wave_sd,
		    ts, wv, rate, 
		    stype, bo, nchan,
		    offset, length);
}

EST_write_status EST_WaveFile::save_esps(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
  return save_using(save_wave_sd,
		    fp, wv,
		    stype, bo);
}


EST_read_status EST_WaveFile::load_audlab(EST_TokenStream &ts, 
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  return load_using(load_wave_audlab,
		    ts, wv, rate, 
		    stype, bo, nchan,
		    offset, length);
}

EST_write_status EST_WaveFile::save_audlab(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
  return save_using(save_wave_audlab, fp, wv, stype, bo);
}


EST_read_status EST_WaveFile::load_snd(EST_TokenStream &ts, 
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  return load_using(load_wave_snd,
		    ts, wv, rate, 
		    stype, bo, nchan,
		    offset, length);
}

EST_write_status EST_WaveFile::save_snd(FILE *fp,
					const EST_Wave &wv,
					EST_sample_type_t stype, int bo)
{
  return save_using(save_wave_snd, fp, wv, stype, bo);
}


EST_read_status EST_WaveFile::load_raw(EST_TokenStream &ts, 
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  short *data;
  int nsamp;
  int wsize;
  int srate = rate;

  EST_read_status status =  load_wave_raw(ts, 
					  &data, &nsamp, &nchan, 
					  &wsize, 
					  &srate, &stype, &bo,
					  offset, length,
					  rate, stype, bo, nchan);

  if (status == read_ok)
    {
      wv.values().set_memory(data, 0, nsamp, nchan, TRUE);
      wv.set_sample_rate(srate);
    }

  return status;
}

EST_write_status EST_WaveFile::save_raw(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
EST_write_status status =  save_wave_raw(fp,
					 (short *)wv.values().memory(), 
					 0, wv.num_samples(), wv.num_channels(),
					 wv.sample_rate(),
					 stype, bo);
return status; 
}


EST_read_status EST_WaveFile::load_ulaw(EST_TokenStream &ts,
			  EST_Wave &wv,
			  int rate,
			  EST_sample_type_t stype, int bo, int nchan,
			  int offset, int length)
{
  return load_using(load_wave_ulaw,
		    ts, wv, rate, 
		    stype, bo, nchan,
		    offset, length);
}

EST_write_status EST_WaveFile::save_ulaw(FILE *fp,
					 const EST_Wave &wv,
					 EST_sample_type_t stype, int bo)
{
    EST_Wave localwv = wv;
    localwv.resample(8000);
    return save_using(save_wave_ulaw, fp, localwv, stype, bo);
}

static int parse_esps_r_option(EST_String arg, int &offset, int &length)
{
    EST_String s, e;

    if (arg.contains("-"))
    {
	s = arg.before("-");
	e = arg.after("-");
    }
    else if (arg.contains(":"))
    {
	s = arg.before(":");
	e = arg.after(":");
    }
    else
    {
	cerr << "Argument to -r is illformed " << arg << endl;
	return -1;
    }
    
    if (!s.matches(RXint))
    {
	cerr << "First argument to -r must be an integer " << arg << endl;
	return -1;
    }

    offset = atoi(s);
    if (e.contains("+"))
    {	
	e = e.after("+");
	length = atoi(e);
    }
    else
	length = atoi(e) - offset;

    if (length <= 0)
    {
	cerr << "length is negative or zero " << arg << endl;
	return -1;
    }

    return 0;
}

EST_read_status read_wave(EST_Wave &sig, const EST_String &in_file, 
			  EST_Option &al)
{
    char *sr;
    EST_String fname, file_type, sample_type;
    int sample_rate;
    EST_read_status rval;
    int num_channels;
    int offset, length;
    int bo;
    
    if (in_file == "-")
	fname = stdin_to_file();
    else
	fname = in_file;

    if (al.present("-n"))
	num_channels = al.ival("-n", 0);
    else 
	num_channels = 1;

    if (al.present("-ulaw"))
    {
	al.add_item("-itype","ulaw");
	al.add_item("-f","8000");
    }
    if (al.present("-iswap"))
	al.add_item("-ibo","other");

    if (al.present("-istype"))
	sample_type = al.val("-istype");
    else 
	sample_type = sig.sample_type(); // else default type;

    if (al.present("-itype"))
	file_type = al.val("-itype");  // else default type;
    else
	file_type = "undef";


    if (al.present("-f"))
	sample_rate = al.ival("-f", 0);
    else if ((sr = getenv("NA_PLAY_SAMPLE_RATE")) != NULL)
    {
	sample_rate = atoi(sr);
	cerr << "Warning: no sample rate specified, " <<
	    " using NA_PLAY_SAMPLE_RATE environment variable\n";
    }
    else
    {
      sample_rate = EST_Wave::default_sample_rate;
	if (file_type == "raw")
	    cerr << "Warning: no sample rate specified - using default " << 
		sample_rate << endl;
    }
    
    if (file_type == "ulaw")
    {
	sample_rate = 8000;
	sample_type = "mulaw";
    }
	
    if (al.present("-r")) // only load in part of waveform
    {
	if (parse_esps_r_option(al.val("-r"), offset, length) != 0)
	    return read_error;
    }
    else
	offset = length = 0;

    if (al.present("-iswap"))
	bo = str_to_bo("swap");
    else
	bo = str_to_bo("native");
    if (al.present("-ibo"))   // can override -iswap
	bo = str_to_bo(al.val("-ibo"));

    if (file_type == "" ||file_type == "undef")
      rval = sig.load(fname, offset, length, sample_rate);
    else
      rval = sig.load_file(fname,file_type, sample_rate,
			   sample_type, bo, num_channels, offset, length);

    if ((rval == wrong_format) && (al.present("-basic")))
    {
	// For HTML audio/basic, it seems to mean headered or ulaw 8k
	// so try to load it again as ulaw 8k.
	rval = sig.load_file(fname, "raw", 8000,
			     "mulaw", bo, 1, offset, length);
    }
    if (rval != format_ok)
    {
	if (in_file == "-") unlink(fname);
	cerr << "Cannot recognize file format or cannot access file: \"" << in_file << "\"\n";
	return read_error;
    }

    if (al.present("-start") || al.present("-end") 
	|| al.present("-to") || al.present("-from"))
	extract(sig, al);

    if (in_file == "-") unlink(fname);
    return read_ok;
}

EST_write_status write_wave(EST_Wave &sig, const EST_String &out_file, 
			    EST_Option &al)
{
    EST_String file_type, sample_type;
    int bo;

    if (al.present("-otype"))
	file_type = al.val("-otype");
    else
	file_type = sig.file_type();

    if (al.present("-ostype"))
	sample_type = al.val("-ostype");
    else
	sample_type = "undef";

    if (al.present("-oswap"))
	bo = str_to_bo("swap");
    else
	bo = str_to_bo("native");

    if (al.present("-obo"))   // can over ride -oswap
	bo = str_to_bo(al.val("-obo"));

    if (sample_type == "undef" || sample_type == "")
      sample_type = "short";

    if (sig.save_file(out_file, file_type,
		      sample_type, bo) != write_ok)
    {
	cerr << "Cannot write file: \"" << out_file << "\"\n";
	return write_error;
    }

    return write_ok;
}

EST_String EST_WaveFile::options_short(void)
{
    EST_String s("");
    
    for(int n=0; n< EST_WaveFile::map.n() ; n++)
    {
	const char *nm = EST_WaveFile::map.name(EST_WaveFile::map.token(n));
	
	if (s != "")
	    s += ", ";
	
	s += nm;
	
    }
    return s;
}

EST_String EST_WaveFile::options_supported(void)
{
    EST_String s("Available wave file formats:\n");
    
    for(int n=0; n< EST_WaveFile::map.n() ; n++)
    {
	const char *nm = EST_WaveFile::map.name(EST_WaveFile::map.token(n));
	const char *d = EST_WaveFile::map.info(EST_WaveFile::map.token(n)).description;
	
	s += EST_String::cat("        ", nm, EST_String(" ")*(12-strlen(nm)), d, "\n");
    }
    return s;
}

typedef struct TInfo {
  bool recognise;
  const char *description;
} TInfo;

// note the order here defines the order in which loads are tried.


static 
EST_TValuedEnumDefinition<EST_WaveFileType, const char *, EST_WaveFile::Info> wavefile_names[] =
{
  { wff_none,	{ NULL }, 
    { FALSE, NULL, NULL, "unknown track file type"} },
  { wff_nist,	{ "nist", "timit" }, 
    { TRUE, EST_WaveFile::load_nist,  EST_WaveFile::save_nist, "nist/timit" } },
  { wff_est,	{ "est"}, 
    { TRUE, EST_WaveFile::load_est,  EST_WaveFile::save_est, "est" } },
  { wff_esps,	{ "esps", "sd"}, 
    { TRUE,  EST_WaveFile::load_esps,  EST_WaveFile::save_esps, "esps SD waveform" } },
  { wff_audlab, { "audlab", "vox"}, 
    { TRUE,  EST_WaveFile::load_audlab,  EST_WaveFile::save_audlab, "audlab waveform" } },
  { wff_snd,	{ "snd", "au"}, 
    { TRUE,  EST_WaveFile::load_snd,  EST_WaveFile::save_snd, "Sun snd file" } },
  { wff_aiff,	{ "aiff" }, 
    { TRUE,  EST_WaveFile::load_aiff,  EST_WaveFile::save_aiff, "Apple aiff file" } },
  { wff_riff,	{ "riff", "wav" }, 
    { TRUE,  EST_WaveFile::load_riff,  EST_WaveFile::save_riff, "Microsoft wav/riff file" } },
  { wff_raw,	{ "raw" }, 
    { FALSE,  EST_WaveFile::load_raw,  EST_WaveFile::save_raw, "Headerless File" } },
  { wff_ulaw,	{ "ulaw", "basic" }, 
    { FALSE,  EST_WaveFile::load_ulaw,  EST_WaveFile::save_ulaw, "Headerless 8K ulaw  File" } },
  { wff_none,	{NULL} }
};

EST_TNamedEnumI<EST_WaveFileType, EST_WaveFile::Info> EST_WaveFile::map(wavefile_names);

#if defined(INSTANTIATE_TEMPLATES)

#include "../base_class/EST_TNamedEnum.cc"
template class EST_TNamedEnumI<EST_WaveFileType, EST_WaveFile::Info>;
template class EST_TValuedEnumI<EST_WaveFileType, const char *, 
EST_WaveFile::Info>;

#endif
