/* $Id */
/* $Log */
/*********************************************************************

This software module was originally developed by

Eric D. Scheirer (MIT Media Laboratory)

in the course of development of the MPEG-2 NBC/MPEG-4 Audio standard
ISO/IEC 13818-7, 14496-1,2 and 3. This software module is an
implementation of a part of one or more MPEG-2 NBC/MPEG-4 Audio tools
as specified by the MPEG-2 NBC/MPEG-4 Audio standard.  ISO/IEC gives
users of the MPEG-2 NBC/MPEG-4 Audio standards free license to this
software module or modifications thereof for use in hardware or
software products claiming conformance to the MPEG-2 NBC/ MPEG-4 Audio
standards. Those intending to use this software module in hardware or
software products are advised that this use may infringe existing
patents. The original developer of this software module and his/her
company, the subsequent editors and their companies, and ISO/IEC have
no liability for use of this software module or modifications thereof
in an implementation.

This software module is hereby released into the public domain.

***********************************************************************/

/***************************************************************\
*   aifif.c                                                     *
*   Detailed parsing of AIFF and AIFC files for read/write      *
*   dpwe@media.mit.edu 1993feb26 after seeaif.c, 1993jan07      *
\***************************************************************/

/*
 * Revision history: 1994feb22 : Unified version from Audition version plus
 *                    dpwe       mods for compiling on DEC for PlaySound.mexds
 *	1994may04 dpwe	Finally got both the bytemodes set in aifOpen*
 *      1994may11 dpwe  Changed aifLongGet and aifLongSet to always work 
 *                      explicitly in BIGENDIAN mode after some weird 
 *                      signed long int behavior on the alpha
 *
 */

/* #define DEBUG /* */

/* #define SUSPEND_MALLOCS  /* OK for decompression (BrowsFX), disaster for 
                               writing (yympeg) */
/* #define MAIN     /* for test code at bottom */

/* includes */
#include "aifif.h"      /* pulls in stdio.h, assert.h etc */
#include "genutils.h"

/* constants */
#define DFLT_SRATE  (44100.0)   /* default parameters for new snd structs */
#define DFLT_CHANS  2           
#define DFLT_BITSPS 16

/* set up the debug macros: DBGFPRINTF to report an error, ASSERT and EXIT */
#ifdef DEBUG
#ifdef THINK_C
# define STREAMOUT      /* cause lots of info to be reported */
#  define ASSERT(a)     if(!(a))    Debugger(); /* don't do nothing */
#  define EXIT          Debugger()
# else /* !THINK_C - DEBUG under Unix */
#  define ASSERT(a)     assert a
#  define EXIT          abort()
# endif /* THINK_C */
#else /* !DEBUG - silence all the debug mechanisms */
# define ASSERT(a)
# define EXIT    	return 0
      /* a bit yukky, but don't want to exit */
#endif /* DEBUG */

/* typedefs */

/* static data */

/* have to define all these IDs in static data space so that we 
   can refer to them by address below */
static AiffID aiffType = AIFF_TYPE;
static AiffID aifcType = AIFC_TYPE;
static AiffID formId = FORM_ID;
static AiffID fverId = FVER_ID;
static AiffID commId = COMM_ID;
static AiffID ssndId = SSND_ID;
static AiffID markId = MARK_ID;
static AiffID instId = INST_ID;
static AiffID applId = APPL_ID;
static AiffID nameId = NAME_ID;
static AiffID authId = AUTH_ID;
static AiffID copyId = COPY_ID;
static AiffID annoId = ANNO_ID;
static AiffID comtId = COMT_ID;
static AiffID noneId = NONE_ID;
static AiffID saxlId = SAXL_ID;
static AiffID midiId = MIDI_ID;
static AiffID aesdId = AESD_ID;

#ifdef THINK_C
static char AIFCREATOR[4] = { 'S','d','2','a' };    /* a handy creator */

#ifdef THINK_C
static OSErr SetVref(short *pVref, short *pOref)        /* make a good vol reference */
    {
    char    volName[32];
    OSErr   rc = noErr;

    rc = GetVol(volName, pOref);    /* remember entry vol ref */
    if(*pVref)
        rc = SetVol("",*pVref);
    else
        *pVref = *pOref;            /* no volume provided - so return actual current */
    return(rc);
    }

int MacSetTypCreAs(fName,vRef,typ,cre)  /* set the creator & type of this file */
    char    *fName;             /* PASC name of soundfile - already created */
    short   vRef;
    long    typ, cre;
    {
    FInfo   fInfo;
    short   oVol;
    
/*  Debugger();  */

/*  fprintf(stderr, "MacSetTC: file %#s type %.4s crea %.4s\n", fName, &typ, &cre);  */
    if(SetVref(&vRef,&oVol)) {
        DBGFPRINTF((stderr, ".. failed\n"));
        return(-1);         /* bad volume */
    }
    GetFInfo(fName,vRef,&fInfo);                /* get creator & type */
    fInfo.fdType = typ;
    fInfo.fdCreator = cre;
    SetFInfo(fName,vRef,&fInfo);                /* rewrite modified */
    SetVol("",oVol);
    return(0);
    }
#endif 

#endif /* THINK_C */

long fSize(FILE *f) {  /* returns minus one for unseekable streams */

    long        l,p;
	
	l = -1;
    if(f != NULL)
        {	
        p = ftell(f);
        if(p >= 0)
            {
            fseek(f, 0L, SEEK_END);
            l = ftell(f);
            fseek(f, p, SEEK_SET);
            }
        }
    return l;
    }

/*----------------------- static function prototypes -------------------*/
static void aifInitForm(AIF_STRUCT *aifs, long fileSize);
static void aifInitComm(AIF_STRUCT *aifs);
static void aifInitFver(AIF_STRUCT *aifs);
static void aifInitInst(AIF_STRUCT *aifs);
static void aifInitMark(AIF_STRUCT *aifs);
static void aifInitSsnd(AIF_STRUCT *aifs);
static void aifInitComt(AIF_STRUCT *aifs);

AIF_STRUCT *aifNew(void)
{       /* allocate & initialize the basic object, prior to aifOpenRead
           or setting up parameters for write */
    AIF_STRUCT *aifs;

    aifs = TMMALLOC(AIF_STRUCT, 1, "aifNew");
    aifs->file          = NULL;         /* indicates neither read or write */
    aifs->fileName      = NULL;
    aifs->fileSize      = -1;
    aifs->filePos       = -1;
    aifs->sndBsize      = 0;
    aifs->sndBpos       = 0;
    aifs->isRead        = 1;            /* who knows? */
    aifs->isAifc        = 1;            /*   ''       */
    aifs->isSeekable    = 1;            /* almost certainly */
    aifs->chunkMask     = 0;            /* no chunks present */
    aifs->bytemode      = BM_INORDER;   /* bytes on file rel to mem (DEC) */
    aifs->commPos       = 0;            /* (indicates not read) */
    aifs->fverPos       = 0;
    aifs->markPos       = 0;
    aifs->comtPos       = 0;
    aifs->instPos       = 0;
    aifs->ssndPos       = 0;
    aifs->compressionName = NULL;
    aifs->markers       = NULL;
    aifs->comments      = NULL;
    aifs->textChunks    = NULL;
    aifs->dataChunks    = NULL;
    aifInitForm(aifs, UNK_LEN); /* a little silly */
    aifInitComm(aifs);
    aifInitFver(aifs);
    aifInitInst(aifs);
    aifInitMark(aifs);
    aifInitComt(aifs);
    aifInitSsnd(aifs);
    return aifs;
}

static void aifFreeDataNodeList(AnyNode *node)
{   /* call free() on a linked list of nodes whose 'next' field is 1st
       and whose second field is a pointer to allocated data (also free()d) */
    AnyNode *next;
    
    while(node)  {
        next = node->next;      /* AnyNode has .next as 1st el, matches rest */
        /* next pointer may point to allocated data to be freed also */
        if(node->data)      TMFREE(node->data, "aifFreeDataNodeList:d");
        TMFREE(node, "aifFreeDataNodeList");
        node = next;
    }
}

void aifFree(AIF_STRUCT *aifs)
{       /* destroy allocated structure */
    /* free comments, marks, pstrings etc */
    if(aifs->compressionName)       TMFREE(aifs->compressionName, "aifFree:cn");
    if(aifs->fileName)              TMFREE(aifs->fileName, "aifFree:fn");
    aifFreeDataNodeList((AnyNode *)aifs->markers);
    aifFreeDataNodeList((AnyNode *)aifs->comments);
    aifFreeDataNodeList((AnyNode *)aifs->textChunks);
    aifFreeDataNodeList((AnyNode *)aifs->dataChunks);
    /* free parent structure */
    TMFREE(aifs, "aifFree");
}

/* --------------------- time operations --------------------- */
#ifdef STREAMOUT	/* only print times for output stream */

#define TIMEOFFS   (66*1461*6*60*60)
/* seconds between jan1 1904 (mac) and jan1 1970 (unix)
   Subtract this number from timeStamp to get a unixable time. */

#include <time.h>

char *aifTimeStr(long time)
{       /* return an ascii string for a mac time */
    time_t      clock;
    struct tm   *tm;
#ifdef THINK_C
	char s[32];
	sprintf(s,"%d",time);
	return s;
#else
    clock = time - TIMEOFFS;
    tm = localtime(&clock);
    return asctime(tm);
#endif
}
#endif /* STREAMOUT */

/* -------------------- AiffID operations (4 char literals) ---------------- */
int aifIdCmp(AiffIDPtr a, AiffIDPtr b)
{   /* compare two IDs; return 1 if they are the same */
    int mc;
    BYTE *c, *d;
    
    c = (BYTE *)a;  d = (BYTE *)b;
    if(c[0] == d[0] && c[1] == d[1] && c[2] == d[2] && c[3] == d[3])
        return 1;
    else
        return 0;

/*    mc = memcmp((const void *)a, (const void *)b, 4);
    return(mc==0); */
    
}

void aifIdSet(AiffIDPtr d, AiffIDPtr s)
{   /* copy an ID to a specified location */
    memcpy((void *)d, (const void *)s, 4);
}

char *aifIdStr(AiffIDPtr id)
{       /* return a printf-able string for an ID */
    char *a, *b, *idString;

    a = (char *)id;
    b = idString = TMMALLOC(char, sizeof(AiffID)+1, "aifIdStr");
    memcpy(b, a, sizeof(AiffID));
    b[sizeof(AiffID)] = '\0';       /* add terminator */
    return idString;
}

/* -------------------- LONG operations (non-4byte aligned int32) -------- */
void aifLongSet(LONG *dst, long val)
{       /* copy a long onto 2 shorts, preserve binary image */
    unsigned char *bp = (unsigned char *)dst;

    bp[3] = val&0xFF;
    bp[2] = (val&0xFF00)>>8L;
    bp[1] = (val&0xFF0000)>>16L;
    bp[0] = (val&0xFF000000)>>24L;
}

long aifLongGet(LONG *src)
{       /* read 2 shorts as a long, preserve binary image */
    long  val;
    unsigned char *bp = (unsigned char *)src;

    val = ((long)bp[3]) + (((long)bp[2])<<8L) + (((long)bp[1])<<16L) + (((long)(0x7F&bp[0]))<<24L);
    if(0x80&bp[0])
	val -= 0x80000000;
    return val;
}

/* ------------------------ Byteswapping operations ---------------- */
static int bytemode = BM_INORDER;

static int aifSetBytemode(int mode)
{   /* setup the bytemode context; return previous mode */
    int tmp = bytemode;
    
    bytemode = mode;
    return tmp;
}

