#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/ultrasound.h>
#include <limits.h>
#include <string.h>

#include "mod.h"

/* Agnus clocksignal (CLK):
 * PAL = 28.37516 MHz ; NTSC = 28.63636 MHz
 *
 * Paula clockcycle period: T = 1/(CLK/8)
 * PAL = 281.937 ns   ; NTSC = 279.365 ns
 *
 * C-2 frequency : 261.632 Hz
 * C-2 period    : 428 (this is the value written to AUDxPER of Paula)
 * C-2 sample rate : F = 1/(428*T)
 * PAL = 8287 Hz      ; NTSC = 8363 Hz
 */

/* Speed     = Number of ticks per line (defaults to 6)
 * 1 Beat    = Time it takes to play 4 lines at speed 6 (i.e 4*6=24 ticks)
 *             (defaults to 125)
 * Tempo     = Decides the time per tick, measured in BPM (Beats Per Minute)
 *
 * Time/tick = 60/(4*6*tempo) s = 2.5/tempo s
 *
 * NOTE: As the calculation of time per tick doesn't take into account
 *       current speed it isn't a real BPM value (at least not if you stick
 *       to the above definition of 1 beat)
 */

/* Sample #0 is used as a "silent sample" that will cause the active voice
 * to be silenced.
 */

/* Sequencer-related variables */

SEQ_DECLAREBUF();
extern int seqfd, gus_dev;

/* Module and options */

extern struct mod_info M;
extern struct options opt;

/* Variables used to play module */

struct voice V[MAX_VOICES];
struct effects efx;
struct event *cur_event;

int songpos, linepos, tick, speed, tempo;
double mod_time, tick_time;
char song_end;

/* Tables */

extern int periodtable[12*5*16];
extern char *notenames[12*5];
extern char *effectnames[NR_EFX];


void play_module(void)
{
    int i;
    int advance_songpos;

    if(opt.verbose >= 5)
	printf("Hinote: %d Lonote: %d\n", opt.high_note, opt.low_note);

    GUS_NUMVOICES(gus_dev, M.nr_voices);

    for(i=0; i < M.nr_voices; ++i) {
	SEQ_MAIN_VOLUME(gus_dev, i,(opt.active_voices&(1<<i) ? 100 : 0));
	SEQ_PANNING(gus_dev, i, get_voice_balance(i));
	SEQ_BENDER_RANGE(gus_dev, i, 8191);
	SEQ_PITCHBEND(gus_dev, i, 0);
	
	/* Clear voice-info */
	bzero((void*)&V[i], sizeof(struct voice)); 
	
	/* Set note and period to valid values so pitchbender won't screw up */
	V[i].note=BASE_NOTE+2*12;
	V[i].period=V[i].real_period=periodtable[V[i].note-BASE_NOTE];
	
	/* Waveforms are retriggered sines by default */
	V[i].vibrato_waveform=WAVEFORM_SINE;
	V[i].vibrato_retrig=1;
	V[i].tremolo_waveform=WAVEFORM_SINE;
	V[i].tremolo_retrig=1;
    }
    
    if(opt.verbose == 1)
	printf("\n");
    
    if(opt.verbose) {
	printf("Playing module '%s'", M.name);
	fflush(stdout);
    }

    if(opt.showlines && opt.verbose)
	printf("\n");
    
    SEQ_START_TIMER();
    mod_time=0;
    
    /* Make sure everything gets correctly initialized on the 1st iteration */

    efx.pattern_delay=0;

    efx.PosJumpFlag=0;
    efx.PBreakFlag=0;
    efx.PBreakPos=0;
    
    song_end=0;
    if(opt.start_pos >= M.songlength)
	opt.start_pos=0;
    
    songpos=opt.start_pos-1;
    advance_songpos=1; /* Not needed, but removes a compiler warning */
    linepos=63;
    
    speed=opt.speed;
    tempo=opt.tempo;
    
    tick_time=250.0/tempo;
    tick=speed;

    for(;;) {
	tick++;
	if(tick >= speed) { /* Next line/position ... */
	    if(opt.sync_output)
		ioctl (seqfd, SNDCTL_SEQ_SYNC, 0);
	    
	    tick=0;
	    
	    if(efx.pattern_delay)   /* Update patterndelay if needed */
		--efx.pattern_delay;
	    
	    /* Check line jumps (EFX_LOOP) on tick #0 in each line */
	    
	    if(efx.PBreakFlag) {
		linepos=efx.PBreakPos;
		efx.PBreakPos=0;
		efx.PBreakFlag=0;
		advance_songpos=0;
	    }
	    
	    /* Check patternjumps (EFX_BREAK|JMP) on tick #0 in each line. */
	    
	    /*
	     * NOTE: A line with EFX_LOOP and then EFX_BREAK on a voice with
	     * higher number will change pattern and start on line 0 regardless
	     * of the argument to EFX_BREAK. This seems like a bug, but
	     * as this is the behaviour of ProTracker, we will emulate it.
	     */
	    
	    if(efx.PosJumpFlag) {
		linepos=efx.PBreakPos;  /* Set line to continue at */
		advance_songpos=1;
	    }
	    else {
		if(!efx.pattern_delay) {
		    if(++linepos == 64) { /* Just advance normally */
			linepos=0;
			advance_songpos=1;
		    }
		}
	    }
	    
	    if(advance_songpos) {
		songpos=(songpos+1)&0x7f; /* PT only allows 0x7f songpos's */
		
		if(songpos >= M.songlength) {
		    if(--opt.loopcount > 0)
			songpos=(M.restartpos<M.songlength ? M.restartpos : 0);
		    else
			song_end=1;
		}
		efx.PBreakPos=0;
		efx.PosJumpFlag=0;
	    }

	    if(opt.maxtime && opt.maxtime*100 <= mod_time)
		song_end=1;
	}
	
	if(song_end)  /* Exits loop when song is done */
	    break;
	
	if(!tick && !efx.pattern_delay) {
	    if(advance_songpos) {
		if(opt.showlines)
		    printf("\nSongpos: %d/%d Pattern: %d/%d",
			   songpos, M.songlength-1,
			   M.patterntable[songpos], M.nr_patterns);
		advance_songpos=0;
	    }
	    if(opt.showlines)
		print_line(M.patterntable[songpos], linepos);
	}
	
	process_line(efx.pattern_delay ? 1 : 0);

	mod_time+=tick_time;
	SEQ_WAIT_TIME((int)(mod_time+0.5));
	SEQ_DUMPBUF();
    }
    
    for(i=0; i < M.nr_voices; ++i)
	SEQ_STOP_NOTE(gus_dev, i, V[i].note, 0);
    
    SEQ_WAIT_TIME((int)mod_time+10); /* Wait 10 extra milli-seconds */
    SEQ_WAIT_TIME((int)mod_time+11); /* Dummy to make sure we don't exit until
				      * above events has taken effect.
				      */
    
    SEQ_DUMPBUF();
    ioctl(seqfd, SNDCTL_SEQ_SYNC, 0);/* Wait until soundoutput is done */
    
    mod_time/=100;

    if(opt.verbose || opt.showlines)
	printf("\n");
    if(opt.verbose)
	printf("\nPlaying-time was %d:%02d.\n", (int)mod_time/60,
	       (int)((int)mod_time)%60);
}


