/*
	$Id: file.c,v 1.5 2001/09/03 18:53:11 rogue Exp $

	Copyright (c) 1999 - 2001 Xforge project
*/

#include <Xm/Xm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HPUX		/* HPUX wants both <string.h> and <strings.h> */
#include <strings.h>
#endif
#include "xforge.h"
#include "file.h"
#include "glob.h"
#include "bufutil.h"

/*
	mybasename(s) works like basename(); it's implemented to avoid
	compile problems
*/

static char *mybasename(char *s) {
	char *p;

	if ((p = strrchr(s, '/')))
		p++;
	else
		p = s;
	return p;
}

/*
	be_to_long returns unsigned 32 bit value of next 4 characters in
	char array given as argument
*/

static unsigned long be_to_long(unsigned char *s) {
	return (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
}

/*
	le_to_long returns unsigned 32 bit value of next 4 characters in
	char array given as argument
*/

static unsigned long le_to_long(unsigned char *s) {
	return s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24);
}

/*
	be_to_short returns unsigned 16 bit value of next 2 characters in
	char array given as argument
*/

static unsigned short be_to_short(unsigned char *s) {
	return (s[0] << 8) | s[1];
}

/*
	le_to_short returns unsigned 16 bit value of next 2 characters in
	char array given as argument
*/

static unsigned short le_to_short(unsigned char *s) {
	return s[0] | (s[1] << 8);
}

/*
	long_to_be writes unsigned 32 bit value to next 4 characters in
	char array given as argument
*/

static void long_to_be(unsigned char *s, unsigned long value) {
	s[0] = (value >> 24) & 0xff;
	s[1] = (value >> 16) & 0xff;
	s[2] = (value >> 8) & 0xff;
	s[3] = (value) & 0xff;
}

/*
	long_to_le writes unsigned 32 bit value to next 4 characters in
	char array given as argument
*/

static void long_to_le(unsigned char *s, unsigned long value) {
	s[0] = (value) & 0xff;
	s[1] = (value >> 8) & 0xff;
	s[2] = (value >> 16) & 0xff;
	s[3] = (value >> 24) & 0xff;
}

/*
	short_to_be writes unsigned 16 bit value to next 2 characters in
	char array given as argument
*/

static void short_to_be(unsigned char *s, unsigned long value) {
	s[0] = (value >> 8) & 0xff;
	s[1] = (value) & 0xff;
}

/*
	short_to_le writes unsigned 16 bit value to next 2 characters in
	char array given as argument
*/

static void short_to_le(unsigned char *s, unsigned long value) {
	s[0] = (value) & 0xff;
	s[1] = (value >> 8) & 0xff;
}

/*
	copy_le_8 copies channel-interleaved array of 8-bit integers to
	concatenated arrays of floats
*/

static void copy_le_8(float *q, signed char *p, int n, int s, int ch) {
	int i, j, skip;
	signed char *dp;

	skip = ch;
	for (i = 0; i < ch; i++) {
		dp = p + i;
		if (s)
			for (j = 0; j < n; j++)
				*q++ = (dp[j * skip]) / 128.0f;
		else
			for (j = 0; j < n; j++)
				*q++ = (((int)dp[j * skip] & 0xff) - 0x80) /
					128.0f;
	}
}

/*
	copy_le_16 copies channel-interleaved array of 16-bit little endian
	integers to concatenated arrays of floats
*/

static void copy_le_16(float *q, signed char *p, int n, int s, int ch) {
	int i, j, skip;
	signed char *dp;

	skip = ch * 2;
	for (i = 0; i < ch; i++) {
		dp = p + 2 * i;
		/* FIXME: note 0x100 underflow fix - what a hack! */
		for (j = 0; j < n; j++)
			*q++ = (dp[j * skip] + 256 * dp[j * skip + 1] -
				(s ? 0 : 32768) + (dp[j * skip] < 0 ?
				0x100 : 0)) / 32768.0f;
	}
}

/*
	copy_le_24 copies channel-interleaved array of 24-bit little endian
	integers to concatenated arrays of floats
*/