static void aifFixLong(LONG *lg)
{   /* fixup a single LONG (int32) in word-aligned memory */
    if(bytemode != BM_INORDER)  {
        /* aifLongSet(lg, lshuffle(aifLongGet(lg), bytemode));  */
	/* LongGet and LongSet always implicitly use 
	   BIGENDIAN order, so no need to modify them here */
    }
}

static void aifFixWord(short *wd)
{   /* fixup a single word (int16) for native byteorder */
    *wd = wshuffle(*wd, bytemode);
}

static void aifUnfixLong(LONG *lg)
{   /* convert a regular 32 bit number into an order suitable for diskwrite */
    aifFixLong(lg);
}

static void aifUnfixWord(short *wd)
{   /* convert a native int16 suitable to write to disk */
    aifFixWord(wd);
}

/* ------------------ Raw file operations (why?) ------------------------- */
long aifRawRead(AIF_STRUCT *aifs, BYTE *buf, long n)
{
    FILE    *file = aifs->file;
    long        red;

    if( (red = fread(buf, sizeof(BYTE), n, file)) != n)  {
        DBGFPRINTF((stderr, "aifRawRead: wanted %ld got %ld.\n",
                    n, red));
        EXIT;	/* becomes return 0 if not DEBUG */
    }
    aifs->filePos += red;
    return red;
}

long aifRawWrite(AIF_STRUCT *aifs, BYTE *buf, long n)
{
    FILE    *file = aifs->file;
    long        wrote;

    if( (wrote = fwrite(buf,sizeof(char),n,file)) != n)  {
        DBGFPRINTF((stderr, "aifRawWrite: wanted %ld got %ld.\n",
                    n, wrote));
        EXIT;	/* EXIT -> return 0 in non-debug */
    }
    aifs->filePos += wrote;
    return wrote;
}

static long aifWriteChunk(AIF_STRUCT *aifs, void *data, long towrite)
{   /* Write out a block of data presumed to start with a 
       chunk header (CkHdr), including 'fixing' the ckSize field 
       for byte order.  If <towrite> is zero, write out the number of 
       bytes implied by the chunk header, else only write <towrite> 
       bytes.  Refixup ckSize before returning total # bytes written. */
    FILE    *file = aifs->file;
    CkHdr *chunk = (CkHdr *)data;
    long  wrote;
    
    if(towrite == 0)   {
        towrite = aifLongGet(&chunk->ckSize)
                     + sizeof(CkHdr);   /* by definition */
    }
    DBGFPRINTF((stderr, "Writing chunk '%s' of cksize %ld at %ld\n", 
            aifIdStr(ADR chunk->ckID), aifLongGet(&chunk->ckSize), 
            ftell(aifs->file)));
    aifUnfixLong((LONG *)&chunk->ckSize);
    wrote = aifRawWrite(aifs, data, towrite);
    aifFixLong((LONG *)&chunk->ckSize);
    return wrote;
}

long aifSeekAbs(AIF_STRUCT *aifs, long pos)
{
    FILE    *file = aifs->file;
    long        sought;

    if( (sought = fseek(file,pos,SEEK_SET)) != 0)  {
        DBGFPRINTF((stderr, "aifSeekAbs: sought %ld returned %ld (not 0).\n",
                    pos, sought));
        EXIT;
    }
    aifs->filePos = pos;
    return pos;
}

long aifSeekRel(AIF_STRUCT *aifs, long pos)
{
    FILE    *file = aifs->file;
    long        sought;

    if( (sought = fseek(file,pos,SEEK_CUR)) != 0)  {
        DBGFPRINTF((stderr, "aifSeekRel: sought %ld returned %ld (not 0).\n",
                    pos, sought));
        EXIT;
    }
    aifs->filePos += pos;
    return aifs->filePos;       /* absolute, not relative, pos returned */
}

long aifFsize(AIF_STRUCT *aifs)
{   /* only works for read? */
    FILE    *file = aifs->file;
    long    size = aifs->fileSize;
    
    if(size < 0 && (size = fSize(file)) >= 0)  {
        aifs->fileSize = size;
    }
    return size;
}

int aifFeof(AIF_STRUCT *aifs)
{   /* are we at the end of the file (as we know it)?  Return 1 if so. */
    if(aifs->filePos < aifs->fileSize)
        return 0;
    else if(aifs->filePos == aifs->fileSize)
        return 1;
    else {
        DBGFPRINTF((stderr, "aifFeof: pos %ld siz %ld??\n", aifs->filePos,
                    aifs->fileSize));
        return 1;
    }
}

long aifFtell(AIF_STRUCT *aifs)
{
    FILE    *file = aifs->file;
    long    pos = ftell(file);
    
    if(pos != aifs->filePos)  {
        DBGFPRINTF((stderr, "aifFtell: surprise: at %ld, expected %ld\n", 
                    pos, aifs->filePos));
    }
    return (aifs->filePos = pos);

/*  if(aifs->filePos >= 0)
        return aifs->filePos;
    else
        return ftell(file);
 */
}

/* --------------------- read p-strings direct from disk --------------- */

#ifdef SUSPEND_MALLOCS
#define SCRATCHSIZE 1024
static char scratch[SCRATCHSIZE];
long scratchtodo, scratchnow;
#endif /* SUSPEND_MALLOCS */

static long aifReadPstring(AIF_STRUCT *aifs, char **pstr)
{       /* file is lined up at pstring.  Read in, alloc and convert, return
           bytes read */
    FILE    *file = aifs->file;
    long        strLen, redLen, red;
    BYTE        c;
    long        dummy;

    red = 1;
    aifRawRead(aifs, (BYTE *)&c, red);
    strLen = (int)c;
    redLen = strLen+(((strLen&1)==0)?1:0);      /* make total read even */
    if(strLen)  {
#ifdef SUSPEND_MALLOCS
        *pstr = NULL;
        scratchtodo = redLen;
        scratchnow  = -1;
        while(scratchtodo && scratchnow)  {
            scratchnow = MIN(SCRATCHSIZE, scratchtodo);
            scratchnow = aifRawRead(aifs, (BYTE *)(scratch), scratchnow);
            scratchtodo -= scratchnow;
        }
#else /* !SUSPEND_MALLOCS */
        *pstr = TMMALLOC(char, (redLen+1), "aifReadPstring");
        aifRawRead(aifs, (BYTE *)(*pstr), redLen);
        (*pstr)[strLen] = '\0';                     /* terminate */
#endif /* SUSPEND_MALLOCS */
    } else { /* strlen = 0 - don't malloc */
        aifRawRead(aifs, (BYTE *)&dummy, redLen);
        *pstr = NULL;
    }               
    return redLen+red;                          /* TOTAL bytes read fr file */
}

static long aifWritePstring(AIF_STRUCT *aifs, char *str)
{       /* file is lined up;  write out str as a pstring (size first);
           return the (always even) number of bytes written. */
    FILE    *file = aifs->file;
    long        len, wrote, towrite;
    BYTE        c;

    if(str == NULL)         /* special case */
        str = "";           /* .. treat as one of these */
    len = strlen(str);      /* (does not count '\0' at end) */
    c = (BYTE)len;
    wrote = aifRawWrite(aifs, &c, 1);
    /* since we have written one byte so far, must write an odd number 
       of further bytes to make the total even */
    towrite = len;
    if( (len&1) == 0)       towrite += 1;
    wrote += aifRawWrite(aifs, (BYTE *)str, towrite);   /* may include '\0' */
    return wrote;
}

static int aifPstrLen(char *Cstr)
{   /* return the number of bytes used on file for the 
       *Cstr* passed if represented as a *Pstr* complete with padding 
       so number returned is always even */
    long len;
    
    if(Cstr == NULL)    Cstr = "";
    len = strlen(Cstr) + 1; /* each nonterm chr + 1 for len */
    return (len & 1)?len + 1: len;  /* add one if odd */
}

/* ------------------------- parse fn for FORM ------------------- */

static long aifParseForm(AIF_STRUCT *aifs, FILE *stream)
{       /* read form block, assumed seeked to right place.
           Checks that chunkID is 'FORM' and that formType is 'AIFF' or 'AIFC'.
           Return bytes remaining in file's FORM block */
    long        pos;
    long        fileSize, formSize;
    int         goodFile;

    pos = aifFtell(aifs);           /* remember start of FORM */
    if(aifRawRead(aifs, (BYTE *)&aifs->form, sizeof(FormHdr)) < sizeof(FormHdr))  {
        /* file was too short even for the form header */
        DBGFPRINTF((stderr, "aifParseForm: file too short for formHdr\n"));
        return -1;
    }
    if(!aifIdCmp(ADR aifs->form.ckHdr.ckID, ADR formId))  {
        /* don't alert this error by default, since this is the 
           way we decide if a file is an AIF or not - failure 
           may be perfectly reasonable */
        DBGFPRINTF((stderr, "aifParseForm: ckID '%s' is not '%s'\n",
                    aifIdStr(ADR aifs->form.ckHdr.ckID), aifIdStr(ADR formId)));
        return -1;
    }
    aifFixLong((LONG *)&aifs->form.ckHdr.ckSize);   /* ob. */
    fileSize = aifFsize(aifs);
    formSize = aifLongGet(&aifs->form.ckHdr.ckSize);
    if(formSize + sizeof(CkHdr) == fileSize)
        goodFile = TRUE;
    else
        goodFile = FALSE;
#ifdef STREAMOUT
    if(stream)  {
        fprintf(stream,
              "FORM id '%s' type '%s' formSize %ld + 8 %s fileSize %ld\n",
              aifIdStr(ADR aifs->form.ckHdr.ckID), aifIdStr(ADR aifs->form.formType),
              aifLongGet(&aifs->form.ckHdr.ckSize), 
              goodFile? "==": "!=", fileSize);
    }
#endif /* STREAMOUT */
    if(aifIdCmp(ADR aifs->form.formType, ADR aifcType))
        aifs->isAifc = TRUE;
    else if(aifIdCmp(ADR aifs->form.formType, ADR aiffType))
        aifs->isAifc = FALSE;
    else {
        DBGFPRINTF((stderr, "aifParseForm: formType '%s' neither '%s' nor '%s'\n",
                aifIdStr(ADR aifs->form.formType), aifIdStr(ADR aifcType), 
                aifIdStr(ADR aiffType)));
        return -1;
    }
    /* store where the form actually was (? is this variable?) */
    /* aifs->formPos = pos; */  /* don't - must always be zero */
    /* return the number of bytes supposedly in this file */
    return formSize - (sizeof(FormHdr) - sizeof(CkHdr));   /* remaining bytes */
    /* do nothing with <goodFile> for now */
}

static void aifInitForm(AIF_STRUCT *aifs, long fileSize)
{   /* preset form fields */
    aifIdSet(ADR aifs->form.ckHdr.ckID, ADR formId);
    if(fileSize == UNK_LEN)  {
        aifLongSet(&aifs->form.ckHdr.ckSize, UNK_LEN);
    } else {
        aifLongSet(&aifs->form.ckHdr.ckSize, fileSize - sizeof(CkHdr));
    }
    aifIdSet(ADR aifs->form.formType, aifs->isAifc? ADR aifcType : ADR aiffType);
}   

static long aifWriteForm(AIF_STRUCT *aifs, long fileSize)
{   /* Initialize the form to reflect the TOTAL filesize (if known) */
    long towrite = sizeof(FormHdr);
    long wrote;

    aifInitForm(aifs, fileSize);
    wrote = aifWriteChunk(aifs, &aifs->form, towrite);
    return wrote;
}

