#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

#include "mod.h"

/* Variables used for processing effects */

extern struct mod_info M;
extern struct options opt;

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

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

/* Tables */

extern short vibrato_tables[4][64];
extern int periodtable[12*5*16];


void check_tick0_efx(int v)
{
    int e, tmp;
    
    /* Make sure V[v].note is set when we get a new note (and the note
     * isn't an argument to the toneportamento-effect). Also take care
     * of retriggering vibrato/tremolo-waveforms.
     */
    if(cur_event->note && cur_event->effect != EFX_PORTANOTE &&
       cur_event->effect != EFX_PORTANOTEVOLSLIDE) {
	if(V[v].real_period != V[v].period) {
	    efx.set_finepitch=1;
	}
	V[v].note=cur_event->note; /* Set V[v].note */
	V[v].real_period=V[v].period=periodtable[V[v].note-BASE_NOTE];
	if(V[v].vibrato_retrig)
	    V[v].vibrato_pos=0;
	if(V[v].tremolo_retrig)
	    V[v].tremolo_pos=0;
    }
    
    /* Reset pitch if we have an empty event, just like PT */
    if(!cur_event->sample && !cur_event->note &&
       !cur_event->effect && !cur_event->arg) {
	if(V[v].real_period != V[v].period) {
	    efx.set_finepitch=1;
	}
	V[v].real_period=V[v].period;
    }

    /* The following effects are only checked on the first tick */
    
    e=cur_event->effect;
    switch(e) {
      case EFX_SPEED:
	if(!cur_event->arg) { /* Speed 0 handling */
	    if(opt.speed0stop)
		song_end=1;
	    break;
	}

	if(cur_event->arg < 32 || opt.nobpm)
	    speed=cur_event->arg;
	else {
	    tempo=cur_event->arg;
	    tick_time=250.0/tempo;
	}
	break;
      case EFX_BREAK:
	efx.PBreakPos=(cur_event->arg&0x0f)+((cur_event->arg>>4)&0x0f)*10;
	if(efx.PBreakPos > 63)
	    efx.PBreakPos=0;
	efx.PosJumpFlag=1;
	break;
      case EFX_JUMP:
	if(cur_event->arg <= songpos && --opt.loopcount <= 0)
	    songpos=M.songlength;     /* Make sure song_end gets set */
	else
	    songpos=cur_event->arg-1; /* -1 as songpos++ is always done */
	
	efx.PBreakPos=0;
	efx.PosJumpFlag=1;
	break;

      case EFX_VOLUME:
	V[v].volume=V[v].real_volume=MIN(cur_event->arg, 64);
	efx.set_volume=1;
	break;
      case EFX_SAMPLEOFFSET:
	if(cur_event->arg)
	    V[v].last_sampleoffset=cur_event->arg*256;
	
	if(V[v].last_sampleoffset < M.sample[V[v].sample].length)
	    V[v].sampleoffset=V[v].last_sampleoffset;
	else { 
	    /* An invalid offset silences the voice */
	    efx.kill_voice=1;
	}
	break;
	
	/* The following four effects only initialize toneportamento and
	 * vibrato for the ticks to come.
	 */
      case EFX_PORTANOTE:
	if(cur_event->arg)
	    V[v].toneporta_speed=cur_event->arg;
      case EFX_PORTANOTEVOLSLIDE:
	if(cur_event->note) {
	    tmp=cur_event->note;
	    tmp=MIN(tmp, opt.high_note); /* Not too high. PT: B-3 */
	    tmp=MAX(tmp, opt.low_note);  /* Not too low . PT: C-1 */
	    V[v].period_goal=periodtable[tmp-BASE_NOTE];
	    efx.dont_trigger_note=1; /* Make sure note doesn't get played */
	}
	break;
	
      case EFX_VIBRATO:
	tmp=cur_event->arg;
	if(tmp&0x0f)
	    V[v].vibrato_amplitude=tmp&0x0f;
	if((tmp>>4)&0x0f)
	    V[v].vibrato_speed=(tmp>>4)&0x0f;
      case EFX_VIBRATOVOLSLIDE:
	/* Nothing to do here when sliding volume */
	break;
	
	/* In the protracker sources the following EFXs are checked on
	 * ALL ticks, but aborts if tick != 0. So we check'em here.
	 */
		
      case EFX_FINEVOLSLIDEUP:
	slide_volume(v, cur_event->arg);
	break;
      case EFX_FINEVOLSLIDEDOWN:
	slide_volume(v, -cur_event->arg);
	break;
      case EFX_FINEPORTAUP:
	do_set_portamento(v, -cur_event->arg);
	break;
      case EFX_FINEPORTADOWN:
	do_set_portamento(v, cur_event->arg);
	break;
      case EFX_PATTERNDELAY:
	efx.pattern_delay=cur_event->arg+1; /* +1 as we should  play current
					     * line + arg EXTRA lines.
					     */
	break;
      default:
	check_remaining_efx(v); /* Effects that always should be processed */
    }

    /* Reset real period if needed. This is a real UGLY way to do it, but
     * PT does it this way, and to make sure all cases get correct I just
     * mimic it's behaviour.
     */
    if(e<=0x0f && e!=EFX_SAMPLEOFFSET && e!=EFX_JUMP && e!=EFX_BREAK &&
       e!=EFX_SPEED && e!=EFX_VOLUME) {
	if(V[v].real_period != V[v].period) {
	    V[v].real_period=V[v].period;
	    efx.set_finepitch=1;
	}
    }
}


