/*  MikMod example player
	(c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for
	complete list.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.
 
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
 
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id: mikmod.c,v 1.11 2003/10/08 02:08:48 raph Exp $

  Module player example of MikMod

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#ifdef HAVE_GETOPT_LONG_ONLY
#include <getopt.h>
#else
#include "getopt.h"
#endif
#include <ctype.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef NEED_USLEEP
void usleep(unsigned long);
#endif
#if defined(__OS2__)||defined(__EMX__)
#define INCL_DOS
#define INCL_KBD
#define INCL_DOSPROCESS
#include <os2.h>
#endif

#if !defined(__OS2__)&&!defined(__EMX__)
#ifdef HAVE_NCURSES_H
#include <ncurses.h>
#else
#include <curses.h>
#endif
#else
#include <conio.h>
#endif

#include <mikmod.h>

#include "player.h"
#include "mwindow.h"
#include "mdialog.h"
#include "attr.h"

#if defined(__FreeBSD__)||defined(__NetBSD__)||defined(__OpenBSD__)
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/resource.h>
#include <sys/types.h>
#ifdef __FreeBSD__
#include <sys/rtprio.h>
#endif
#endif
#if defined(__linux)
#ifdef BROKEN_SCHED
#define _P __P
#endif
#include <sched.h>
#endif

/* Long options definition */
static struct option options[]={
	/* Output options */
	{"driver",			required_argument,	NULL,'d'},
	{"output",			required_argument,	NULL,'o'},
	{"frequency",		required_argument,	NULL,'f'},
	{"interpolate",		no_argument,		NULL,'i'},
	{"nointerpolate",	no_argument,		NULL,  1},
	{"hqmixer",    		no_argument,		NULL,  2},
	{"nohqmixer",  		no_argument,		NULL,  3},
	{"surround",		no_argument,		NULL,  4},
	{"nosurround",		no_argument,		NULL,  5},
	{"reverb",			required_argument,	NULL,'r'},
	/* Playback options */
	{"volume",			required_argument,	NULL,'v'},
    {"fadeout",			no_argument,		NULL,'F'},
    {"nofadeout",		no_argument,		NULL,  6},
	{"loops",			no_argument,		NULL,'l'},
	{"noloops",			no_argument,		NULL,  7},
	{"panning",			no_argument,		NULL,'a'},
	{"nopanning",		no_argument,		NULL,  8},
	{"protracker",		no_argument,		NULL,'x'},
	{"noprotracker",	no_argument,		NULL,  9},
	{"exitafter",		no_argument,		NULL,'X'},
	{"noexitafter",		no_argument,		NULL, 15},
	/* Loading options */
	{"curious",			no_argument,		NULL,'c'},
	{"nocurious",		no_argument,		NULL, 10},
	{"playmode",		required_argument,	NULL,'p'},
	{"tolerant",		no_argument,		NULL,'t'},
	{"notolerant",		no_argument,		NULL, 11},
	/* Scheduling options */
	{"renice",			no_argument,		NULL,'s'},
	{"norenice",		no_argument,		NULL, 12},
	{"realtime",		no_argument,		NULL,'S'},
	{"norealtime",		no_argument,		NULL, 12},
	/* Display options */
	{"quiet",			no_argument,		NULL,'q'},
	{"terse",			no_argument,		NULL, 14},
#ifdef ENABLE_COLOR_INTERFACE
	{"color",			no_argument,		NULL,'C'},
	{"nocolor",			no_argument,		NULL, 16},
#endif
	/* Information options */
	{"information",		no_argument,		NULL,'n'},
	{"version",			no_argument,		NULL,'V'},
	{"help",			no_argument,		NULL,'h'},
	{NULL,				0,					NULL,  0}
};

static CHAR *PRG_NAME;
PLAYLIST playlist;
CONFIG config;

/* current module */
MODULE *mf=NULL;

/* set if quiet mode is enabled */
BOOL quiet=0;
BOOL semiquiet=0;

static BOOL player_on=0;
static BOOL library_on=0;
static BOOL quit=0;

/* archive handling
   these variables need to be accessible from signal handlers */
static BOOL clean=1;
static char *playfile=NULL;
static char *archive=NULL;

/* playlist handling */
static int next=0;			/* 0 or a PL_CONT_xxx code */
static int next_pl_pos=0;	/* for PL_CONT_POS, next pos in playlist */
static int next_sng_pos=0;	/* next pos in module */

