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

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

Purpose:
  Write header information to a AIFF-C file

Description:
  This routine opens and writes header information to a AIFF-C format audio
  file.

  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")
     12     4    char   COMM chunk identifier ("COMM")
     16     4    int    COMM chunk length
     24     2    int    Number of interleaved channels
     26     4    int    Number of sample frames
     30     2    int    Bits per sample
     32    10    float  Sample frames per second
     42     4    int    Compression type
     46   ...    char   Compression name
    ...    ...   ...    ...
      S     4    char   SSND chunk identifier ("SSND")
    S+4     4    int    SSND chunk length
    S+8     4    int    Data offset (0)
    S+12    4    int    Block size (0)
    S+16  ...    ...    Audio data
  8-bit mu-law, 8-bit A-law, 8-bit integer, and 16-bit integer data formats are
  supported.

Parameters:
  <-  AFILE AFwrAIhead
      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_INT8    = 4,  two's-complement 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.23 $  $Date: 1998/06/26 20:52:36 $

-------------------------------------------------------------------------*/

static char rcsid [] = "$Id: AFwrAIhead.c 1.23 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/AIpar.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), (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_setCOMM p_((struct AI_CkCOMM *COMM, int Format, long int Nframe,
	       long int Nchan, double Sfreq));


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

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

{
  AFILE *AFp;
  long int size, Ldata;
  double ScaleF;
  struct {
    struct AI_CkPreamb FORM_p;
    char AIFFid[4];
    struct AI_CkPreamb COMM_p;
    struct AI_CkCOMM COMM;
    struct AI_CkPreamb FVER_p;
    struct AI_CkFVER FVER;
    struct AI_CkPreamb ANNO_p;
    struct AI_CkPreamb SSND_p;
    struct AI_CkSSND SSND;
  } 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./AI_SF_NONE16;
    break;
  case FD_INT8:
    ScaleF = 1./AI_SF_NONE8;
    break;
  case FD_MULAW8:
    ScaleF = 1./AI_SF_ULAW;
    break;
  case FD_ALAW8:
    ScaleF = 1./AI_SF_ALAW;
    break;
  default:
    UTwarn ("AFwrAIhead - %s", AFM_AIFF_UnsData);
    return NULL;
  }

  if (Nframe != AF_NFRAME_UNDEF)
    Ldata = Nframe * AF_DL[Format];
  else if (FLseekable (fp))
    Ldata = 0L;
  else {
    UTwarn ("AFwrAIhead - %s", AFM_AIFF_WRAccess);
    return NULL;
  }

  /* Set up the fixed header parameters */
  MCOPY (FM_IFF, Fhead.FORM_p.ckID);
  /* defer filling in FORM_p.ckDataSize */
  MCOPY (FM_AIFF_C, Fhead.AIFFid);

  /* COMM chunk */
  MCOPY (CKID_COMM, Fhead.COMM_p.ckID);
  Fhead.COMM_p.ckDataSize = (uint4_t) AF_setCOMM (&Fhead.COMM, Format,
						  Nframe, Nchan, Sfreq);

  /* FVER chunk */
  MCOPY (CKID_FVER, Fhead.FVER_p.ckID);
  Fhead.FVER_p.ckDataSize = 4;
  Fhead.FVER.timestamp = AIFCVersion1;

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

  /* ANNO chunk */
  MCOPY (CKID_ANNOTATION, Fhead.ANNO_p.ckID);
  Fhead.ANNO_p.ckDataSize = (uint4_t) (4 + Hinfo->N);

  /* SSND chunk */
  MCOPY (CKID_SSND, Fhead.SSND_p.ckID);
  Fhead.SSND_p.ckDataSize = 8 + Ldata;
  Fhead.SSND.offset = 0;
  Fhead.SSND.blockSize = 0;

 /* Fill in the FORM chunk size */
  size = 4 + 8 + RNDUPV(Fhead.COMM_p.ckDataSize, ALIGN)
           + 8 + RNDUPV(Fhead.FVER_p.ckDataSize, ALIGN)
           + 8 + RNDUPV(Fhead.SSND_p.ckDataSize, ALIGN);
  if (Hinfo->N > 0)
    size += 8 + RNDUPV(Fhead.ANNO_p.ckDataSize, ALIGN);
  Fhead.FORM_p.ckDataSize = (uint4_t) size;