static void copy_le_24(float *q, signed char *p, int n, int s, int ch) {
	int i, j, skip;
	signed char *dp;

	skip = ch * 3;
	for (i = 0; i < ch; i++) {
		dp = p + 3 * i;
		/* FIXME: see above */
		for (j = 0; j < n; j++)
			*q++ = (dp[j * skip] + 256 * dp[j * skip + 1] +
				65536 * dp[j * skip + 2] - (s ? 0 : 8388608) +
				(dp[j * skip] < 0 ? 0x100 : 0) +
				(dp[j * skip + 1] < 0 ? 0x10000 : 0)) /
				8388608.0f;
	}
}

/*
	copy_le_32 copies channel-interleaved array of 32-bit little endian
	integers to concatenated arrays of floats
*/

static void copy_le_32(float *q, signed char *p, int n, int s, int ch) {
	int i, j, skip;
	signed char *dp;

	skip = ch * 3;
	for (i = 0; i < ch; i++) {
		dp = p + 4 * i;
		/* FIXME: see above */
		for (j = 0; j < n; j++) 
			*q++ = (dp[j * skip] + 256 * dp[j * skip + 1] +
				65536 * dp[j * skip + 2] + 16777216 *
				dp[j * skip + 3] - (s ? 0 : -I32WRAP) +
				(dp[j * skip] < 0 ? 0x100 : 0) +
				(dp[j * skip + 1] < 0 ? 0x10000 : 0) +
				(dp[j * skip + 2] < 0 ? 0x1000000 : 0)) /
				2147483648.0f;
	}
}

/*
	rawnotify() issues a warning that file will be loaded as raw.
*/

static void rawnotify(char *path) {
	char msgbuf[1024];

	sprintf(msgbuf, "%s: format not supported, loading as raw file\n",
		path);
	xferr(msgbuf);
}

static void open_raw(struct file_handle *f) {
	f->filef = FILEF_RAW;	/* assume 16 bit raw, little endian */
	f->data_offset = 0;
	f->data_size = f->file_size;
	f->rate = 44100;
	f->endianness = ENDIAN_LITTLE;
	f->bits = 16;
	f->issigned = 1;
	f->channels = 1;
}

static void open_sun(struct file_handle *f) {
	unsigned char scanbuf[24];

	/* the size of interesting portion of header is 24 bytes; clear the */
	/* scan buffer and read the header */
	memset(scanbuf, 0, sizeof(scanbuf));	/* in case header is shorter */
	rewind(f->fp);
	fread(scanbuf, 1, sizeof(scanbuf), f->fp);

	f->filef = FILEF_SUN;
	f->data_offset = be_to_long(&scanbuf[4]);
	f->data_size = be_to_long(&scanbuf[8]);
	f->rate = be_to_long(&scanbuf[16]);
	f->endianness = ENDIAN_BIG;
	switch (be_to_long(&scanbuf[12])) {
		case SND_FORMAT_LINEAR_8:
			f->bits = 8;
			break;
		case SND_FORMAT_LINEAR_16:
			f->bits = 16;
			break;
		case SND_FORMAT_LINEAR_24:
			f->bits = 24;
			break;
		case SND_FORMAT_LINEAR_32:
			f->bits = 32;
			break;
		default:
			rawnotify(f->path);
			open_raw(f);
			return;
	}
	f->issigned = 1;
	f->channels = be_to_long(&scanbuf[20]);
}

static void open_riff(struct file_handle *f, int endianness) {
	unsigned char scanbuf[0x2c];

	/* the size of interesting portion of header is 0x2c bytes; clear */
	/* the scan buffer and read the header */
	memset(scanbuf, 0, sizeof(scanbuf));	/* in case header is shorter */
	rewind(f->fp);
	fread(scanbuf, 1, sizeof(scanbuf), f->fp);

	if (endianness == ENDIAN_BIG)
		f->filef = FILEF_RIFX;
	else
		f->filef = FILEF_RIFF;
	/* format tag must be PCM */
	if (le_to_short(&scanbuf[0x14]) != 1) {
		rawnotify(f->path);
		open_raw(f);
		return;
	}
	f->data_offset = 0x2c;
	f->data_size = le_to_long(&scanbuf[0x28]);
	f->rate = le_to_long(&scanbuf[0x18]);
	f->endianness = endianness;
	f->bits = le_to_short(&scanbuf[0x22]);
	f->issigned = (f->bits == 8 ? 0 : 1);
	f->channels = le_to_short(&scanbuf[0x16]);
}

