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

Routine:
  AFILE *AFgetAIpar (FILE *fp, const char Fname[], long int *Nsamp,
                     long int *Nchan, float *Sfreq, FILE *fpout)

Purpose:
  Get file format information from an AIFF or AIFF-C audio file

Description:
  This routine reads the header for an AIFF or AIFF-C audio file.  The header
  information is used to set the file data format information in the audio
  file pointer structure and to set the returned argument values.  A banner
  identifying the audio file and its parameters is printed.

  AIFF or AIFF-C audio file:
   Offset Length Type    Contents
      0     4    char   File identifier ("FORM")
      4     4    int    Form chunk length
      8     4    char   File identifier ("AIFF" or "AIFC")
    ...   ...    ...    ...
      f     4    char   COMM chunk identifier ("COMM")
    f+4     4    int    COMM chunk length
    f+8     2    int    Number of interleaved channels
    f+10    4    int    Number of sample frames
    f+14    2    int    Bits per sample
    f+16   10    float  Sample frames per second
    f+26    4    int    Compression type
    f+30  ...    char   Compression name
    ...    ...   ...    ...
      d     4    char   SSND chunk identifier ("SSND")
    d+4     4    int    SSND chunk length
    d+8     4    int    Data offset
    d+12    4    int    Block size
    d+16  ...    ...    Audio data
  8-bit mu-law, 8-bit A-law, 8-bit integer, and 16-bit integer data formats are
  supported.

Parameters:
  <-  AFILE *AFgetAIpar
      Audio file pointer for the audio file
   -> FILE *fp
      File pointer for the file
   -> const char Fname[]
      File name
  <-  long int *Nsamp
      Total number of samples in the file (all channels)
  <-  long int *Nchan
      Number of channels
  <-  float *Sfreq
      Sampling frequency from the file header
   -> FILE *fpout
      File pointer for printing the audio file identification information.  If
      fpout is NULL, no information is printed.

Author / revision:
  P. Kabal  Copyright (C) 1996
  $Revision: 1.11 $  $Date: 1996/08/15 23:02:15 $

-------------------------------------------------------------------------*/

static char rcsid [] = "$Id: AFgetAIpar.c 1.11 1996/08/15 AFsp-V2R1 $";

#include <stdio.h>
#include <string.h>
#include <libtsp.h>
#include <libtsp/nucleus.h>
#include <libtsp/AFpar.h>
#include <libtsp/AIpar.h>

#define RHEAD_S(fp,offs,string) \
			AFreadHead (fp, (long int) (offs), (void *) (string), \
				    1, sizeof (string), DS_NATIVE)
#define RHEAD_V(fp,offs,value,swap) \
			AFreadHead (fp, (long int) (offs), (void *) &(value), \
				    sizeof (value), 1, swap)
#define SAME_CSTR(str,ref) 	(memcmp (str, ref, sizeof (str)) == 0)

#define ICEILV(n, m)	(((n) + ((m) - 1)) / (m))	/* int n,m >= 0 */
#define RUP(value)	(2 * ICEILV (value, 2))		/* Round up */

static int
AF_AIreadCOMM p_((FILE *fp, long int offs, int Ftype,
		  struct AI_CkCOMM *CkCOMM));

AFILE *
AFgetAIpar (fp, Fname, Nsamp, Nchan, Sfreq, fpout)

     FILE *fp;
     const char Fname[];
     long int *Nsamp;
     long int *Nchan;
     float *Sfreq;
     FILE *fpout;

