#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PAT2RAW_LITTLE_ENDIAN 1234
#define PAT2RAW_BIG_ENDIAN 4321


/* --- uncomment one of these, the 1st for i386, the 2nd for ppc :) */
#define PAT2RAW_BYTE_ORDER PAT2RAW_LITTLE_ENDIAN
/* #define PAT2RAW_BYTE_ORDER PAT2RAW_BIG_ENDIAN */


#if !defined(PAT2RAW_BYTE_ORDER)
#if defined(__NetBSD__)
#if _BYTE_ORDER == _LITTLE_ENDIAN
#define PAT2RAW_BYTE_ORDER PAT2RAW_LITTLE_ENDIAN
#else /* _BIG_ENDIAN */
#define PAT2RAW_BYTE_ORDER PAT2RAW_BIG_ENDIAN
#endif
#else /* !defined(__NetBSD__) */
#error Please define PAT2RAW_BYTE_ORDER based on your architecture.
#endif
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif

#define buffer_size 1024
#define XCHG_SHORT(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
#define XCHG_LONG(x) ((((x)&0xFF)<<24) | \
	(((x)&0xFF00)<<8) | \
	(((x)&0xFF0000)>>8) | \
	(((x)>>24)&0xFF))
#if PAT2RAW_BYTE_ORDER == PAT2RAW_LITTLE_ENDIAN
#define LE_SHORT(x) (x)
#define LE_LONG(x) (x)
#define BE_SHORT(x) XCHG_SHORT(x)
#define BE_LONG(x) XCHG_LONG(x)
#else /* _BIG_ENDIAN */
#define BE_SHORT(x) (x)
#define BE_LONG(x) (x)
#define LE_SHORT(x) XCHG_SHORT(x)
#define LE_LONG(x) XCHG_LONG(x)
#endif
#define RDZ0RZ(x, y) \
	if (x != read(fd, y, x)) { \
		printf("Error reading sample %d\n", i); \
		exit(1); \
	}
#define SKIPZ0RZ(x) \
	if (-1 == lseek(fd, x, SEEK_CUR)) { \
		printf("Seek error reading sample %d\n", i); \
		exit(1); \
	}
#define PRNZ0RZ(x, y) \
	snprintf(tmp, buffer_size - 1, x, y); \
	tmp[buffer_size - 1] = '\000'; \
	write(ofd, tmp, strlen(tmp))

#define READ_CHAR(thing) RDZ0RZ(1, &thing);
#define READ_SHORT(thing) RDZ0RZ(2, &tmpshort); thing = LE_SHORT(tmpshort)
#define READ_LONG(thing) RDZ0RZ(4, &tmplong); thing = LE_LONG(tmplong)

#define MODES_16BIT     (1<<0)
#define MODES_UNSIGNED  (1<<1)
#define MODES_LOOPING   (1<<2)
#define MODES_PINGPONG  (1<<3)
#define MODES_REVERSE   (1<<4)
#define MODES_SUSTAIN   (1<<5)
#define MODES_ENVELOPE  (1<<6)
#define MODES_CLAMPED   (1<<7) /* ?? (for last envelope??) */

char *basename(char *name)
{
	int l = strlen(name);
	char *retval = strdup(name);

	if (retval[l - 4] == '.') {
		retval[l - 4] = '\000';
	}

	return retval;
}