/* --------------- Parse the many kinds of block we may find ------------ */
/* These functions all have the same structure, and should be invoked 
   from a table.  They are passed the parent AIF_STRUCT, a filled-in
   (and byteswapped) ckHdr, which has just been read.  They are also 
   passed a stream, to which debug information should be sent if nonnull.
   They consume the frame (except ssnd?), fill in appropriate fields in 
   the AIF_STRUCT, and return the number of bytes they actually ate. 
 */

static int aifCalcFrameSize(AIF_STRUCT *aifs)
{   /* fill-in the 'frame-size' field based on the other fields */
    int size = aifs->comm.numChannels *
                (int)((aifs->comm.sampleSize+BITS_PER_BYTE - 1)/BITS_PER_BYTE);
    
    if(aifs->isAifc && !aifIdCmp(ADR aifs->comm.compressionType, ADR noneId))  {
        aifs->frameSize = 0;
    } else {
        aifs->frameSize = size;
    }
    return size;
}

static void aifInitComm(AIF_STRUCT *aifs)
{   /* set up the comm struct with reasonable values */
    char *name = "not compressed";  /* prototype to copy in */

    aifs->commPos = 0;      /* not read or written yet */
    aifIdSet(ADR aifs->comm.ckHdr.ckID, ADR commId);
    aifs->comm.numChannels = DFLT_CHANS;
    aifLongSet(&aifs->comm.numSampleFrames, UNK_LEN);
    aifs->comm.sampleSize = DFLT_BITSPS;
    double_to_ieee_80(DFLT_SRATE, (BYTE *)ADR aifs->comm.sampleRate);
    aifIdSet(ADR aifs->comm.compressionType, ADR noneId);
#ifdef SUSPEND_MALLOCS
    aifs->compressionName = NULL;
#else 
    aifs->compressionName = TMMALLOC(char, strlen(name)+1, "aifInitComm");
    strcpy(aifs->compressionName, name);
#endif /* SUSPEND_MALLOCS */
    aifCalcFrameSize(aifs);
}