void open_form(struct file_handle *f) {
	char msgbuf[1024];
	unsigned char scanbuf[12], *commbuf;
	int comm_found = 0, ssnd_found = 0;
	unsigned long ckid, cklen;
	double rate;

	memset(scanbuf, 0, sizeof(scanbuf));	/* in case header is shorter */
	rewind(f->fp);
	fread(scanbuf, 1, sizeof(scanbuf), f->fp);

	/* in a FORM file, type should be in scanbuf[8] to scanbuf[11] */
	f->magic2 = be_to_long(&scanbuf[8]);
	if (f->magic2 == MAGIC_FORM_AIFF)
		f->filef = FILEF_AIFF;
	else if (f->magic2 == MAGIC_FORM_AIFC)
		f->filef = FILEF_AIFC;
	else {
		rawnotify(f->path);
		open_raw(f);
		return;
	}

	f->endianness = ENDIAN_BIG;
	f->issigned = 1;

	/* we now need to scan for COMM and SSND chunks in the file */
	/* all other chunks will be just skipped */
	while (!(comm_found && ssnd_found)) {
		fread(scanbuf, 1, 8, f->fp);
		ckid = be_to_long(&scanbuf[0]);
		cklen = be_to_long(&scanbuf[4]);
		switch (ckid) {
			case ckID_COMM:
				comm_found = 1;
				if (!(commbuf = malloc(cklen))) {
					/* eek. Loading as raw is probably */
					/* stupid but it is the only safe */
					/* way to exit this routine because */
					/* f fields need to be set [FIXME] */
					syserr(EMEM_MSG);
					rawnotify(f->path);
					open_raw(f);
					return;
				}
				fread(commbuf, 1, cklen, f->fp);
				if (f->filef == FILEF_AIFC) {
					/* compressionType MUST be "NONE" */
					if (be_to_long(&commbuf[18]) !=
						0x4e4f4e45) {
						rawnotify(f->path);
						open_raw(f);
						return;
					}
				}
				f->channels = be_to_short(&commbuf[0]);
				f->bits = be_to_short(&commbuf[6]);
				/* read the sample rate */
				rate = ConvertFromIeeeExtended(&commbuf[8]);
				f->rate = (int)rate;
				free(commbuf);
				break;
			case ckID_SSND:
				ssnd_found = 1;
				/* SSND chunk has offset and blockSize, skip */
				f->data_offset = ftell(f->fp) + 8;
				f->data_size = cklen - 8;
				fseek(f->fp, cklen, SEEK_CUR);
				break;
			default:
				fseek(f->fp, cklen, SEEK_CUR);
				break;
		}
	}
}

/*
	io_openfile(buf, path) will scan the file path header and convert the
	file to floating point array depending on header description. It is
	not perfect and can fail on broken files, in which case segmentation
	fault is not far away [FIXME].
*/