/* help text */
#define S_B(b) ((b)?"Yes":"No")
static void help(CONFIG *c)
{
	char output[4];
	char *conf_name=CF_GetFilename();

	puts(mikcopyr);

#ifdef HAVE_SNPRINTF
	snprintf(output,4,"%s%c",c->mode_16bit?"16":"8",c->stereo?'s':'m');
#else
	sprintf(output,"%s%c",c->mode_16bit?"16":"8",c->stereo?'s':'m');
#endif
	printf(
		"\n"
		"Usage: %s [option]... [module|playlist]...\n"
		"\n"
		"Output options:\n"
		"  -d, --driver n,options     Use nth driver for output (0: autodetect),\n"
		"                                default: %d\n"
		"  -o, --output 8m|8s|16m|16s 8/16 bit output in stereo/mono, default: %s\n"
		"  -f, --frequency nnnnn      Set mixing frequency, default: %d\n"
		"* -i, --interpolate          Use interpolate mixing, default: %s\n"
		"*     --hqmixer              Use high-quality (but slower) software mixer,\n"
		"                                default: %s\n"
		"*     --surround             Use surround mixing, default: %s\n"
		"  -r, --reverb nn            Set reverb amount (0-15), default: %d\n"
		"Playback options:\n"
		"  -v, --volume nn            Set volume from 0%% (silence) to 100%%, default: %d%%\n"
		"* -F, --fadeout              Force volume fade at the end of module,\n"
		"                                default: %s\n"
		"* -l, --loops                Enable in-module loops, default: %s\n"
		"* -a, --panning              Process panning effects, default: %s\n"
		"* -x, --protracker           Disable extended protracker effects, default: %s\n"
		"* -X, --exitafter            Exit MikMod when the playlist is finished,\n"
		"                                default: %s\n"
		"Loading options:\n"
		"* -c, --curious              Look for hidden patterns in module, default: %s\n"
		"  -p, --playmode n           Playlist mode (1: loop module, 2: list multi,\n"
		"                                4: shuffle list, 8: list random), default: %d\n"
		"* -t, --tolerant             Don't halt on file access errors, default: %s\n",
		PRG_NAME,c->driver,output,c->frequency,S_B(c->interpolate),
		S_B(c->hqmixer),S_B(c->surround),c->reverb,c->volume,S_B(c->fade),
		S_B(c->loop),S_B(c->panning),S_B(!c->extspd),S_B(c->exit_after),
		S_B(c->curious),c->playmode,S_B(c->tolerant)
			);

#if defined(__OS2__)||defined(__EMX__)||defined(__linux)||defined(__FreeBSD__)||defined(__NetBSD__)||defined(__OpenBSD__)

#if defined(__OS2__)||defined(__EMX__)
	puts("Scheduling options:");
#else
	puts("Scheduling options (need root privileges or a setuid root binary):");
#endif
	printf(
		"* -s, --renice               Renice to -20 (more scheduling priority),\n"
		"                                default: %s\n",
		(c->renice==RENICE_PRI?"Yes":"No"));
#if !defined(__NetBSD__)&&!defined(__OpenBSD__)
	printf(
		"* -S, --realtime             Get realtime priority (will hog CPU power),\n"
		"                                default: %s\n",
		(c->renice==RENICE_REAL?"Yes":"No"));
#endif

#endif

	printf(
		"Display options:\n"
		"  -q, --quiet                Quiet mode, no interface, displays only errors.\n"
		"      --terse                Terse output : status line only.\n"
#ifdef ENABLE_COLOR_INTERFACE
		"  -C, --color                Enable color interface. default: %s\n"
#endif
		"Information options:\n"
		"  -n, --information          List all available drivers and module loaders.\n"
		"  -V, --version              Display MikMod version.\n"
		"  -h, --help                 Display this help screen.\n"
		"Configuration option:\n"
		"      --norc                 Don't parse the file '%s'\n"
		"                                on startup\n"
		"\n"
        "Options marked with '*' also exist in negative form (eg --nointerpolate)\n"
		"F1 or H while playing: Display help panel.\n",
#ifdef ENABLE_COLOR_INTERFACE
		S_B(c->color),	
#endif
		conf_name);

	if (conf_name)
		free(conf_name);
} 

