/*-------------- Telecommunications & Signal Processing Lab ---------------
                             McGill University

Routine:
  AFILE *AFsetRead (FILE *fp, int Ftype, int Format, int Swapb, double Sfreq,
                    double ScaleF, long int Nchan, long int Ldata,
		    long int Nsamp, const struct AF_info *Hinfo, int Fix)

Purpose:
  Set up the parameters in an audio file structure for reading

Description:
  This routine checks the input parameters and puts them into a new audio file
  structure.  The data length (Ldata) and number of samples (Nsamp) parameters
  are checked for consistency and modified if necessary.

  For certain file types, the Nsamp value needs to be "fixed".  Optional fixes
  can be specified for the case that Nsamp is larger or smaller than the data
  space available in the file.  The latter case is not normally an error.  If
  a fixup is done, a warning message is printed.  A fixup can also be specified
  for values of Nsamp which are too large.

  If the data does not have fixed length elements (for instance, text data),
  Nsamp must be specified.  In that case, Ldata has no specific meaning.  If
  defined, Ldata is checked against the file size.

  The file must be positioned at the start of the audio data.

Parameters:
  <-  AFILE *AFp
      Audio file pointer for the audio file.  This routine allocates the space
      for this structure.  If an error is detected, a NULL pointer is returned.
   -> int Ftype
      File type; FT_NH, FT_AU, FT_WAVE, etc.
   -> int Format
      Data format; FD_MULAW8, FD_ALAW8, FD_UINT8, FD_INT8, FD_INT16,
      FD_FLOAT32, FD_FLOAT64, or FD_TEXT
   -> int Swabp
      Byte swap flag:
      DS_EB     - File data is in big-endian byte order
      DS_EL     - FIle data is in little-endian byte order
      DS_NATIVE - File data is in native byte order
      DS_SWAP   - File data is byte-swapped
   -> double Sfreq
      Sampling rate
   -> double ScaleF
      Scale factor applied to the data
   -> long int Nchan
      Number of interleaved channels
   -> long int Ldata
      The data length is the size of the area (in bytes) in the file reserved
      for storing the data.  The data need not fully occupy this area.  If
      Ldata is undefined (equal to AF_LDATA_UNDEF), it is set to the size of
      the file, less the size of the header.  Ldata is checked against the
      actual size of the file.  It is an error for this value to exceed the
      actual space in the file.
   -> long int Nsamp
      Number of samples.  Nsamp is used to calculate the actual amount of
      space occupied by the data (Nsamp is multiplied by the data element size.
      If this value exceeds the space available, this is an error.  If Nsamp
      is undefined (equal to AF_NSAMP_UNDEF), it is determined from Ldata,
      setting it equal to the integer number of samples that fit into Ldata.
      If Nsamp is so determined, a warning message is issued if Ldata is not a
      multiple of the data element size.  A warning message is also printed if
      Nsamp is not a multiple of the number of channels.
   -> const struct AF_info *Hinfo
      Information string from the file header.  This string contains
      information records.  This routine checks for a "sample_rate:" record
      which can specify a non-integer sampling frequency.  Hinfo can be NULL.
   -> int Fix
      Fix flag, sum of the following subcodes
         AF_FIX_NSAMP_HIGH - Reduce Nsamp if it larger than the data
                             space available
         AF_FIX_NSAMP_LOW  - Increase Nsamp if it is smaller than the
                             data space available
         AF_FIX_LDATA_HIGH - Reduce Ldata if it is larger than the data
	                     space available

Author / revision:
  P. Kabal  Copyright (C) 1998
  $Revision: 1.17 $  $Date: 1998/06/18 23:02:15 $

-------------------------------------------------------------------------*/

static char rcsid [] = "$Id: AFsetRead.c 1.17 1998/06/18 libtsp-v3r0 $";

#include <assert.h>
#include <math.h>
#include <string.h>

#include <libtsp.h>
#include <libtsp/nucleus.h>
#include <libtsp/AFdataio.h>
#include <libtsp/AFheader.h>
#include <libtsp/AFmsg.h>
#define AF_DATA_LENGTHS
#include <libtsp/AFpar.h>

static long int
AF_Dspace p_((FILE *fp, long int *Dstart));
static long int
AF_Nrec p_((FILE *fp));
static int
AF_checkLdata p_((FILE *fp, int Lw, long int *Dstart, long int Ldata,
		  long int *Nsamp, int Fix));
static int
AF_checkNsamp p_((int Lw, long int Ldata, long int *Nsamp, int Fix));
static double
AF_srateInfo p_((const struct AF_info *Hinfo, double Sfreq));
static void
AF_setInfo p_((const struct AF_info *Hinfo, struct AF_info *AFinfo));


