/*
 * sndheader.c
 */
/*
 * Jim Zelenka, CMU/ITC, 9 Jun 1992 (rewritten from my old sources)
 * Roger Dannenberg, CMU, Mar 1993 (extensive changes and additions)
 */

/* $Id: sndheader.c,v 1.1 1992/08/15 21:28:58 jz1j Exp $ */

#include <stdio.h>
#include "sys/file.h"
#include "sndheader.h"
#include <sys/stat.h>
#include <netinet/in.h>
#include "xlisp.h"
/* for HUGE_VAL: */
#include "math.h"

/* min(n, sizeof(long)) doesn't work on RS6K without this: 
 * (I never tracked down what min() was called and what was wrong.)
 */
#define min(a, b) ((a) < (b) ? (a) : (b))

double read_ieee_extended(int file);

/*
 * reset the file to read from the beginning
 */
#define resetfile(file) { lseek(file, (off_t) 0, L_SET); }


/*
 * reads a an item of type T from F into L, if unsuccessful,
 * resets file to beginning and returns
 */
#define readitem(F,L,T) { \
int r; \
r = read(F,L,sizeof(T)); \
if (r != sizeof(T)) { resetfile(file); return file; } \
read_in += sizeof(T); \
}

#define readlong(F,L) { readitem(F,L,long); *L = ntohl(*L); }

#define readshort(F,L) { readitem(F,L,short); *L = ntohs(*L); }

#define readfloat(F,L) { readitem(F,L,float); }

#define readuchar(F,L) { readitem(F,L,unsigned char); }


/*
 * write a an item of type T to F from L (no error handling yet)
 */
#define writeitem(F,L,T) write(F,L,sizeof(T));

#define writelong(F,L) { long tmp = L; tmp = htonl(tmp); \
	writeitem(F,&tmp,long); }

#define writeshort(F,L) { short tmp = L; tmp = htons(tmp); \
	writeitem(F,&tmp,short); }

#define writefloat(F,L) { float tmp = L; writeitem(F,&tmp,float); }

#define writeuchar(F,L) { char tmp = L; \
	writeitem(F,&tmp,unsigned char); }


static int fail(int file, char *str)
{
    xlfail(str);
    resetfile(file);
    return file;
}