/* When continue_effects is nonzero we just continue uppdating effects that
 * are updated when tick is nonzero (this is for EFX_PATTERNDELAY).
 */

void process_line(int continue_effects)
{
    int v;  /* Active voice */

    for(v=0; v < M.nr_voices; ++v) {
	efx.set_volume=0;
	efx.set_finepitch=0;
	efx.dont_trigger_note=0;
	efx.retrig_note=0;
	efx.kill_voice=0;
	
	cur_event=&M.voice_pattern[v][M.patterntable[songpos]].line[linepos];
	
	if(!tick && !continue_effects) { /* Tick #0 */
	    
	    /* Sample change on the voice? */
	    if(cur_event->sample)
		if(cur_event->sample <= MAX_SAMPLE &&
		   M.sample[cur_event->sample].valid) {
		    if(cur_event->sample != V[v].sample) {
			/* If the new sample is a looped one and there
			 * is no new note, the old note should be
			 * retriggered (at the same pitch that the old one
			 * was playing) starting at the loopstartpoint of
			 * the new sample. The tricky thing is that the
			 * retriggering shouldnt happen until the previous
			 * sample is done with it's current loop (or just
			 * done if it wasn't looped; if the sample already
			 * had all its samples played the retriggering is
			 * done at once). If a new note comes inbetween,
			 * the retriggering is never to be done.
			 *
			 * The above behaviour is a sideeffect of the way 
			 * Amiga moduleplayers handle looping with Paula.
			 * If you really want to understand why it works
			 * like this, read the ProTracker sources.
			 *
			 * Very few modules depend on this feature... 
			 * "Behind the Walls" does.
			 *
			 * This is a bit tricky to implement under Linux, so
			 * we'll deal with it later :-)
			 */
			SEQ_SET_PATCH (gus_dev, v, cur_event->sample);
			V[v].sample=cur_event->sample;
		    }
		    
		    /* Try to reduce number of calls to do_set_finepitch() */
		    if(V[v].finetune != M.sample[cur_event->sample].finetune
		       || V[v].pitchbend)
			efx.set_finepitch=1;
		    
		    V[v].finetune=M.sample[cur_event->sample].finetune;
		    V[v].sampleoffset=0;
		}
		else {
		    /* Change to nonspecified sample -> silence the voice */
		    efx.kill_voice=1;
		}
	    
	    /* Reset volume to default instrument-volume if we have
	     * a (valid) sample specified. Also make sure it affects
	     * a sample that is already playing.
	     */
	    if(cur_event->sample && M.sample[cur_event->sample].valid) {
		V[v].volume=V[v].real_volume=M.sample[V[v].sample].volume;
		efx.set_volume=1;
	    }

	    check_tick0_efx(v); /* Check effects */

	    /* Silence voice if needed */
	    if(efx.kill_voice) {
		V[v].sample=0;
		V[v].volume=V[v].real_volume=0;
		SEQ_START_NOTE(gus_dev, v, 255, 0);
	    }

	    /* If we have a note and no EFX_PORTANOTE(&VS) or EFX_NOTEDELAY,
	     * we trigger it.
	     */
	    if(cur_event->note && !efx.dont_trigger_note) {
		if(V[v].sample) {
		    SEQ_START_NOTE(gus_dev, v, cur_event->note,
				   MIN(2*V[v].real_volume, 127));
		    if(V[v].sampleoffset)
			GUS_VOICE_POS(gus_dev, v, V[v].sampleoffset);
		    if(efx.set_finepitch)
			do_set_finepitch(v);
		}
	    }
	    else { /* No note to play, change volume/finepitch if needed
		    * (and there is a sample specified for the voice).
		    */
		if(V[v].sample) {
		    if(efx.set_volume)
			SEQ_START_NOTE(gus_dev, v, 255,
				       MIN(2*V[v].real_volume, 127));
		    if(efx.set_finepitch)
			do_set_finepitch(v);
		}
	    }
	}
	else {  /* NOT tick #0 */
	    
	    check_efx(v); /* Update effects */
	    
	    if(V[v].sample) {
		if(efx.retrig_note && opt.verbose >= 5)
		    printf("[R:%d]", tick);
		
		if(efx.retrig_note || efx.set_volume)
		    SEQ_START_NOTE(gus_dev, v,
				   (efx.retrig_note ? cur_event->note : 255),
				   MIN(2*V[v].real_volume, 127));
		
		if(efx.retrig_note && V[v].sampleoffset)
		    GUS_VOICE_POS(gus_dev, v, V[v].sampleoffset);
		
		/* Here we always set finepitch if we have efx.retrig_note
		 * as the efx.set_finepitch that is set at tick #0 is already
		 * cleared, and there could be a finetunedifference.
		 */
		if(efx.retrig_note || efx.set_finepitch)
		    do_set_finepitch(v);
	    }
	}
    }
}