AFILE *
AFsetRead (fp, Ftype, Format, Swapb, Sfreq, ScaleF, Nchan, Ldata, Nsamp,
	   Hinfo, Fix)

     FILE *fp;
     int Ftype;
     int Format;
     int Swapb;
     double Sfreq;
     double ScaleF;
     long int Nchan;
     long int Ldata;
     long int Nsamp;
     const struct AF_info *Hinfo;
     int Fix;

{
  AFILE *AFp;
  long int Dstart;
  int Lw;

  assert (Ftype > 0 && Ftype < NFT);
  assert (Format > 0 && Format < NFD);

/* Check Nchan */
  if (Nchan <= 0L) {
    UTwarn ("AFsetRead - %s: \"%ld\"", AFM_BadNChan, Nchan);
    return NULL;
  }

/* Check data consistency */
  Lw = AF_DL[Format];
  if (AF_checkLdata (fp, Lw, &Dstart, Ldata, &Nsamp, Fix))
    return NULL;

/* Sampling frequency */
  Sfreq = AF_srateInfo (Hinfo, Sfreq);
  if (Sfreq <= 0.0) {
    UTwarn (AFMF_BadSFreq, "AFsetRead -", Sfreq, AF_SFREQ_DEFAULT);
    Sfreq = AF_SFREQ_DEFAULT;
  }

/* Samples / channels consistency check */
  if (Nsamp != AF_NSAMP_UNDEF && Nsamp % Nchan != 0) {
    UTwarn ("AFsetRead - %s", AFM_NSampNChan);
    UTwarn (AFMF_NSampNChan, "           ", Nsamp, Nchan);
  }

  if (Lw <= 1)
    Swapb = DS_NATIVE;
  else
    Swapb = UTswapCode (Swapb);

/* Set the parameters for file access */
  AFp = (AFILE *) UTmalloc (sizeof (AFILE));
  AFp->fp = fp;
  AFp->Op = FO_RO;
  AFp->Error = 0;
  AFp->Ftype = Ftype;
  AFp->Format = Format;
  AFp->Swapb = Swapb;
  AFp->Sfreq = Sfreq;
  AFp->ScaleF = ScaleF;
  AFp->Nchan = Nchan;
  AFp->Start = Dstart;
  AFp->Isamp = 0L;
  AFp->Nsamp = Nsamp;
  AFp->Novld = 0L;
  AF_setInfo (Hinfo, &AFp->Hinfo);

  return AFp;
}

/* Check file data size and number of samples */


static int
AF_checkLdata (fp, Lw, Dstart, Ldata, Nsamp, Fix)

     FILE *fp;
     int Lw;
     long int *Dstart;
     long int Ldata;
     long int *Nsamp;
     int Fix;

{
  long int nsamp, Dspace;

  nsamp = *Nsamp;

/* Preliminary checks */
  if (Ldata != AF_LDATA_UNDEF && Ldata < 0) {
    UTwarn ("AFsetRead - %s: \"%ld\"", AFM_BadDLen, Ldata);
    return 1;
  }
  if (nsamp != AF_NSAMP_UNDEF && nsamp < 0) {
    UTwarn ("AFsetRead - %s: \"%ld\"", AFM_BadNSamp, nsamp);
    return 1;
  }

/* Check the data space in the file */
  *Dstart = 0L;		/* Initialize for non-random access files */
  if (FLseekable (fp)) {

    Dspace = AF_Dspace (fp, Dstart);
    if (Dspace < 0L)
      return 1;

    /* Set Ldata if necessary */
    if (Ldata == AF_LDATA_UNDEF)
      Ldata = Dspace;
    else if (Ldata > Dspace) {
      if (Fix & AF_FIX_LDATA_HIGH) {
	UTwarn ("AFsetRead - %s", AFM_FixDLen);
	Ldata = Dspace;
      }
      else {
	UTwarn ("AFsetRead - %s", AFM_ErrDLen);
	return 1;
      }
    }

    /* Set Nsamp if necessary */
    if (nsamp == AF_NSAMP_UNDEF) {
      if (Lw == 0) {
	nsamp = AF_Nrec (fp);
	if (nsamp == AF_NSAMP_UNDEF)
	  return 1;	/* Error in AF_Nrec */
      }
      else {
	nsamp = Ldata / Lw;
	if (Lw * nsamp != Ldata)
	  UTwarn ("AFsetRead - %s", AFM_NonIntNSamp);
      }
    }
  }

  /* Not seekable */
  else if (nsamp == AF_NSAMP_UNDEF) {

    /* Trust Ldata if set */
    if (Ldata != AF_LDATA_UNDEF && Lw != 0) {
      nsamp = Ldata / Lw;
      if (Lw * nsamp != Ldata)
	UTwarn ("AFsetRead - %s", AFM_NonIntNSamp);
    }
    
    /* Check if an "unknown" Nsamp is allowed */
    else if (! AFopt_NsampND) {
      UTwarn ("AFsetRead - %s", AFM_NoNSamp);
      return 1;
    }
  }

/* Check Nsamp against Ldata */
  if (AF_checkNsamp (Lw, Ldata, &nsamp, Fix))
    return 1;

  *Nsamp = nsamp;
  return 0;
}

