/*
 * tfmx.c
 * File loader and UI for TFMX player.
 * jhp 29Feb96
 */

#include <asm/byteorder.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "tfmxsong.h"
#include "player.h"
#include <sys/stat.h>
#include <fcntl.h>

U32 outRate=44100;
extern int force8;

struct Hdr hdr;
extern struct Hdb hdb[8];
extern struct Pdblk pdb;
extern int LoopOff();
extern struct Mdb mdb;

extern char act[8];

char outf[128]="/dev/dsp";
unsigned int mlen;
U32 editbuf[16384];
U8 *smplbuf;
int *macros;
int *patterns;
short ts[512][8];

int num_ts,num_pat,num_mac;

int songnum=0;
int gubed=0;
int printinfo=0;
int startPat=-1;
int gemx=0;
int loops=0;
extern int blend,filt,over;

usage(char *x)
{
	fprintf(stderr,"tfmxplay v0.2 by Jon Pickard <marxmarv@antigates.com>,
Neochrome <David.Banz@smail.inf.fh-rhein-sieg.de> and others.
Copyright 1996-2000, see accompanying README for details.

Usage: %s [options] mdat-file [smpl-file]
where options is one or more of:
-b mode		set stereo mode (0=mono, default 1=headphone, 2=stereo)
-8		generate 8-bit output
-p num		subsong to play (default 0)
-f freq		suggest playback rate in samples/sec (default 44100)
-o file		write audio output to file (default /dev/dsp)
-i		print info about the module (text, subsong, etc.)
-w num		set low-pass filter frequency (0=none, 3=lowest, default 0)
-l num		set number of loops through (default 0=infinite, 1=no repeat)
-v              disable oversampling (=linear interpolation)
",x
);

}

dump_macro(int *a)
{
	int x=0,y,s=0;
	while (((x&0xff000000)!=(0x07000000))&&(s<511)) {
		x=ntohl(a[s]);
		printf("%04x: %08x ",s,x);
		puts("");
		s++;
	}
}

int load_tfmx(char *mfn,char *sfn)
{
	FILE *gfd;
	struct stat s;
	int x,y,z=0;
	U16 *sh,*lg;

	if ((gfd=fopen(mfn,"r"))<=0) {
		perror("fopen");
		return(1);
	}
	
	if (!fread(&hdr,sizeof(hdr),1,gfd)) {
		perror("fread");
		fclose(gfd);
		return(1);
	}
	if (strncmp("TFMX-SONG",hdr.magic,9)&&
	    strncmp("TFMX_SONG",hdr.magic,9)&&
	    strncasecmp("TFMXSONG",hdr.magic,8) &&
	    strncmp("TFMX",hdr.magic,4)) {
		fclose(gfd);
		return(2);
	}
		
	if (!(x=fread(&editbuf,sizeof(int),16384,gfd))) {
		perror("fread");
		fclose(gfd);
		return(1);
	}

	fclose(gfd);

	mlen=x;

	editbuf[x]=-1;
	if (!hdr.trackstart) hdr.trackstart=0x180; else
	hdr.trackstart=(ntohl(hdr.trackstart)-0x200)>>2;
	if (!hdr.pattstart) hdr.pattstart=0x80; else
	hdr.pattstart=(ntohl(hdr.pattstart)-0x200)>>2;
	if (!hdr.macrostart) hdr.macrostart=0x100; else
	hdr.macrostart=(ntohl(hdr.macrostart)-0x200)>>2;
	if (x<136) {
		return(2);
	}

	for (x=0;x<32;x++) {
		hdr.start[x]=ntohs(hdr.start[x]);
		hdr.end[x]=ntohs(hdr.end[x]);
		hdr.tempo[x]=ntohs(hdr.tempo[x]);
	}

/* Now that we have pointers to most everything, this would be a good time to
   fix everything we can... ntohs tracksteps, convert pointers to array
   indices, ntohl patterns and macros.  We fix the macros first, then the
   patterns, and then the tracksteps (because we have to know when the
   patterns begin to know when the tracksteps end...) */
	z=hdr.macrostart;
	macros = &editbuf[z];
	
	for (x=0;x<128;x++) {
		y=(ntohl(editbuf[z])-0x200);
		if ((y&3)||((y>>2)>mlen)) /* probably not strictly right */
			break;
		editbuf[z++]=y>>2;
	}
	num_mac=x;

	z=hdr.pattstart;
	patterns = &editbuf[z];
	for (x=0;x<128;x++) {
		y=(ntohl(editbuf[z])-0x200);
		if ((y&3)||((y>>2)>mlen)) 
			break;
		editbuf[z++]=y>>2;
	}
	num_pat=x;

	lg=(U16 *)&editbuf[patterns[0]];
	sh=(U16 *)&editbuf[hdr.trackstart];
	num_ts=(patterns[0]-hdr.trackstart)>>2;
	y=0;
	while (sh<lg) {
		x=ntohs(*sh);
		*sh++=x;
	}	

/* Now at long last we load the sample file. */

	if ((y=open(sfn,O_RDONLY))<=0) {
		perror("fopen");
		return(1);
	}
	if (fstat(y,&s)) {
		perror("fstat");
		close(y);
		return(1);
	}
	if (!(smplbuf=(void *)malloc(s.st_size))) {
		perror("malloc");
		close(y);
		return(1);
	}
	if (!read(y,smplbuf,s.st_size)) {
		perror("read");
		close(y);
		free(smplbuf);
		return(1);
	}
	close(y);
	return (0);
}