int open_snd_file(filename, format, channels, mode, bits, srate, length, flags)
  char *filename;	/* the file to open */
  long *format;		/* the header format detected */
  long *channels;	/* the number of channels (default 1) */
  long *mode,		/* mode = encoding (pcm, alaw, ...) */
       *bits;		/* bits per sample */
  double *srate;	/* sample rate */
  long *length;		/* length in bytes of sample data only */
  long *flags;		/* tells which attributes were set */
{
    struct stat statbuf;
    long magic, bytemode,len;
    short type=IRCAM_SND_COMMENT, size=0, encoding;
    unsigned char buf[SIZEOF_IRCAM_HEADER];
    int file,read_in=0;
    long *aiff_id = (long *) "FORM";
#define AIFF_SND_MAGIC (*aiff_id)
    long *riff_id = (long *) "RIFF";
#define WAVE_SND_MAGIC (*riff_id)

/*  These will be initialized by XLISP code:
    *format = SND_HEAD_NONE;
    *channels = 1;
    *mode = MODE_PCM;
    *bits = 16;
    *srate = 44100.0;
 */
    *flags = 0;
    if ((file = open(filename, O_RDONLY, 0666)) < 0) {
	/* could not open file- return error fd */
	return(file);
    }
    fstat(file,&statbuf);
    len = (long)statbuf.st_size;        /* get length of file */
    readlong(file, &magic);
    if (magic == IRCAM_SND_MAGIC) {
	float sr;
	*format = SND_HEAD_IRCAM;
	readfloat(file, &sr);
	*srate = sr;	/* float to double */
	readlong(file, channels);
	readlong(file, &bytemode);
	(*flags) |= SND_HEAD_SRATE|SND_HEAD_CHANNELS;
	/* now SF_ULAW, SF_SHORT, SF_FLOAT,AE_CHAR, AE_ALAW, AE_ULAW, AE_SHORT, AE_LONG, AE_FLOAT or other */
	while (type != IRCAM_SND_END && type != IRCAM_SND_AUDIOENCODE) {
	    readshort(file, &type);
	    readshort(file, &size);
	    if (size > 2*sizeof(short)) {
		int rc;
		/* make sure we don't overflow buffer */
		if (size < SIZEOF_IRCAM_HEADER) {
		    rc = read(file, buf, (size-2*sizeof(short)));
		} else {
		    rc = 0; /* force error */
		}
		if (rc != (size-2*sizeof(short))) {
		    return fail(file, "bad IRCAM header");
		}
		read_in += size-2*sizeof(short);
	    }
	}
	if (type == IRCAM_SND_AUDIOENCODE) {
	    printf("Got IRCAM sound format\n");
	    encoding = *((short *)(buf));
	    (*flags) |= SND_HEAD_MODE|SND_HEAD_BPS;
	    switch(encoding) {
		case IRCAM_SND_CHAR:
		    *mode = MODE_PCM;
		    *bits = 8;
		    break;
		case IRCAM_SND_ALAW:
		    *mode = MODE_ALAW;
		    *bits = 8;
		    break;
		case IRCAM_SND_ULAW:
		    *mode = MODE_ULAW;
		    *bits = 8;
		    break;
		case IRCAM_SND_SHORT:
		    *mode = MODE_PCM;
		    *bits = 16;
		    break;
		case IRCAM_SND_LONG:
		    *mode = MODE_PCM;
		    *bits = 32;
		    break;
		case IRCAM_SND_FLOAT:
		    *mode = MODE_FLOAT;
		    *bits = 32;
		    break;
		default:
		    (*flags) &= ~(SND_HEAD_MODE|SND_HEAD_BPS);
		    break;
	    }
	} else {
	    *bits = bytemode << 3;
	    (*flags) |= SND_HEAD_BPS;
	    switch (bytemode) {
		case sizeof(char):
		    (*flags) |= SND_HEAD_MODE;
		    *mode = MODE_ULAW;
		    break;
		case sizeof(short):
		    *mode = MODE_PCM;
		    break;
		case sizeof(float):
		    *mode = MODE_FLOAT;
		    break;
		default:
		    *mode = MODE_PCM;
		    break;
	    }
	}
	/* seek to end of header */
	lseek(file, (off_t) SIZEOF_IRCAM_HEADER, L_SET);
    } else if (magic == NEXT_SND_MAGIC) {
	long hdr_size, trash, rate;
	*format = SND_HEAD_NEXT;
	readlong(file, &hdr_size); /* dataLocation */
	readlong(file, &trash); /* dataSize */
	readlong(file, &bytemode); /* dataFormat */
	readlong(file, &rate); /* samplingRate */
	readlong(file, channels); /* channelCount */

	(*srate) = (float) rate;
	(*flags) = SND_HEAD_SRATE|SND_HEAD_CHANNELS;
	switch(bytemode) {
	    case NEXT_SND_FORMAT_ULAW_8:
		(*bits) = 8;
		(*mode) = MODE_ULAW;
		(*flags) |= SND_HEAD_BPS|SND_HEAD_MODE;
		break;
	    case NEXT_SND_FORMAT_LINEAR_8:
		(*bits) = 8;
		(*mode) = MODE_PCM;
		(*flags) |= SND_HEAD_BPS|SND_HEAD_MODE;
		break;
	    case NEXT_SND_FORMAT_LINEAR_16:
		(*bits) = 16;
		(*mode) = MODE_PCM;
		(*flags) |= SND_HEAD_BPS|SND_HEAD_MODE;
		break;
	    case NEXT_SND_FORMAT_LINEAR_24:
		(*bits) = 24;
		(*mode) = MODE_PCM;
		(*flags) |= SND_HEAD_BPS|SND_HEAD_MODE;
		break;
	    case NEXT_SND_FORMAT_LINEAR_32:
		(*bits) = 32;
		(*mode) = MODE_PCM;
		(*flags) |= SND_HEAD_BPS|SND_HEAD_MODE;
		break;
	    case NEXT_SND_FORMAT_FLOAT:
		(*bits) = sizeof(float) * 8;
		(*mode) = MODE_FLOAT;
		(*flags) |= SND_HEAD_BPS|SND_HEAD_MODE;
		break;
	    case NEXT_SND_FORMAT_DOUBLE:
		(*bits) = sizeof(double) * 8;
		(*mode) = MODE_FLOAT;
		(*flags) |= SND_HEAD_BPS|SND_HEAD_MODE;
		break;
	    case NEXT_SND_FORMAT_INDIRECT:
		break;
	    case NEXT_SND_FORMAT_DSP_DATA_8:
		(*bits) = 8;
		(*flags) |= SND_HEAD_BPS;
		break;
	    case NEXT_SND_FORMAT_DSP_DATA_16:
		(*bits) = 16;
		(*flags) |= SND_HEAD_BPS;
		break;
	    case NEXT_SND_FORMAT_DSP_DATA_24:
		(*bits) = 24;
		(*flags) |= SND_HEAD_BPS;
		break;
	    case NEXT_SND_FORMAT_DSP_DATA_32:
		(*bits) = 32;
		(*flags) |= SND_HEAD_BPS;
		break;
	    case NEXT_SND_FORMAT_MULAW_SQUELCH:
		(*mode) = MODE_ULAW;
		(*flags) |= SND_HEAD_MODE;
		break;
	    default:
		break;
	}
	/* position file past header */
	if (read_in < hdr_size) {
	    lseek(file, hdr_size, L_SET);
	    read_in = hdr_size;
	}
    } else if (magic == AIFF_SND_MAGIC) {
	unsigned long totalsize;
	char buf[4];
        long blocksize;
        long offset;

	*format = SND_HEAD_AIFF;
	readlong(file, &totalsize);
	if (read(file, buf,  4) != 4 || strncmp(buf, "AIFF", 4) != 0) {
	    return fail(file, 
			"AIFF 'FORM' chunk does not specify 'AIFF' as type\n");
	}
	/* Skip everything but the COMM chunk and the SSND chunk */
	/* The SSND chunk must be the last in the file */
	while (1) {
	    if (read(file, buf, 4) != 4) {
		return fail(file, "Missing SSND chunk in AIFF file\n");
	    }
	    if (strncmp(buf, "COMM", 4) == 0) {
		/* COMM chunk */
		long chunksize;
		long frames;
		short short_bits;
		short chans;
		readlong(file, &chunksize);
		if (chunksize != 18) {
		    return fail(file, "AIFF COMM chunk has bad size\n");
                }
		readshort(file, &chans);
		*channels = chans;	/* short to long */
		readlong(file, &frames);
		readshort(file, &short_bits);
		*mode = MODE_PCM;
		*bits = short_bits;	/* returned value is long */
		*srate = read_ieee_extended(file);
		*length = frames * *channels * (short_bits >> 3);
		(*flags) = SND_HEAD_MODE | SND_HEAD_BPS | SND_HEAD_SRATE |
			   SND_HEAD_CHANNELS | SND_HEAD_LEN;
	    } else if (strncmp(buf, "SSND", 4) == 0) {
		/* SSND chunk */
		long chunksize;
		readlong(file, &chunksize);
		readlong(file, &offset);
		readlong(file, &blocksize);
		break;
	    } else {
		long chunksize;
		readlong(file, &chunksize);
		/* skip the chunk */
		lseek(file, chunksize, L_INCR);
	    }
	}
	/* SSND chunk just read */
	if (blocksize != 0) {
	    return fail(file, "AIFF header specifies nonzero blocksize.");
        }
	lseek(file, offset, L_INCR);
    } else if (magic == WAVE_SND_MAGIC) {
	long size;
	char buf[4];
	short format;
	short chans;
	long sr;
	short short_bits;

	readlong(file, &size);
	if (read(file, buf,  4) != 4 || strncmp(buf, "WAVE", 4) != 0) {
	    return fail(file, 
			"RIFF file does not specify 'WAVE' as type\n");
	}

	/* Skip to the next "fmt " or end of file */
	while (1) {
	    long siz;
	    if (read(file, buf, 4) != 4) {
		return fail(file, "WAVE file missing fmt spec");
	    }
	    if (strncmp("fmt ", buf, 4) == 0) break;
	    readlong(file, &siz);
	    while (siz > 0) {
		read(file, buf, 1);
		siz--;
	    }
	}
	readlong(file, &size);
	readshort(file, &format);
	switch (format) {
	  case WAVE_FORMAT_UNKNOWN:
	    return fail(file, "file in Microsoft Official Unknown format");
	  case WAVE_FORMAT_PCM:
	    break;    
	  case WAVE_FORMAT_ADPCM:
	    return fail(file, "file in ADPCM format");
	  case WAVE_FORMAT_ALAW:
	    return fail(file, "file in ALAW format");
	  case WAVE_FORMAT_MULAW:
	    return fail(file, "file in ULAW format");
	  case WAVE_FORMAT_OKI_ADPCM:
	    return fail(file, "file in OKI ADPCM format");
	  case WAVE_FORMAT_DIGISTD:
	    return fail(file, "file in Digistd format");
	  case WAVE_FORMAT_DIGIFIX:
	    return fail(file, "file in Digifix format");
	  case IBM_FORMAT_MULAW:
	    return fail(file, "file in IBM U-law format");
	  case IBM_FORMAT_ALAW:
	    return fail(file, "file in IBM A-law format");
	  case IBM_FORMAT_ADPCM:
	    return fail(file, "file in IBM ADPCM format");
	  default:
	    return fail(file, "file in unknown format");
	}	
	readshort(file, &chans);
	*channels = chans;	/* short to long */
	readlong(file, &sr);
	*srate = sr;		/* long to double */
	/* reuse sr and chans for convenience: */
	readlong(file, &sr);	/* Average bytes/second */
	readshort(file, &chans);	/* Block align */
	readshort(file, &short_bits);
	*bits = short_bits;
	*mode = MODE_PCM;
	if (short_bits = 8) *mode = MODE_UPCM;	/* unsigned */
	*flags = SND_HEAD_MODE | SND_HEAD_BPS | SND_HEAD_SRATE |
		SND_HEAD_CHANNELS | SND_HEAD_LEN;
	size -= 16;
	lseek(file, (off_t) size, L_INCR);
	read(file, buf, 4);
	if (strncmp("data", buf, 4)) {
	    return fail(file, "missing data portion");
	}
	readlong(file, length);
    } else {
	(*flags) = 0;
	resetfile(file);
	return file;
    }

    /* If already determined from file header, assume remainder of file
	is sound data */
    if (!(*flags & SND_HEAD_LEN)) {
	(*length) = (len - read_in);
	(*flags) |= SND_HEAD_LEN;
    }
    return(file);
}