static long aifParseComm(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{   /* when a COMM block is found, fill in the structure.
       pos is assumed to be just after the chunk header, passed in 
       in ckHdr.  Return the total number of bytes consumed.  */
    long        red;
    int         bitsPsamp;
    char        cmpName[256];

    aifs->commPos = aifFtell(aifs) - sizeof(CkHdr);
    aifs->comm.ckHdr = *ckHdr;
    if(aifs->isAifc)
        red = sizeof(AIFCCommonChunk) - sizeof(CkHdr);
    else
        red = sizeof(CommonChunk) - sizeof(CkHdr);
    aifRawRead(aifs, ((BYTE *)&aifs->comm)+sizeof(CkHdr), red);
    aifFixWord(&aifs->comm.numChannels);
    aifFixWord(&aifs->comm.sampleSize);
    aifFixLong(&aifs->comm.numSampleFrames);

    /* set up bytes per frame, will overwrite with 0 if this is compressed */
    aifCalcFrameSize(aifs);
    if(aifs->isAifc)  {
        if(aifs->compressionName)   /* free existing name */
            TMFREE(aifs->compressionName, "aifParseComm");
        red += aifReadPstring(aifs, &aifs->compressionName);
    }
#ifdef STREAMOUT
    if(stream)  {
        fprintf(stream, "  chans %d, sframes %ld, ssize %d, sr %.2f\n",
                aifs->comm.numChannels, aifLongGet(&aifs->comm.numSampleFrames),
                aifs->comm.sampleSize, 
                ieee_80_to_double((BYTE *)ADR aifs->comm.sampleRate));
        if(aifs->isAifc)
            fprintf(stream, "  cpr type '%s' ('%s')\n",
                    aifIdStr(ADR aifs->comm.compressionType), 
                    aifs->compressionName);
    }
#endif /* STREAMOUT */
    return red;
}

static long aifWriteComm(AIF_STRUCT *aifs)
{   /* write out COMM chunk.  Assume seek in right place.  
       Fixup chunk header ourselves. */
    long size;
    long wrote = 0;

    aifs->commPos = aifFtell(aifs);
    aifIdSet(ADR aifs->comm.ckHdr.ckID, ADR commId);
    if(aifs->isAifc)  {
        size = sizeof(AIFCCommonChunk) + aifPstrLen(aifs->compressionName);
    } else {
        size = sizeof(CommonChunk);     /* regular AIFF one */
    }
    aifLongSet(&aifs->comm.ckHdr.ckSize, size - sizeof(CkHdr)); /* so def'd */

    aifUnfixWord(&aifs->comm.numChannels);
    aifUnfixWord(&aifs->comm.sampleSize);
    aifUnfixLong(&aifs->comm.numSampleFrames);
    if(aifs->isAifc)  {
        wrote += aifWriteChunk(aifs, &aifs->comm, sizeof(AIFCCommonChunk));
        wrote += aifWritePstring(aifs, aifs->compressionName);
    } else {
        wrote += aifWriteChunk(aifs, &aifs->comm, 0L);
    }
    aifFixWord(&aifs->comm.numChannels);
    aifFixWord(&aifs->comm.sampleSize);
    aifFixLong(&aifs->comm.numSampleFrames);

    return wrote;
}

static void aifInitSsnd(AIF_STRUCT *aifs)
{   /* preset SSND to sane vals */
    aifs->ssndPos = 0;      /* not read or written yet */
    aifs->sndBpos = 0;
    aifs->sndBsize = 0;
    aifLongSet(&aifs->ssnd.offset, 0);
    aifLongSet(&aifs->ssnd.blockSize, 0);
    aifIdSet(ADR aifs->ssnd.ckHdr.ckID, ADR ssndId);
}

static long aifParseSsnd(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{   /* read top of ssnd chunk (block size and offset);  return
       with pos at start of actual sound data (NOT offset-adjusted, 
       but aifs->sndBpos *is* offset adjusted, as is ->sndBsize) */
    long red = 0;
    long offset;

    aifs->ssndPos = aifFtell(aifs) - sizeof(CkHdr);
    aifs->ssnd.ckHdr = *ckHdr;
    red = sizeof(SoundDataHdr) - sizeof(CkHdr);
    aifRawRead(aifs, ((BYTE *)&aifs->ssnd)+sizeof(CkHdr), red);
    aifFixLong(&aifs->ssnd.offset);
    aifFixLong(&aifs->ssnd.blockSize);
#ifdef STREAMOUT
    if(stream)  {
        fprintf(stream, "  offset %ld, blocksize %ld\n",
                aifLongGet(&aifs->ssnd.offset), 
                aifLongGet(&aifs->ssnd.blockSize));
        if(aifs->frameSize)  {  /* (known & not compressed) */
            long remChunk;

            remChunk = aifLongGet(&aifs->ssnd.ckHdr.ckSize) - red;
            fprintf(stream, "  frames %ld\n", 
                    remChunk/aifs->frameSize);
        }
    }
#endif /* STREAMOUT */
    offset = aifLongGet(&aifs->ssnd.offset);
    aifs->sndBsize = aifLongGet(&aifs->ssnd.ckHdr.ckSize) 
                    - (sizeof(SoundDataHdr) 
                    - sizeof(CkHdr)) - offset;
    aifs->sndBpos = aifFtell(aifs) + offset;
    return red;
}

static long aifWriteSsnd(AIF_STRUCT *aifs, long dataSize)
{   /* write out SSND chunk.  Assume seek in right place.  
       Fixup chunk header in anticipation of <datasize> bytes of 
       data in the rest of the chunk.  ->offset ignored. */
    long size;
    long wrote = 0;
    long offset;

    aifs->ssndPos = aifFtell(aifs);
    aifIdSet(ADR aifs->ssnd.ckHdr.ckID, ADR ssndId);
    if(dataSize == UNK_LEN)  {
        aifLongSet(&aifs->ssnd.ckHdr.ckSize, UNK_LEN);
    } else {
        size = sizeof(SoundDataHdr) + dataSize;
        aifLongSet(&aifs->ssnd.ckHdr.ckSize, size - sizeof(CkHdr)); /* def */
    }
    aifUnfixLong(&aifs->ssnd.offset);
    aifUnfixLong(&aifs->ssnd.blockSize);
    wrote += aifWriteChunk(aifs, &aifs->ssnd, sizeof(SoundDataHdr));
    aifFixLong(&aifs->ssnd.offset);
    aifFixLong(&aifs->ssnd.blockSize);
    offset = aifLongGet(&aifs->ssnd.offset);
    aifs->sndBsize = aifLongGet(&aifs->ssnd.ckHdr.ckSize) 
                    - (sizeof(SoundDataHdr) 
                    - sizeof(CkHdr)) - offset;
    aifs->sndBpos = aifFtell(aifs) + offset;
    return wrote;   /* leave seek directly after header */
}

MarkerNode *aifSetMark(AIF_STRUCT *aifs, int markID, long pos, char *name)
{   /* change or add a mark of the specified ID to the specified value */
    MarkerNode **pmark;

#ifdef SUSPEND_MALLOCS
    return NULL;
#else /* SUSPEND_MALLOCS */
    if(markID <= 0)  {
        DBGFPRINTF((stderr, "aifSetMark:  ID (%d) must be > 0\n", markID));
        /* continue nonetheless */
    }
    pmark = &aifs->markers;
    /* marks are linked in ascending *ID* order, so find right place */
    while( (*pmark) && (*pmark)->marker.id < markID)  {
        pmark = &(*pmark)->next;
    }
    if( (*pmark) == NULL || markID < (*pmark)->marker.id)  {   
        /* didn't find - insert new mark node */
        MarkerNode *new = TMMALLOC(MarkerNode, 1, "aifSetMark");
        
        new->next = *pmark;
        new->marker.id = markID;
        new->markerName = NULL;
        ++aifs->mark.numMarkers;
        *pmark = new;
    }
    aifLongSet(&(*pmark)->marker.position, pos);
    if(name)  { /* new name */
        if((*pmark)->markerName) TMFREE((*pmark)->markerName,"aifSetMark"); /* junk old */
        (*pmark)->markerName = TMMALLOC(char, strlen(name)+1, "MarkName");
        strcpy((*pmark)->markerName, name);
    }
    aifs->chunkMask |= AIF_M_MARK;  /* remember we have marks */
    return *pmark;
#endif /* SUSPEND_MALLOCS */
}

MarkerNode *aifGetMark(AIF_STRUCT *aifs, int markID, long *ppos, char **pname)
{   /* return a pointer to the MarkerNode and its position if it is found, 
       else NULL and 0L */
    MarkerNode **pmark;

    pmark = &aifs->markers;
    /* marks are linked in ascending *ID* order, so find right place */
    while( (*pmark) && (*pmark)->marker.id < markID)  {
        pmark = &(*pmark)->next;
    }
    if( (*pmark) == NULL || markID < (*pmark)->marker.id)  {   /* no find */
        *ppos  = 0L;
        *pname = NULL;
        return NULL;
    } else {
        /* set pos return value */
        *ppos = aifLongGet(&(*pmark)->marker.position);
        *pname = (*pmark)->markerName;
        return *pmark;
    }
}

static void aifInitMark(AIF_STRUCT *aifs)
{   /* preset marks to sane structure */
    aifs->markPos = 0;
    aifIdSet(ADR aifs->mark.ckHdr.ckID, ADR markId);
    aifs->markers = NULL;
    aifs->mark.numMarkers = 0;
/*  aifSetMark(aifs, 1, 0, NULL); */   /* debug */
}

static long aifParseMark(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{   /* read MARK struct, also read in each mark into pointer array */
    long        red = 0;
    long        remChunk;
    MarkerNode  **pmarker, *mrkNode;
    Marker      marker;
    short       nMarks;
    int         markCnt = 0;

    aifs->markPos = aifFtell(aifs) - sizeof(CkHdr);
    aifs->mark.ckHdr = *ckHdr;
    remChunk = aifLongGet(&aifs->mark.ckHdr.ckSize);

    red = sizeof(MarkerChunk) - sizeof(CkHdr);
    aifRawRead(aifs, ((BYTE *)&aifs->mark)+sizeof(CkHdr), red);
    aifFixWord(&aifs->mark.numMarkers);
    nMarks = aifs->mark.numMarkers;
#ifdef STREAMOUT
    if(stream)  {
        fprintf(stream, "  nMarks %d\n", nMarks);
    }
#endif /* STREAMOUT */
/*    pmarker = &aifs->markers;     /* linked list of marker nodes, assu emp */
    while(remChunk > red && nMarks > markCnt)  {
    /*  *pmarker = TMMALLOC(MarkerNode, 1, "aifParseMarks:marker");
        red += aifRawRead(aifs, (BYTE *)&(*pmarker)->marker, 
                          sizeof(Marker));      */
        red += aifRawRead(aifs, (BYTE *)&marker, sizeof(Marker));
        aifFixWord((short *)&marker.id);
        aifFixLong(&marker.position);
        mrkNode = aifSetMark(aifs, marker.id, aifLongGet(&marker.position), 
                             NULL);
        /* since marker name is variable length, have to store by 
           pointer in a different place, not image of disk */
        red += aifReadPstring(aifs, &mrkNode->markerName);
#ifdef STREAMOUT
        if(stream)  {
            fprintf(stream,  "  mark %d: id %d pos %ld name '%s'\n",
                    markCnt, mrkNode->marker.id, 
                    aifLongGet(&mrkNode->marker.position), 
                    mrkNode->markerName);
        }
#endif /* STREAMOUT */
    /*    pmarker = &(*pmarker)->next;  */
        ++markCnt;
    }
    if(remChunk > red)  {
        DBGFPRINTF((stderr, "  ** %ld bytes left unread\n", remChunk-red));
    }
    if(nMarks > markCnt)  {
        DBGFPRINTF((stderr, "  ** %d marks not found\n", markCnt - nMarks));
    }
    return red;
}

static long aifWriteMark(AIF_STRUCT *aifs)
{   /* write out MARK chunk including marks in linked list. */
    long size = 0;
    long wrote = 0;
    MarkerNode *marker;
    int  nMarkers = 0;

    aifs->markPos = aifFtell(aifs);
    aifIdSet(ADR aifs->mark.ckHdr.ckID, ADR markId);
    /* figure total size of marks */
    marker = aifs->markers;
    while(marker)  {
        ++nMarkers;
        size += sizeof(Marker) + aifPstrLen(marker->markerName);
        marker = marker->next;
    }
    aifLongSet(&aifs->mark.ckHdr.ckSize, 
               size + sizeof(MarkerChunk) - sizeof(CkHdr));
    aifs->mark.numMarkers = nMarkers;
    aifUnfixWord(&aifs->mark.numMarkers);
    wrote += aifWriteChunk(aifs, &aifs->mark, sizeof(MarkerChunk));
    aifFixWord(&aifs->mark.numMarkers);
    /* write out all the marks */
    marker = aifs->markers;
    while(marker)  {
        aifUnfixWord((short *)&marker->marker.id);
        aifUnfixLong(&marker->marker.position);
        wrote += aifRawWrite(aifs, (BYTE *)&marker->marker, 
                             sizeof(Marker));
        aifFixWord((short *)&marker->marker.id);
        aifFixLong(&marker->marker.position);
        /* now write variably-lengthed marker name */
        wrote += aifWritePstring(aifs, marker->markerName);
        marker = marker->next;
    }
    return wrote;
}

static long aifReadBytes(AIF_STRUCT *aifs, char **pstr, long count, int terminateq)
{       /* Allocate memory and read in the next <count> bytes into it. 
           Always read an even number of bytes from file.
           If <terminateq>, add a '\0' on the end to terminate a 
           printable C string.
           return value is number of bytes read from file. */
    FILE *file = aifs->file;
    long red;

    red = count + ( (count & 1)? 1:0);   /* red is even */
#ifdef SUSPEND_MALLOCS
    scratchtodo = red;
    scratchnow  = -1;
    while(scratchtodo && scratchnow)  {
        scratchnow = MIN(SCRATCHSIZE, scratchtodo);
        scratchnow = aifRawRead(aifs, (BYTE *)(scratch), scratchnow);
        scratchtodo -= scratchnow;
    }
    *pstr = NULL;
#else /* SUSPEND_MALLOCS */
    *pstr = TMMALLOC(char, count+((terminateq || (count & 1))?1:0), 
                     "aifReadBytes");
    aifRawRead(aifs, (BYTE *)*pstr, red);
    if(terminateq)  {
        *(*pstr+count) = '\0';       /* add Cstring terminator */
    }
#endif /* SUSPEND_MALLOCS */
    return red;
}

static long aifWriteBytes(AIF_STRUCT *aifs, BYTE *data, long count)
{       /* Write out string of bytes plus pad as needed so total is odd*/
    FILE *file = aifs->file;
    long wrote = 0;
    BYTE c = 0;
    
    wrote += aifRawWrite(aifs, data, count);
    if(count&1)  {
        wrote += aifRawWrite(aifs, &c, 1);
    }
    return wrote;
}

CommentNode *aifSetComment(AIF_STRUCT *aifs, int markID, long time, 
                           char *text, long len)
{   /* add a comment to the specified markID with timestamp and len of text.
       At present, can have several comments per mark.  Should perhaps limit 
       to one, then comment replaces previous if same markID. */
    CommentNode **pcomt, *new;

    pcomt = &aifs->comments;
    /* comments are linked in ascending *markID* order, so find right place */
    while( (*pcomt) && (*pcomt)->comment.marker < markID)  {
        pcomt = &(*pcomt)->next;
    }
    if( (*pcomt)->comment.marker == markID )  {
        /* free existing marker ? */
        /* .. close up *pmark if you do */
    /*  old = *pcomt;
        *pcomt = (*pcomt)->next;
        if(old->text)   TMFREE(old->text,"aifSetComment:t");
        TMFREE(old,"aifSetComment:t");
     */
    }
    /* create and insert new comment */
#ifdef SUSPEND_MALLOCS
    return NULL;
#else /* SUSPEND_MALLOCS */
    new = TMMALLOC(CommentNode, 1, "aifSetComt");
    new->comment.marker = markID;
    aifLongSet(&new->comment.timeStamp, time);
    new->comment.count = len;
    if(len) {
        new->text = TMMALLOC(char, len, "aifSetComt:text");
        memcpy(new->text, text, len);
    } else {
        new->text = NULL;
    }
    new->next = *pcomt;
    *pcomt = new;
    aifs->chunkMask |= AIF_M_COMT;
    return *pcomt;
#endif /* SUSPEND_MALLOCS */
}

CommentNode *aifGetComment(AIF_STRUCT *aifs, int markID, long *time, 
                           char **text, long *len)
{   /* return a pointer to the first CommentNode and its contents attached 
       to markID if it is found, else NULL and zeros */
    CommentNode **pcomt;

    pcomt = &aifs->comments;
    /* comments are linked in ascending *markID* order, so search through */
    while( (*pcomt) && (*pcomt)->comment.marker < markID)  {
        pcomt = &(*pcomt)->next;
    }
    if( (*pcomt == NULL) || (*pcomt)->comment.marker != markID )  {
        *time = 0L;
        *text = NULL;
        *len  = 0L;
        return NULL;
    } else {
        *time = aifLongGet(&(*pcomt)->comment.timeStamp);
        *text = (*pcomt)->text;
        *len  = (*pcomt)->comment.count;
        return *pcomt;
    }
}

static void aifInitComt(AIF_STRUCT *aifs)
{   /* preset comts to sane structure */
    aifs->comtPos = 0;
    aifIdSet(ADR aifs->comt.ckHdr.ckID, ADR comtId);
    aifs->comments = NULL;
    aifs->comt.numComments = 0;
}

#ifdef SUSPEND_MALLOCS
static Comment scratchComment;
static char *pscratchstring;
#endif /* SUSPEND_MALLOCS */

static long aifParseComt(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{   /* comment block has several strings */
    long        red = 0;
    long        remChunk;
    CommentNode **pcomment;
    short       nComts;
    int         comtNum = 0;
    long        time;
    short       mrkr;
    short       count;
    char        *text;

    aifs->comtPos = aifFtell(aifs) - sizeof(CkHdr);
    aifs->comt.ckHdr = *ckHdr;
    remChunk = aifLongGet(&aifs->comt.ckHdr.ckSize);

    red = sizeof(CommentsChunk) - sizeof(CkHdr);
    aifRawRead(aifs, ((BYTE *)&aifs->comt)+sizeof(CkHdr), red);
    aifFixWord(&aifs->comt.numComments);
    nComts = aifs->comt.numComments;
#ifdef STREAMOUT
    if(stream)  {
        fprintf(stream, "  nComts %d\n", nComts);
    }
#endif /* STREAMOUT */
    pcomment = &aifs->comments;
    while(remChunk > red && nComts > comtNum)  {
#ifdef SUSPEND_MALLOCS
        red += aifRawRead(aifs, (BYTE *)&scratchComment, sizeof(Comment));
        aifFixLong(&scratchComment.timeStamp);
        aifFixWord((short *)&scratchComment.marker);
        aifFixWord(&scratchComment.count);
        if(scratchComment.count)
            red += aifReadBytes(aifs, &pscratchstring, scratchComment.count, 1);
        time = aifLongGet(&scratchComment.timeStamp);
        mrkr = scratchComment.marker;
        count = scratchComment.count;
        text = "<not loaded>";
#else /* SUSPEND_MALLOCS */
        *pcomment = TMMALLOC(CommentNode, 1, "aifParseComts:comment");
        red += aifRawRead(aifs, (BYTE *)&(*pcomment)->comment, 
                          sizeof(Comment));
        aifFixLong(&(*pcomment)->comment.timeStamp);
        aifFixWord((short *)&(*pcomment)->comment.marker);
        aifFixWord(&(*pcomment)->comment.count);
        time = aifLongGet(&(*pcomment)->comment.timeStamp);
        mrkr = (*pcomment)->comment.marker;
        count = (*pcomment)->comment.count;
        if(count)
            red += aifReadBytes(aifs, &(*pcomment)->text, count, 1);
        else
            (*pcomment)->text = NULL;
        text = (*pcomment)->text;
#endif /* SUSPEND_MALLOCS */
#ifdef STREAMOUT
        if(stream)  {
            fprintf(stream,  "  comt %d:  mrkr %d, text %d, time %s",
                    comtNum, mrkr, count, aifTimeStr(time));
            fprintf(stream,  "  '%s'\n", text);
        }
#endif /* STREAMOUT */
#ifndef SUSPEND_MALLOCS
        pcomment = &(*pcomment)->next;
#endif /* SUSPEND_MALLOCS */
        ++comtNum;
    }
    if(remChunk > red)  {
        DBGFPRINTF((stderr, "  ** %ld bytes left unread\n", remChunk - red));
    }
    if(nComts > comtNum)  {
        DBGFPRINTF((stderr, "  ** %d comts not found\n", comtNum - nComts));
    }
    return red;
}

static long aifWriteComt(AIF_STRUCT *aifs)
{   /* write out COMT chunk including comments found in linked list. */
    long size = 0;
    long wrote = 0;
    CommentNode *comment;
    int  nComts = 0;

    aifs->comtPos = aifFtell(aifs);
    aifIdSet(ADR aifs->comt.ckHdr.ckID, ADR instId);
    /* figure total size of comments */
    comment = aifs->comments;
    while(comment)  {
        ++nComts;
        size += sizeof(Comment) + aifPstrLen(comment->text);
        comment = comment->next;
    }
    aifLongSet(&aifs->comt.ckHdr.ckSize, size - sizeof(CkHdr));     /* def */
    aifs->comt.numComments = nComts;
    aifUnfixWord(&aifs->comt.numComments);
    wrote += aifWriteChunk(aifs, &aifs->comt, sizeof(CommentsChunk));
    aifFixWord(&aifs->comt.numComments);
    /* write out all the comments */
    comment = aifs->comments;
    while(comment)  {
        aifUnfixLong(&comment->comment.timeStamp);
        aifUnfixWord((short *)&comment->comment.marker);
        aifUnfixWord(&comment->comment.count);
        wrote += aifRawWrite(aifs, (BYTE *)&comment->comment, 
                             sizeof(Comment));
        aifFixLong(&comment->comment.timeStamp);
        aifFixWord((short *)&comment->comment.marker);
        aifFixWord(&comment->comment.count);
        /* now write variably-lengthed marker name */
        wrote += aifWritePstring(aifs, comment->text);
        comment = comment->next;
    }
    return wrote;
}

static void aifDescribeLoop(FILE *stream, char *name, Loop *loop)
{   /* print text description of loop structure */
    char *modeName;
    short playMode, beginMark, endMark;

    playMode  = loop->playMode;
    beginMark = loop->beginMark;
    endMark   = loop->endMark;

    if(playMode == ForwardLooping)
        modeName = "Forward looping";
    else if(playMode == ForwardBackwardLooping)
        modeName = "Fwd/Bkwd looping";
    else if(playMode == NoLooping)
        modeName = "no looping";
    else
        modeName = "unrec looping type";        /* we assume */
#ifdef STREAMOUT
    fprintf(stream, "  %s loop: start %d, end %d (%s)\n",
            name, beginMark, endMark, modeName);
#endif /* STREAMOUT */
}

static void aifFixLoop(Loop *loop)
{   /* byteswap the words in a loop */
    aifFixWord(&loop->playMode);
    aifFixWord(&loop->beginMark);
    aifFixWord(&loop->endMark);
}

static void aifUnfixLoop(Loop *loop)
{   /* byteswap the words in a loop */
    aifUnfixWord(&loop->playMode);
    aifUnfixWord(&loop->beginMark);
    aifUnfixWord(&loop->endMark);
}

static void aifInitLoop(Loop *loop)
{   /* preset loop to some values */
    loop->playMode = NoLooping;
    loop->beginMark = 1;
    loop->endMark = 1;
}

static void aifInitInst(AIF_STRUCT *aifs)
{   /* preset inst to sane values */
    aifs->instPos = 0;
    aifIdSet(ADR aifs->inst.ckHdr.ckID, ADR instId);
    aifs->inst.baseNote = 60;   /* MIDI middle C */
    aifs->inst.detune = 0;
    aifs->inst.lowNote = 0;
    aifs->inst.highNote = 0;
    aifs->inst.lowVelocity = 1;
    aifs->inst.highVelocity = 127;
    aifs->inst.gain = 0;
    aifInitLoop(&aifs->inst.sustainLoop);
    aifInitLoop(&aifs->inst.releaseLoop);
}

static long aifParseInst(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{
    long   red = 0;

    aifs->instPos = aifFtell(aifs) - sizeof(CkHdr);
    aifs->inst.ckHdr = *ckHdr;

    red = sizeof(InstrumentChunk) - sizeof(CkHdr);
    aifRawRead(aifs, ((BYTE *)&aifs->inst)+sizeof(CkHdr), red);
    aifFixWord(&aifs->inst.gain);
    aifFixLoop(&aifs->inst.sustainLoop);
    aifFixLoop(&aifs->inst.releaseLoop);
#ifdef STREAMOUT
    if(stream)  {
        fprintf(stream, "  base %d, detune %d, lowNote %d, highNote %d\n",
                aifs->inst.baseNote, aifs->inst.detune, 
                aifs->inst.lowNote, aifs->inst.highNote);
        fprintf(stream, "  lowV %d, hiV %d, gain %d\n",
                aifs->inst.lowVelocity, aifs->inst.highVelocity, 
                aifs->inst.gain);
        aifDescribeLoop(stream, "Sustain", &aifs->inst.sustainLoop);
        aifDescribeLoop(stream, "Release", &aifs->inst.releaseLoop);
    }
#endif /* STREAMOUT */
    return red;
}

static long aifWriteInst(AIF_STRUCT *aifs)
{   /* write out INST chunk */
    long size = 0;
    long wrote = 0;

    aifs->instPos = aifFtell(aifs);
    aifIdSet(ADR aifs->inst.ckHdr.ckID, ADR instId);
    aifLongSet(&aifs->inst.ckHdr.ckSize,
               sizeof(InstrumentChunk) - sizeof(CkHdr));
    aifUnfixWord(&aifs->inst.gain);
    aifUnfixLoop(&aifs->inst.sustainLoop);
    aifUnfixLoop(&aifs->inst.releaseLoop);
    wrote += aifWriteChunk(aifs, &aifs->inst, sizeof(InstrumentChunk));
    aifFixWord(&aifs->inst.gain);
    aifFixLoop(&aifs->inst.sustainLoop);
    aifFixLoop(&aifs->inst.releaseLoop);

    return wrote;
}

DataChunkNode *aifSetTextOrData(AIF_STRUCT *aifs, AiffIDPtr id, BYTE *data, 
                                long datalen, int textq)
{   /* Set up a data chunk node in our linked list with the 
       specified chunk id <id>, consisiting of <datalen> bytes found 
       at <data> (which is copied) <textq> set means this is stored 
       in the text list, else data list. */
    DataChunkNode **pnode, *node;       /* text chunks stored in linked list */
    DataChunkNode **root;       /* where to insert this new struct */
    
    if(textq)  {
        root = &aifs->textChunks;
    } else { 
        root = &aifs->dataChunks;
    }
    /* see if we already have one of these */
    pnode = root;
    while(*pnode)  {
        if(aifIdCmp(ADR (*pnode)->ckHdr.ckID, id))
            break;
        pnode = &(*pnode)->next;
    }
    if(*pnode != NULL)  {       /* found it, but want to replace it */
        node = *pnode;
        *pnode = node->next;    /* close up list */
        node->next;             /* unlink our node .. */
        aifFreeDataNodeList((AnyNode *)node);   /* .. and kill it */
    }
#ifdef SUSPEND_MALLOCS
    return NULL;
#else /* SUSPEND_MALLOCS */
    /* make a new node in which to store it */
    node = TMMALLOC(DataChunkNode, 1, "aifSetTextOrData:n");
    node->next = *root;
    *root = node;       /* insert at head of linked list */
    /* file position of this text chunk */
    node->pos   = -1;   /* not in file */
    aifIdSet(ADR node->ckHdr.ckID, id);
    aifLongSet(&node->ckHdr.ckSize, datalen);
    node->len   = datalen;
    if(datalen)  {
        node->data  = TMMALLOC(BYTE, datalen, "aifSetTextOrData:d");
        memcpy(node->data, data, datalen);
    } else {
        node->data = NULL;
    }
  /*
    if(textq)  {
        aifs->chunkMask |= AIF_M_TEXT;
    } else {
        aifs->chunkMask |= AIF_M_DATA;
    }
   */    /* AIF_M_TEXT and AIF_M_DATA are too general.  Handle elsewhere */
    return node;
#endif /* SUSPEND_MALLOCS */
}


DataChunkNode *aifGetTextOrData(AIF_STRUCT *aifs, AiffIDPtr id, BYTE **pdata, 
                                long *pdatalen, int textq)
{   /* Search data or text linked lists for the specified nodes.
       Current architecture only allows one of each type, so 
       only needs to return first if any.  NULL if none found */
    DataChunkNode *node;        /* text chunks stored in linked list */
    DataChunkNode **root;       /* where to insert this new struct */
    
    if(textq)  {
        root = &aifs->textChunks;
    } else { 
        root = &aifs->dataChunks;
    }
    /* see if we already have one of these */
    node = *root;
    while(node)  {
        if(aifIdCmp(ADR node->ckHdr.ckID, id))
            break;
        node = node->next;
    }
    if(node)  {
        *pdata = node->data;
        *pdatalen = node->len;
    }
    return node;
}

static long aifParseTextOrData(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream, 
                               int textq)
{   /* parse a text chunk i.e. a generic unstructured chunk where the 
       data may be binary or printable text (treatment varies by <textq>) */
    long        red = 0;
    long        remChunk;
    char        *text;
    DataChunkNode *node;        /* text chunks stored in linked list */
    long        datalen;
/*  DataChunkNode **root;       /* where to insert this new struct */
    
/*
    if(textq)  {
        root = &aifs->textChunks;
    } else { 
        root = &aifs->dataChunks;
    }
 */
    remChunk = aifLongGet(&ckHdr->ckSize);  
    /* may be odd, in which case red is rem+1 */
    if(remChunk)  {
        red = aifReadBytes(aifs, &text, remChunk, textq);
        datalen = remChunk + ((textq)?1:0); /* remember terminating byte */ 
    } else { 
        text = NULL;        /* forget printability */
        datalen = 0;
    }
#ifdef STREAMOUT
    if(stream)  {
        if(textq)  {
            fprintf(stream, "  text:'%s'\n", text);
        } else {
            fprintf(stream, "  data:0x%0lx...\n", *(long *)text);
        }
    }
#endif /* STREAMOUT */
    /* where do we store this? */
/*    node = TMMALLOC(DataChunkNode, 1, "aifParseTextOrData");
    node->next = *root;
    *root = node;
    node->ckHdr = *ckHdr;
    node->len   = remChunk;
    node->data  = (BYTE *)text;
 */
    node = aifSetTextOrData(aifs, ADR ckHdr->ckID, (BYTE *)text, datalen, textq);
    if(text)    TMFREE(text, "aifParseT");  /* setTorD makes own copy */
    node->pos   = aifFtell(aifs) - sizeof(CkHdr);
    
    return red;
}

static long aifWriteTextOrData(AIF_STRUCT *aifs, AiffIDPtr id, 
                               DataChunkNode *node)
{   /* write out raw chunk from DATA or TEXT linked lists */
    long size = 0;
    long wrote = 0;

    node->pos = aifFtell(aifs);
    aifIdSet(ADR node->ckHdr.ckID, id);
    aifLongSet(&node->ckHdr.ckSize, node->len);
    wrote += aifWriteChunk(aifs, &node->ckHdr, sizeof(CkHdr));
    wrote += aifWriteBytes(aifs, node->data, node->len);
    return wrote;
}

static long aifParseText(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{   /* parse a text chunk i.e. a generic unstructured chunk where the 
       data is in fact printable ascii (although we have to add our 
       own terminator). */
    return aifParseTextOrData(aifs, ckHdr, stream, 1);
}

static long aifParseData(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{   /* parse a data chunk i.e. unstructured chunk where the data 
       is not even ascii (else it would be a text chunk).  
       Examples are SAXL and MIDI. */
    return aifParseTextOrData(aifs, ckHdr, stream, 0);
}

static long aifWriteText(AIF_STRUCT *aifs, AiffIDPtr id)
{   /* write out any existing text or data chunk matching id, 
       else create a new empty one and write that */
    DataChunkNode   *node;
    long    len;
    BYTE    *data;
    BYTE    dummy[2];

    if( (node = aifGetTextOrData(aifs, id, &data, &len, 1)) == NULL)  {
        node = aifSetTextOrData(aifs, id, dummy, 0, 1);
    }
    return aifWriteTextOrData(aifs, id, node);
}

static long aifWriteData(AIF_STRUCT *aifs, AiffIDPtr id)
{   /* write out any existing text or data chunk matching id, 
       else create a new empty one and write that */
    DataChunkNode   *node;
    long    len;
    BYTE    *data;
    BYTE    dummy[2];

    if( (node = aifGetTextOrData(aifs, id, &data, &len, 0)) == NULL)  {
        node = aifSetTextOrData(aifs, id, dummy, 0, 0);
    }
    return aifWriteTextOrData(aifs, id, node);
}

static AiffIDPtr aifMtoID(long mask);   /* declared later */

static long aifWriteTextM(AIF_STRUCT *aifs, long mask)
{   /* write out any existing text or data chunk matching <mask>, 
       else create a new empty one and write that */
    return aifWriteText(aifs, aifMtoID(mask));
}

static long aifWriteDataM(AIF_STRUCT *aifs, long mask)
{   /* write out any existing text or data chunk matching <mask>, 
       else create a new empty one and write that */
    return aifWriteData(aifs, aifMtoID(mask));
}

static long aifWriteAllText(AIF_STRUCT *aifs)
{   /* write out any existing text chunks */
    DataChunkNode   *node;
    long rc = 0;

    node = aifs->textChunks;
    while(node)  {
        rc += aifWriteText(aifs, ADR node->ckHdr.ckID);
        node = node->next;
    }
    return rc;
}

static long aifWriteAllData(AIF_STRUCT *aifs)
{   /* write out any existing text chunks */
    DataChunkNode   *node;
    long rc = 0;

    node = aifs->dataChunks;
    while(node)  {
        rc += aifWriteText(aifs, ADR node->ckHdr.ckID);
        node = node->next;
    }
    return rc;
}

static void aifInitFver(AIF_STRUCT *aifs)
{   /* initialize this block to sane values */
    aifs->fverPos = 0;
    aifIdSet(ADR aifs->fver.ckHdr.ckID, ADR fverId);
    aifLongSet(&aifs->fver.timestamp, AIFCVersion1);
}

static long aifParseFver(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream)
{   /* read in format version chunk */
    long   red = 0;

    aifs->fverPos = aifFtell(aifs) - sizeof(CkHdr);
    aifs->fver.ckHdr = *ckHdr;
    red = sizeof(FormatVersionChunk) - sizeof(CkHdr);
    aifRawRead(aifs, ((BYTE *)&aifs->fver)+sizeof(CkHdr), red);
    aifFixLong(&aifs->fver.timestamp);
#ifdef STREAMOUT
    if(stream)  {
        fprintf(stream, "  datestamp: %s",
                aifTimeStr(aifLongGet(&aifs->fver.timestamp)));
    }
#endif /* STREAMOUT */
    return red;
}

static long aifWriteFver(AIF_STRUCT *aifs)
{   /* write out FVER chunk */
    long size = 0;
    long wrote = 0;

    aifs->fverPos = aifFtell(aifs);
    aifIdSet(ADR aifs->fver.ckHdr.ckID, ADR fverId);
    aifLongSet(&aifs->fver.ckHdr.ckSize, 
               sizeof(FormatVersionChunk) - sizeof(CkHdr));
    aifUnfixLong(&aifs->fver.timestamp);
    wrote += aifWriteChunk(aifs, &aifs->fver, 
                           sizeof(FormatVersionChunk));
    aifFixLong(&aifs->fver.timestamp);

    return wrote;
}

static int aifCheckCkHdr(CkHdr *ckHdr)
{   /* see if we can spot bad ckHdrs */
    long size = aifLongGet(&ckHdr->ckSize);
    int  i;
    char c, *s = (char *)ADR ckHdr->ckID;
    
    for(i=0; i<4; ++i)  {
        if( (c = s[i]) < ' ' || c > (char)0x7E )  {
            /* bad ascii */
            DBGFPRINTF((stderr, "  **bad char 0x%02x in ckId\n", c));
            EXIT;	/* EXIT is return 0 in non-debug */
        }
    }
    if(size < 0 || size > 500000000)  {
        DBGFPRINTF((stderr, "  **implausible chunk size %ld\n", size));
        EXIT;
    }
    return 1;       /* OK so far as we can tell */
}
        
typedef long (*ParseFn)(AIF_STRUCT *aifs, CkHdr *ckHdr, FILE *stream);
typedef long (*WriteFn)(AIF_STRUCT *aifs, long data);
typedef struct {                        /* ID-to-parse-function lookup table */
    AiffID      id;
    long        mask;
    ParseFn     parseFn;
    WriteFn     writeFn; 
    long        writeData;
} idParseTabEntry;

#ifdef THINK_C   /* no static initialization in THINK_C code resource */
idParseTabEntry *idParseTable=NULL;
#define PARSETABSIZE 16   /* hard-coded */
static void aifInitParseTab(void)
{    /* since we can't statically set up the parse table, do it on the fly here */
    if(idParseTable == NULL)  {
 		idParseTabEntry *ie;
     	idParseTable = TMMALLOC(idParseTabEntry, 16, "aifInitParseTab");
		ie = idParseTable;
     	    ie->id=FVER_ID; ie->mask=AIF_M_FVER; ie->parseFn=aifParseFver; ie->writeFn=(WriteFn)aifWriteFver; ie->writeData = 0;
     	(++ie)->id=COMM_ID; ie->mask=AIF_M_COMM; ie->parseFn=aifParseComm; ie->writeFn=(WriteFn)aifWriteComm; ie->writeData = UNK_LEN;
     	(++ie)->id=INST_ID; ie->mask=AIF_M_INST; ie->parseFn=aifParseInst; ie->writeFn=(WriteFn)aifWriteInst; ie->writeData = 0;
     	(++ie)->id=MARK_ID; ie->mask=AIF_M_MARK; ie->parseFn=aifParseMark; ie->writeFn=(WriteFn)aifWriteMark; ie->writeData = 0;
     	(++ie)->id=COMT_ID; ie->mask=AIF_M_COMT; ie->parseFn=aifParseComt; ie->writeFn=(WriteFn)aifWriteComt; ie->writeData = 0;
     	(++ie)->id=NAME_ID; ie->mask=AIF_M_NAME; ie->parseFn=aifParseText; ie->writeFn=(WriteFn)aifWriteTextM; ie->writeData = AIF_M_NAME;
     	(++ie)->id=AUTH_ID; ie->mask=AIF_M_AUTH; ie->parseFn=aifParseText; ie->writeFn=(WriteFn)aifWriteTextM; ie->writeData = AIF_M_AUTH;
     	(++ie)->id=COPY_ID; ie->mask=AIF_M_COPY; ie->parseFn=aifParseText; ie->writeFn=(WriteFn)aifWriteTextM; ie->writeData = AIF_M_COPY;
     	(++ie)->id=ANNO_ID; ie->mask=AIF_M_ANNO; ie->parseFn=aifParseText; ie->writeFn=(WriteFn)aifWriteTextM; ie->writeData = AIF_M_ANNO;
     	(++ie)->id=APPL_ID; ie->mask=AIF_M_APPL; ie->parseFn=aifParseData; ie->writeFn=(WriteFn)aifWriteDataM; ie->writeData = AIF_M_APPL;
     	(++ie)->id=AESD_ID; ie->mask=AIF_M_AESD; ie->parseFn=aifParseData; ie->writeFn=(WriteFn)aifWriteDataM; ie->writeData = AIF_M_AESD;
     	(++ie)->id=MIDI_ID; ie->mask=AIF_M_MIDI; ie->parseFn=aifParseData; ie->writeFn=(WriteFn)aifWriteDataM; ie->writeData = AIF_M_MIDI;
     	(++ie)->id=SAXL_ID; ie->mask=AIF_M_SAXL; ie->parseFn=aifParseData; ie->writeFn=(WriteFn)aifWriteDataM; ie->writeData = AIF_M_SAXL;
     	(++ie)->id=NONE_ID; ie->mask=AIF_M_TEXT; ie->parseFn=aifParseText; ie->writeFn=(WriteFn)aifWriteAllText; ie->writeData = 0;
     	(++ie)->id=NONE_ID; ie->mask=AIF_M_DATA; ie->parseFn=aifParseData; ie->writeFn=(WriteFn)aifWriteAllData; ie->writeData = 0;
     	(++ie)->id=SSND_ID; ie->mask=AIF_M_SSND; ie->parseFn=aifParseSsnd; ie->writeFn=(WriteFn)aifWriteSsnd; ie->writeData = UNK_LEN;
		DBGFPRINTF((stderr, "aifParseTab inited\n"));
    }
}

#else /* !THINK_C */

/* If you add to this table, modify the MAC parse table initialization function above.
   You must also manually fix the PARSETABLESIZE constant for the MAC */

idParseTabEntry idParseTable[] = {
    { FVER_ID, AIF_M_FVER, aifParseFver, (WriteFn)aifWriteFver, 0},
    { COMM_ID, AIF_M_COMM, aifParseComm, (WriteFn)aifWriteComm, UNK_LEN},
    { INST_ID, AIF_M_INST, aifParseInst, (WriteFn)aifWriteInst, 0 }, 
    { MARK_ID, AIF_M_MARK, aifParseMark, (WriteFn)aifWriteMark, 0 }, 
    { COMT_ID, AIF_M_COMT, aifParseComt, (WriteFn)aifWriteComt, 0 },
    { NAME_ID, AIF_M_NAME, aifParseText, (WriteFn)aifWriteTextM, AIF_M_NAME}, 
    { AUTH_ID, AIF_M_AUTH, aifParseText, (WriteFn)aifWriteTextM, AIF_M_AUTH}, 
    { COPY_ID, AIF_M_COPY, aifParseText, (WriteFn)aifWriteTextM, AIF_M_COPY}, 
    { ANNO_ID, AIF_M_ANNO, aifParseText, (WriteFn)aifWriteTextM, AIF_M_ANNO}, 
    { APPL_ID, AIF_M_APPL, aifParseData, (WriteFn)aifWriteDataM, AIF_M_APPL}, 
    { AESD_ID, AIF_M_AESD, aifParseData, (WriteFn)aifWriteDataM, AIF_M_AESD}, 
    { MIDI_ID, AIF_M_MIDI, aifParseData, (WriteFn)aifWriteDataM, AIF_M_MIDI}, 
    { SAXL_ID, AIF_M_SAXL, aifParseData, (WriteFn)aifWriteDataM, AIF_M_SAXL}, 
    { NONE_ID, AIF_M_TEXT, aifParseText, (WriteFn)aifWriteAllText, 0}, 
    { NONE_ID, AIF_M_DATA, aifParseData, (WriteFn)aifWriteAllData, 0}, 
    /* SSND must be last so it is written out last */
    { SSND_ID, AIF_M_SSND, aifParseSsnd, (WriteFn)aifWriteSsnd, UNK_LEN},
};
#define PARSETABSIZE (sizeof(idParseTable)/sizeof(idParseTabEntry))
#endif /* THINK_C */

static AiffIDPtr aifMtoID(long mask)
{   /* return the ID corresponding to <mask> in the table */
    int tbsize = PARSETABSIZE;
    int i;

    for(i = 0; i<tbsize; ++i)  {
        if(idParseTable[i].mask == mask)
            return ADR (idParseTable[i].id);
    }
    return NULL;
}

long aifWriteAllChunks(AIF_STRUCT *aifs)
{   /* write all chunks according to chunkmask with fns from 
       parseTable */
    int tbsize = PARSETABSIZE;
    int i;
    long wrote = 0;

    for(i = 0; i<tbsize; ++i)  {
        if(aifs->chunkMask & idParseTable[i].mask)
            wrote += (*idParseTable[i].writeFn)(aifs,
                                                idParseTable[i].writeData);
    }
    return wrote;
}

static int aifParseChunks(AIF_STRUCT *aifs, FILE *stream)
{   /* parse all regular chunks (excluding form); fill in aifs
       Diagnostics to stream if nonnull. 
       Will always seek to the end of the file;  not suitable when 
       aifs->isSeekable == 0 */
    CkHdr   ckHdr;
    long    red, remChunk;
    int     i, pTabSize = PARSETABSIZE;
    ParseFn parseFn;
    long    mask;
    
    while(!aifFeof(aifs)) {
        /* read another chunk header and its remnants */
        red = aifRawRead(aifs, (BYTE *)&ckHdr, sizeof(ckHdr));
        aifFixLong((LONG *)&ckHdr.ckSize);      /* fixup byteorder of long */
        /* test for validity of supposed chunk header */
        if(red < sizeof(ckHdr) || !aifCheckCkHdr(&ckHdr))           return 0;
#ifdef STREAMOUT
        if(stream)  {
            fprintf(stream, "chunk '%s' size 8+%ld:\n", aifIdStr(ADR ckHdr.ckID), 
                    aifLongGet(&ckHdr.ckSize));
        }
#endif /* STREAMOUT */
        parseFn = NULL;     mask = 0;
        i = 0;      /* match ckId against parse fn from table */
        while(parseFn == NULL && i<pTabSize)  {
            if(aifIdCmp(ADR ckHdr.ckID, ADR idParseTable[i].id)) {
                parseFn = idParseTable[i].parseFn;
                mask = idParseTable[i].mask;
            }
            ++i;
        }
        if(parseFn == NULL)  {
            DBGFPRINTF((stderr, "  ** Unknown chunk '%s' treated as data\n",
                        aifIdStr(ADR ckHdr.ckID)));
            parseFn = aifParseData;
        }
        remChunk = aifLongGet(&ckHdr.ckSize);
        if(remChunk&1)      ++remChunk;    /* odd lengths are padded to even */
        red = (*parseFn)(aifs, &ckHdr, stream);     /* run found function */
        aifs->chunkMask |= mask;            /* set appro. bit in mask */
        if(red < remChunk)  {  /* parse function didn't consume whole chunk */
            aifSeekRel(aifs, remChunk - red);
        } else if (red > remChunk)  {
            DBGFPRINTF((stderr, "  ** odd;  ckSize is %ld but parse read %ld\n",
                        remChunk, red));
            /* but continue none the less */
        }
    }
    return 1;   /* success */
}

void aifSetParams(AIF_STRUCT *aifs, long *pvBuf, int pvLen)
{   /* set parameters of AIF object.  <pvBuf> is an array of
       <pvLen> longs; the even-numbered elements are taken from
       the AIF_P_.. enum in aifif.h; the odd-numbered are the values for
       the parameters so referred */
    long    i = 0, d, err = 0;
    char    *s;
    
    while(i < pvLen)  {
        d = pvBuf[i+1];
        switch(pvBuf[i])  {
        case AIF_P_FILETYPE:
            aifs->isAifc = (d==AIF_FT_AIFC)? TRUE:FALSE;                break;
        case AIF_P_SAMPRATE:
            double_to_ieee_80((double)d, 
                              (BYTE *)ADR aifs->comm.sampleRate);       break;
        case AIF_P_SAMPSIZE:    aifs->comm.sampleSize = d;              break;
        case AIF_P_CHANNELS:    aifs->comm.numChannels = d;             break;
        case AIF_P_NFRAMES:  aifLongSet(&aifs->comm.numSampleFrames,d); break;
        case AIF_P_SNDBPOS:     aifs->sndBpos = d;                      break;
        case AIF_P_SNDBSIZE:    aifs->sndBsize = d;                     break;
        case AIF_P_CHUNKMASK:   aifs->chunkMask = d;                    break;
        case AIF_P_COMPID:      
            aifIdSet(ADR aifs->comm.compressionType, (AiffIDPtr)&d);        break;
        case AIF_P_COMPNAME:
            s = (char *)d;
            /* free any existing compression name string */
            if(aifs->compressionName != NULL)       TMFREE(aifs->compressionName, "aifSetParams");
            /* allocate new string and copy across parameter */
#ifndef SUSPEND_MALLOCS
            aifs->compressionName 
                    = TMMALLOC(char, strlen(s)+1, "aifSetParams:cmpName");
            strcpy(aifs->compressionName, s);
#endif /* SUSPEND_MALLOCS */
            break;
        case AIF_P_FVERTIME:    aifLongSet(&aifs->fver.timestamp, d);   break;
        default:    DBGFPRINTF((stderr, "aifSetParams: unrec param %ld = %ld\n", 
                                pvBuf[i], d));
                    err = -1;
        }
        i += 2;
    }
    aifCalcFrameSize(aifs); /* make sure it's up to date */
    /* return err; */
}
        
void aifGetParams(AIF_STRUCT *aifs, long *pvBuf, int pvLen)
{   /* read parameters of AIF object.  Even-numbered elements
       of <pvLen> longs in <pvBuf> specify AIF_P_.. parameter;
       their values are written into the odd locations on return. */
    long    i = 0, *p, err = 0;
    char    *s;
    
    while(i < pvLen)  {
        p = &pvBuf[i+1];
        switch(pvBuf[i])  {
        case AIF_P_FILETYPE:    
            *p = aifs->isAifc? AIF_FT_AIFC:AIF_FT_AIFF;                 break;
        case AIF_P_SAMPRATE:
            *(float *)p = ieee_80_to_double((BYTE *)ADR aifs->comm.sampleRate);
            break;
        case AIF_P_SAMPSIZE:    *p = aifs->comm.sampleSize;             break;
        case AIF_P_CHANNELS:    *p = aifs->comm.numChannels;            break;
        case AIF_P_NFRAMES: *p = aifLongGet(&aifs->comm.numSampleFrames);break;
        case AIF_P_SNDBPOS:     *p = aifs->sndBpos;                     break;
        case AIF_P_SNDBSIZE:    *p = aifs->sndBsize;                    break;
        case AIF_P_CHUNKMASK:   *p = aifs->chunkMask;                   break;
        case AIF_P_FILEPTR:     *p = (long)aifs->file;                  break;
        case AIF_P_COMPID:      
            aifIdSet((AiffIDPtr)p, ADR aifs->comm.compressionType);         break;
        case AIF_P_COMPNAME:
            /* allocate new string and copy across parameter */
#ifndef SUSPEND_MALLOCS
            if( (s = aifs->compressionName) != NULL) {
                *(char **)p = TMMALLOC(char, strlen(s)+1, 
                                       "aifGetParams:cmpName");
                strcpy(*(char **)p, s);
            } else 
#endif /* SUSPEND_MALLOCS */            
            {
                *(char **)p = NULL;
            }
            break;
        case AIF_P_FVERTIME:    *p = aifLongGet(&aifs->fver.timestamp); break;
        default:    DBGFPRINTF((stderr, "aifGetParams: unrec param %ld\n", 
                                pvBuf[i]));
                    err = -1;
        }
        i += 2;
    }
}


int aifOpenRead(AIF_STRUCT *aifs, char *path)
{   /* open the file whose name is in <path> and attach it to the
       pre-allocated <aifs>; parse all the chunks; return at start of SSND
       Return AIF_E_.. error code: 0 means success */
#ifdef DEBUG
    FILE *stream = stderr;      /* diagnostics: oui s'il vous plait */
#else /* !DEBUG */
    FILE *stream = NULL;        /* diagnostics: non merci */
#endif /* DEBUG */
    long fileRem, offset, sndBytes, err = 0;
    long mask = aifs->isAifc?AIFC_MASK:AIFF_MASK;   /* chunk mask */

#ifdef THINK_C
	aifInitParseTab();
#endif /* THINK_C */
    aifSetBytemode(aifs->bytemode = HostByteMode());
    if( (aifs->file = fopen(path, "rb")) == NULL)  {
        DBGFPRINTF((stderr, "aifOpenRead: cannot open '%s'\n", path));
        return AIF_E_CANT_OPEN;
    }
    aifs->isRead = TRUE;
    aifs->filePos = 0;
    if( (fileRem = aifParseForm(aifs, stream)) <= 0)  {
        /* aifParseForm has already reported error (if DEBUG) */
        err = AIF_E_NOT_AIFFC;
    } else {
        mask = aifs->isAifc?AIFC_MASK:AIFF_MASK;
        if(aifParseChunks(aifs, stream) == 0)  {
            /* ? something failed parsing chunks (DEBUG -> EXIT in rawRead) */
            err = AIF_E_BAD_CHUNK;
/*      } else if( aifs->commPos == 0 || aifs->ssndPos == 0 
            || ( (aifs->isAifc) && (aifs->fverPos == 0)))  {  */
        } else if( (aifs->chunkMask & mask) != mask)  {
            /* REQUIRED chunks must have been found */
            DBGFPRINTF((stderr, "aifOpenRead:  required chunks absent (comm %ld ssnd %ld aifc %ld fver %ld)\n", aifs->commPos, aifs->ssndPos, aifs->isAifc, aifs->fverPos));
            err = AIF_E_MISSING_CHUNK;
        }
    }
    if(err == 0)  {     /* we're ok .. */
        /* seek to start of sound data */
        aifSeekAbs(aifs, aifs->sndBpos);
        return AIF_E_NO_ERR;
    } else {            /* something was wrong : close & abandon the file */
        fclose(aifs->file);
        aifs->file = NULL;
        return err;
    }
}

int aifOpenWrite(AIF_STRUCT *aifs, char *path, long sndSize)
{    /* create the file whose name is in <path> ready to write
       an AIF file described by the pre-initialized <aifs>.
       Specifying <sndSize> as the total number of bytes of sample
       data that will be written in the SSND chunk will allow the
       code to write the file sizes right first time; passing UNK_LEN (-1)
       will make it go back and rewrite those sizes on aifClose().
       Returns AIF_E_ code (0 on success). */
    long filesize, sampFrames;
    long wrote = 0;

#ifdef THINK_C
	aifInitParseTab();
#endif /* THINK_C */
    aifSetBytemode(aifs->bytemode = HostByteMode());
    if( (aifs->file = fopen(path, "wb+")) == NULL)  {   /* may reread later */
        DBGFPRINTF((stderr, "aifOpenWrite: cannot create '%s'\n", path));
        return AIF_E_CANT_OPEN;
    }
    aifs->fileName = TMMALLOC(char, strlen(path)+1, "aifOpenWrite");
    strcpy(aifs->fileName, path);
    aifs->isRead = FALSE;
    aifs->filePos = 0;
    if(sndSize == UNK_LEN)  {
        filesize = UNK_LEN;
    } else {    /* most basic prediction for now */
        filesize = sndSize + sizeof(SoundDataHdr)
                    + sizeof(FormHdr) 
                    + (aifs->isAifc? sizeof(AIFCCommonChunk) 
                                     + sizeof(FormatVersionChunk) 
                                     + aifPstrLen(aifs->compressionName) :
                                     sizeof(CommonChunk) );
        sampFrames = aifLongGet(&aifs->comm.numSampleFrames);
        if(aifs->comm.sampleSize > 0 && aifs->comm.numChannels > 0
            && (sampFrames == 0 || sampFrames == UNK_LEN))  {
            aifLongSet(&aifs->comm.numSampleFrames, 
                       sndSize/aifCalcFrameSize(aifs));
        }
    }
    aifs->chunkMask |= (aifs->isAifc?AIFC_MASK:AIFF_MASK);

    wrote += aifWriteForm(aifs, filesize);
    wrote += aifWriteAllChunks(aifs);
/*
    if(aifs->isAifc)  {
        wrote += aifWriteFver(aifs);
    }
    wrote += aifWriteComm(aifs);
    wrote += aifWriteMark(aifs);
    wrote += aifWriteInst(aifs);
    wrote += aifWriteSsnd(aifs, sndSize);
*/  
    return AIF_E_NO_ERR;
}

void aifClose(AIF_STRUCT *aifs)
{   /* Close the file, after rewriting size fields if file was open 
       for write.  In that case, current fpos assumed to be the end 
       of the SSND chunk */
    long sndSize, fileSize;
    char zero = '\0';
    
    if(aifs->isRead == FALSE)  {    /* rewrite fields on CloseWrite */
    
        sndSize = aifFtell(aifs) - (aifs->ssndPos + sizeof(SoundDataHdr));
        if(sndSize & 1)     {   /* can't have odd length chunk */
            aifRawWrite(aifs, (BYTE *)&zero, 1);
        }
            
        /* maybe write out other chunks (following ssnd) here */
        fileSize = aifFtell(aifs);
    
        aifSeekAbs(aifs, aifs->ssndPos);
        aifWriteSsnd(aifs, sndSize);            /* rewrite sndSize */
    
        if(aifs->frameSize 
           && (aifLongGet(&aifs->comm.numSampleFrames) == UNK_LEN))  {
            aifLongSet(&aifs->comm.numSampleFrames, sndSize/aifs->frameSize);
        }
        aifSeekAbs(aifs, aifs->commPos);
        aifWriteComm(aifs);                     /* rewrite numSampleFrames */
    
        aifSeekAbs(aifs, 0);
        aifWriteForm(aifs, fileSize);           /* rewrite formSize */
#ifdef THINK_C
        CtoPstr(aifs->fileName);
        MacSetTypCreAs(aifs->fileName,0/*vref*/,
                       (aifs->isAifc?*(long *)&aifcType:*(long *)&aiffType), 
                       *(long *)AIFCREATOR); /* make an Sd2 file */
        PtoCstr(aifs->fileName);
#endif /* THINK_C */
        fclose(aifs->file);
     } else {
        fclose(aifs->file);
     }
    aifs->file = NULL;
}

long aifReadFrames(AIF_STRUCT *aifs, void *buf, long nFrames)
{   /* read 'frames' - sample frames for uncompressed, bytes for 
       compressed, both in the ssnd region */
    long ssPos  = aifs->filePos - aifs->sndBpos;
    long ssBrem = aifs->sndBsize - ssPos;
    int chans   = aifs->comm.numChannels;
    int bpFrame = aifs->frameSize;
    int bpSamp  = bpFrame / chans;  /* bytes per sample for fmt cnvsn */
    long red;
    
    if(bpFrame == 0)    bpFrame = 1;    /* default to bytes */
    if(nFrames * bpFrame > ssBrem)  {
        nFrames = ssBrem / bpFrame;     /* clip to end of ssnd block */
    }
    red = aifRawRead(aifs, (BYTE *)buf, nFrames * bpFrame);
    if(bpFrame > 1 && aifs->bytemode != BM_INORDER)
        ConvertBufferBytes(buf, nFrames * bpFrame, bpSamp, aifs->bytemode);

    return red/bpFrame;
}

long aifWriteFrames(AIF_STRUCT *aifs, void *buf, long nFrames)
{   /* write 'frames' or bytes for compressed data */
    int chans   = aifs->comm.numChannels;
    int bpFrame = aifs->frameSize;
    int bpSamp  = bpFrame / chans;  /* bytes per sample for fmt cnvsn */
    long wrote;
    
    if(bpFrame == 0)    bpFrame = 1;
    if(bpFrame > 1 && aifs->bytemode != BM_INORDER)
        ConvertBufferBytes(buf, nFrames * bpFrame, bpSamp, aifs->bytemode);
    wrote = aifRawWrite(aifs, (BYTE *)buf, nFrames * bpFrame);
    if(bpFrame > 1 && aifs->bytemode != BM_INORDER)
        ConvertBufferBytes(buf, nFrames * bpFrame, bpSamp, aifs->bytemode);
    return wrote / bpFrame;
}

long aifFrameFtell(AIF_STRUCT *aifs)
{   /* do an 'ftell' relative to the ssnd region, in frames */
    long ssPos  = aifs->filePos - aifs->sndBpos;
    int  bpFrame = aifs->frameSize;
    
    if(bpFrame == 0)    bpFrame = 1;    /* default to bytes */
    return ssPos/bpFrame;
}
        
long aifFrameFeof(AIF_STRUCT *aifs)
{   /* do an 'feof' relative to the ssnd region */
    long nframes = aifs->sndBsize / MAX(1, aifs->frameSize);

    if(aifFrameFtell(aifs) == nframes)
        return 1;
    else
        return 0;
}
        
long aifFrameSeek(AIF_STRUCT *aifs, long frame)
{   /* do an 'ftell' relative to the ssnd region, in frames */
    long sndPos  = aifs->sndBpos;
    int  bpFrame = aifs->frameSize;
    long sook;
    
    if(bpFrame == 0)    bpFrame = 1;    /* default to bytes */
    sook = aifSeekAbs(aifs, sndPos + bpFrame * frame);
    return (sook - sndPos)/bpFrame;
}
        
char *aifErrMsg(int err)
{   /* return appropriate error message */
    if(err == AIF_E_NO_ERR)  {
        return "no error";
    } else if(err == AIF_E_CANT_OPEN)  {
        return "cannot open file";
    } else if(err == AIF_E_NOT_AIFFC)  {
        return "file not AIFF/C";
    } else if(err == AIF_E_BAD_CHUNK)  {
        return "error in AIFF-C chunk structure";
    } else if(err == AIF_E_MISSING_CHUNK)  {
        return "obligatory AIFF chunk is missing";
    } else if(err == AIF_E_BAD_TYPE)  {
        return "BAD TYPE error";
    } else 
        return "unknown error!";
}

    

#ifdef MAIN         /*-------------------- test code below ----------------*/

#ifdef THINK_C
#include <tc_main.h>
#endif /* THINK_C */

/* static function prototype */
static void usage(void);

#define DEF_DATA  337
/* static data */
       char  *programName;
static int   verbose = TRUE;            /* -v flag */
static int   force   = FALSE;           /* -f flag */
static int   data    = DEF_DATA;        /* -d argument */

/* functions */

#define BUFBYTES    1024

main(argc,argv)
    int         argc;
    char        **argv;
    {
    int         i = 0;
    int         err = 0;
    char        *inPath = NULL;
    AIF_STRUCT  *aifs, *aifs2;
    long        sndBytes, left, chunklen = BUFBYTES;
    BYTE        buf[BUFBYTES];
    long        pvb[16], pvl;

    programName = argv[0];
    i = 0;
    while(++i<argc && err == 0)
        {
        char c, *token, *arg, *nextArg;
        int  argUsed;

        token = argv[i];
        if(*token++ == '-')
            {
            if(i+1 < argc)      nextArg = argv[i+1];
            else                nextArg = "";
            argUsed = 0;
            while(c = *token++)
                {
                if(NumericQ(token))     arg = token;
                else                    arg = nextArg;
                switch(c)
                    {
                case 'd':       data    = atoi(arg); argUsed = 1; break;
                case 'v':       verbose = FALSE; break;
                case 'f':       force   = TRUE;  break;
                case 'o': case 'r': case 'c': case 'e': /* allow -force w/o err */
                        break;
                default:        fprintf(stderr,"%s: unrec option %c\n",
                                        programName, c);
                                err = 1; break;
                    }
                if(argUsed)
                    {
                    if(arg == token)    token = "";   /* no more from token */
                    else                ++i;          /* skip arg we used */
                    arg = ""; argUsed = 0;
                    }
                }
            }
        else{
            if(inPath == NULL)          inPath = argv[i];
            else{ fprintf(stderr,"%s: excess arg %s\n", programName, argv[i]);
                  err = 1;  }
            }
        }
    /* other tests on args */
    if(data < 0)
        { fprintf(stderr,"%s: bad data %d: must be >= 0\n", programName, data);
          err = 1; }

    if(err || inPath == NULL)
        usage();                /* never returns */

/*    fprintf(stderr,"%s: data = %d\n",programName,data);
    fprintf(stderr,"%s: verbose = %s\n",programName,verb?"yes":"no");
    fprintf(stderr,"%s: infile %s\n", programName,inPath);
 */
    aifs  = aifNew();
    aifs2 = aifNew();
    if( aifOpenRead(aifs, inPath) )    exit(0);
    pvl = 0;
    pvb[pvl++] = AIF_P_FILETYPE;    ++pvl;
    pvb[pvl++] = AIF_P_SNDBSIZE;    ++pvl;
    pvb[pvl++] = AIF_P_SAMPSIZE;    ++pvl;
    pvb[pvl++] = AIF_P_CHANNELS;    ++pvl;
    pvb[pvl++] = AIF_P_SAMPRATE;    ++pvl;
    pvb[pvl++] = AIF_P_COMPID;      ++pvl;
    pvb[pvl++] = AIF_P_COMPNAME;    ++pvl;
    aifGetParams(aifs, pvb, pvl);
    pvb[1] = AIF_FT_AIFC;       /* force AIFC output */
    pvb[pvl++] = AIF_P_CHUNKMASK;   pvb[pvl++] = AIF_M_COPY;
    aifSetParams(aifs2, pvb, pvl);
    if( aifOpenWrite(aifs2, "tmp.aifc", UNK_LEN) < 0)   exit(0);
    left = pvb[3]/(pvb[5]*pvb[7]/BITS_PER_BYTE);
    fprintf(stderr, "frames = %ld - taking middle two quarters (at %ld)\n", 
            left, aifFrameFtell(aifs));
        fprintf(stderr, "%ld - %ld\n", aifs->frameSize, aifs2->frameSize);
    aifFrameSeek(aifs, left/4);
    left /= 2;  
    chunklen /= (pvb[5]*pvb[7]/BITS_PER_BYTE);
    while(left)  {
        chunklen = MIN(chunklen, left);
        aifReadFrames(aifs, buf, chunklen);
        aifWriteFrames(aifs2, buf, chunklen);
        left -= chunklen;
    }
    fprintf(stderr, "finished at %ld\n", aifFrameFtell(aifs));
    aifClose(aifs2);
    aifClose(aifs);
}

static void usage(void)         /* print syntax & exit */
    {
    fprintf(stderr,
    "usage: %s [-d data][-v] infile\n",programName);
    fprintf(stderr,"where (defaults in brackets)\n");
    fprintf(stderr," -d data  is a data argument            (%5d)\n",DEF_DATA);
    fprintf(stderr," -v       verbose mode\n");
    fprintf(stderr," -f       force reads even if file looks bad\n");
    fprintf(stderr," infile   input file\n");
    exit(1);
    }
#endif /* MAIN */
