/*

Name:
TCLMIKMODUX.C

Description:
Main C player.  Deals with the Tcl/Tk interface via pipes.  Check out
tcldisplay.c for other stuff...

Original by MikMak <mikmak@via.nl>, 
then Chris Conn <cconn@tohs.abacom.com>,
then Steve McIntyre <stevem@chiark.greenend.org.uk>
But a lot more recently done by Peter Amstutz <amstpi@freenet.tlh.fl.us>

HISTORY
=======

v1.00 (06/12/96) - first "versioned" version.
v1.01 (06/01/97) - print the mikbanner to go with the help output.
v1.02 (07/01/97) - don't set mp_volume to 100 if already set by -v option.
v1.03 (09/01/97) - Re-add "q" to allowed options, remove "o".
v1.04 (10/01/97) - minor changes for compilation with -Wall
v1.05 (19/01/97) - internal playlist handling moved out to mlist.c or
			tcllist.c (Peter Amstutz)
v1.06 (08/02/97) - added ability to peer inside zip files & load what is
			there...  Should be moved to marchive.c but
			works fine for now. (Peter Amstutz)
v1.07 (28/03/97) - added -a command line option to delete entries from playlist
			after playing (P.A.)
v1.08 (20/04/97) - assorted modifications to work with the new playing
			library (MikMod 3.0), also changed -a to -k (P.A.)
v1.09 (22/04/97) - completly re-wrote the command-line parsing to avoid
			using getopt(), added -rp (random play), modified from curses
			version to use tclmikmod stuff. (P.A.)
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "mikmod.h"
#include <fnmatch.h>
#include "ui.h"


#ifdef CPUTIME_SNAGGER
#ifdef BROKEN_SCHED
#include <linux/sched.h>
#else
#include <sched.h>
#endif
#endif /* CPUTIME_SNAGGER */

PLAYLIST playlist;

char helptext[]=

"Available switches (CaSe SeNsItIvE!):\n"
"\n"
"  -d x    use device-driver #x for output (0 is autodetect). Default=0\n"
"  -ld     List all available device-drivers\n"
"  -ll     List all available loaders\n"
"  -x      disables protracker extended speed\n"
"  -p      disables panning effects (9fingers.mod)\n"
"  -v xx   Sets volume from 0 (silence) to 250. Default=100\n"
"  -f xxxx Sets mixing frequency. Default=44100\n"
"  -m      Force mono output (so sb-pro can mix at 44100)\n"
"  -8      Force 8 bit output\n"
"  -i      Use interpolated mixing\n"
"  -r      Restart a module when it's done playing\n"
"  -q      Quiet mode (interactive commands disabled, displays only errors)\n"
"  -k      Kill module after play (that is, remove from playlist)\n"
"  -rp     Random play\n"

#ifdef CPUTIME_SNAGGER
"  -s      Renice to -20 (more scheduling priority)\n"
"  -S      Get realtime priority (snag all the cpu needed, beware :))\n"
#endif /* CPUTIME_SNAGGER */

"  -t      Tolerant mode - do not stop on non-fatal file access errors.";

int quiet=0;           /* set if quiet mode is enabled */
UBYTE md_type=0;       /* default is a non-wavetable sound device */

void tickhandler(void)
{
	MP_HandleTick();    /* play 1 tick of the module */
//	MD_SetBPM(mp_bpm);
}

#if (defined(NEEDS_USLEEP))

/*
 *  NAME:
 *      usleep     -- This is the precision timer for Test Set
 *                    Automation. It uses the select(2) system
 *                    call to delay for the desired number of
 *                    micro-seconds. This call returns ZERO
 *                    (which is usually ignored) on successful
 *                    completion, -1 otherwise.
 *
 *  ALGORITHM:
 *      1) We range check the passed in microseconds and log a
 *         warning message if appropriate. We then return without
 *         delay, flagging an error.
 *      2) Load the Seconds and micro-seconds portion of the
 *         interval timer structure.
 *      3) Call select(2) with no file descriptors set, just the
 *         timer, this results in either delaying the proper
 *         ammount of time or being interupted early by a signal.
 *
 *  HISTORY:
 *      Added when the need for a subsecond timer was evident.
 *	Modified for Solaris-specific bits by SAM 24/10/96
 *  AUTHOR:
 *      Michael J. Dyer                   Telephone:   AT&T 414.647.4044
 *      General Electric Medical Systems        GE DialComm  8 *767.4044
 *      P.O. Box 414  Mail Stop 12-27         Sect'y   AT&T 414.647.4584
 *      Milwaukee, Wisconsin  USA 53201                      8 *767.4584
 *      internet:  mike@sherlock.med.ge.com     GEMS WIZARD e-mail: DYER
 */