/* write_zeros -- add zeros to end of file */
/**/
void write_zeros(int fout, long n)
{
    long zero = 0;
    while (n > 0) {
	/* don't put min() in the arg list of write on an RS6K */
	/* there seems to be a compiler bug */
	long len = min(n, sizeof(long));
	write(fout, &zero, len);
	n -= sizeof(long);
    }
}


/* write_ircam_start -- write an IRCAM header at current file position */
/*
 * length is the total length of the header to be written; this is normally
 * SIZEOF_IRCAM_HEADER, but in the case of the hybrid CMIX headers, length
 * will be shorter to accommodate the NeXT header prefix
 */
void write_ircam_start(int fout, long mode, long bits, long chans, double sr,
	long length)
{
    short encoding;
    
    writelong(fout, IRCAM_SND_MAGIC);
    writefloat(fout, sr);
    writelong(fout, chans);
    writelong(fout, bits >> 3);
    /* now write the "CODE" section */
    writeshort(fout, IRCAM_SND_AUDIOENCODE);
    writeshort(fout, 3 * sizeof(short));    /* size of this record */
    if (bits == 8) {
        encoding = IRCAM_SND_CHAR;
        if (mode == MODE_ULAW) encoding = IRCAM_SND_ULAW;
        if (mode == MODE_ALAW) {
	    printf("ALAW not implemented, writing 8-bit PCM\n");
        }
    } else if (bits == 16) {
        encoding = IRCAM_SND_SHORT;
    } else if (bits == 32) {
        encoding = IRCAM_SND_FLOAT;
        if (mode == MODE_PCM) encoding = IRCAM_SND_LONG;
    }
    writeshort(fout, encoding);
    
    /* end the "CODE" section */
    writeshort(fout, IRCAM_SND_END);
    writeshort(fout, 2 * sizeof(short));

    /* write filler */
        length -= ( 16 /* head */ + 6 /* AudioEncode */ + 4 /* End */ );

    write_zeros(fout, length);
}


