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

Routine:
  AFILE *AFwrWVhead (FILE *fp, int Format, long int Nframe, long int Nchan,
                     double Sfreq)

Purpose:
  Write header information to a RIFF WAVE file

Description:
  This routine opens and writes header information to a RIFF WAVE format audio
  file.

  RIFF WAVE file:
   Offset Length Type    Contents
      0     4    char   File identifier ("RIFF")
      4     4    int    RIFF chunk length (initially set to 36)
      8     4    char   File identifier ("WAVE")
     12     4    char   Format chunk identifier ("fmt ")
     16     4    int    Format chunk length (16)
     20     2    int    Audio data type
     22     2    int    Number of interleaved channels
     24     4    int    Sample rate
     28     4    int    Average bytes/sec
     32     2    int    Block align
     34     2    int    Data word length (bits)
      D     4    char   Data chunk identifier ("data")
    ...   ...    ...    other chunks
    D+4     4    int    Data chunk length (initially set to 0)
    D+8    ...   ...    Audio data
  8-bit mu-law, 8-bit A-law, 8-bit integer, 16-bit integer data formats are
  supported.

Parameters:
  <-  AFILE AFwrWVhead
      Audio file pointer for the audio file.  In case of error, the audio file
      pointer is set to NULL.
   -> FILE *fp
      File pointer for the audio file
   -> int Format
      Audio file data format code,
        FD_MULAW8  = 1,  mu-law 8-bit data
        FD_ALAW8   = 2,  A-law 8-bit data
        FD_UINT8   = 3,  offset-binary 8-bit integer data
        FD_INT16   = 5,  two's-complement 16-bit integer data
   -> long int Nframe
      Number of sample frames.  This value is used to set the file header
      information for non-random access files for which the header cannot be
      updated when the file is closed.  If this value is AF_NFRAME_UNDEF, the
      file must be random access.
   -> long int Nchan
      Number of channels
   -> double Sfreq
      Sampling frequency

Author / revision:
  P. Kabal  Copyright (C) 1998
  $Revision: 1.33 $  $Date: 1998/06/26 20:53:08 $

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

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

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

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

#define MCOPY(src,dest)		memcpy ((void *) (dest), \
					(const void *) (src), sizeof (dest))
#define WRPAD(fp,size,align) \
     AFwriteHead (fp, NULL, 1, (int) (RNDUPV(size, align) - (size)), \
		  DS_NATIVE);

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

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

static int
AF_setFMT p_((struct WV_PCMformat *fmt, int Format, long int Nchan,
	      double Sfreq));
static int
AF_wrFMT p_((FILE *fp, const struct WV_PCMformat *fmt));


AFILE *
AFwrWVhead (fp, Format, Nframe, Nchan, Sfreq)

     FILE *fp;
     int Format;
     long int Nframe;
     long int Nchan;
     double Sfreq;