int split_gus_instrument(char *name)
{
	unsigned char tmp[buffer_size];
	int fd;
	int ofd;
	int sample_count;
	int i;
	char *bname;

	unsigned int tmplong;
	unsigned short int tmpshort;

	char wave_name[8];
	unsigned char fractions;
	unsigned int data_length;
	unsigned int loop_start;
	unsigned int loop_end;
	unsigned int sample_rate;
	unsigned short int low_freq;
	unsigned int high_freq;
	unsigned int root_freq;
	unsigned short int tuning;
	unsigned char panning;
	unsigned char tremolo_sweep;
	unsigned char tremolo_rate;
	unsigned char vibrato_sweep;
	unsigned char vibrato_rate;
	unsigned char vibrato_depth;
	unsigned char modes;
	void *data;

	if (!name) {
		printf("eh?  what's that filename again?\n");
		exit(1);
	}

	fd = open(name, O_RDONLY|O_BINARY);
	if (fd == -1) {
		perror("Can't open file");
		exit(errno);
	}

	/* Read some headers and do cursory sanity checks. There are loads
	of magic offsets. This could be rewritten... */

	if ((239 != read(fd, tmp, 239)) ||
		(memcmp(tmp, "GF1PATCH110\0ID#000002", 22) &&
		memcmp(tmp, "GF1PATCH100\0ID#000002", 22)))
		/* don't know what the differences are */
	{
		printf("%s: not an instrument", name);
		close(fd);
		exit(1);
	}

	if (tmp[82] != 1 && tmp[82] != 0)
		/* instruments. To some patch makers, 0 means 1 */
	{
		printf("Can't handle patches with %d instruments", tmp[82]);
		close(fd);
		exit(1);
	}

	if (tmp[151] != 1 && tmp[151] != 0) /* layers. What's a layer? */
	{
		printf("Can't handle instruments with %d layers", tmp[151]);
		close(fd);
		exit(1);
	}

	bname = basename(name);

	sample_count = tmp[198];
	for (i = 0; i < sample_count; i++)
	{
		memset(wave_name, 0, sizeof(wave_name));
		RDZ0RZ(7, wave_name);
		READ_CHAR(fractions);
		READ_LONG(data_length); /* in bytes */
		READ_LONG(loop_start); /* in bytes */
		READ_LONG(loop_end); /* in bytes */
		READ_SHORT(sample_rate);
		READ_LONG(low_freq);
		READ_LONG(high_freq);
		READ_LONG(root_freq);
		READ_SHORT(tuning);
		READ_CHAR(panning);
		SKIPZ0RZ(13);
		READ_CHAR(tremolo_sweep);
		READ_CHAR(tremolo_rate);
		READ_CHAR(vibrato_sweep);
		READ_CHAR(vibrato_rate);
		READ_CHAR(vibrato_depth);
		READ_CHAR(modes);
		SKIPZ0RZ(40);
			/* skip the useless scale frequency, scale factor
			(what's it mean?), and reserved space */
		data = malloc(data_length + 2);
		RDZ0RZ(data_length, data); /* little endian data */

		sprintf(tmp, "%s-%d.txt", bname, i);
		ofd = open(tmp, O_CREAT|O_WRONLY, 0666);
		if (ofd == -1) {
			perror("Could not open output file.");
			exit(errno);
		}
		PRNZ0RZ("source: %s\n", name);
		PRNZ0RZ("name: %s\n", wave_name);
		PRNZ0RZ("fractions: %d\n", fractions);
		PRNZ0RZ("data_length: %d\n", data_length);
		PRNZ0RZ("loop_start: %d\n", loop_start);
		PRNZ0RZ("loop_end: %d\n", loop_end);
		PRNZ0RZ("sample_rate: %d\n", sample_rate);
		PRNZ0RZ("low_freq: %d\n", low_freq);
		PRNZ0RZ("high_freq: %d\n", high_freq);
		PRNZ0RZ("root_freq: %d\n", root_freq);
		PRNZ0RZ("tuning: %d\n", tuning);
		PRNZ0RZ("panning: %d\n", panning);
		PRNZ0RZ("tremolo_sweep: %d\n", tremolo_sweep);
		PRNZ0RZ("tremolo_rate: %d\n", tremolo_rate);
		PRNZ0RZ("vibrato_sweep: %d\n", vibrato_sweep);
		PRNZ0RZ("vibrato_rate: %d\n", vibrato_rate);
		PRNZ0RZ("vibrato_depth: %d\n", vibrato_depth);
		PRNZ0RZ("modes_16bit: %d\n", 0 != (modes & MODES_16BIT));
		PRNZ0RZ("modes_unsigned: %d\n", 0 != (modes & MODES_UNSIGNED));
		PRNZ0RZ("modes_looping: %d\n", 0 != (modes & MODES_LOOPING));
		PRNZ0RZ("modes_pingpong: %d\n", 0 != (modes & MODES_PINGPONG));
		PRNZ0RZ("modes_reverse: %d\n", 0 != (modes & MODES_REVERSE));
		PRNZ0RZ("modes_sustain: %d\n", 0 != (modes & MODES_SUSTAIN));
		PRNZ0RZ("modes_envelope: %d\n", 0 != (modes & MODES_ENVELOPE));
		PRNZ0RZ("modes_clamped: %d\n", 0 != (modes & MODES_CLAMPED));
		snprintf(tmp, buffer_size - 1,
			"convert_to_wav: sox %s %s %s-r %d %s-%d.raw %s-%d.wav\n",
			(0 == (modes & MODES_16BIT)) ? "-b" : "-w",
			(0 == (modes & MODES_UNSIGNED)) ? "-s" : "-u",
			(PAT2RAW_BYTE_ORDER == PAT2RAW_LITTLE_ENDIAN) ?
				"" : "-x ",
			sample_rate,
			bname,
			i,
			bname,
			i);
		write(ofd, tmp, strlen(tmp));
		close(ofd);

		sprintf(tmp, "%s-%d.raw", bname, i);
		ofd = open(tmp, O_CREAT|O_WRONLY|O_BINARY, 0666);
		if (ofd == -1) {
			perror("Could not open output file.");
			exit(errno);
		}
		write(ofd, data, data_length);
		close(ofd);

		free(data);
	}
	close(fd);
	return 1;
}

int main(int argc, char *argv[])
{
	if (argc < 2 || argc > 3) {
		printf("\nUsage: %s [GUS .PAT file]\n\n"
"Splits a GUS .PAT file into individual audio .raw files and .txt files.\n"
"pat2raw v1.1 is written by Ben Collver <collver@peak.org> and is based\n"
"on timidity.  See also: wav2pat.\n"
"http://terrorpin.net/~ben/docs/alt/music/converters/pat2raw.c\n\n",
			argv[0]);
		exit(1);
	}
	split_gus_instrument(argv[1]);
	exit(0);
}
