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

Routine:
  AFILE *AFrdWVhead (FILE *fp)

Purpose:
  Get file format information from a RIFF WAVE file

Description:
  This routine reads the header for a RIFF WAVE file.  The header information
  is used to set the file data format information in the audio file pointer
  structure.

  RIFF WAVE file:
   Offset Length Type    Contents
      0     4    char   File identifier ("RIFF")
      4     4    int    RIFF chunk length
      8     4    char   File identifier ("WAVE")
    ...   ...    ...    ...
      f     4    char   Format chunk identifier ("fmt ")
    f+4     4    int    Format chunk length
    f+8     2    int    Audio data type
    f+10    2    int    Number of interleaved channels
    f+12    4    int    Sample rate
    f+16    4    int    Average bytes/sec
    f+20    2    int    Block align
    f+22    2    int    Data word length (bits)
    ...    ...   ...    ...
      d     4    char   Data chunk identifier ("data")
    d+4     4    int    Data chunk length
    d+8    ...   ...    Audio data
  8-bit mu-law, 8-bit A-law, offset-binary 8-bit integer, and 16-bit integer
  data formats are supported.

Parameters:
  <-  AFILE *AFrdWVhead
      Audio file pointer for the audio file
   -> FILE *fp
      File pointer for the file

Author / revision:
  P. Kabal  Copyright (C) 1998
  $Revision: 1.48 $  $Date: 1998/06/26 20:40:42 $

-------------------------------------------------------------------------*/

static char rcsid [] = "$Id: AFrdWVhead.c 1.48 1998/06/26 libtsp-v3r0 $";

#include <setjmp.h>
#include <string.h>

#include <libtsp.h>
#include <libtsp/AFheader.h>
#include <libtsp/AFmsg.h>
#include <libtsp/AFpar.h>
#include <libtsp/WVpar.h>
#include <libtsp/nucleus.h>

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

#define SAME_CSTR(str,ref) 	(memcmp (str, ref, sizeof (str)) == 0)

#define ALIGN		2	/* Chunks padded out to a multiple of ALIGN */

/* setjmp / longjmp environment */
extern jmp_buf AFR_JMPENV;

static void
AF_rdRIFF p_((FILE *fp));
static void
AF_rdFMT p_((FILE *fp, long int cksize, struct WV_PCMformat *CkPCM));
static void
AF_decFMT p_((struct WV_PCMformat *CkPCM, int *Format, double *ScaleF));
static void
AF_rdLIST p_((FILE *fp, int Size, struct AF_info *Hinfo));


AFILE *
AFrdWVhead (fp)

     FILE *fp;

{
  AFILE *AFp;
  int Format;
  double ScaleF;
  char Info[AF_MAXINFO];
  struct AF_info Hinfo;
  struct WV_CkPreamb CkHead;
  struct WV_PCMformat CkPCM;

/* Set the long jump environment; on error return a NULL */
  if (setjmp (AFR_JMPENV))
    return NULL;	/* Return from a header read error */

/* Check the file magic */
  AF_rdRIFF (fp);

/* Look for specific chunks, ignore others */
  Hinfo.Info = Info;
  Hinfo.N = 0;
  Format = FD_UNDEF;
  while (1) {

    /* Chunk preamble */
    RHEAD_S (fp, CkHead.ckid);
    RHEAD_V (fp, CkHead.cksize, DS_EL);

    if (SAME_CSTR (CkHead.ckid, "fmt ")) {
      AF_rdFMT (fp, (long int) CkHead.cksize, &CkPCM);
      AF_decFMT (&CkPCM, &Format, &ScaleF);
    }
    else if (SAME_CSTR (CkHead.ckid, "data"))
      break;
    else if (SAME_CSTR (CkHead.ckid, "LIST"))
      AF_rdLIST (fp, (int) CkHead.cksize, &Hinfo);
    else if (SAME_CSTR (CkHead.ckid, "JUNK"))
      AFrdHinfo (fp, (int) CkHead.cksize, &Hinfo, ALIGN);
    else
      RSKIP (fp, RNDUPV (CkHead.cksize, ALIGN));	/* Skip to next chunk */
  }
  if (Format == FD_UNDEF) {
    UTwarn ("AFrdWVhead - %s", AFM_WV_BadHeader);
    return NULL;
  }

/* Set the parameters for file access */
  AFp = AFsetRead (fp, FT_WAVE, Format, DS_EL, (double) CkPCM.SamplesPerSec,
		   ScaleF, (long int) CkPCM.Channels, (long int) CkHead.cksize,
		   AF_NSAMP_UNDEF, &Hinfo, AF_FIX_LDATA_HIGH);

  return AFp;
}

/* Check the RIFF file preamble:  On error return via longjmp */


static void
AF_rdRIFF (fp)

     FILE *fp;

