/*

Name:
MIKMODUX.C

Description:
Modplaying example of mikmod - bare unix? version.

Original by MikMak <mikmak@via.nl>, 
then Chris Conn <cconn@tohs.abacom.com>,
then Steve McIntyre <stevem@chiark.greenend.org.uk>
also 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	
v1.08 (20/04/97) - assorted modifications to work with the new playing
			library (MikMod 3.0), also changed -a to -k
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#ifndef __FreeBSD__
#include <curses.h>
#else
#include <ncurses.h>
#endif
#if (!defined(SGI))
#include <fnmatch.h>
#endif
#include <signal.h>
#include "mikmod.h"
#include "mikmodux.h"
#include "mmio.h"


#ifdef CPUTIME_SNAGGER
#ifndef __FreeBSD__
#ifdef BROKEN_SCHED
#include <linux/sched.h>
#else
#include <sched.h>
#endif
#endif
#ifdef __FreeBSD__
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/rtprio.h>
#endif
#endif /* CPUTIME_SNAGGER */

PLAYLIST playlist;
#ifdef __FreeBSD__
struct rtprio rtp;
#endif

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"
"  -e xx   Sets reverb from 0 to 15.\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" (3.0 doesn't yet) */
"  -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"
"  -pl xxx Use playlist\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.\n"
"  -so     simple output - hark to the old days of mikmod, just status line";

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;

#if (defined(SOLARIS) || (defined(SGI)))
	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;
int semiquiet=0;
BOOL next;

void GotoNext()
{
	next=1;
	signal(SIGUSR1, GotoNext);
}

void GotoPrev()
{
	char string[256];
	PL_GetPrev(&playlist,string,string);
	PL_GetPrev(&playlist,string,string);
	next=1; 
	signal(SIGUSR2, GotoPrev);
}

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;
	extern float speed_constant;   /* tempo multiplier, initialised to 1*/
	char filename[255];
	char archive[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, c;

	/*
		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   = 32768;            /* 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       = 0;               /* Reverb (max 15) */
   md_mode = DMODE_16BITS | 
   		DMODE_STEREO | 
		DMODE_SOFT_MUSIC;  /* default mixing mode */

	/* Register the loaders we want to use..  */
	MikMod_RegisterAllLoaders();

	/* Register the drivers we want to use: */
	MikMod_RegisterAllDrivers();

	MikMod_RegisterDriver(drv_wav);
	MikMod_RegisterDriver(drv_raw);
	MikMod_RegisterDriver(drv_nos);

/*
#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
#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; t<nargc; t++)
	{
		if(strcmp(nargv[t],"-d")==0)
		{
			t++;
			md_device=atoi(nargv[t]);
			continue;
		}
		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; continue; }
		if(strcmp(nargv[t],"-m")==0)
			{ md_mode&=~DMODE_STEREO; continue; }
		if(strcmp(nargv[t],"-8")==0)
			{ md_mode&=~DMODE_16BITS; continue; }
		if(strcmp(nargv[t],"-i")==0)
			{ md_mode|=DMODE_INTERP; continue; }
		if(strcmp(nargv[t],"-x")==0)
			{ cfg_extspd=0; continue; }
		if(strcmp(nargv[t],"-p")==0)
		{
			t++;
			cfg_panning=atoi(nargv[t]);
			continue;
		}
		if(strcmp(nargv[t],"-v")==0)
		{
			t++;
			md_volume=(atoi(nargv[t])*128)/100;
			continue;
		}
		if(strcmp(nargv[t],"-f")==0)
		{
			t++;
			md_mixfreq=atol(nargv[t]);
			continue;
		}
		if(strcmp(nargv[t],"-e")==0)
		  {
		       t++;
		       md_reverb=atoi(nargv[t]);
		       continue; 	 
		  }     
		if(strcmp(nargv[t],"-h")==0)
		{
			puts(helptext);
			exit(0);
		}
		if(strcmp(nargv[t],"-t")==0)
			{ tolerant=1; continue; }
		if(strcmp(nargv[t],"-q")==0)
			{ quiet=1; continue; }
		if(strcmp(nargv[t],"-k")==0)
			{ delafterplay=1; continue; }

#ifdef CPUTIME_SNAGGER
		if(strcmp(nargv[t],"-s")==0)
		{
			#ifndef __FreeBSD__
			if (nice(-20) == -1)
				perror("renice to -20");
			continue;
			#else
			if(setpriority(PRIO_PROCESS, 0, -20) == -1)
				perror("renice to -20");	
			continue;
			#endif
		}
		if(strcmp(nargv[t],"-S")==0)
		{
#ifndef __FreeBSD__
			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");
			continue;
		        #else
			rtp.type = RTP_PRIO_REALTIME;
			rtp.prio = 0;
			if (rtprio(RTP_SET, 0, &rtp) == -1)
				perror("realtime priority");
			continue; 
			#endif
		}
#endif /* CPUTIME_SNAGGER */

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

		if(strcmp(nargv[t],"-so")==0)
			{ semiquiet=1; continue; }

		if(strcmp(nargv[t],"-pl")==0)
			{ PL_Load(&playlist, nargv[++t]); continue; }

		MA_FindFiles(&playlist, nargv[t]);
	}

	if (!quiet)
		puts(mikbanner);

	/*  initialize interface */
	init_display();

	MikMod_Init();

	/*  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));	
		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=MikMod_LoadSong(playfile,cfg_maxchn)) == NULL)
        {   
		/* didn't work -> exit with errormsg. */
			if(!tolerant)
			{
				if(!quiet) endwin();
				printf("MikMod Error\n");
				MD_Exit();
				exit(1);
			}
			foo=1;
		}
		free(playfile);
		
		if(!foo)
		{

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

		Player_Start(mf);

		display_version();
		display_driver();
		display_file();

		display_name();
		firstinst=0; 
		display_instruments();

		next=0;
		signal(SIGUSR1, GotoNext);
		signal(SIGUSR2, GotoPrev);

		/* if we have a quit signal, exit loop, and try to kludge
		around that damn "over the edge" bug... */
		while(Player_Active() && !quit && !next && mf->numpos<256)
		{ 

			MikMod_Update();

			usleep(500);

			if (!md_type) /* handled elsewhere for GUS cards */
			{
				/* update the status display... */
				display_status();
			}
			if(!quiet && (c=getch())!=ERR)
			{
				switch(c)
				{
					case '+': 
					case KEY_RIGHT:
						MP_NextPosition(mf); break;
					case '-': 
					case KEY_LEFT:
						MP_PrevPosition(mf); break;
					case 'N':
					case 'n': next=1; break;
					case 'p':
					case 'P':
						PL_GetPrev(&playlist,filename,archive);
						PL_GetPrev(&playlist,filename,archive);
						next=1; break;
					case ' ':
						Player_TogglePause();
						break;
					case 'q':
					case 'Q': quit=1; break;
					case KEY_DOWN:
/*
						switch(dispsamp)
						{
							case 0:
								firstinst+=firstinst<(mf->numsmp-38) ? 1 : 0; 
								break;
							case 1:
								firstinst+=firstinst<(mf->numins-38) ? 1 : 0; 
								break;
							case 2:
								firstinst++;
								break;
						}
*/
						firstinst++;
						display_instruments();
						break;
					case KEY_UP:
						firstinst-=firstinst>0 ? 1 : 0; 
						display_instruments();
						break;
					case 'W':
					case 'w':
						PL_Save(&playlist, "./mikmodplaylist");
						break;
					case 'I':
					case 'i':
						firstinst=0;
						dispsamp=!dispsamp;
						display_all();
						break;
					case 'M':
					case 'm':
						if(mf->comment!=NULL)
						{
							firstinst=0;
							dispsamp=2;
							display_all();
						}
						break;
					case 12: /* ^L */
						display_all();
						break;
				}
			}
			if(!quiet) flushinp();
		}
		Player_Stop();          /* stop playing */
		MikMod_FreeSong(mf);            /* and free the module */
		if(delafterplay) PL_DelCurrent(&playlist);
		}
	}

	MikMod_Exit();
	exit_display();
	if (quit)
		{
		return 0;
		}
	if(!quiet)
		if(mf==NULL){
			printf("MikMod Error\n");
		}
		else
			printf("Finished playlist...\n");
	return 1;
}