char *pattcmds[]={
"End --Next track  step--",
"Loop[count     / step.w]",
"Cont[patternno./ step.w]",
"Wait[count 00-FF--------",
"Stop--Stop this pattern-",
"Kup^-Set key up/channel]",
"Vibr[speed     / rate.b]",
"Enve[speed /endvolume.b]",
"GsPt[patternno./ step.w]",
"RoPt-Return old pattern-",
"Fade[speed /endvolume.b]",
"PPat[patt./track+transp]",
"Lock---------ch./time.b]",
"----------No entry------",
"Stop-Stop custompattern-",
"NOP!-no operation-------"
};

char *macrocmds[]={
"DMAoff+Resetxx/xx/xx flag/addset/vol   ",
"DMAon (start sample at selected begin) ",
"SetBegin    xxxxxx   sample-startadress",
"SetLen      ..xxxx   sample-length     ",
"Wait        ..xxxx   count (VBI''s)     ",
"Loop        xx/xxxx  count/step        ",
"Cont        xx/xxxx  macro-number/step ",
"-------------STOP----------------------",
"AddNote     xx/xxxx  note/detune       ",
"SetNote     xx/xxxx  note/detune       ",
"Reset   Vibrato-Portamento-Envelope    ",
"Portamento  xx/../xx count/speed       ",
"Vibrato     xx/../xx speed/intensity   ",
"AddVolume   ....xx   volume 00-3F      ",
"SetVolume   ....xx   volume 00-3F      ",
"Envelope    xx/xx/xx speed/count/endvol",
"Loop key up xx/xxxx  count/step        ",
"AddBegin    xx/xxxx  count/add to start",
"AddLen      ..xxxx   add to sample-len ",
"DMAoff stop sample but no clear        ",
"Wait key up ....xx   count (VBI''s)     ",
"Go submacro xx/xxxx  macro-number/step ",
"--------Return to old macro------------",
"Setperiod   ..xxxx   DMA period        ",
"Sampleloop  ..xxxx   relative adress   ",
"-------Set one shot sample-------------",
"Wait on DMA ..xxxx   count (Wavecycles)",
"Random play xx/xx/xx macro/speed/mode  ",
"Splitkey    xx/xxxx  key/macrostep     ",
"Splitvolume xx/xxxx  volume/macrostep  ",
"Addvol+note xx/fe/xx note/CONST./volume",
"SetPrevNote xx/xxxx  note/detune       ",
"Signal      xx/xxxx  signalnumber/value",
"Play macro  xx/.x/xx macro/chan/detune ",
"SID setbeg  xxxxxx   sample-startadress",
"SID setlen  xx/xxxx  buflen/sourcelen  ",
"SID op3 ofs xxxxxx   offset            ",
"SID op3 frq xx/xxxx  speed/amplitude   ",
"SID op2 ofs xxxxxx   offset            ",
"SID op2 frq xx/xxxx  speed/amplitude   ",
"SID op1     xx/xx/xx speed/amplitude/TC",
"SID stop    xx....   flag (1=clear all)"
};

dump_pattern(int x)
{
	const char *n1="CCDDEFFGGAAB",*n2=" # #  # # # ";
	UNI a;
	int y=0,z,zz;
	static char n[]={0,0,0,0};
	printf("Pattern %02x:\n",x);
	x=patterns[x];
	a.b.b0=0;
	while (a.b.b0!=0xF0) {
		a.l=ntohl(editbuf[x++]);
		if ((z=a.b.b0)<0xF0) {
			zz=(z&0x3F)+6;
			n[2]=48+(zz/12);
			zz%=12;
			n[0]=n1[zz];
			n[1]=n2[zz];
		}
		if (z<0x80) {
			printf("%04x: %02x %s %02x %x %x %02x\n",
			       y++,
			       a.b.b0,
			       n,
			       a.b.b1,
			       a.b.b2>>4,
			       a.b.b2&0xF,
			       a.b.b3
			       );
		} else if (z<0xC0) {
			printf("%04x: %02x %s %02x %x %x %02x wait\n",
			       y++,
			       a.b.b0,
			       n,
			       a.b.b1,
			       a.b.b2>>4,
			       a.b.b2&0xF,
			       a.b.b3
			       );
		} else if (z<0xF0) {
			printf("%04x: %02x %s %02x %x %x %02x porta\n",
			       y++,
			       a.b.b0,
			       n,
			       a.b.b1,
			       a.b.b2>>4,
			       a.b.b2&0xF,
			       a.b.b3
			       );
		} else {
			printf("%04x: %02x %s %02x %x %x %02x\n",
			       y++,
			       a.b.b0,
			       pattcmds[z-0xF0],
			       a.b.b1,
			       a.b.b2>>4,
			       a.b.b2&0xF,
			       a.b.b3
			       );
		}
	}
}