/* Write out the header */
  WHEAD_S (fp, Fhead.FORM_p.ckID);
  WHEAD_V (fp, Fhead.FORM_p.ckDataSize, DS_EB);
  WHEAD_S (fp, Fhead.AIFFid);

  WHEAD_S (fp, Fhead.COMM_p.ckID);
  WHEAD_V (fp, Fhead.COMM_p.ckDataSize, DS_EB);
  WHEAD_V (fp, Fhead.COMM.numChannels, DS_EB);
  WHEAD_V (fp, Fhead.COMM.numSampleFrames, DS_EB);
  WHEAD_V (fp, Fhead.COMM.sampleSize, DS_EB);
  WHEAD_S (fp, Fhead.COMM.sampleRate);
  WHEAD_S (fp, Fhead.COMM.compressionType);
  WHEAD_P (fp, Fhead.COMM.compressionName);
  WRPAD (fp, Fhead.COMM_p.ckDataSize, ALIGN);

  WHEAD_S (fp, Fhead.FVER_p.ckID);
  WHEAD_V (fp, Fhead.FVER_p.ckDataSize, DS_EB);
  WHEAD_V (fp, Fhead.FVER.timestamp, DS_EB);

  if (Hinfo->N > 0) {
    WHEAD_S (fp, Fhead.ANNO_p.ckID);
    WHEAD_V (fp, Fhead.ANNO_p.ckDataSize, DS_EB);
    WHEAD_SN (fp, FM_AFSP, (sizeof FM_AFSP) - 1);	/* Omit null char */
    WHEAD_SN (fp, Hinfo->Info, Hinfo->N);
    WRPAD (fp, Fhead.ANNO_p.ckDataSize, ALIGN);
  }

  WHEAD_S (fp, Fhead.SSND_p.ckID);
  WHEAD_V (fp, Fhead.SSND_p.ckDataSize, DS_EB);
  WHEAD_V (fp, Fhead.SSND.offset, DS_EB);
  WHEAD_V (fp, Fhead.SSND.blockSize, DS_EB);

/* Set the parameters for file access */
  AFp = AFsetWrite (fp, FT_AIFF_C, Format, DS_EB, Sfreq, ScaleF, Nchan);

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


static int
AF_setCOMM (COMM, Format, Nframe, Nchan, Sfreq)

  struct AI_CkCOMM *COMM;
  int Format;
  long int Nframe;
  long int Nchan;
  double Sfreq;

{
/* Set up the encoding parameters */
  switch (Format) {
  case FD_INT16:
    MCOPY (CT_NONE, COMM->compressionType);
    strcpy (COMM->compressionName, CN_NONE);
    COMM->sampleSize = 8 * FDL_INT16;
    break;
  case FD_INT8:
    MCOPY (CT_NONE, COMM->compressionType);
    strcpy (COMM->compressionName, CN_NONE);
    COMM->sampleSize = 8 * FDL_INT8;
    break;
  case FD_MULAW8:
    MCOPY (CT_ULAW, COMM->compressionType);
    strcpy (COMM->compressionName, CN_ULAW);
    COMM->sampleSize = 16;		/* Uncompressed data size in bits */
    break;
  case FD_ALAW8:
    MCOPY (CT_ALAW, COMM->compressionType);
    strcpy (COMM->compressionName, CN_ALAW);
    COMM->sampleSize = 16;		/* Uncompressed data size in bits */
    break;
  default:
    assert (0);
  }

  COMM->numChannels = (uint2_t) Nchan;
  if (Nframe == AF_NFRAME_UNDEF)
    COMM->numSampleFrames = (uint4_t) 0;
  else
    COMM->numSampleFrames = (uint4_t) Nframe;
  UTeIEEE80 (Sfreq, COMM->sampleRate);
  /* Fhead.COMM.compressionType filled in above */
  /* Fhead.COMM.compressionName filled in above */
  /* Fhead.COMM.sampleSize filled in above */

  return (22 + strlen (COMM->compressionName));
}