/* write_next_start -- write a NeXT header */
/*
 * Note: uses length for Length field, but does not fill with zeros.
 * Instead, this routine writes only the NeXT 24 byte header.
 */
void write_next_start(int fout, long mode, long bits, long chans, double sr,
	long length)
{
    short encoding;
    
    writelong(fout, NEXT_SND_MAGIC);
    /* header size matches cmix's bsd format */
    writelong(fout, length);
    writelong(fout, 0); /* data size, 0 -> unspecified */
    if (bits == 8) {
	encoding = NEXT_SND_FORMAT_LINEAR_8;
	if (mode == MODE_ULAW) encoding = NEXT_SND_FORMAT_ULAW_8;
	if (mode == MODE_ALAW) {
	    printf("ALAW not implemented, writing 8-bit PCM\n");
	}
    } else if (bits == 16) {
	encoding = NEXT_SND_FORMAT_LINEAR_16;
    } else if (bits == 32) {
	encoding = NEXT_SND_FORMAT_FLOAT;
	if (mode == MODE_PCM) encoding = NEXT_SND_FORMAT_LINEAR_32;
    }
    writelong(fout, encoding);
    writelong(fout, sr);
    writelong(fout, chans);
}


/* write_sndheader_start -- write header, position file for sample data */
/**/
void write_sndheader_start(int fout, long format, long mode, long bits, 
			   long chans, double sr)
{
    long nframes = 0x7f000000;

    switch (format) {
      case SND_HEAD_NONE:
	break;
      case SND_HEAD_AIFF: {
	int hsize =
		8 /*COMM hdr*/ + 18 /*COMM chunk*/ +
		8 /*SSND hdr*/ + 12 /*SSND chunk*/;
	if (bits != 8 && bits != 16) {
	    printf("Warning: using 16 bits per sample instead of %d\n", bits);
	    bits = 16;
	}
	write(fout, "FORM", 4); /* IFF header */
	/* (bogus) file size: */
	writelong(fout, hsize + nframes * (bits >> 3) * chans);
	write(fout, "AIFF", 4); /* File type */

	/* COMM chunk -- describes encoding (and #frames) */
	write(fout, "COMM", 4);
	writelong(fout, 18); /* COMM chunk size */
	writeshort(fout, chans); /* nchannels */
	writelong(fout, nframes); /* number of frames */
	writeshort(fout, bits); /* sample width, in bits */
	write_ieee_extended(fout, sr);

	/* SSND chunk -- describes data */
	write(fout, "SSND", 4);
	writelong(fout, 8 + nframes * (bits >> 3) * chans); /* chunk size */
	writelong(fout, 0); /* offset */
	writelong(fout, 0); /* block size */
        break; }
      case SND_HEAD_IRCAM: 
	write_ircam_start(fout, mode, bits, chans, sr, SIZEOF_IRCAM_HEADER);
	break;
      case SND_HEAD_NEXT:
	/* for compatibility with CMIX, we will always write an IRCAM
	 * header after the NeXT header, and use 1024 bytes of header.
	 */
	write_next_start(fout, mode, bits, chans, sr, SIZEOF_IRCAM_HEADER);
	write_ircam_start(fout, mode, bits, chans, sr, 
			  SIZEOF_IRCAM_HEADER - 24);
	break;
      case SND_HEAD_WAVE:
	write(fout, "RIFF", 4);
	writelong(fout, 8+16+12 + nframes * (bits >> 3) * chans);
	write(fout, "WAVE", 4);
	write(fout, "fmt ", 4);
	writelong(fout, (long) 16);
	writeshort(fout, 1);
	writeshort(fout, chans);
	writelong(fout, sr);
	writelong(fout, (((long) sr) * chans * bits + 7) / 8);	/* avg. rate */
	writeshort(fout, (chans * bits + 7) / 8);	/* Block Align */
	writeshort(fout, bits);
	write(fout, "data", 4);
	writelong(fout, nframes * (bits >> 3) * chans);
	break;	
      default:
	break;
    }
}