#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/types.h>

int     usleep( unsigned long int microSeconds )
{
        unsigned int            Seconds, uSec;
        int                     nfds;
        struct  timeval         Timer;

#ifdef SOLARIS
	fd_set                 readfds, writefds, exceptfds;

	nfds = 0;
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
#else
	int			readfds, writefds, exceptfds;
 	nfds = readfds = writefds = exceptfds = 0;
#endif

        if( (microSeconds == (unsigned long) 0)
                || microSeconds > (unsigned long) 4000000 )
        {
                errno = ERANGE;         /* value out of range */
                perror( "usleep time out of range ( 0 -> 4000000 ) " );
                return -1;
        }

        Seconds = microSeconds / (unsigned long) 1000000;
        uSec    = microSeconds % (unsigned long) 1000000;

        Timer.tv_sec            = Seconds;
        Timer.tv_usec           = uSec;

        if( select( nfds, &readfds, &writefds, &exceptfds, &Timer ) < 0 )
        {
                perror( "usleep (select) failed" );
                return -1;
        }

        return 0;
}
#endif /*NEEDS_USLEEP*/

UNIMOD *mf=NULL;
int dorandom=0, delafterplay=0;
extern int firstinst, writeme;

int main(int nargc,char *nargv[])
{
	int quit;
	int cmderr=0;                   /* error in commandline flag */
	int morehelp=0;                 /* set if user wants more help */
	int t, n, foo, i;
	extern float speed_constant;   /* tempo multiplier, initialised to 1*/
	char filename[255];
	char archive[255];
	char string[255];
	char *playfile;
	FILE *file;
static BOOL  cfg_extspd  = 1,      // Extended Speed enable
             cfg_panning = 1,      // DMP panning enable (8xx effects)
             cfg_loop    = 0;      // auto song-looping disable
	int cfg_maxchn=64;
	BOOL next;

	/*
		Initialize soundcard parameters.. you _have_ to do this
		before calling MD_Init(), and it's illegal to change them
		after you've called MD_Init()
	*/

	int tolerant    =0;
	playlist.numused=0;
   md_mixfreq      = 44100;            // standard mixing freq
   md_dmabufsize   = 32000;            // standard dma buf size (max 32000)
   md_device       = 0;                // standard device: autodetect
   md_volume       = 96;               // driver volume (max 128)
   md_musicvolume  = 128;              // music volume (max 128)
   md_sndfxvolume  = 128;              // sound effects volume (max 128)
   md_pansep       = 128;              // panning separation (0 = mono, 128 = full stereo)
   md_stereodelay  = 10;               // Stereo Delay (max 15)
   md_reverb       = 10;               // Reverb (max 15)
   md_mode = DMODE_16BITS | DMODE_STEREO;  // default mixing mode

	/*
		Register the loaders we want to use..
	*/

   ML_RegisterLoader(&load_it);
   ML_RegisterLoader(&load_xm);
   ML_RegisterLoader(&load_s3m);
   ML_RegisterLoader(&load_mod);
   ML_RegisterLoader(&load_mtm);
   ML_RegisterLoader(&load_stm);
   ML_RegisterLoader(&load_dsm);
   ML_RegisterLoader(&load_med);
   ML_RegisterLoader(&load_far);   
   ML_RegisterLoader(&load_ult);

	/*
		Register the drivers we want to use:
	*/


#ifdef SUN
	MD_RegisterDriver(&drv_sun);
#elif defined(SOLARIS)
	MD_RegisterDriver(&drv_sun);
#elif defined(__alpha)
        MD_RegisterDriver(&drv_AF);
#elif defined(OSS)
        MD_RegisterDriver(&drv_oss);
	#ifdef ULTRA
	       MD_RegisterDriver(&drv_ultra);
	#endif /* ULTRA */
#elif defined(__hpux)
        MD_RegisterDriver(&drv_hp);
#elif defined(AIX)
        MD_RegisterDriver(&drv_aix);
#elif defined(SGI)
        MD_RegisterDriver(&drv_sgi);
#endif

	MD_RegisterDriver(&drv_raw);
    MD_RegisterDriver(&drv_nos);

	MD_RegisterPlayer(tickhandler);

	PL_InitList(&playlist);
	/* Parse option switches by hand 'cause getopt is too limited: */

	for(t=1, n=0;t<nargc;t++, n=0)
	{
		if(strcmp(nargv[t],"-d")==0)
		{
			t++;
			md_device=atoi(nargv[t]);
			n=1;
		}
		if(strcmp(nargv[t],"-ld")==0)
		{
			MD_InfoDriver();
			exit(0);
		}
		if(strcmp(nargv[t],"-ll")==0)
		{
			ML_InfoLoader();
			exit(0);
		}
		if(strcmp(nargv[t],"-r")==0)
			{ cfg_loop=1; n=1; }
		if(strcmp(nargv[t],"-m")==0)
			{ md_mode&=~DMODE_STEREO; n=1; }
		if(strcmp(nargv[t],"-8")==0)
			{ md_mode&=~DMODE_16BITS; n=1; }
		if(strcmp(nargv[t],"-i")==0)
			{ md_mode|=DMODE_INTERP; n=1; }
		if(strcmp(nargv[t],"-x")==0)
			{ cfg_extspd=0; n=1; }
		if(strcmp(nargv[t],"-p")==0)
		{
			t++;
			cfg_panning=atoi(nargv[t]);
			n=1;
		}
		if(strcmp(nargv[t],"-v")==0)
		{
			t++;
			md_volume=(atoi(nargv[t])*128)/100;
			n=1;
		}
		if(strcmp(nargv[t],"-f")==0)
		{
			t++;
			md_mixfreq=atol(nargv[t]);
			n=1;
		}
		if(strcmp(nargv[t],"-h")==0)
		{
			puts(helptext);
			exit(0);
		}
		if(strcmp(nargv[t],"-t")==0)
			{ tolerant=1; n=1; }
		if(strcmp(nargv[t],"-q")==0)
			{ quiet=1; n=1; }
		if(strcmp(nargv[t],"-k")==0)
			{ delafterplay=1; n=1; }

#ifdef CPUTIME_SNAGGER
		if(strcmp(nargv[t],"-s")==0)
		{
			if (nice(-20) == -1)
				perror("renice to -20");
			n=1;
		}
		if(strcmp(nargv[t],"-S")==0)
		{
			struct sched_param sp;
			memset(&sp, 0, sizeof(struct sched_param));
			sp.sched_priority = 1;
			if (sched_setscheduler(0, SCHED_RR, &sp) == -1)
				perror("realtime priority");
			n=1;
		}
#endif /* CPUTIME_SNAGGER */

		if(strcmp(nargv[t],"-rp")==0)
			{ dorandom=1; n=1; }

		if( !n )
		{
	#ifdef USE_ZIP
			if(fnmatch("*.[Zz][Ii][Pp]",nargv[t],0)!=0)
	#endif
				PL_Add(&playlist,nargv[t],NULL);
	#ifdef USE_ZIP
			else {
				sprintf(string,"unzip -l %s > /tmp/mikzipdir",
					nargv[t]);
				system(string);
				file=fopen("/tmp/mikzipdir","r");
				fgets(string,255,file);
				while(!feof(file))
				{
					string[strlen(string)-1]=0;
					if(!fnmatch("*.[Mm][Oo][Dd]",(string+27),0) ||
						!fnmatch("*.[Ss]3[Mm]",(string+27),0) ||
						!fnmatch("*.[Ss][Tt][Mm]",(string+27),0) ||
						!fnmatch("*.[Un][Nn][Ii]",(string+27),0) ||
						!fnmatch("*.[Mm][Tt][Mm]",(string+27),0) ||
						!fnmatch("*.[Uu][Ll][Tt]",(string+27),0) ||
						!fnmatch("*.[Ii][Tt]",(string+27),0) ||
						!fnmatch("*.[Xx][Mm]",(string+27),0))
					{
						PL_Add(&playlist,(string+27),nargv[t]);
					}
					fgets(string,255,file);
				}
			}
	#endif
		}

	}

	if (!quiet)
		puts(mikbanner);

	/*  initialize interface */
	init_display();

	/*  initialize soundcard */

	if(!MD_Init()){
		printf("Driver error: %s.\n",_mm_error);
		return 0;
	}

	/*  initialize volume and tempo multipliers */

#ifdef __hpux
#include <sys/signal.h>
		/* without the following line, quiting mikmod by CTRL-C
		 * would close the terminal window, which is probably
		 * not intended by the user
		 */
		signal(SIGINT, SIG_IGN);
#endif



	quit=0;
	while(!quit)
	{
		memset(filename,0,sizeof(filename));	
		memset(archive,0,sizeof(archive));	
		while(filename[0]==0 && get_ui()!=UI_QUIT)
		{ 
			quit=(dorandom==0 ? 
			(PL_GetNext(&playlist,filename,archive))!=0 :
			(PL_GetRandom(&playlist,filename,archive))!=0);
		}
		if(filename[0]==0) {quit=1; break;}

		/* load the module */
		foo=0;
		playfile=MA_dearchive(archive,filename);
        if((mf=ML_LoadFN(playfile,cfg_maxchn)) == NULL)
        {   
		/* didn't work -> exit with errormsg. */
			if(!tolerant)
			{
				if(!quiet) exit_display();
				printf("MikMod Error: %s\n",_mm_error);
				MD_Exit();
				exit(1);
			}
			foo=1;
		}
		free(playfile);
		
		if(!foo)
		{

        mf->extspd  = cfg_extspd;
        mf->panflag = cfg_panning;
        mf->loop    = cfg_loop;

		MP_Init(mf);

		display_driver();
		display_file();

		display_name();
		display_instruments();

		/*	set the number of voices to use.. you
			could add extra channels here (e.g. md_numchn=mf->numchn+4; )
			to use for your own soundeffects:
		*/
		md_numchn=mf->numchn;

		/*  start playing the module: */
		MP_PlayStart(mf);

		write(writeme,".top1.fra2.fra30.rad5 deselect\n",31);
		write(writeme,".top1.fra2.fra30.rad7 select\n",31);
		next=0;
		while(!MP_Ready(mf) && !quit && !next){ /* if we have a quit signal, exit loop */

			MD_Update();

			/* no need to wait with the unix drivers */

			if (!md_type) /* handled elsewhere for GUS cards */
			{
				/* update the status display... */
				display_status();
			}

				switch(get_ui())
					{
					case UI_NEXT_SONG:
						next=1;
						break;
					case UI_PREVIOUS_SONG:
							PL_GetPrev(&playlist,NULL,NULL);
							PL_GetPrev(&playlist,NULL,NULL);
							next=1;
						break;
					case UI_QUIT:
						quit=1;
						break;
					case UI_JUMP_TO_NEXT_PATTERN:
						MP_NextPosition(mf);
						break;
					case UI_JUMP_TO_PREV_PATTERN:
						MP_PrevPosition(mf);
						break;
					case UI_STOP:
						MP_PlayStop(mf);
						mf->patpos=0;
						mf->sngpos=0;
						break;
					case UI_PLAY:
						MP_PlayStart(mf);
						break;
					case UI_PAUSE:
						MP_TogglePause(mf);
						break;
					case UI_SPEED_UP:
						break;
					case UI_SLOW_DOWN:
						break;
					case UI_NORMAL_SPEED:
						break;
					case UI_VOL_UP:
						break;
					case UI_VOL_DOWN:
						break;
					case UI_NORMAL_VOL:
						break;
/*
					case UI_SELECT_STEREO:
						md_mode|=DMODE_STEREO;
						break;
					case UI_SELECT_MONO:
						md_mode&=~DMODE_STEREO;
						break;
					case UI_SELECT_INTERP:
						md_mode|=DMODE_INTERP;
						break;
					case UI_SELECT_NONINTERP:
						md_mode&=~DMODE_INTERP;
						break;
					case UI_SELECT_8BIT:
						md_mode&=~DMODE_16BITS;
						break;
					case UI_SELECT_16BIT:
						md_mode|=DMODE_16BITS;
						break;
*/
					case UI_RANDOM:
						dorandom=1;
						break;
					case UI_ORDERLY:
						dorandom=0;
						break;
					case UI_DEL_AFTER_PLAY:
						delafterplay=1;
						break;
					case UI_NO_DEL_AFTER_PLAY:
						delafterplay=0;
						break;
					default:
						break;
					}
		}
		MP_PlayStop(mf);          /* stop playing */
		ML_Free(mf);            /* and free the module */
		if(delafterplay) PL_DelCurrent(&playlist);
		}
	}

	MD_Exit();
	exit_display();
	if (quit)
		{
		return 0;
		}
	if(!quiet)
		if(mf==NULL){
			printf("MikMod Error: %s\n",_mm_error);
			if(_mm_error)
				printf("%s\n",_mm_error);
		}
		else
			printf("Finished playlist...\n");
	return 1;
}