#if defined(__EMX__)||defined(__OS2__)
#ifdef __EMX__
static int kbhit(void)
{
	KBDKEYINFO k;
     
	if (KbdPeek(&k,0)) return 0;
	else return(k.fbStatus&KBDTRF_FINAL_CHAR_IN);
}
#endif

static int mikmod_getch(void)
{
	int c=0;
	if (kbhit()) {
		c=getch();
		if ((!c)||(c==0xe0)) c=0x100|getch();
	}
	return c;
}

#define RETSIGTYPE void
#else

static int mikmod_getch(void)
{
	int c=getch();

	return c==ERR?0:c;
}
#endif

/* nice exit function */
static void exit_player(int status,char *message,...)
{
	va_list args;

	win_exit();
	if (library_on) {
		MikMod_Exit();
		library_on=0;
	}
	va_start(args,message);
	if (status>0)
		vfprintf(stderr,message,args);
	else
		vprintf(message,args);
	va_end(args);

	set_term_title(NULL); // reset the title
	
	exit(status);
}

/* signal handlers */
static RETSIGTYPE GotoNext(int signum)
{
	next=PL_CONT_NEXT;

	signal(SIGUSR1,GotoNext);
}

static RETSIGTYPE GotoPrev(int signum)
{
	next=PL_CONT_PREV; 

	signal(SIGUSR2,GotoPrev);
}

static RETSIGTYPE ExitGracefully(int signum)
{
	/* can't exit now if playing */
	if (player_on) {
		quit=1;
		signal(signum,ExitGracefully);
	} else {
		win_exit();

		/* erase temporary files if necessary */
		if ((clean)&&(playfile)&&(archive)&&(archive[0])) unlink(playfile);

		if (!quiet)
			fputs((signum==SIGTERM)?"Halted by SIGTERM\n":"Halted by SIGINT\n",stderr);

		signal(SIGINT,SIG_DFL);
		signal(SIGTERM,SIG_DFL);
		set_term_title(NULL); // reset the title
		exit(0);
	}
}

static void Player_SetNextModPos(int pos,int sng_pos)
{
	if (pos<0 || pos>=PL_GetLength(&playlist)) {
		next=PL_CONT_NEXT;
		next_sng_pos=sng_pos;
	} else {
		next_pl_pos=pos;
		next_sng_pos=sng_pos;
		next=PL_CONT_POS;
	}
}

void Player_SetNextMod(int pos)
{
	Player_SetNextModPos(pos,0);
}