void write_sndheader_finish(int fout, long format, long mode, long bits, 
			   long chans, long len)
{
    long n;
    switch (format) {
      case SND_HEAD_NONE:
	break;
      case SND_HEAD_AIFF: {
	int hsize =
		8 /*COMM hdr*/ + 18 /*COMM chunk*/ +
		8 /*SSND hdr*/ + 12 /*SSND chunk*/;

	/* get the current position = file size */
	n = lseek(fout, (off_t) 0, L_INCR);
	if (n != 8 /* 'FORM'+size */ + hsize + len * (bits >> 3) * chans) {
	    printf("Actual file size %ld does not match predicted size %ld\n",
		   n, hsize + len * (bits >> 3) * chans);
        }
	/* write filesize in the header */
	lseek(fout, (off_t) 4, L_SET);
	writelong(fout, n - 8 /* 'FORM'+size do not count */);

	/* write number of frames in COMM chunk */
	lseek(fout, (off_t) (4 /* 'AIFF' */ + 4 /* 'COMM' */ + 4 /* size */ +
			     2 /* channels */), L_INCR);
	writelong(fout, len);	/* number of frames */

	/* write size in SSND chunk */
	lseek(fout, (off_t) (2 /* bits */ + 10 /* sr */ + 4 /* 'SSND' */), 
	      L_INCR);
	writelong(fout, 8 + len * (bits >> 3) * chans); /* chunk size */
        break; }
      case SND_HEAD_IRCAM:
	break;
      case SND_HEAD_NEXT:
	break;
      case SND_HEAD_WAVE:
	/* get the current position = file size */
	n = lseek(fout, (off_t) 0, L_INCR);
	/* back to the top */
	lseek(fout, (off_t) 4, L_SET);
	writelong(fout, n - 8);	/* file size - ['RIFF', len] */
	lseek(fout, (off_t) 40, L_SET);
	writelong(fout, n - 40); /* data size */
	break;
      default:
	break;
    }
}