int io_openfile(struct buffer *buf, char *path) {
	struct stat statbuf;
	int i;
	signed char *p, tmp;
	float *q;
	char msgbuf[1024];
	unsigned char scanbuf[4];	/* space for magic */
	struct file_handle f;

	/* clear the scan buffer */
	memset(scanbuf, 0, sizeof(scanbuf));

	f.path = path;
	if (stat(path, &statbuf) == -1) {
		syserr(path);
		return -1;
	}

	f.file_size = statbuf.st_size;

	if (!(f.fp = fopen(path, "rb"))) {
		syserr(path);
		return -1;
	}

	/* resolve file type */
	if (f.file_size < 4) {		/* too small for magic */
		open_raw(&f);
	} else {
		fread(scanbuf, 1, sizeof(scanbuf), f.fp);
		rewind(f.fp);
		f.magic = be_to_long(scanbuf);
		switch (f.magic) {
			case MAGIC_SUN:
				open_sun(&f);
				break;
			case MAGIC_RIFF:
				open_riff(&f, ENDIAN_LITTLE);
				break;
			case MAGIC_RIFX:
				open_riff(&f, ENDIAN_BIG);
				break;
			case MAGIC_FORM:
				open_form(&f);
				break;
			default:
				open_raw(&f);
		}
	}

	/* go to beginning of data */
	fseek(f.fp, f.data_offset, SEEK_SET);

	if (!(p = malloc(f.data_size))) {
		syserr(EMEM_MSG);
		fclose(f.fp);
		return -1;
	}

	if (f.data_size != fread(p, 1, f.data_size, f.fp)) {
		syserr(path);
	}

	f.samples = f.data_size / (f.bits / 8);
	f.datalen = f.samples / f.channels;

	if (!(q = malloc(f.samples * sizeof(float)))) {
		syserr(EMEM_MSG);
		free(p);
		fclose(f.fp);
		return -1;
	}

	/* convert the buffer to little endian */
	if (f.endianness == ENDIAN_BIG) {
		switch (f.bits) {
			case 16:
				for (i = 0; i < f.data_size; i = i + 2) {
					tmp = p[i];
					p[i] = p[i + 1];
					p[i + 1] = tmp;
				}
				break;
			case 24:
				for (i = 0; i < f.data_size; i = i + 3) {
					tmp = p[i];
					p[i] = p[i + 1];
					p[i + 1] = p[i + 2];
					p[i + 2] = tmp;
				}
				break;
			case 32:
				for (i = 0; i < f.data_size; i = i + 4) {
					tmp = p[i];
					p[i] = p[i + 1];
					p[i + 1] = p[i + 2];
					p[i + 2] = p[i + 3];
					p[i + 3] = tmp;
				}
				break;
		}
	}

	switch (f.bits) {
		case 8:
			copy_le_8(q, p, f.datalen, f.issigned, f.channels);
			break;
		case 16:
			copy_le_16(q, p, f.datalen, f.issigned, f.channels);
			break;
		case 24:
			copy_le_24(q, p, f.datalen, f.issigned, f.channels);
			break;
		case 32:
			copy_le_32(q, p, f.datalen, f.issigned, f.channels);
			break;
	}

	free(p);
	fclose(f.fp);

	buf->data = q;
	buf->len = f.datalen;
	buf->dispstart = 0;
	buf->dispend = buf->len - 1;
	if (buf->path)
		free(buf->path);
	buf->path = strdup(path);
	setbufname(buf, mybasename(path));
	XmStringFree(buf->xname);
	buf->xname = XmStringCreateLocalized(buf->name);
	buf->filef = f.filef;
	buf->channels = f.channels;
	buf->rate = f.rate;
	buf->bits = f.bits;
	buf->mod = 0;
	buf->selmask = 0;
	buf->selstart = 0;
	buf->selend = 0;
	return 0;
}

/* FIXME: quad channel samples are not yet implemented */