{
  AFILE *AFp;
  int Lw;
  long int size, Ldata;
  double ScaleF;
  struct {
    struct WV_CkPreamb riff_p;
    char waveid[4];
    struct WV_CkPreamb fmt_p;
    struct WV_PCMformat fmt;
    struct WV_CkPreamb junk_p;
    struct WV_CkPreamb data_p;
  } Fhead;
  struct AF_info *Hinfo;

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

/* Set up the encoding parameters */
  switch (Format) {
  case FD_INT16:
    ScaleF = 1./WV_SF_PCM16;
    break;
  case FD_UINT8:
    ScaleF = 1./WV_SF_PCM8;
    break;
  case FD_MULAW8:
    ScaleF = 1./WV_SF_MULAW;
    break;
  case FD_ALAW8:
    ScaleF = 1./WV_SF_ALAW;
    break;
  default:
    UTwarn ("AFsetWVpar - %s", AFM_WV_UnsData);
    return NULL;
  }

/* Set up the fixed header parameters */
  Lw = AF_DL[Format];
  if (FLseekable (fp))
    Ldata = 0L;
  else if (Nframe != AF_NFRAME_UNDEF)
    Ldata = Nframe * Nchan * Lw;
  else {
    UTwarn ("AFwrWVhead - %s", AFM_WV_WRAccess);
    return NULL;
  }

  /* riff chunk */
  MCOPY (FM_RIFF, Fhead.riff_p.ckid);
  /* defer filling in the chunk size */ 
  MCOPY (FM_WAVE, Fhead.waveid);

  /* fmt chunk */
  MCOPY ("fmt ", Fhead.fmt_p.ckid);
  Fhead.fmt_p.cksize = AF_setFMT (&Fhead.fmt, Format, Nchan, Sfreq);

  /* Header information records */
  Hinfo = AFgenHinfo (Sfreq);

  /* junk chunk */
  MCOPY ("JUNK", Fhead.junk_p.ckid);
  Fhead.junk_p.cksize = (uint4_t) (4 + Hinfo->N);

  /* data chunk */
  MCOPY ("data", Fhead.data_p.ckid);
  Fhead.data_p.cksize = (uint4_t) Ldata;

  /* riff chunk size */
  size = 4 + 8 + RNDUPV(Fhead.fmt_p.cksize, ALIGN)
           + 8 + RNDUPV(Fhead.data_p.cksize, ALIGN);
  if (Hinfo->N > 0)
    size += 8 + RNDUPV(Fhead.junk_p.cksize, ALIGN);
  Fhead.riff_p.cksize = (uint4_t) size;

/* Write out the header */
  WHEAD_S (fp, Fhead.riff_p.ckid);
  WHEAD_V (fp, Fhead.riff_p.cksize, DS_EL);
  WHEAD_S (fp, Fhead.waveid);
  WHEAD_S (fp, Fhead.fmt_p.ckid);
  WHEAD_V (fp, Fhead.fmt_p.cksize, DS_EL);
  AF_wrFMT (fp, &Fhead.fmt);
  WRPAD (fp, Fhead.fmt_p.cksize, ALIGN);
  if (Hinfo->N > 0) {
    WHEAD_S (fp, Fhead.junk_p.ckid);
    WHEAD_V (fp, Fhead.junk_p.cksize, DS_EL);
    WHEAD_SN (fp, FM_AFSP, (sizeof FM_AFSP) - 1);	/* Omit null char */
    WHEAD_SN (fp, Hinfo->Info, Hinfo->N);
    WRPAD (fp, Fhead.junk_p.cksize, ALIGN);
  }
  WHEAD_S (fp, Fhead.data_p.ckid);
  WHEAD_V (fp, Fhead.data_p.cksize, DS_EL);

/* Set the parameters for file access */
  AFp = AFsetWrite (fp, FT_WAVE, Format, DS_EL,
		    (double) Fhead.fmt.SamplesPerSec, ScaleF, Nchan);

  return AFp;
}

/* Fill in the fmt chunk */


static int
AF_setFMT (fmt, Format, Nchan, Sfreq)

     struct WV_PCMformat *fmt;
     int Format;
     long int Nchan;
     double Sfreq;

{
  int Lw;

  switch (Format) {
  case FD_INT16:
    fmt->FormatTag = WAVE_FORMAT_PCM;
    break;
  case FD_UINT8:
    fmt->FormatTag = WAVE_FORMAT_PCM;
    break;
  case FD_MULAW8:
    fmt->FormatTag = WAVE_FORMAT_MULAW;
    break;
  case FD_ALAW8:
    fmt->FormatTag = WAVE_FORMAT_ALAW;
    break;
  default:
    assert (0);
  }

  Lw = AF_DL[Format];
  fmt->Channels = (uint2_t) Nchan;
  fmt->SamplesPerSec = (uint4_t) (Sfreq + 0.5);	/* Rounding */
  fmt->AvgBytesPerSec = (uint4_t) (fmt->SamplesPerSec * Nchan * Lw);
  fmt->BlockAlign = (uint2_t) (Lw * Nchan);
  fmt->BitsPerSample = (uint2_t) (8 * Lw);

  return WV_FMT_CKSIZE;
}

/* Write the format chunk */


static int
AF_wrFMT (fp, fmt)

     FILE *fp;
     const struct WV_PCMformat *fmt;

{
  long offs;

  offs = 0L;
  offs += WHEAD_V (fp, fmt->FormatTag, DS_EL);
  offs += WHEAD_V (fp, fmt->Channels, DS_EL);
  offs += WHEAD_V (fp, fmt->SamplesPerSec, DS_EL);
  offs += WHEAD_V (fp, fmt->AvgBytesPerSec, DS_EL);
  offs += WHEAD_V (fp, fmt->BlockAlign, DS_EL);
  offs += WHEAD_V (fp, fmt->BitsPerSample, DS_EL);

  assert (offs == WV_FMT_CKSIZE);

  return offs;
}