/* These effects are checked on all ticks ticks except the first
 * one on each line.
 */

void check_efx(int v)
{
    int e;

    e=cur_event->effect;
    switch(e) {
      case EFX_ARPEGGIO:
	if(!cur_event->arg)
	    break;
	do_arpeggio(v, cur_event->arg);
	break;

      case EFX_PORTAUP:
	do_set_portamento(v, -cur_event->arg);
	break;
      case EFX_PORTADOWN:
	do_set_portamento(v, cur_event->arg);
	break;
      case EFX_PORTANOTEVOLSLIDE:
	do_volumeslide(v, cur_event->arg);
      case EFX_PORTANOTE:
	do_toneportamento(v);
	break;
	
	break;
      case EFX_VIBRATOVOLSLIDE:
	do_volumeslide(v, cur_event->arg);
      case EFX_VIBRATO:
	do_vibrato(v);
	break;
	
      case EFX_TREMOLO:
	break;
      case EFX_VOLSLIDE:
	do_volumeslide(v, cur_event->arg);
	break;
      case EFX_RETRIGGER:
	if(cur_event->arg && !(tick%cur_event->arg))
	    efx.retrig_note=1;
	break;
      default:
	check_remaining_efx(v);
    }

    /* Reset real period if needed. This is a real UGLY way to do it, but
     * PT does it this way, and to make sure all cases get correct I just
     * mimic it's behaviour.
     */
    if(e<=0x0f && e!=EFX_ARPEGGIO && e!=EFX_PORTAUP && e!=EFX_PORTADOWN &&
       e!=EFX_PORTANOTE && e!=EFX_PORTANOTEVOLSLIDE && e!=EFX_VIBRATO &&
       e!=EFX_VIBRATOVOLSLIDE) {
	if(V[v].real_period != V[v].period) {
	    V[v].real_period=V[v].period;
	    efx.set_finepitch=1;
	}
    }
}


/* This function checks and updates the effects that should be checked on
 * every tick.
 */

void check_remaining_efx(int v)
{
    char tmp;

    switch(cur_event->effect) {
      case EFX_GLISSANDO:
	V[v].glissando=cur_event->arg;
	break;
      case EFX_VIBWAVEFORM:
	V[v].vibrato_waveform=cur_event->arg&0x03;
	V[v].vibrato_retrig=(~cur_event->arg)&0x04;
	break;
      case EFX_TREMWAVEFORM:
	V[v].tremolo_waveform=cur_event->arg&0x03;
	V[v].tremolo_retrig=(~cur_event->arg)&0x04;
	break;

      case EFX_FINETUNE:
	tmp=cur_event->arg;
	tmp=(tmp > 7 ? tmp|0xf0 : tmp);
	if(V[v].finetune != tmp) { /* Only set it if it's changed */
	    V[v].finetune=tmp;
	    efx.set_finepitch=1;
	}
	break;
      case EFX_LOOP:
	/* Note: This EFX cannot be moved to the tick #0 block as
	 * it should be processed even if we have EFX_PATTERNDELAY.
	 * Not too nice, but PT does it this way...
	 */
	if(tick)
	    break;
	
	if(!cur_event->arg)
	    V[v].loopstartpos=linepos-1; /* -1 as linepos++ is always done */
	else {
	    if(!V[v].loopcount) {
		V[v].loopcount=cur_event->arg;
		efx.PBreakPos=V[v].loopstartpos;
		efx.PBreakFlag=1;
	    }
	    else {
		if(--V[v].loopcount) {
		    efx.PBreakPos=V[v].loopstartpos;
		    efx.PBreakFlag=1;
		}
	    }
	}
	break;
      case EFX_NOTECUT:
	if(tick == cur_event->arg) {
	    V[v].volume=V[v].real_volume=0;
	    efx.set_volume=1;
	}
	break;
      case EFX_NOTEDELAY:
	if(cur_event->arg) {
	    if(!tick)  /* Prevent note-trigger on tick #0 if arg>0 */
		efx.dont_trigger_note=1;
	    else if(tick == cur_event->arg)
		efx.retrig_note=1; /* Make sure note gets played when arg>0 */
	}
	break;
	
      case EFX_UNUSED2:
      case EFX_FILTER:       /* No sense to implement a LP-filter on a GUS */
      case EFX_INVERTEDLOOP: /* Not feasible to implement on a GUS         */
	break;
    }
}