do_debug()
{
	char in[81];
	int x,y;
	UNI a;
	while(1) {
		for(x=0;x<81;in[x++]=0);
		fgets(in,80,stdin);
		switch(in[0]) {
		case 'p':
			switch(in[1]) {
			case 'm': /* dump macro */
				x=atoi(&in[2]);
				printf("Macro %02x:\n",x);
				x=macros[x];
				a.b.b0=0;
				y=0;
				while (a.b.b0!=0x07) {
					a.l=ntohl(editbuf[x++]);
					printf("%04x: %02x %02x%04x %s\n",
					       y++,
					       a.b.b0,
					       a.b.b1,
					       a.w.w1,
					       macrocmds[a.b.b0]
					       );
				}
				break;
			case 'p': /* dump pattern */
				x=atoi(&in[2]);
				dump_pattern(x);
				break;
			case 's': /* print voice status */
			default:
				puts("?!");
				break;
			}
			break;
		case 'q':
			return;
		default:
			puts("?!");
			break;
		}
	}
}

main(int argc, char **argv) {
	int x,y,z,flag;
	char *c;
	char ch;
	char mfn[128],sfn[128];

	over = -1;
	filt = 0;

	while ((x=getopt(argc,argv,"~GivSb:8o:f:P:V:p:w:l:"))!=-1) {
		switch (x) {
		case '?':
		case ':':
			usage(argv[0]);
			exit(2);
		case 'o':
			strcpy(outf,optarg);
			break;
		case 'P':
			startPat=strtol(optarg,NULL,0);
			break;
		case 'f':
			outRate=strtol(optarg,NULL,0);
			break;
		case 'b':
			blend=strtol(optarg,NULL,0);
			break;
		case 'p':
			songnum=strtol(optarg,NULL,0);
			break;
		case 'w':
			filt=strtol(optarg,NULL,0);
			break;
		case 'l':
			loops=strtol(optarg,NULL,0)+1;
			break;
		case 'v':
			over=0;
			break;
		case 'G':
			gemx=1;
			break;
		case 'S':
			/* left for compatibility, this switch is inactive */
			break;
		case 'i':
			printinfo=1;
			break;
		case 'V':
			c=optarg;
			for(;*c;act[(*c++)&7]=0);
			break;
		case '8':
			force8=1;
			break;
		case '~':
			gubed=1;
			break;
		default:
			fprintf(stderr,"getopt: got code 0x%x\n",x);
		}
	}
	if (optind<argc) {
		strcpy(mfn,argv[optind++]);
		strcpy(sfn,"\0");
		if (optind<argc)
			strcpy(sfn,argv[optind++]);
		else {
			strcpy(sfn,mfn);
			if (!(c=strrchr(sfn,'/'))) c=sfn; else c++;
			if (strncasecmp(c,"mdat.",5)) {
				fputs("'mdat' prefix missing\n",stderr);
				exit(2);
			}
			/*
			 * Case-preserving conversion of "mdat" to "smpl"
			 */
			(*c++)^='m'^'s';
			(*c++)^='d'^'m';
			(*c++)^='a'^'p';
			(*c++)^='t'^'l';
			c-=4;
		}
	} else {
		usage(argv[0]);
		exit(2);
	}

	if ((x=load_tfmx(mfn,sfn))==1) {
		fprintf(stderr,"%s: load_tfmx failed\n",argv[0]);
		exit(1);
	} else if (x==2) {
		fprintf(stderr,"%s: Not an MDAT file\n",c);
		exit(1);
	}

	if (blend) stereo=1;
	blend&=1;
	if (!(c=strrchr(mfn,'/'))) c=mfn; else c++;
	printf("Module: %s\n",c);

	if (printinfo) {
		for (x=0;x<6;x++) printf(">%40.40s\n",hdr.text[x]);
		puts("");

		printf("%d tracksteps at 0x%04x\n",num_ts,(hdr.trackstart<<2)+0x200);
		printf("%d patterns at 0x%04x\n",num_pat,(hdr.pattstart<<2)+0x200);
		printf("%d macros at 0x%04x\n",num_mac,(hdr.macrostart<<2)+0x200);
		for (x=0;x<31;x++)
			if ((hdr.start[x]!=hdr.end[x]) && (hdr.end[x]))
				printf("Song %2d: start %3x end %3x\n",x,ntohs(hdr.start[x]),ntohs(hdr.end[x]));
		
	}
	if (gubed) {
		do_debug();
		exit(0);
	}
/* Now the song is fully loaded.  Everything is done but ntohl'ing the actual
pattern and macro data. The routines that use the data do it for themselves.*/
	open_snddev();
	TfmxInit();
	StartSong(songnum,0);
	x=0;
	x=30;
	hdb[0]=(struct Hdb){0,0x1C01,0x3200,0x15BE,
		&smplbuf[0x4],&smplbuf[0x4+0x1C42],
		0x40,3,&LoopOff,0,NULL};
	play_it();
	TfmxTakedown();
	exit(0);
}