{
  AFILE *AFp;
  long int offs;
  int Lw, Ftype, Format;
  long int Nbytes, Nsampx, Nchanx, Lhead, Sdata, Ldata;
  float ScaleF, Sfreqx;
  int len, nCOMM, nSSND;
  char *FtypeN;

  struct AI_CkPreamb CkHead;
  struct AI_CkCOMM CkCOMM;
  struct AI_CkFVER CkFVER;
  struct AI_CkSSND CkSSND;

/* Get the size of the file */
  Nbytes = FLfileSize (fp);
  if (Nbytes < AI_LHMIN)
    UThalt ("AFgetWVpar: WAVE file header too short");

/* Each chunk has an identifier and a length.  The length gives the number of
   bytes in the chunk (not including the ckID or ckDataSize fields).  If the
   ckDataSize field is odd, a zero byte fills the space before the start of the
   next chunk.  Some AIFF-C files do not have this zero byte at the end of the
   file, i.e. the total file length is odd, yet the FORM chunk size indicates
   that the padding byte is there.
*/

/* Check the FORM chunk */
  offs = 0;
  offs += RHEAD_S (fp, offs, CkHead.ckID);
  if (! SAME_CSTR (CkHead.ckID, FM_IFF))
    UThalt ("AFgetWVpar: Invalid AIFF/AIFF-C file identifier");
  offs += RHEAD_V (fp, offs, CkHead.ckDataSize, DS_EB);
  if (CkHead.ckDataSize < AI_LHMIN - offs ||
      CkHead.ckDataSize > RUP (Nbytes - offs))
    UTwarn ("AFgetAIpar - Invalid AIFF/AIFF-C file FORM chunk size");

  offs += RHEAD_S (fp, offs, CkHead.ckID);
  if (SAME_CSTR (CkHead.ckID, FM_AIFF_C)) {
    Ftype = FT_AIFF_C;
    FtypeN = "AIFF-C";
  }
  else if (SAME_CSTR (CkHead.ckID, FM_AIFF)) {
    Ftype = FT_AIFF;
    FtypeN = "AIFF";
  }
  else
    UThalt ("AFgetAIpar: Invalid AIFF/AIFF-C file identifier");
  
/* Pick up the required chunks */
  nCOMM = 0;
  nSSND = 0;
  while (offs < Nbytes && (nCOMM == 0 || nSSND == 0)) {

    /* Read the chunk preamble */
    offs += RHEAD_S (fp, offs, CkHead.ckID);
    offs += RHEAD_V (fp, offs, CkHead.ckDataSize, DS_EB);
    if (CkHead.ckDataSize > Nbytes - offs)
      UTwarn ("AFgetAIpar - Invalid %s header chunk size", FtypeN);

    /* COMM chunk */
    /* Some AIFF-C COMM chunks are declared to be of length 18 (the size for
       AIFF files) when they are in fact larger.  Here we find the actual
       length of the COMM chunk and use the "actual" length.
    */
    if (SAME_CSTR (CkHead.ckID, CKID_COMM)) {
      nCOMM++;
      len = AF_AIreadCOMM (fp, offs, Ftype, &CkCOMM);
      if (len != CkHead.ckDataSize) {
	UTwarn ("AFgetAIpar - Fixup for invalid %s COMM chunk size", FtypeN);
	CkHead.ckDataSize = len;
      }
    }

    /* FVER chunk */
    else if (Ftype == FT_AIFF_C && SAME_CSTR (CkHead.ckID, CKID_FVER)) {
      RHEAD_V (fp, offs, CkFVER.timestamp, DS_EB);
      if (CkFVER.timestamp != AIFCVersion1)
	UTwarn ("AFgetAIpar - Unrecognized AIFF-C version number");
    }
 
    /* SSND chunk */
    else if (SAME_CSTR (CkHead.ckID, CKID_SSND)) {
      nSSND++;
      RHEAD_V (fp, offs, CkSSND.offset, DS_EB);
      Lhead = offs + 8 + CkSSND.offset;
      Sdata = CkHead.ckDataSize - 8 - CkSSND.offset;
    }

    /* Update the offset */
    offs += RUP (CkHead.ckDataSize);
  }

  if (nCOMM != 1 || nSSND != 1)
    UThalt ("AFgetAIpar: Missing %s header information", FtypeN);

  /* Uncompressed */
  if (SAME_CSTR (CkCOMM.compressionType, CT_NONE)) {
    if (CkCOMM.sampleSize == 16) {
      Lw = FDL_INT16;
      Format = FD_INT16;
      ScaleF = 1.;
    }
    else if (CkCOMM.sampleSize == 8) {
      Lw = FDL_INT8;
      Format = FD_INT8;
      ScaleF = 128.;
    }
    else
      UThalt ("AFgetAIpar: Unsupported %s sample size", FtypeN);
  }

  /* AIFF-C, compressed */
  /* A-law and mu-law are compressed formats; for these formats
   CkCOMM.sampleSize = 16 (not checked) */
  else if (SAME_CSTR (CkCOMM.compressionType, CT_ULAW)) {
    Lw = FDL_MULAW8;
    Format = FD_MULAW8;
    ScaleF = 1.;
  }
  else if (SAME_CSTR (CkCOMM.compressionType, CT_ALAW)) {
    Lw = FDL_ALAW8;
    Format = FD_ALAW8;
    ScaleF = 1.;
  }
  else
    UThalt ("AFgetAIpar: Unsupported AIFF-C compression type");

/* Set up the file data parameters  */
  Nchanx = CkCOMM.numChannels;
  Nsampx = CkCOMM.numSampleFrames * Nchanx;
  Sfreqx = UTdIEEE80 (CkCOMM.sampleRate);
  Ldata = Lw * Nsampx;

/* Check the data length */
  if (Sdata < Ldata)
    UThalt ("AFgetAIpar: Invalid data length");

/* Set the parameters for file access */
  AFp = AFsetAFp (fp, FO_RO, Ftype, Format, DS_EB, ScaleF, Nchanx,
		  Lhead, Ldata, Nsampx);

/* Check and print the header information */
  AFprintAFh (AFp, Fname, "", Sfreqx, fpout);

/* Set the return parameters */
  *Nsamp = Nsampx;
  *Nchan = Nchanx;
  *Sfreq = Sfreqx;

  return AFp;
}

static int
AF_AIreadCOMM (fp, offs, Ftype, CkCOMM)

     FILE *fp;
     long int offs;
     int Ftype;
     struct AI_CkCOMM *CkCOMM;

{
  long int pos;
  char slen[1];

  pos = offs;

  pos += RHEAD_V (fp, pos, CkCOMM->numChannels, DS_EB);
  pos += RHEAD_V (fp, pos, CkCOMM->numSampleFrames, DS_EB);
  pos += RHEAD_V (fp, pos, CkCOMM->sampleSize, DS_EB);
  pos += RHEAD_S (fp, pos, CkCOMM->sampleRate);

  /* Set the compressionType field even for both AIFF and AIFF-C files */
  if (Ftype == FT_AIFF_C) {
    pos += RHEAD_S (fp, pos, CkCOMM->compressionType);
    pos += RHEAD_S (fp, pos, slen);	/* 1 byte string length */
    pos += (int) slen[0];
  }
  else
    memcpy (CkCOMM->compressionType, CT_NONE, sizeof CT_NONE);

  return (int) (pos - offs);
}