double ConvertFromIeeeExtended();

double read_ieee_extended(file)
  int file;
{
	char buf[10];
	if (read(file, buf, 10) != 10)
		xlfail("EOF while reading IEEE extended number");
	return ConvertFromIeeeExtended(buf);
}

write_ieee_extended(file, x)
  int file;
  double x;
{
	char buf[10];
	ConvertToIeeeExtended(x, buf);
	/*
	report("converted %g to %o %o %o %o %o %o %o %o %o %o",
		x,
		buf[0], buf[1], buf[2], buf[3], buf[4],
		buf[5], buf[6], buf[7], buf[8], buf[9]);
	*/
	write(file, buf, 10);
}


/*
 * C O N V E R T   T O   I E E E   E X T E N D E D
 */

/* Copyright (C) 1988-1991 Apple Computer, Inc.
 * All rights reserved.
 *
 * Machine-independent I/O routines for IEEE floating-point numbers.
 *
 * NaN's and infinities are converted to HUGE_VAL or HUGE, which
 * happens to be infinity on IEEE machines.  Unfortunately, it is
 * impossible to preserve NaN's in a machine-independent way.
 * Infinities are, however, preserved on IEEE machines.
 *
 * These routines have been tested on the following machines:
 *    Apple Macintosh, MPW 3.1 C compiler
 *    Apple Macintosh, THINK C compiler
 *    Silicon Graphics IRIS, MIPS compiler
 *    Cray X/MP and Y/MP
 *    Digital Equipment VAX
 *
 *
 * Implemented by Malcolm Slaney and Ken Turkowski.
 *
 * Malcolm Slaney contributions during 1988-1990 include big- and little-
 * endian file I/O, conversion to and from Motorola's extended 80-bit
 * floating-point format, and conversions to and from IEEE single-
 * precision floating-point format.
 *
 * In 1991, Ken Turkowski implemented the conversions to and from
 * IEEE double-precision format, added more precision to the extended
 * conversions, and accommodated conversions involving +/- infinity,
 * NaN's, and denormalized numbers.
 */

#ifndef HUGE_VAL
#ifdef HUGE
#define HUGE_VAL HUGE
#else
/* WARNING: this is machine-dependent and should come from math.h */
#define HUGE_VAL 1.797693134862315e308
#endif /* HUGE */
#endif /*HUGE_VAL*/

# define FloatToUnsigned(f)      ((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L) + 1)

