/*
 *  FILE: sound.c
 *  Support routines for reading IFF/8SVX files.
 *
 *  Public Domain, but keep our names in it as the original authors.
 *  09-Dec-87	Rico Mariani	    original code (modified by JST)
 *  31-Aug-88	Jan Sven Trabandt   first release version
 */


#define I_AM_SOUND
#include "gimmelib/gimmefuncs.h"
#include "gimmelib/sound.h"
#include <stdio.h>


#define GET(iffile)         getc( iffile )

#define getId		    getLong
#define getLen		    getLong
#define getByte 	    GET
#define skip(len, file)     fseek(file, (long)(len), 1)

#define getV8H(v8h, file)   fread(v8h, sizeof(Voice8Header), 1, file)

/* requires an existent variable 'char *errmsg' and a label 'failure_return' */
#define fail_and_return(msg)    {errmsg = msg; goto failure_return;}

/* forward declarations */
static LONG getLong();
static WORD getWord();

static char noMemMsg[] = "insufficient memory\n";
static char badFileMsg[] = "corrupt or incomplete file\n";


SOUND *gimmeSound( filename )
    UBYTE   *filename;
{
    LONG    len, id;
    SOUND   *snd = NULL;
    BYTE    *p;
    void    *mh = NULL;
    FILE    *file = NULL;
    char    *errmsg = " ";
    SHORT   i;

    if( !(file = fopen(filename, "r")) ) {
	fail_and_return( "unable to open iff sound file for read\n" );
    }
    if( getId(file) != ID_FORM ) {
	fail_and_return( "not IFF format\n" );
    }
    getLen( file );
    if( getId(file) != ID_8SVX ) {
	fail_and_return( "not 8SVX format\n" );
    }
    for( ;; ) {
	getChunk( &id, &len, file );
	if( ferror(file) || feof(file) ) {
	    fail_and_return( badFileMsg );
	}
	switch( id ) {
	  case ID_VHDR:
	    snd = chainAllocMem( &mh, (ULONG)sizeof(SOUND),
				    MEMF_PUBLIC | MEMF_CLEAR );
	    if( !snd ) {
	       fail_and_return( noMemMsg );
	    }
	    getV8H( &snd->vh, file );
	    if( len & 1 ) {
		GET(file);
	    }
	    break;
	  case ID_BODY:
	    if( !snd ) {
		fail_and_return( "didn't find VHDR chunk\n" );
	    }
	    switch( snd->vh.sCompression ) {
	      case sCmpNone:
		snd->bodylen = len;
		snd->body = chainAllocMem( &mh, len, MEMF_CHIP );
		if( !snd->body ) {
		    fail_and_return( noMemMsg );
		}
		memget( snd->body, len, file );
		break;
	      case sCmpFibDelta:
		snd->bodylen = len << 1;
		snd->body = chainAllocMem( &mh, (ULONG) snd->bodylen,
					    MEMF_CHIP );
		if( !snd->body ) {
		    fail_and_return( noMemMsg );
		}
		p = AllocMem( len, MEMF_PUBLIC );
		if( !p ) {
		    fail_and_return( noMemMsg );
		}
		memget( p, len, file );
		DUnpack( p, len, snd->body );
		FreeMem( p, len );
		break;
	      default:
		fail_and_return( "unknown compression form\n" );
		break;
	    } /* switch */
	    if( len & 1 ) {
		GET(file);
	    }
	    if( ferror(file) ) {
		fail_and_return( badFileMsg );
	    }
	    snd->memhead = mh;
	    fclose( file );
	    return( snd );
	    break;
	  case ID_NAME:
	  case ID_AUTH:
	  case ID_COPY:
	  case ID_ANNO:
	  case ID_ATAK:
	  case ID_RLSE:
	  default:		/* unimplemented chunk type */
	    skip( EVEN(len), file );
	    break;
	} /* switch */
    } /* for */
failure_return: {}
    if( Output() ) {
	Write( Output(), errmsg, (ULONG)strlen(errmsg) );
    }
    if( mh ) {
	chainFreeMem( mh );
    }
    if( file ) {
	fclose( file );
    }
    return( NULL );
} /* gimmeSound */


short getRidOfSound( sound )
    SOUND   *sound;
{
#ifdef GIMME_WIMPY
    if( !sound ) {
	return( -1 );
    }
#endif
    chainFreeMem( sound->memhead );
    return( 0 );
} /* getRidOfSound */


/* read an IFF Id */
static LONG getLong( file )
    FILE    *file;
{
    register UBYTE  p[4];

    p[0] = GET(file);
    p[1] = GET(file);
    p[2] = GET(file);
    p[3] = GET(file);
    return( (LONG) Mk(p[0],p[1],p[2],p[3]) );
} /* getLong */


/* read an IFF chunk header */
static getChunk( id, len, file )
    LONG    *id, *len;
    FILE    *file;
{
    *id = getId( file );
    *len = getLen( file );
} /* getChunk */


/* get len bytes from the file and put them into memory */
static memget( p, len, file )
    UBYTE   *p;
    LONG    len;
    FILE    *file;
{
    for( ; len > 0L; len -= 0x07fffL, p += 0x07fffL ) {
	fread( p, (int) (len & 0x07fffL), 1, file );
    } /* for */
} /* memget */


/* get a word */
static WORD getWord( file )
    FILE    *file;
{
    WORD    v;

    v = GET(file) << 8;
    return( v | GET(file) );
} /* getWord */


/* get the EGPoint info */
static getEGPoint( pnt, file )
    EGPoint *pnt;
    FILE    *file;
{
    pnt->duration = getWord( file );
    pnt->dest = getLong( file );
} /* getEGPoint */


static BYTE codeToDelta[] = {-34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21};

/* unpack a part of a fibonacci delta encoded sound sample */
static BYTE D1Unpack( source, n, dest, x )
    BYTE    *source;
    LONG    n;
    BYTE    *dest;
    BYTE    x;
{
    UBYTE   d;
    LONG    i, lim;

    lim = n << 1;
    for( i = 0; i < lim; ++i ) {
	d = source[i>>1];
	if( i & 1 ) {
	    d &= 0xf;
	} else {
	    d >>= 4;
	}
	x += codeToDelta[d];
	dest[i] = x;
    } /* for */
    return( x );                /* return last data value */
} /* D1Unpack */


/* unpack a fibonacci delta encoded sound sample */
static DUnpack( source, n, dest )
    BYTE    *source;
    LONG    n;
    BYTE    *dest;
{
    D1Unpack( source + 2, n - 2, dest, source[1] );
} /* DUnpack */