/* Check the data space in the file */


static long int
AF_Dspace (fp, Dstart)

     FILE *fp;
     long int *Dstart;

{
  int ErrCode;
  long int dstart, endpos, Dspace;

/*
   The ANSI C standard does not guarantee that this method of determining
   the file size will work on all systems.  It works on Unix systems and
   probably a lot of other systems.  The alternative is to use stat, but that
   is probably even less portable.
*/
  ErrCode = 0;
  dstart = AFtell (fp, &ErrCode);
  AFseek (fp, AF_SEEK_END, &ErrCode);

  /* Find the end of the file and then restore the original position */
  endpos = AFtell (fp, &ErrCode);
  if (AFseek (fp, dstart, &ErrCode))
    return -1L;

  Dspace = endpos - dstart;
  if (Dspace < 0L)
    UTwarn ("AFsetRead - %s", AFM_BadHeadLen);

  *Dstart = dstart;
  return Dspace;
}

/* Check Nsamp against Ldata and fix if so desired */


static int
AF_checkNsamp (Lw, Ldata, Nsamp, Fix)

     int Lw;
     long int *Nsamp;
     long int Ldata;
     int Fix;

{
  long int nsamp;

  nsamp = *Nsamp;

  if (Lw != 0) {
    if (nsamp * Lw > Ldata) {
      if (Fix & AF_FIX_NSAMP_HIGH) {
	UTwarn
	  ("AFsetRead - %s", AFM_FixDLen);
	nsamp = Ldata / Lw;
      }
      else {
	UTwarn
	  ("AFsetRead - %s", AFM_ErrDLen);
	return 1;
      }
    }
    else if (nsamp * Lw < Ldata && (Fix & AF_FIX_NSAMP_LOW)) {
      UTwarn ("AFsetRead - %s", AFM_LowDLen);
      nsamp = Ldata / Lw;
    }

  }

  *Nsamp = nsamp;
  return 0;
}

/*
  This routine finds the number of text records in a file, starting at the
  current position in the file.  The file position is restored to the current
  position.
*/


static long int
AF_Nrec (fp)

     FILE *fp;

{
  long int pos, Nrec;
  char *p;
  int ErrCode;

  ErrCode = 0;

/* Save the file position */
  pos = AFtell (fp, &ErrCode);

/* Read until End-of-file */
  for (Nrec = 0L; ; ++Nrec) {

    /* Read a line or partial line */
    p = AFgetLine (fp, &ErrCode);
    if (p == NULL || ErrCode)
      break;
  }

/* Reposition the file */
  if (AFseek (fp, pos, &ErrCode))
    return AF_NSAMP_UNDEF;

  return Nrec;
}

/* Scan the header information string for a sampling frequency */


#define ABSV(x)		(((x) < 0) ? -(x) : (x))

static double
AF_srateInfo (Hinfo, Sfreq)

     const struct AF_info *Hinfo;
     double Sfreq;

{
  char *Csf;
  double Fv;

  /* Check "sample_rate:", if the sampling frequency is integer-valued */
  if (Sfreq == floor(Sfreq)) {

    /* Scan the information string for a "sample_rate:" record */
    Csf = AFgetHrec ("sample_rate:", Hinfo);
    if (Csf != NULL) {
      if (STdec1double (Csf, &Fv))
	UTwarn ("AFsetRead - %s", AFM_AU_BadSRate);
      else if (ABSV(Fv - Sfreq) <= 0.5)
	Sfreq = Fv;
      else
	UTwarn ("AFsetRead - %s, %g : %g", AFM_AU_MisSRate, Sfreq, Fv);
    }
  }

  return Sfreq;
}

/* Fill in the AFsp Information structure */


static void
AF_setInfo (Hinfo, AFinfo)

     const struct AF_info *Hinfo;
     struct AF_info *AFinfo;

{
  int N;

  if (Hinfo == NULL || Hinfo->N == 0) {
    AFinfo->Info = NULL;
    AFinfo->N = 0;
  }
  else {
    N = Hinfo->N;
    if (Hinfo->Info[N-1] != '\0')
      ++N;
    AFinfo->Info = (char *) UTmalloc (N);
    memcpy (AFinfo->Info, Hinfo->Info, Hinfo->N);
    AFinfo->Info[N-1] = '\0';	/* Make sure there is a terminating null */
    AFinfo->N = N;
  }

  return;
}