/* I don't know how voices are positioned in an eight track module so I
 * just set them to something... (- is left, + is right)
 */

int get_voice_balance(int v)
{
    static char v4[]={ -100, 100, 100, -100 };
    static char v8[]={ -100, 100, 100, -100, -100, 100, 100, -100 };
    
    if(M.nr_voices == 4)
	return v4[v];
    else if(M.nr_voices == 8)
	return v8[v];
    else
	return 0;
}

/* Sets the pitch for a voice. Should actually be in effects.c, but I wanted
 * to keep it clean from sequencer-commands.
 */

void do_set_finepitch(int v)
{
    set_pitch(v);
    
    SEQ_PITCHBEND(gus_dev, v, V[v].pitchbend+(V[v].finetune*100)/8);
    if(opt.active_voices&(1<<v) && opt.verbose >=5)
	printf("'%d+%d",V[v].pitchbend, V[v].finetune);
}

/* A line is printed on the screen in the following format:

||42||C-3 0F Vol 42|--- 00 Vol 04|D-2 0F Arp 01|C-3 0f       ||42||

*/

void print_line(int p, int l)
{
    char buf[512];
    int pos, v;
    
    sprintf(buf, "||%02X|", l);
    pos=5;
    for(v=0; v < M.nr_voices; ++v) {
	if(opt.active_voices&(1<<v)) {
	    print_event(&buf[pos], &M.voice_pattern[v][p].line[l]);
	    pos+=14;
	}
    }
    printf("\n%s||%02X||", buf, l);
    fflush(stdout);
}


void print_event(char *buf, struct event *n)
{
#if 1
    sprintf(buf, "|%s ",
	    (n->note?notenames[n->note-BASE_NOTE]:"   "));

    if(n->sample)
	sprintf(buf+5, "%02X ", n->sample);
    else
	strcat(buf, "   ");

    if((!n->effect && n->arg) || n->effect)
	sprintf(buf+8, "%s%02X", effectnames[n->effect], n->arg);
    else
	strcat(buf, "      ");

#else    
    if((!n->effect && n->arg) || n->effect) {
	sprintf(buf, "|%s %02X %s%02X",
		(n->note?notenames[n->note-BASE_NOTE]:"---"),
		n->sample,
		effectnames[n->effect],
		n->arg);
    }
    else {
	sprintf(buf, "|%s %02X       ",
		(n->note?notenames[n->note-BASE_NOTE]:"---"),
		n->sample);
    }
#endif
}