static void write_data(FILE *fp, struct buffer *buf, int endianness,
	int issigned) {
	int pmode = 0, i;
	signed int sample;
	float *p, *q, *r, *s, fs;

	p = buf->data;
	q = p + buf->len;
	r = q + buf->len;
	s = r + buf->len;

	switch (buf->channels) {
		case 1:
			pmode = PMODE_MONO;
			break;
		case 2:
			pmode = PMODE_STEREO;
			break;
		case 4:
			pmode = PMODE_QUAD;
			xferr("quad channel saving not implemented");
			break;
	}

	switch (buf->bits) {
		case 8:
			pmode |= PMODE_8;
			break;
		case 16:
			pmode |= PMODE_16;
			break;
		case 24:
			pmode |= PMODE_24;
			break;
		case 32:
			pmode |= PMODE_32;
			break;
	}

	switch (endianness) {
		case ENDIAN_LITTLE:
			switch (pmode) {
				case PMODE_M8:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 128.0f + 0.5f;
						sample = (int)fs;
						if (sample > 127)
							sample = 127;
						if (sample < -128)
							sample = -128;
						sample += issigned ? 0 : 128;
						fputc(sample, fp);
					}
					break;
				case PMODE_M16:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 32768.0f;
						sample = (int)fs;
						if (sample > 32767)
							sample = 32767;
						if (sample < -32768)
							sample = -32768;
						sample += issigned ? 0 :
							32768;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
					}
					break;
				case PMODE_M24:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 8388608.0f;
						sample = (int)fs;
						if (sample > 8388607)
							sample = 8388607;
						if (sample < -8388608)
							sample = -8388608;
						sample += issigned ? 0 :
							8388608;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
					}
					break;
				case PMODE_M32:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 2147483648.0f;
						sample = (int)fs;
						if (fs > 2147483647.0f)
							sample = 2147483647;
						if (fs < -2147483648.0f)
							sample = -I32WRAP;
						sample += issigned ? 0 :
							I32WRAP;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 24 & 0xff, fp);
					}
					break;
				case PMODE_S8:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 128.0f + 0.5f;
						sample = (int)fs;
						if (sample > 127)
							sample = 127;
						if (sample < -128)
							sample = -128;
						sample += issigned ? 0 : 128;
						fputc(sample, fp);
						fs = *q++ * 128.0f + 0.5f;
						sample = (int)fs;
						if (sample > 127)
							sample = 127;
						if (sample < -128)
							sample = -128;
						sample += issigned ? 0 : 128;
						fputc(sample, fp);
					}
					break;
				case PMODE_S16:
					for (i = 0; i < buf->len; i++) {							fs = *p++ * 32768.0f;
						sample = (int)fs;
						if (sample > 32767)
							sample = 32767;
						if (sample < -32768)
							sample = -32768;
						sample += issigned ? 0 :
							32768;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fs = *q++ * 32768.0f;
						sample = (int)fs;
						if (sample > 32767)
							sample = 32767;
						if (sample < -32768)
							sample = -32768;
						sample += issigned ? 0 :
							32768;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
				}
					break;
				case PMODE_S24:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 8388608.0f;
						sample = (int)fs;
						if (sample > 8388607)
							sample = 8388607;
						if (sample < -8388608)
							sample = -8388608;
						sample += issigned ? 0 :
							8388608;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
						fs = *q++ * 8388608.0f;
						sample = (int)fs;
						if (sample > 8388607)
							sample = 8388607;
						if (sample < -8388608)
							sample = -8388608;
						sample += issigned ? 0 :
							8388608;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
					}
					break;
				case PMODE_S32:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 2147483648.0f;
						sample = (int)fs;
						if (fs > 2147483647.0f)
							sample = 2147483647;
						if (fs < -2147483648.0f)
							sample = -I32WRAP;
						sample += issigned ? 0 :
							I32WRAP;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 24 & 0xff, fp);
						fs = *q++ * 2147483648.0f;
						sample = (int)fs;
						if (fs > 2147483647.0f)
							sample = 2147483647;
						if (fs < -2147483648.0f)
							sample = -I32WRAP;
						sample += issigned ? 0 :
							I32WRAP;
						fputc(sample & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 24 & 0xff, fp);
					}
					break;
			}
			break;
		case ENDIAN_BIG:
			switch (pmode) {
				case PMODE_M8:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 128.0f + 0.5f;
						fs += issigned ? 0.0f : 128.0f;
						sample = (int)fs;
						if (sample > 127)
							sample = 127;
						if (sample < -128)
							sample = -128;
						fputc(sample, fp);
					}
					break;
				case PMODE_M16:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 32768.0f;
						sample = (int)fs;
						if (sample > 32767)
							sample = 32767;
						if (sample < -32768)
							sample = -32768;
						sample += issigned ? 0 :
							32768;
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
					}
					break;
				case PMODE_M24:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 8388608.0f;
						sample = (int)fs;
						if (sample > 8388607)
							sample = 8388607;
						if (sample < -8388608)
							sample = -8388608;
						sample += issigned ? 0 :
							8388608;
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
					}
					break;
				case PMODE_M32:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 2147483648.0f;
						sample = (int)fs;
						if (fs > 2147483647.0f)
							sample = 2147483647;
						if (fs < -2147483648.0f)
							sample = -I32WRAP;
						sample += issigned ? 0 :
							I32WRAP;
						fputc(sample >> 24 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
					}
					break;
				case PMODE_S8:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 128.0f + 0.5f;
						sample = (int)fs;
						if (sample > 127)
							sample = 127;
						if (sample < -128)
							sample = -128;
						sample += issigned ? 0 : 128;
						fputc(sample, fp);
						fs = *q++ * 128.0f + 0.5f;
						sample = (int)fs;
						if (sample > 127)
							sample = 127;
						if (sample < -128)
							sample = -128;
						sample += issigned ? 0 : 128;
						fputc(sample, fp);
					}
					break;
				case PMODE_S16:
					for (i = 0; i < buf->len; i++) {							fs = *p++ * 32768.0f;
						sample = (int)fs;
						if (sample > 32767)
							sample = 32767;
						if (sample < -32768)
							sample = -32768;
						sample += issigned ? 0 :
							32768;
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
						fs = *q++ * 32768.0f;
						sample = (int)fs;
						if (sample > 32767)
							sample = 32767;
						if (sample < -32768)
							sample = -32768;
						sample += issigned ? 0 :
							32768;
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
				}
					break;
				case PMODE_S24:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 8388608.0f;
						sample = (int)fs;
						if (sample > 8388607)
							sample = 8388607;
						if (sample < -8388608)
							sample = -8388608;
						sample += issigned ? 0 :
							8388608;
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
						fs = *q++ * 8388608.0f;
						sample = (int)fs;
						if (sample > 8388607)
							sample = 8388607;
						if (sample < -8388608)
							sample = -8388608;
						sample += issigned ? 0 :
							8388608;
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
					}
					break;
				case PMODE_S32:
					for (i = 0; i < buf->len; i++) {
						fs = *p++ * 2147483648.0f;
						sample = (int)fs;
						if (fs > 2147483647.0f)
							sample = 2147483647;
						if (fs < -2147483648.0f)
							sample = -I32WRAP;
						sample += issigned ? 0 :
							I32WRAP;
						fputc(sample >> 24 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
						fs = *q++ * 2147483648.0f;
						sample = (int)fs;
						if (fs > 2147483647.0f)
							sample = 2147483647;
						if (fs < -2147483648.0f)
							sample = -I32WRAP;
						sample += issigned ? 0 :
							I32WRAP;
						fputc(sample >> 24 & 0xff, fp);
						fputc(sample >> 16 & 0xff, fp);
						fputc(sample >> 8 & 0xff, fp);
						fputc(sample & 0xff, fp);
					}
					break;
			}
			break;
	}
}

