/*
 *  effects.c - Processes effects.
 *
 *  (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
 */

#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, restart_song;
extern char is_first_ult_effect;

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[NR_OCTAVES*12];

/* Defines */

#define DOWN 1
#define UP   2

#define NORMAL 0
#define FINE   1

void check_tick0_efx(int v)
{
    int e, e2, tmp;
    unsigned char a;
    
    /* Make sure V[v].note is set when we get a new note (and the note
     * isn't a NOTE_OFF or an argument to the toneportamento-effect). Also
     * take care of retriggering vibrato/tremolo-waveforms.
     */
    if(is_first_ult_effect && cur_event->note &&
       cur_event->note != NOTE_OFF &&
       cur_event->effect != EFX_PORTANOTE &&
       cur_event->effect2 != EFX_PORTANOTE &&
       cur_event->effect != EFX_PORTANOTEVOLSLIDE &&
       cur_event->effect2 != 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 &&
       !cur_event->effect2 && !cur_event->arg2) {
	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;
    a=cur_event->arg;
    switch(e) {
      case EFX_PTSPEED:
	if(a < 32 || opt.nobpm) /* Takes care of speed-0 too */
	    set_speed(a);
	else
	    set_tempo(a);
	break;
      case EFX_SPEED:
	set_speed(a);
	break;
      case EFX_TEMPO:
	if(a >= 32)
	    set_tempo(a);  /* No tempo below 32 set */
	break;
      case EFX_BREAK:
	efx.PBreakPos=(a&0x0f)+((a>>4)&0x0f)*10;
	if(efx.PBreakPos > 63)
	    efx.PBreakPos=0;
	efx.PosJumpFlag=1;
	break;
      case EFX_JUMP:
	if(a <= songpos) { /* Jump backward */
	    restart_song=1;             /* Make sure play.c knows song ended */

	    if(opt.loop_module || !opt.break_loops)
		songpos=a-1; /* -1 as songpos++ is always done */
	    else
		songpos=M.songlength;     /* Make sure song_end gets set */
	}
	else {
	    if(a <= M.songlength-1) /* Jump forward */
		songpos=a-1; /* -1 as songpos++ is always done */
	    else {
		restart_song=1;
		songpos=M.songlength;     /* Make sure song_end gets set */
	    }
	}

	efx.PBreakPos=0;
	efx.PosJumpFlag=1;
	break;

      case EFX_VOLUME:
	V[v].volume=V[v].real_volume=MIN(a, M.volrange);
	efx.set_volume=1;
	break;
      case EFX_SAMPLEOFFSET:    /* Why can't they all do it the same way :( */
      case EFX_SAMPLEOFFSET_ULT:
      case EFX_SAMPLEOFFSET_S3M:
	if(a) {
	    if(e == EFX_SAMPLEOFFSET)
		V[v].last_sampleoffset=a*256;
	    else if(e == EFX_SAMPLEOFFSET_S3M) {
		V[v].last_sampleoffset=a*256;
		if(M.sample[V[v].sample].looped &&
		   V[v].last_sampleoffset > M.sample[V[v].sample].repeat_end) {
		    /* Looplength */
		    tmp=M.sample[V[v].sample].repeat_end+1-
			M.sample[V[v].sample].repeat_start;
		    /* Take loop into account */
		    V[v].last_sampleoffset=M.sample[V[v].sample].repeat_start+
			(V[v].last_sampleoffset-
			 M.sample[V[v].sample].repeat_start)%tmp;
		}
	    }
	    else {
		/* Check for the '99' effect */
		if(cur_event->effect2 == EFX_SAMPLEOFFSET) {
		    if(!is_first_ult_effect) /* Abort for second effect */
			break;
		    V[v].last_sampleoffset=((a<<8)+cur_event->arg2)*4;
		}
		else
		    V[v].last_sampleoffset=a*1024;
	    }
	}

	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(a) {
	    V[v].toneporta_speed=a*4;
	}
	/* Fall through */
      case EFX_PORTANOTEVOLSLIDE:
	if(cur_event->note && cur_event->note != NOTE_OFF) {
	    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=a;
	if(tmp&0x0f)
	    V[v].vibrato_amplitude=tmp&0x0f;
	if((tmp>>4)&0x0f)
	    V[v].vibrato_speed=(tmp>>4)&0x0f;
	/* Fall through */
      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:
	V[v].last_volumeslidetype=FINE;
	slide_volume(v, a, UP);
	break;
      case EFX_FINEVOLSLIDEDOWN:
	V[v].last_volumeslidetype=FINE;
	slide_volume(v, a, DOWN);
	break;
      case EFX_VOLSLIDECONTINUE:
	if(V[v].last_volumeslidetype == FINE)
	    slide_volume(v, 0, 0);
	break;
      case EFX_FINEPORTAUP:
	V[v].last_portamentotype=FINE;
	do_set_portamento(v, a*4, UP);
	break;
      case EFX_FINEPORTADOWN:
	V[v].last_portamentotype=FINE;
	do_set_portamento(v, a*4, DOWN);
	break;
      case EFX_EXTRAFINEPORTAUP:
	V[v].last_portamentotype=FINE;
	do_set_portamento(v, a, UP);
	break;
      case EFX_EXTRAFINEPORTADOWN:
	V[v].last_portamentotype=FINE;
	do_set_portamento(v, a, DOWN);
	break;
      case EFX_PORTAUPCONTINUE:
	if(V[v].last_portamentotype == FINE)
	    do_set_portamento(v, V[v].last_portamento, UP);
	break;
      case EFX_PORTADOWNCONTINUE:
	if(V[v].last_portamentotype == FINE)
	    do_set_portamento(v, V[v].last_portamento, DOWN);
	break;
      case EFX_PATTERNDELAY:
	efx.pattern_delay=a+1; /* +1 as we should  play current
				* line + arg EXTRA lines.
				*/
	break;

      case EFX_BALANCE:
	M.panning[v]=a&0x0f;
	efx.set_balance=1;
	break;
      case EFX_VIBDEPTH:    /* I _think_ this is what the ultradocs means */
	if(a&0x0f)
	    V[v].vibrato_amplitude=a&0x0f;
	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.
     * For ULTs we do this after the second effect has been processed.
     * For S3Ms we do it before effect2 (volume/NULL) is processed (no harm).
     */
    if(M.format != MODFORMAT_ULT || !is_first_ult_effect) {
	e2=cur_event->effect2;
	if(e<=0x0f && e!=EFX_SAMPLEOFFSET && e!=EFX_JUMP && e!=EFX_BREAK &&
	   e!=EFX_PTSPEED && e!=EFX_SPEED && e!=EFX_TEMPO && e!=EFX_VOLUME &&
	   e2<=0x0f && e2!=EFX_SAMPLEOFFSET && e2!=EFX_JUMP && e2!=EFX_BREAK &&
	   e2!=EFX_PTSPEED && e2!=EFX_SPEED && e2!=EFX_TEMPO &&
	   e2!=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, e2;
    unsigned char a, tmp;

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

      case EFX_PORTAUP:
	V[v].last_portamentotype=NORMAL;
	do_set_portamento(v, a*4, UP);
	break;
      case EFX_PORTADOWN:
	V[v].last_portamentotype=NORMAL;
	do_set_portamento(v, a*4, DOWN);
	break;
      case EFX_PORTAUPCONTINUE:
	if(V[v].last_portamentotype == NORMAL)
	    do_set_portamento(v, V[v].last_portamento, UP);
	break;
      case EFX_PORTADOWNCONTINUE:
	if(V[v].last_portamentotype == NORMAL)
	    do_set_portamento(v, V[v].last_portamento, DOWN);
	break;
      case EFX_PORTANOTEVOLSLIDE:
	V[v].last_volumeslidetype=NORMAL;
	do_volumeslide(v, a);
      case EFX_PORTANOTE:
	do_toneportamento(v);
	break;
	
      case EFX_VIBRATOVOLSLIDE:	
	V[v].last_volumeslidetype=NORMAL;
	do_volumeslide(v, a);
      case EFX_VIBRATO:
	do_vibrato(v);
	break;
	
      case EFX_TREMOLO:
	do_tremolo(v);
	break;

      case EFX_VOLSLIDE:
	V[v].last_volumeslidetype=NORMAL;
	do_volumeslide(v, a);
	break;
      case EFX_VOLSLIDECONTINUE:
	if(V[v].last_volumeslidetype == NORMAL)
	    slide_volume(v, 0, 0);
	break;

      case EFX_RETRIGGER:
	if(a && !(tick%a))
	    efx.retrig_note=1;
	break;
      case EFX_RETRIGGERVOLSLIDE: /* S3M's retrigger */
	if(a && !(tick%(a&0x0f))) {
	    efx.retrig_note=1;
	    tmp=(a>>4)&0x0f;
	    switch(tmp) {
	      case 1: case 2: case 3: case 4: case 5:
		slide_volume(v, 1<<(tmp-1), DOWN);
		break;
	      case 9: case 10: case 11: case 12: case 13:
		slide_volume(v, 1<<(tmp-9), UP);
		break;
	      case 6:
		slide_volume(v, V[v].volume-V[v].volume*2/3, DOWN);
		break;
	      case 7:
		slide_volume(v, V[v].volume-V[v].volume/2, DOWN);
		break;
	      case 14:
		slide_volume(v, V[v].volume-V[v].volume*3/2, UP);
		break;
	      case 15:
		slide_volume(v, V[v].volume-V[v].volume*2, UP);
		break;
	      case 8:
	      case 0:
	    }
	}
	break;
	
      case EFX_TREMOR: /* As close as I get w/o more docs */
	tmp=((a>>4)&0x0f)+(a&0x0f);
	if(tmp) {
	    if((tick%tmp) == ((a>>4)&0x0f))
		V[v].real_volume=0;
	    else if(!(tick%tmp))
		V[v].real_volume=V[v].volume;
	    if(V[v].volume != V[v].real_volume)
		efx.set_volume=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.
     * For ULTs we do this after the second effect has been processed.
     */
    if(M.format != MODFORMAT_ULT || !is_first_ult_effect) {
	e2=cur_event->effect2;
	if(e<=0x0f && e!=EFX_ARPEGGIO && e!=EFX_PORTAUP && e!=EFX_PORTADOWN &&
	   e!=EFX_PORTANOTE && e!=EFX_PORTANOTEVOLSLIDE && e!=EFX_VIBRATO &&
	   e!=EFX_VIBRATOVOLSLIDE &&
	   e!=EFX_PORTAUPCONTINUE && e!= EFX_PORTADOWNCONTINUE &&/* S3M only */
	   e2<=0x0f && e2!=EFX_ARPEGGIO && e2!=EFX_PORTAUP &&
	   e2!=EFX_PORTANOTE && e2!=EFX_PORTANOTEVOLSLIDE && e2!=EFX_VIBRATO &&
	   e2!=EFX_VIBRATOVOLSLIDE && e2!=EFX_PORTADOWN) {
	    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;
    unsigned char a;

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

      case EFX_FINETUNE:
	tmp=a;
	tmp=(tmp > 7 ? tmp|0xf0 : tmp);
	if(V[v].finetune != tmp) { /* Only set it if it's changed */
	    V[v].finetune=tmp;  /* Store as signed char */
	    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(!a)
	    V[v].loopstartpos=linepos-1; /* -1 as linepos++ is always done */
	else {
	    if(!V[v].loopcount) {
		V[v].loopcount=a;
		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 == a) {
	    V[v].volume=V[v].real_volume=0;
	    efx.set_volume=1;
	}
	break;
      case EFX_NOTEDELAY:
	/* A notedelay of 0 causes note not to be played at all */
	if(a) {
	    if(!tick)  /* Prevent note-trigger on tick #0 if arg>0 */
		efx.dont_trigger_note=1;
	    else if(tick == a)
		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;

      default: /* Do nothing for other effects */
    }
}


void set_speed(int s)
{
    if(!s) {  /* Speed 0 handling */
	if(opt.speed0stop) { /* stop song */
	    restart_song=1;
	    songpos=M.songlength;
	    efx.PosJumpFlag=1; /* These needed to get song_end set */
	    efx.PBreakPos=0;
	}
    }
    else
	speed=s;
}


void set_tempo(int t)
{
    tempo=t;
    tick_time=250.0/tempo;
}


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(M.format != MODFORMAT_S3M) { /* PT checks UP before DOWN */
	if(arg&0xf0)
	    slide_volume(v, (arg>>4)&0x0f, UP); /* Slide volume up   */
	else
	    slide_volume(v, arg&0x0f, DOWN);   /* Slide volume down */
    }
    else { /* ST3 checks DOWN before UP */
	if(arg&0x0f)
	    slide_volume(v, arg&0x0f, DOWN);   /* Slide volume down */
	else
	    slide_volume(v, (arg>>4)&0x0f, UP); /* Slide volume up   */
    }
}

void slide_volume(int v, int amount, char dir)
{
    if(dir == DOWN)
	amount=-amount;
    
    if(M.format == MODFORMAT_S3M) { /* S3M's use 0 as last value */
	if(!amount)
	    amount=V[v].last_volumeslide;
	else
	    V[v].last_volumeslide=amount;
    }
    
    V[v].volume=V[v].real_volume=MAX(MIN(M.volrange, V[v].volume+amount), 0);
    efx.set_volume=1;
}

void do_set_portamento(int v, int amount, char dir)
{
    int per;

    if(M.format == MODFORMAT_S3M)
	V[v].last_portamento=amount;

    if(dir == UP)
	amount=-amount;
    
    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;
	}
    }
    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;
	}
    }
    
    /* 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)
{
    if(V[v].period < 20)
	error("ARHGH2: %d\n", V[v].period);

    V[v].real_period=V[v].period+(
	(vibrato_tables[V[v].vibrato_waveform][V[v].vibrato_pos/4]
	 *V[v].vibrato_amplitude)/128)*4; /* /64 on old trackers */
    /* The *4 above is because X-2 => X-4 */

    if(V[v].real_period < 20)
	error("ARHGH: %d\n", V[v].real_period);
    
    V[v].vibrato_pos=(V[v].vibrato_pos+V[v].vibrato_speed*4)%256;
    
    efx.set_finepitch=1;
}

/* Tremolo is scaled with M.volrange */

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)*M.volrange/64;
    V[v].real_volume=MAX(MIN(V[v].real_volume, M.volrange), 0);
    
    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);
    }
}