{
  int offs;
  long int Lfile;
  struct WV_CkPreamb CkHead;

  offs = RHEAD_S (fp, CkHead.ckid);
  if (! SAME_CSTR (CkHead.ckid, FM_RIFF)) {
    UTwarn ("AFrdWVhead - %s", AFM_WV_BadId);
    longjmp (AFR_JMPENV, 1);
  }

  offs += RHEAD_V (fp, CkHead.cksize, DS_EL);
  Lfile = CkHead.cksize + offs;
  if (Lfile < WV_LHMIN)
    UTwarn ("AFrdWVhead - %s", AFM_WV_BadRIFF);

  RHEAD_S (fp, CkHead.ckid);
  if (! SAME_CSTR (CkHead.ckid, FM_WAVE)) {
    UTwarn ("AFrdWVhead - %s", AFM_WV_BadId);
    longjmp (AFR_JMPENV, 1);
  }

  return;
}

/* Read the format chunk */


static void
AF_rdFMT (fp, cksize, CkPCM)

     FILE *fp;
     long int cksize;
     struct WV_PCMformat *CkPCM;

{
  int offs;

  if (cksize < WV_FMT_CKSIZE) {
    UTwarn ("AFrdWVhead - %s", AFM_WV_BadPCM);
    longjmp (AFR_JMPENV, 1);
  }

  offs  = RHEAD_V (fp, CkPCM->FormatTag, DS_EL);
  offs += RHEAD_V (fp, CkPCM->Channels, DS_EL);
  offs += RHEAD_V (fp, CkPCM->SamplesPerSec, DS_EL);
  /* skip two values */
  offs += RSKIP (fp, sizeof (CkPCM->AvgBytesPerSec));
  offs += RSKIP (fp, sizeof (CkPCM->BlockAlign));
  offs += RHEAD_V (fp, CkPCM->BitsPerSample, DS_EL);

  /* Skip over any extra data at the end of the fmt chunk */
  if (offs > cksize)
    UTwarn ("AFrdWVhead - %s", AFM_WV_BadPCM);
  else
    RSKIP (fp, RNDUPV (cksize, ALIGN) - offs);

  return;
}

/* Decode the data format: On error return via longjmp */


static void
AF_decFMT (CkPCM, Format, ScaleF)

     struct WV_PCMformat *CkPCM;
     int *Format;
     double *ScaleF;

{
  if (CkPCM->FormatTag == WAVE_FORMAT_PCM) {
    if (CkPCM->BitsPerSample == 16) {
      *Format = FD_INT16;
      *ScaleF = WV_SF_PCM16;
    }
    else if (CkPCM->BitsPerSample == 8) {
      *Format = FD_UINT8;
      *ScaleF = WV_SF_PCM8;
    }
    else {
      UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_UnsDSize,
	      (int) CkPCM->BitsPerSample);
      longjmp (AFR_JMPENV, 1);
    }
  }
  else if (CkPCM->FormatTag == WAVE_FORMAT_MULAW) {
    if (CkPCM->BitsPerSample != 8)
      UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_BadMulaw,
	      (int) CkPCM->BitsPerSample);
    *Format = FD_MULAW8;
    *ScaleF = WV_SF_MULAW;
  }
  else if (CkPCM->FormatTag == WAVE_FORMAT_ALAW) {
    if (CkPCM->BitsPerSample != 8)
      UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_BadAlaw,
	      (int) CkPCM->BitsPerSample);
    *Format = FD_ALAW8;
    *ScaleF = WV_SF_ALAW;
  }
  else {
    UTwarn ("AFrdWVhead - %s: \"%d\"", AFM_WV_UnsData, (int) CkPCM->FormatTag);
    longjmp (AFR_JMPENV, 1);
  }

  return;
}

/* Read the LIST-INFO records from the header */


static void
AF_rdLIST (fp, Size, Hinfo)

     FILE *fp;
     int Size;
     struct AF_info *Hinfo;

{
  int offs;
  char ID[4];
  struct WV_CkPreamb CkHead;

  offs = RHEAD_S (fp, ID);
  if (SAME_CSTR (ID, "INFO")) {

    while (offs < Size) {
      offs += RHEAD_S (fp, CkHead.ckid);
      offs += RHEAD_V (fp, CkHead.cksize, DS_EL);
      if (SAME_CSTR (CkHead.ckid, "IARL"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "archival_location: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "IART"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "artist: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ICMS"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "commissioned: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ICMT"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "comments: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ICOP"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "copyright: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ICRD"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "creation_date: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "IENG"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "engineer: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "IGNR"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "genre: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "IKEY"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "keywords: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "IMED"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "medium: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "INAM"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "name: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "IPRD"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "product: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ISBJ"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "subject: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ISFT"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "software: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ISRC"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "source: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ISRF"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "source_form: ",
			   Hinfo, ALIGN);
      else if (SAME_CSTR (CkHead.ckid, "ITCH"))
	offs += AFrdHtext (fp, (int) CkHead.cksize,  "technician: ",
			   Hinfo, ALIGN);
    }
  }
  RSKIP (fp, RNDUPV (Size, ALIGN) - offs);

  return;
}