int io_savefile(struct buffer *buf, char *path) {
	FILE *fp;
	unsigned char header[64];	/* AIFF-C header size */
	int datasize, issigned = 1, endianness = ENDIAN_LITTLE;

	if (!(fp = fopen(path, "wb"))) {
		syserr(path);
		return -1;
	}

	datasize = buf->channels * buf->len;

	switch (buf->filef) {
		case FILEF_SUN:
			long_to_be(&header[0], MAGIC_SUN);
			long_to_be(&header[4], 24);
			long_to_be(&header[8], datasize * buf->bits / 8);
			switch (buf->bits) {
				case 8:
					long_to_be(&header[12],
						SND_FORMAT_LINEAR_8);
					break;
				case 16:
					long_to_be(&header[12],
						SND_FORMAT_LINEAR_16);
					break;
				case 24:
					long_to_be(&header[12],
						SND_FORMAT_LINEAR_24);
					break;
				case 32:
					long_to_be(&header[12],
						SND_FORMAT_LINEAR_32);
					break;
			}
			long_to_be(&header[16], buf->rate);
			long_to_be(&header[20], buf->channels);
			if (fwrite(header, 24, 1, fp) != 1) {
				syserr(path);
				fclose(fp);
				return -1;
			}
			issigned = 1;
			endianness = ENDIAN_BIG;
			break;
		case FILEF_RIFF:
			long_to_be(&header[0x0], MAGIC_RIFF);
			/* exclude ckid and size */
			long_to_le(&header[0x4], datasize * buf->bits / 8
				+ 0x2c - 8);
			/* "WAVE" */
			long_to_be(&header[0x8], 0x57415645);
			/* "fmt " */
			long_to_be(&header[0xc], 0x666d7420);
			/* fmt ck size */
			long_to_le(&header[0x10], 0x10);
			/* fmt tag: PCM */
			short_to_le(&header[0x14], 0x01);
			/* # channels */
			short_to_le(&header[0x16], buf->channels);
			/* sample rate */
			long_to_le(&header[0x18], buf->rate);
			/* avg bytes per second */
			long_to_le(&header[0x1c], buf->rate * buf->channels *
				buf->bits / 8);
			/* bytes / sample */
			short_to_le(&header[0x20], buf->channels * buf->bits /
				8);
			/* bits / sample */
			short_to_le(&header[0x22], buf->bits);
			/* "data" */
			long_to_be(&header[0x24], 0x64617461);
			/* data length */
			long_to_le(&header[0x28], datasize * buf->bits / 8);
			if (fwrite(header, 0x2c, 1, fp) != 1) {
				syserr(path);
				fclose(fp);
				return -1;
			}
			issigned = (buf->bits == 8 ? 0 : 1);
			endianness = ENDIAN_LITTLE;
			break;
		case FILEF_RIFX:
			long_to_be(&header[0x0], MAGIC_RIFX);
			/* exclude ckid and size */
			long_to_le(&header[0x4], datasize * buf->bits / 8
				+ 0x2c - 8);
			/* "WAVE" */
			long_to_be(&header[0x8], 0x57415645);
			/* "fmt " */
			long_to_be(&header[0xc], 0x666d7420);
			/* fmt ck size */
			long_to_le(&header[0x10], 0x10);
			/* fmt tag: PCM */
			short_to_le(&header[0x14], 0x01);
			/* # channels */
			short_to_le(&header[0x16], buf->channels);
			/* sample rate */
			long_to_le(&header[0x18], buf->rate);
			/* avg bytes per second */
			long_to_le(&header[0x1c], buf->rate * buf->channels *
				buf->bits / 8);
			/* bytes / sample */
			short_to_le(&header[0x20], buf->channels * buf->bits /
				8);
			/* bits / sample */
			short_to_le(&header[0x22], buf->bits);
			/* "data" */
			long_to_be(&header[0x24], 0x64617461);
			/* data length */
			long_to_le(&header[0x28], datasize * buf->bits / 8);
			if (fwrite(header, 0x2c, 1, fp) != 1) {
				syserr(path);
				fclose(fp);
				return -1;
			}
			issigned = (buf->bits == 8 ? 0 : 1);
			endianness = ENDIAN_BIG;
			break;
		case FILEF_AIFF:
			long_to_be(&header[0], MAGIC_FORM);
			long_to_be(&header[4], datasize * buf->bits / 8 + 46);
			long_to_be(&header[8], MAGIC_FORM_AIFF);
			long_to_be(&header[12], ckID_COMM);
			long_to_be(&header[16], 18);
			short_to_be(&header[20], buf->channels);
			long_to_be(&header[22], buf->len);
			short_to_be(&header[26], buf->bits);
			ConvertToIeeeExtended((double)(buf->rate),
				(char *)(&header[28]));
			long_to_be(&header[38], ckID_SSND);
			long_to_be(&header[42], datasize * buf->bits / 8 + 8);
			long_to_be(&header[46], 0);	/* offset */
			long_to_be(&header[50], 0);	/* blockSize */
			if (fwrite(header, 54, 1, fp) != 1) {
				syserr(path);
				fclose(fp);
				return -1;
			}
			issigned = 1;
			endianness = ENDIAN_BIG;
			break;
		case FILEF_AIFC:
			long_to_be(&header[0], MAGIC_FORM);
			long_to_be(&header[4], datasize * buf->bits / 8 + 56);
			long_to_be(&header[8], MAGIC_FORM_AIFC);
			long_to_be(&header[12], ckID_COMM);
			long_to_be(&header[16], 28);
			short_to_be(&header[20], buf->channels);
			long_to_be(&header[22], buf->len);
			short_to_be(&header[26], buf->bits);
			ConvertToIeeeExtended((double)(buf->rate),
				(char *)(&header[28]));
			long_to_be(&header[38], 0x4e4f4e45);	/* "NONE" */
			long_to_be(&header[42], 0x044e6f6e);	/* 4, "Non" */
			short_to_be(&header[46], 0x6500);	/* "e", pad */
			long_to_be(&header[48], ckID_SSND);
			long_to_be(&header[52], datasize * buf->bits / 8 + 8);
			long_to_be(&header[56], 0);	/* offset */
			long_to_be(&header[60], 0);	/* blockSize */
			if (fwrite(header, 64, 1, fp) != 1) {
				syserr(path);
				fclose(fp);
				return -1;
			}
			issigned = 1;
			endianness = ENDIAN_BIG;
			break;
		case FILEF_RAW:
			issigned = 1;
			endianness = ENDIAN_LITTLE;
			break;
	}

	write_data(fp, buf, endianness, issigned);

	if (buf->path)
		free(buf->path);
	buf->path = strdup(path);
	setbufname(buf, mybasename(path));
	XmStringFree(buf->xname);
	buf->xname = XmStringCreateLocalized(buf->name);
	buf->mod = 0;

	fclose(fp);
	return 0;
}