static void Player_InitLib(void)
{
	long engineversion=MikMod_GetVersion();

	if (engineversion<LIBMIKMOD_VERSION)
		exit_player(2,"The current engine version (%ld.%ld.%ld) is too old.\n"
					  "This programs requires at least version %ld.%ld.%ld\n",
				(engineversion>>16)&255,(engineversion>>8)&255,(engineversion)&255,
				LIBMIKMOD_VERSION_MAJOR,LIBMIKMOD_VERSION_MINOR,LIBMIKMOD_REVISION);

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

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

static void set_priority(CONFIG *cfg)
{
	if (cfg->renice==RENICE_PRI) {
#if defined(__FreeBSD__)||defined(__NetBSD__)||defined(__OpenBSD__)
		setpriority(PRIO_PROCESS,0,-20);
#endif
#ifdef __linux
		nice(-20);
#endif
#if defined(__OS2__)||defined(__EMX__)
		DosSetPriority(PRTYS_PROCESSTREE,PRTYC_NOCHANGE,20,0);
#endif
	} else if (cfg->renice==RENICE_REAL) {
#ifdef __FreeBSD__
		struct rtprio rtp;

		rtp.type=RTP_PRIO_REALTIME;
		rtp.prio=0;
		rtprio(RTP_SET,0,&rtp);
#endif
#ifdef __linux
		struct sched_param sp;

		memset(&sp,0,sizeof(struct sched_param));
		sp.sched_priority=sched_get_priority_min(SCHED_RR);
		sched_setscheduler(0,SCHED_RR,&sp);
#endif
#if defined(__OS2__)||defined(__EMX__)
		DosSetPriority(PRTYS_PROCESSTREE,PRTYC_TIMECRITICAL,20,0);
#endif
	}
}

static BOOL cmp_bit(int value,int mask,BOOL cmp)
{
	return (BTST(value,mask))?cmp:!cmp;
}

void Player_SetConfig(CONFIG *cfg)
{
	static char *driveroptions=NULL;
	BOOL restart=MikMod_Active() && ((cfg->frequency!=md_mixfreq)||
	             ((cfg->driver)&&(cfg->driver!=md_device))||
	             (!cmp_bit(md_mode,DMODE_16BITS,cfg->mode_16bit))||
	             (!cmp_bit(md_mode,DMODE_STEREO,cfg->stereo))||
	             (!cmp_bit(md_mode,DMODE_HQMIXER,cfg->hqmixer))
#if LIBMIKMOD_VERSION >= 0x030107
				 ||((!driveroptions && cfg->driveroptions) ||
				  (driveroptions && strcmp(driveroptions,cfg->driveroptions)))
#endif
	             );

#if LIBMIKMOD_VERSION >= 0x030107
	if (driveroptions) free(driveroptions);
	driveroptions=strdup(cfg->driveroptions);
#endif

	md_pansep=128; /* panning separation (0=mono 128=full stereo) */
	md_volume=(cfg->volume*128)/100;
	md_reverb=cfg->reverb;

	md_device=cfg->driver;
	md_mixfreq=cfg->frequency;
	md_mode|=DMODE_SOFT_MUSIC;

	if (cfg->interpolate) md_mode|=DMODE_INTERP;
	else md_mode&=~DMODE_INTERP;

	if (cfg->hqmixer) md_mode|=DMODE_HQMIXER;
	else md_mode&=~DMODE_HQMIXER;

	if (cfg->surround) md_mode|=DMODE_SURROUND;
	else md_mode&=~DMODE_SURROUND;

	if (cfg->mode_16bit) md_mode|=DMODE_16BITS;
	else md_mode&=~DMODE_16BITS;

	if (cfg->stereo) md_mode|=DMODE_STEREO;
	else md_mode&=~DMODE_STEREO;

	if (restart) {
		int cur=PL_GetCurrentPos(&playlist),pos=0;

		if (cur>=0) {
			if (mf) pos=mf->sngpos;
			Player_SetNextModPos(cur,pos);
		}
#if LIBMIKMOD_VERSION >= 0x030107
		if (MikMod_Reset(cfg->driveroptions))
#else
		if (MikMod_Reset())
#endif
			exit_player(1,"MikMod reset error : %s\n",
						MikMod_strerror(MikMod_errno));
	} else
		win_panel_repaint();
	if (!semiquiet)
		win_init_status(cfg->statusbar);

	if (mf)
		mf->wrap=(BTST(config.playmode,PM_MODULE)?1:0);
}

/* Display the error when loading a file, and take the appropriate resume
   action */
static void handle_ListError(BOOL tolerant,CHAR *filename,CHAR *archive,BOOL mm_error)
{
	char buf[PATH_MAX+40]="";

	if (!tolerant) {
		if (mm_error)
#ifdef HAVE_SNPRINTF
			snprintf(buf,PATH_MAX+40,"(reason: %s)\n",MikMod_strerror(MikMod_errno));
#else
			sprintf(buf,"(reason: %s)\n",MikMod_strerror(MikMod_errno));
#endif
		if (!filename)
			exit_player(1,"Corrupted playlist, filename is NULL.\n%s",buf);
		else if (archive)
			exit_player(1,"MikMod error: can't load \"%s\" from archive \"%s\".\n%s",
			            filename,archive,buf);
		else
			exit_player(1,"MikMod error: can't load %s\n%s",filename,buf);
	} else {
		if (filename)
#ifdef HAVE_SNPRINTF
			snprintf(buf,PATH_MAX+40,"Error loading list entry \"%s\" !",filename);
#else
			sprintf(buf,"Error loading list entry \"%s\" !",filename);
#endif
		else
#ifdef HAVE_SNPRINTF
			snprintf(buf,PATH_MAX+40,"Error loading list entry !");
#else
			sprintf(buf,"Error loading list entry !");
#endif
		display_message(buf);
		PL_DelEntry(&playlist,PL_GetCurrentPos(&playlist));
	}
}

/* parse an integer argument */
static void get_int(char *arg,int *value,int min,int max)
{
	char *end=NULL;
	int t=min-1;

	if (arg)
		t=strtol(arg,&end,10);
	if ((end)&&(!*end)&&(t>=min)&&(t<=max))
		*value=t;
	else
		exit_player(1,mikcopyr "\n\n"
		            "Argument '%s' out of bounds, must be between %d and %d.\n"
		            "Use '%s --help' for more information.\n",
		            arg?arg:"(not given)",min,max,PRG_NAME);
}

int main(int argc,char *argv[])
{
	int t,c=0;
	BOOL finished=0,norc=0,settime=1;
	char *filename,*pos=NULL;
	int cfg_maxchn=128;
	int uservolume=128;
	long engineversion=MikMod_GetVersion();

#ifdef __EMX__
	_wildcard(&argc,&argv);
#endif

	/* Find program name without path component */
	PRG_NAME=argv[0];
	while (*PRG_NAME) PRG_NAME++;
	while ((PRG_NAME>=argv[0])&&(*PRG_NAME!=PATH_SEP)) PRG_NAME--;
	PRG_NAME++;

	/* Check if configuration file should be loaded */
	for (t=0;t<argc;t++)
		if ((!strcmp(argv[t],"-norc"))||(!strcmp(argv[t],"--norc"))) {
			norc=1;
			argv[t][0]=0;
			break;
		}

	/* Read configuration */
	CF_Init(&config);
	if (!norc) CF_Load(&config);

	/* Initialize libmikmod */
	Player_InitLib();

	/* Setup playlist */
	PL_InitList(&playlist);

	/* Parse commandline */
	opterr=0;
	while ((t=getopt_long_only(argc,argv,
			   "d:o:f:ir:v:Flaxcp:tsSqnVhX"
#ifdef ENABLE_COLOR_INTERFACE
			   "C"
#endif
			   ,options,NULL))
#if defined(__OS2__)||defined(__EMX__)
	      >=0
#else
	      !=ERR
#endif
		) {
		switch (t) {
			case 'd': /* -d --driver */
#if LIBMIKMOD_VERSION >= 0x030107
				if (strlen(optarg)>2) {
					char *opts=strchr(optarg,',');

					if (opts) {
						*opts=0;

						/* numeric driver specification ? */
						if (opts-optarg<=2)
							get_int(optarg,&config.driver,0,999);
						else
							config.driver=MikMod_DriverFromAlias(optarg);

						CF_set_string(&config.driveroptions,++opts,99);
					} else
						config.driver=MikMod_DriverFromAlias(optarg);
				} else
#endif
					get_int(optarg,&config.driver,0,999);
				break;
			case 'o': /* -o --output */
				for (pos=optarg;pos&&*pos;pos++)
					switch (toupper((int)*pos)) {
						case '1':
						case '6':
							config.mode_16bit=1;
							break;
						case '8':
							config.mode_16bit=0;
							break;
						case 'S':
							config.stereo=1;
							break;
						case 'M':
							config.stereo=0;
							break;
					}
				break;
			case 'f': /* -f --frequency */
				get_int(optarg,&config.frequency,4000,60000);
				break;
			case 'i': /* -i --interpolate */
				config.interpolate=1; break;
			case   1: /* --nointerpolate */
				config.interpolate=0; break;
			case   2: /* --hqmixer */
				config.hqmixer=1; break;
			case   3: /* --nohqmixer */
				config.hqmixer=0; break;
			case   4: /* --surround */
				config.surround=1; break;
			case   5: /* --nosurround */
				config.surround=0; break;
			case 'r': /* -r --reverb */
				get_int(optarg,&config.reverb,0,15);
				break;
			case 'v': /* -v --volume */
				get_int(optarg,&config.volume,0,100);
				break;
			case 'F': /* -F --fadeout */
				config.fade=1; break;
			case   6: /* --nofadeout */
				config.fade=0; break;
			case 'l': /* -l --loops */
				config.loop=1; break;
			case   7: /* --noloops */
				config.loop=0; break;
			case 'a': /* -a --panning */
				config.panning=1; break;
			case   8: /* --nopanning */
				config.panning=0; break;
			case 'x': /* -x --protracker */
				config.extspd=0; break;
			case   9: /* --noprotracker */
				config.extspd=1; break;
			case 'X': /* -X --exitafter */
				config.exit_after=1; break;
			case  15: /* --noexitafter */
				config.exit_after=0; break;
			case 'c': /* -c --curious */
				config.curious=1; break;
			case  10: /* --nocurious */
				config.curious=0; break;
			case 'p': /* -p --playmode */
				get_int(optarg,&config.playmode,0,
				        PM_MODULE|PM_MULTI|PM_SHUFFLE|PM_RANDOM);
				break;
			case 't': /* -t --tolerant */
				config.tolerant=1; break;
			case  11: /* --notolerant */
				config.tolerant=0; break;
			case 's': /* -s --renice */
				config.renice=RENICE_PRI; break;
			case 'S': /* -S --realtime */
				config.renice=RENICE_REAL; break;
			case  12: /* --norenice --norealtime */
				config.renice=RENICE_NONE; break;
			case 'q': /* -q --quiet */
				quiet=1; break;
			case  14: /* --terse */
				semiquiet=1; break;
#ifdef ENABLE_COLOR_INTERFACE
			case 'C': /* --color */
				config.color=1; break;
			case 16: /* --nocolor */
				config.color=0; break;
#endif
			case 'n': /* -n --information */
				puts(mikcopyr);
				printf("Sound engine version %ld.%ld.%ld\n",
				       (engineversion>>16)&255,(engineversion>>8)&255,
				       (engineversion)&255);
				printf("\nAvailable drivers are :\n%s\n"
				       "\nRecognized module formats are :\n%s\n",
				       MikMod_InfoDriver(),MikMod_InfoLoader());
				exit(0);
			case 'V': /* -V --version */
				puts(mikcopyr);
				printf("Sound engine version %ld.%ld.%ld\n",
				       (engineversion>>16)&255,(engineversion>>8)&255,
				       (engineversion)&255);
				exit(0);
			case 'h': /* -h --help */
				help(&config);
				exit(0);
			default:
				/* ignore errors */
				break;
		}
	}
	set_priority(&config);

	/* Add remaining parameters to the playlist */
	for (t=optind;t<argc;t++) {
		if (!quiet) {
			printf("\rScanning files... %c (%d left) ",("/-\\|")[t&3],argc-t);
			fflush(stdout);
		}
		MA_FindFiles(&playlist,argv[t]);
	}

	if ((!PL_GetLength(&playlist)) && (!norc))
		PL_LoadDefault(&playlist);

	PL_DelDouble(&playlist);
	PL_InitCurrent(&playlist);
	if (BTST(config.playmode,PM_SHUFFLE)) PL_Randomize(&playlist,0);

	if (!quiet)
		puts(mikbanner);

	Player_SetConfig(&config);
#if LIBMIKMOD_VERSION >= 0x030107
	if (MikMod_Init(config.driveroptions))
#else
	if (MikMod_Init())
#endif
		exit_player(1,"MikMod initialisation error : %s\n",
		            MikMod_strerror(MikMod_errno));
	library_on=1;

	/*  initialize interface */
	win_init(quiet);
	attr_init_colors(quiet);
	display_init();

	signal(SIGTERM,ExitGracefully);
	signal(SIGINT,ExitGracefully);
	signal(SIGUSR1,GotoNext);
	signal(SIGUSR2,GotoPrev);

	while (!quit) {
		mf=NULL;
		filename=archive=NULL;
		switch (next) {
			case 0:
			case PL_CONT_NEXT:
				finished=!PL_ContNext(&playlist,&filename,&archive,config.playmode);
				break;
			case PL_CONT_PREV:
				finished=!PL_ContPrev(&playlist,&filename,&archive);
				break;
			case PL_CONT_POS:
				finished=!PL_ContPos(&playlist,&filename,&archive,next_pl_pos);
				break;
		}
		next=0;
		settime=1;
		if (finished && ((PL_GetLength(&playlist)>0) || quiet || semiquiet)) break;
		if (!finished) {
			if (!filename) {
				handle_ListError(config.tolerant,filename,archive,0);
				continue;
			}

			/* load the module */
			playfile=MA_dearchive(archive,filename);
			if (!playfile) {
				handle_ListError(config.tolerant,filename,archive,0);
				continue;
			}
			display_loadbanner();

			clean=1;
			mf=Player_Load(playfile,cfg_maxchn,config.curious);
			if ((archive)&&(archive[0])) unlink(playfile);
			clean=0;
			free(playfile);
			if (!mf) {
				handle_ListError(config.tolerant,filename,archive,1);
				continue;
			}

			mf->extspd=config.extspd;
			mf->panflag=config.panning;
			mf->wrap=(BTST(config.playmode,PM_MODULE)?1:0);
			mf->loop=config.loop;
			mf->fadeout=config.fade;
			player_on=1;
			Player_Start(mf);
			if (mf->volume>uservolume) Player_SetVolume(uservolume);
			if (next_sng_pos>0) {
				Player_SetPosition(next_sng_pos);
				settime=0;
				next_sng_pos=0;
			}
		}
		display_start();

		/* if we have a quit signal, exit loop */
		while (!quit && ((Player_Active() && !next) ||
						 (finished && (PL_GetLength(&playlist)<=0))) ) {
			MikMod_Update();
			if (config.volrestrict && mf)
				if (mf->volume>uservolume) Player_SetVolume(uservolume);

#if defined(__OS2__)||defined(__EMX__)
			DosSleep(5);
#else
			usleep(5000);
#endif

			/* update the status display... */
			display_status();

			if (!quiet && (c=mikmod_getch())) {
				if (win_handle_key(c)) c=0;
				if ((c<256) && (isalpha(c))) c=toupper(c);

				/* always enabled commands */
				switch (c) {
					case ' ':					/* toggle pause */
						Player_TogglePause();
						win_panel_repaint();
						break;
					case 'N':
						next=PL_CONT_NEXT;
						break;
					case 'P':
						next=PL_CONT_PREV;
						break;
					case 'Q':
						quit=1;
						break;
					case CTRL_L:
#if !defined(__OS2__)&&!defined(__EMX__)
					case KEY_CLEAR:
#endif
						win_panel_repaint();
						break;
					case 'H':
						win_change_panel(1);
						break;
					case 'S':
						win_change_panel(2);
						break;
					case 'I':
						win_change_panel(3);
						break;
					case 'M':
						win_change_panel(4);
						break;
					case 'L':
						win_change_panel(5);
						break;
					case 'C':
						win_change_panel(6);
						break;
				}
				/* commands which only work when module is not paused */
				if (!Player_Paused())
					switch(c) {
						case '+': 
						case KEY_RIGHT:
							Player_NextPosition();
							settime=0;
							break;
						case '-': 
						case KEY_LEFT:
							Player_PrevPosition();
							settime=0;
							break;
						case 'R':
							Player_SetPosition(0);
							settime=1;
							break;
						case '(':
							if (mf) Player_SetSpeed(mf->sngspd-1);
							settime=0;
							break;
						case ')':
							if (mf) Player_SetSpeed(mf->sngspd+1);
							settime=0;
							break;
						case '{':
							if (mf) Player_SetTempo(mf->bpm-1);
							settime=0;
							break;
						case '}':
							if (mf) Player_SetTempo(mf->bpm+1);
							settime=0;
							break;
						case ';':
						case ':':
							md_mode^=DMODE_INTERP;
							display_header();
							break;
						case 'U':
							md_mode^=DMODE_SURROUND;
							display_header();
							break;
						case '1':
						case '2':
						case '3':
						case '4':
						case '5':
						case '6':
						case '7':
						case '8':
						case '9':
							Player_SetVolume(uservolume=(((c-'0')<<7)+5)/10);
							break;
						case '0':
							Player_SetVolume(uservolume=128);
							break;
						case '<':
							if (mf && mf->volume)
								Player_SetVolume(uservolume=mf->volume-1);
							break;
						case '>':
							if (mf && mf->volume<128)
								Player_SetVolume(uservolume=mf->volume+1);
							break;
					}
#if !defined(__OS2__)&&!defined(__EMX__)
				flushinp();
#endif
			}
			win_refresh();
		}
		if (!finished) {
			if ((!quit)&&!BTST(config.playmode,PM_MODULE)&&(!next)&&(settime))
				PL_SetTimeCurrent(&playlist,mf->sngtime);
			PL_SetPlayedCurrent(&playlist);

			Player_Stop();		/* stop playing */
			Player_Free(mf);	/* and free the module */
		}
		player_on=0;
	}

	MikMod_Exit();
	library_on=0;
	win_exit();
	if ((!quit)&&(!quiet)) {
		if (!finished)
			exit_player(1,"MikMod error : %s\n",MikMod_strerror(MikMod_errno));
		else
			puts("Finished playlist...");
	}
	if (!norc) {
		if (config.save_config) CF_Save(&config);
		if (config.save_playlist) PL_SaveDefault(&playlist);
	}

	set_term_title(NULL); // reset the title
	exit(0);
}

/* ex:set ts=4: */