ConvertToIeeeExtended(num, bytes)
double num;
char *bytes;
{
    int    sign;
    int expon;
    double fMant, fsMant;
    unsigned long hiMant, loMant;

    if (num < 0) {
        sign = 0x8000;
        num *= -1;
    } else {
        sign = 0;
    }

    if (num == 0) {
        expon = 0; hiMant = 0; loMant = 0;
    }
    else {
        fMant = frexp(num, &expon);
        if ((expon > 16384) || !(fMant < 1)) {    /* Infinity or NaN */
            expon = sign|0x7FFF; hiMant = 0; loMant = 0; /* infinity */
        }
        else {    /* Finite */
            expon += 16382;
            if (expon < 0) {    /* denormalized */
                fMant = ldexp(fMant, expon);
                expon = 0;
            }
            expon |= sign;
            fMant = ldexp(fMant, 32);          
            fsMant = floor(fMant); 
            hiMant = FloatToUnsigned(fsMant);
            fMant = ldexp(fMant - fsMant, 32); 
            fsMant = floor(fMant); 
            loMant = FloatToUnsigned(fsMant);
        }
    }
    
    bytes[0] = expon >> 8;
    bytes[1] = expon;
    bytes[2] = hiMant >> 24;
    bytes[3] = hiMant >> 16;
    bytes[4] = hiMant >> 8;
    bytes[5] = hiMant;
    bytes[6] = loMant >> 24;
    bytes[7] = loMant >> 16;
    bytes[8] = loMant >> 8;
    bytes[9] = loMant;
}


/*
 * C O N V E R T   F R O M   I E E E   E X T E N D E D  
 */

/* 
 * Copyright (C) 1988-1991 Apple Computer, Inc.
 * All rights reserved.
 *
 * Machine-independent I/O routines for IEEE floating-point numbers.
 *
 * NaN's and infinities are converted to HUGE_VAL or HUGE, which
 * happens to be infinity on IEEE machines.  Unfortunately, it is
 * impossible to preserve NaN's in a machine-independent way.
 * Infinities are, however, preserved on IEEE machines.
 *
 * These routines have been tested on the following machines:
 *    Apple Macintosh, MPW 3.1 C compiler
 *    Apple Macintosh, THINK C compiler
 *    Silicon Graphics IRIS, MIPS compiler
 *    Cray X/MP and Y/MP
 *    Digital Equipment VAX
 *
 *
 * Implemented by Malcolm Slaney and Ken Turkowski.
 *
 * Malcolm Slaney contributions during 1988-1990 include big- and little-
 * endian file I/O, conversion to and from Motorola's extended 80-bit
 * floating-point format, and conversions to and from IEEE single-
 * precision floating-point format.
 *
 * In 1991, Ken Turkowski implemented the conversions to and from
 * IEEE double-precision format, added more precision to the extended
 * conversions, and accommodated conversions involving +/- infinity,
 * NaN's, and denormalized numbers.
 */

#ifndef HUGE_VAL
# define HUGE_VAL HUGE
#endif /*HUGE_VAL*/

# define UnsignedToFloat(u)         (((double)((long)(u - 2147483647L - 1))) + 2147483648.0)

/****************************************************************
 * Extended precision IEEE floating-point conversion routine.
 ****************************************************************/

double ConvertFromIeeeExtended(bytes)
unsigned char *bytes;	/* LCN */
{
    double    f;
    int    expon;
    unsigned long hiMant, loMant;
    
    expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF);
    hiMant    =    ((unsigned long)(bytes[2] & 0xFF) << 24)
            |    ((unsigned long)(bytes[3] & 0xFF) << 16)
            |    ((unsigned long)(bytes[4] & 0xFF) << 8)
            |    ((unsigned long)(bytes[5] & 0xFF));
    loMant    =    ((unsigned long)(bytes[6] & 0xFF) << 24)
            |    ((unsigned long)(bytes[7] & 0xFF) << 16)
            |    ((unsigned long)(bytes[8] & 0xFF) << 8)
            |    ((unsigned long)(bytes[9] & 0xFF));

    if (expon == 0 && hiMant == 0 && loMant == 0) {
        f = 0;
    }
    else {
        if (expon == 0x7FFF) {    /* Infinity or NaN */
            f = HUGE_VAL;
        }
        else {
            expon -= 16383;
            f  = ldexp(UnsignedToFloat(hiMant), expon-=31);
            f += ldexp(UnsignedToFloat(loMant), expon-=32);
        }
    }

    if (bytes[0] & 0x80)
        return -f;
    else
        return f;
}