void do_arpeggio(int v, int arg)
{
    int note;
    
    switch(tick%3) {
      case 0:
	V[v].real_period=V[v].period;
	break;
      case 1:
	note=period2note(V[v].period)+((arg>>4)&0x0f);
	/* Only change pitch if we don't go too high. PT: B-3 */
	if(note <= opt.high_note) {
	    V[v].real_period=periodtable[note-BASE_NOTE];
	}
	break;
      case 2:
	note=period2note(V[v].period)+(arg&0x0f);
	/* Only change pitch if we don't go too high. PT: B-3 */
	if(note <= opt.high_note) {
	    V[v].real_period=periodtable[note-BASE_NOTE];
	}
    }
    efx.set_finepitch=1;
}


void do_volumeslide(int v, unsigned char arg)
{
	if(arg&0xf0)
	    slide_volume(v, (arg>>4)&0x0f); /* Slide volume up   */
	else
	    slide_volume(v, -(arg&0x0f));   /* Slide volume down */
}

void slide_volume(int v, int amount)
{
    V[v].volume=V[v].real_volume=MAX(MIN(64, V[v].volume+amount), 0);
    efx.set_volume=1;
}

void do_set_portamento(int v, int amount)
{
    int per;
    
    per=V[v].period+amount;
    
    /* No too high or too low */
    per=MAX(per, periodtable[opt.high_note-BASE_NOTE]);
    per=MIN(per, periodtable[opt.low_note-BASE_NOTE]);

    V[v].period=V[v].real_period=per;
    efx.set_finepitch=1;
}


void do_toneportamento(int v)
{
    if(!V[v].period_goal)
	return;
    
    if(V[v].period_goal > V[v].period) {
	V[v].period+=V[v].toneporta_speed;
	if(V[v].period >= V[v].period_goal) {
	    V[v].period=V[v].period_goal;
	    V[v].period_goal=0;
	    if(opt.verbose >= 5)
		printf("D");
	}
    }
    else {
	V[v].period-=V[v].toneporta_speed;
	if(V[v].period <= V[v].period_goal) {
	    V[v].period=V[v].period_goal;
	    V[v].period_goal=0;
	    if(opt.verbose >= 5)
		printf("D");
	}
    }
    
    /* Modify pitch to closest note if we have glissando on */
    if(V[v].glissando)
	V[v].real_period=periodtable[period2note(V[v].period)-BASE_NOTE];
    else
	V[v].real_period=V[v].period;
    
    efx.set_finepitch=1;
}


void do_vibrato(int v)
{
    V[v].real_period=V[v].period+
	(vibrato_tables[V[v].vibrato_waveform][V[v].vibrato_pos/4]
	 *V[v].vibrato_amplitude)/128; /* /64 on old trackers */
    
    V[v].vibrato_pos=(V[v].vibrato_pos+V[v].vibrato_speed*4)%256;
    
    efx.set_finepitch=1;
}


void do_tremolo(int v)
{
    V[v].real_volume=V[v].volume+
	(vibrato_tables[V[v].tremolo_waveform][V[v].tremolo_pos/4]
	 *V[v].tremolo_amplitude)/64;
    
    V[v].tremolo_pos=(V[v].tremolo_pos+V[v].tremolo_speed*4)%256;
}


void set_pitch(int v)
{
    int note, period, noteperiod, note_m1_period;
    
    period=V[v].real_period;                 /* period to bend to  */
    note=period2note(period);                /* "closest" note     */

    noteperiod=periodtable[note-BASE_NOTE];  /* period of note     */
    
    if(noteperiod == period) { /* Right on a note */
	V[v].pitchbend=(note-V[v].note)*100;
	return;
    }

    /* The following is always safe because of the above test */
    note_m1_period=periodtable[note-1-BASE_NOTE]; /* period of (note-1) */
    
    if(note < V[v].note) {
	V[v].pitchbend=(note-V[v].note)*100-
	    ((period-noteperiod)*100)/(note_m1_period-noteperiod);
    }
    else {
	V[v].pitchbend=(note-V[v].note-1)*100+
	    ((note_m1_period-period)*100)/
		(note_m1_period-noteperiod);
    }
}
