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

#ifdef linux
#include <sys/soundcard.h>
#endif

#include "str.h"

#ifdef linux
extern int audio;
extern unsigned char *audiobuf;
extern int abuf_ptr;
extern int abuf_size;
#else
extern FILE *audio;
#endif

extern char *command;
extern int quiet_mode, verbose_mode;
extern int mute[4];
#ifdef linux
extern int dsp_stereo;
extern int dsp_samplesize;
extern long dsp_speed;
#endif

extern FILE *fp;

static int VSYNC;	/* number of sample bytes to output in 1/50 sec */

static int notes, channel, vsync, bytes, pat_num;	/* loop variables */
static int note, pat;
static int end_pattern;

/* song data and parameters */
static int speed;
static char songlength, songrepeat, tune[128];
static int nvoices;
static Voice voices[32];
static char num_patterns;
static Pattern patterns[128];
static Channel ch[4];

/* table for generating pitches */
static int step_table[1024];

/* sine wave table for the vibrato effect */
static int sin_table[] = {
    0x00, 0x18, 0x31, 0x4A, 0x61, 0x78, 0x8D, 0xA1,
    0xB4, 0xC5, 0xD4, 0xE0, 0xEB, 0xF4, 0xFA, 0xFD,
    0xFF, 0xFD, 0xFA, 0xF4, 0xEB, 0xE0, 0xD4, 0xC5,
    0xB4, 0xA1, 0x8D, 0x78, 0x61, 0x4A, 0x31, 0x18
};

/* period table for notes C0-B2, used for arpeggio effect */
static int periods[] = {
    0x358, 0x328, 0x2FA, 0x2D0, 0x2A6, 0x280,
    0x25C, 0x23A, 0x21A, 0x1FC, 0x1E0, 0x1C5,
    0x1AC, 0x194, 0x17D, 0x168, 0x153, 0x140,
    0x12E, 0x11D, 0x10D, 0x0FE, 0x0F0, 0x0E2,
    0x0D6, 0x0CA, 0x0BE, 0x0B4, 0x0AA, 0x0A0,
    0x097, 0x08F, 0x087, 0x07F, 0x078, 0x071
};

/* skip n bytes of file f - stdin has no fseek */
void 
byteskip(f, n)
FILE *f;
int n;
{
    while(n--)
	fgetc(f);
}

/* get string of length <len> from file f */
char *
getstring(f, len)
FILE *f;
int len;
{
    static char s[256];
    int i;

    for(i = 0; i < len; i++) {
	if((s[i] = fgetc(f)) == '\1')
	    s[i] = '\0';		/* bug fix for some few modules */
	if(s[i] < 32 && s[i]) {
	    fprintf(stderr, "This is not a string. Wrong format?\n");
	    exit(EXIT_FAILURE);
	}
    }
    s[len] = '\0';

    return s;
}

#ifdef linux
inline void 
audioput(c)
int c;
{
    audiobuf[abuf_ptr++] = c;
    if(abuf_ptr >= ABUF_SIZE) {
	if(write(audio, audiobuf, abuf_ptr) == -1) {
	    perror("audio_write");
	    exit(EXIT_FAILURE);
	}
	abuf_ptr = 0;
    }
}

inline void
audioput16(c)
int c;
{
    audiobuf[abuf_ptr++] = c & 0xFF;
    audiobuf[abuf_ptr++] = c >> 8;
    if((abuf_ptr + 1) >= ABUF_SIZE) {
	if(write(audio, audiobuf, abuf_ptr) == -1) {
	    perror("audio_write");
	    exit(EXIT_FAILURE);
	}
	abuf_ptr = 0;
    }
}

#else
/************************************************************************/
/* For routine 'cvt' only						*/
/************************************************************************/
/* Copyright 1989 by Rich Gopstein and Harris Corporation		*/
/************************************************************************/

unsigned int
cvt(ch)
int ch;
{
    int mask;

    if(ch < 0) {
	ch = -ch;
	mask = 0x7F;
    } else
	mask = 0xFF;

    if(ch < 32)
	ch = 0xF0 | (15 - (ch >> 1));
    else if(ch < 96)
	ch = 0xE0 | (15 - ((ch - 32) >> 2));
    else if(ch < 224)
	ch = 0xD0 | (15 - ((ch - 96) >> 3));
    else if(ch < 480)
	ch = 0xC0 | (15 - ((ch - 224) >> 4));
    else if(ch < 992)
	ch = 0xB0 | (15 - ((ch - 480) >> 5));
    else if(ch < 2016)
	ch = 0xA0 | (15 - ((ch - 992) >> 6));
    else if(ch < 4064)
	ch = 0x90 | (15 - ((ch - 2016) >> 7));
    else if(ch < 8160)
	ch = 0x80 | (15 - ((ch - 4064) >> 8));
    else
	ch = 0x80;

    return mask & ch;
}
#endif

void
play_song()
{
    int i;

    /* set up pitch table:
     * 3576872 is the base clock frequency of the Amiga's Paula chip,
     * the pitch i is the number of cycles between outputting two samples.
     * => i / 3576872 is the delay between two bytes
     * => our_freq * i / 3576872 is our delay.
     * The 65536 are just there for accuracy of integer operations (<< 16).
     */
    step_table[0] = 0;
    for(i = 1; i < 1024; i++)
#ifdef linux
	step_table[i] = (int) (((3575872.0 / dsp_speed) / i) * 65536.0);
#else /* the sparc has a fixed frequency of 80000 Hz */
	step_table[i] = (int) (((3575872.0 / 8000.0) / i) * 65536.0);
#endif

    /* VSYNC is the number of bytes in 1/50 sec
     * computed as freq * (1 / 50).
     */
#ifdef linux
    VSYNC = (dsp_speed + 25) / 50;	/* with round */
#else
    VSYNC = 8000 / 50;
#endif

    /* read song name */
    if(quiet_mode)
	byteskip(fp, 20);
    else
	printf("Module : %s\n\n", getstring(fp, 20));

    /* determine number of voices from command name */
    nvoices = strstr(command, "15") == NULL? 31: 15;

    /* read in the sample information tables */
    voices[0].length = 0;
    for(i = 1; i <= nvoices; i++) {
	/* sample name */
	if(quiet_mode)
	    byteskip(fp, 22);
	else if(nvoices == 15)
	    printf("%6d : %s\n", i, getstring(fp, 22));
	else {
	    printf("%6d : %22s", i, getstring(fp, 22));
	    if(!(i & 1))
		printf("\n");
	}
	/* the sample length and repeat data are stored as numbers of words,
	 * we want the number of bytes << 16.
	 */
	voices[i].length = (fgetc(fp) << 25) | (fgetc(fp) << 17);
	fgetc(fp);
	if((voices[i].volume = fgetc(fp)) > 64)
	    voices[i].volume = 64;
	voices[i].rep_start = (fgetc(fp) << 25) | (fgetc(fp) << 17);
	voices[i].rep_end = (fgetc(fp) << 25) | (fgetc(fp) << 17);
	if(voices[i].rep_end <= 4 << 16)
	    voices[i].rep_end = 0;
	else {
	    voices[i].rep_end += voices[i].rep_start;
	    if(voices[i].rep_end > voices[i].length)
		voices[i].rep_end = voices[i].length;
	}
    }

    /* read sequence data: length and repeat position */
    songlength = fgetc(fp);
    songrepeat = fgetc(fp);
    if(songrepeat > songlength)		/* this is often buggy in modules */
	songrepeat = 0;

    /* read in the sequence and determine the number of patterns
     * by looking for the highest pattern number.
     */
    num_patterns = 0;
    for(i = 0; i < 128; i++) {
	tune[i] = fgetc(fp);
	if(tune[i] > num_patterns)
	    num_patterns = tune[i];
    }
    num_patterns++;			/* pattern numbers begin at 0 */

    /* skip over sig in new modules (usually M.K.).
     * note: this sig could be used for determining whether the module
     * is of an old type (15 samples max) or new (up to 31 samples)
     */
    if(nvoices == 31)
	byteskip(fp, 4);

    /* read in the patterns.
     * Each pattern consists of 64 lines,
     * each containing data for the four voices.
     */
    for(pat_num = 0; pat_num < num_patterns; pat_num++)
	for(notes = 0; notes < 64; notes++)
	    for(channel = 0; channel < 4; channel++) {
		note = (fgetc(fp) << 8) | fgetc(fp);
		patterns[pat_num].sample[notes][channel] = (note >> 8) & 0x10;
		patterns[pat_num].period[notes][channel] =
			MIN(note & 0xFFF, 1023);
		note = (fgetc(fp) << 8) | fgetc(fp);
		patterns[pat_num].sample[notes][channel] |= note >> 12;
		patterns[pat_num].effect[notes][channel] = (note >> 8) & 0xF;
		patterns[pat_num].params[notes][channel] = note & 0xFF;
	    }

    /* store each sample as an array of char */
    for(i = 1; i <= nvoices; i++)
	if(voices[i].length) {
	    if((voices[i].info = malloc(voices[i].length >> 16)) == NULL) {
		fprintf(stderr, "%s: can't allocate memory\n", command);
		exit(EXIT_FAILURE);
	    }
	    fread(voices[i].info, 1, voices[i].length >> 16, fp);
	    if(voices[i].rep_end)
		voices[i].length = voices[i].rep_end;
	    voices[i].rep_start -= voices[i].length;
	}

    /* initialize global and channel replay data */
    speed = 6;
    for(i = 0; i < 4; i++) {
	ch[i].pointer = 0;
	ch[i].step = 0;
	ch[i].volume = 0;
	ch[i].pitch = 0;
    }

    if(!quiet_mode)
	if(verbose_mode)
	    printf("\n");
	else {
	    printf("\nPosition (%d):", songlength);
	    fflush(stdout);
	}


    /*******************************/
    /* Here begins the replay part */
    /*******************************/

    for(pat_num = 0; pat_num < songlength; pat_num++) {
	pat = tune[pat_num];

	if(!quiet_mode)
	    if(verbose_mode)
		printf("   --------------+--------------+--------------+-----"
			"---------  %02X (%02X) = %02X\n", pat_num,
			songlength, pat);
	    else {
		printf("\r\t\t%3d", pat_num);
		fflush(stdout);
	    }

	end_pattern = FALSE;
	for(notes = 0; notes < 64; notes++) {
	    int samp, pitch, cmd, para;
	    Channel *curch;

	    if(!quiet_mode && verbose_mode)
		printf("%02X ", notes);

	    curch = ch;
	    for(channel = 0; channel < 4; channel++, curch++) {
		samp = patterns[pat].sample[notes][channel];
		pitch = patterns[pat].period[notes][channel];
		cmd = patterns[pat].effect[notes][channel];
		para = patterns[pat].params[notes][channel];

		if(verbose_mode && !quiet_mode)
		    if(pitch)
			printf("  %03X %2X %X%02X%s", pitch, samp, cmd, para,
				channel == 3? "\n": "  |");
		    else
			printf("  --- %2X %X%02X%s", samp, cmd, para,
				channel == 3? "\n": "  |");

		if(mute[channel]) {
		    if(cmd == 0x0B || cmd == 0x0D || cmd == 0x0F)
			switch(cmd) {
			case 0xB:			/* Pattern jump */
			    end_pattern = TRUE;
			    pat_num = (para & 0xF) + (10 * (para >> 4));
			    break;
			case 0xD:			/* Pattern break */
			    end_pattern = TRUE;
			    break;
			case 0xF:			/* Set speed */
			    speed = para;
			    break;
			}
		    continue;
		}

		/* if a sample number is given, it is loaded and the note
		 * is restarted
		 */
		if(samp) {
		    curch->samp = samp;
		    /* load new instrument */
		    curch->volume = voices[samp].volume;
		}

		if(samp || (pitch && cmd != 3)) {
		    if(pitch)
			curch->pitch = curch->arp[0] = pitch;
		    curch->pointer = 0;
		    curch->viboffs = 0;
		}

		/* by default there is no effect */
		curch->doarp = FALSE;
		curch->doslide = FALSE;
		curch->doporta = FALSE;
		curch->dovib = FALSE;
		curch->doslidevol = FALSE;

		switch(cmd) {			/* Do effects */
		case 0:				/* Arpeggio */
		    if(para) {
			for(i = 0; i < 36 && periods[i] > curch->arp[0]; i++);
			if(i + (para >> 4) < 36 && i + (para & 0xF) < 36) {
			    curch->doarp = TRUE;
			    curch->arp[0] = periods[i];
			    curch->arp[1] = periods[i + (para >> 4)];
			    curch->arp[2] = periods[i + (para & 0xF)];
			}
		    }
		    break;
		case 1:				/* Portamento up */
		    curch->doslide = TRUE;
		    if(para)
			curch->slide = -para;
		    break;
		case 2:				/* Portamento down */
		    curch->doslide = TRUE;
		    if(para)
			curch->slide = para;
		    break;
		case 3:				/* Note portamento */
		    curch->doporta = TRUE;
		    if(para)
			curch->portarate = para;
		    if(pitch)
			curch->pitchgoal = pitch;
		    break;
		case 4:				/* Vibrato */
		    curch->dovib = TRUE;
		    if(para) {
			curch->vibspeed = (para >> 2) & 0x3C;
			curch->vibamp = para & 0xF;
		    }
		    break;
		case 0xA:			/* Volume slide */
		    curch->doslidevol = TRUE;
		    if(para) {
			if(!(para & 0xF0))
			    curch->volslide = -para;
			else
			    curch->volslide = (para >> 4);
		    }
		    break;
		case 0xB:			/* Pattern jump */
		    end_pattern = TRUE;
		    pat_num = (para & 0xF) + (10 * (para >> 4));
		    break;
		case 0xC:			/* Set volume */
		    curch->volume = MIN(para, 64);
		    break;
		case 0xD:			/* Pattern break */
		    end_pattern = TRUE;
		    break;
		case 0xF:			/* Set speed */
		    speed = para;
		    break;
		default:
		    /* printf(" [%d][%d] ", cmd, para); */
		    break;
		}
	    }

	    {
		register Channel *curch;
		register Voice *curv;

		for(vsync = 0; vsync < speed; vsync++) {

		    ch[0].step = step_table[ch[0].pitch];
		    ch[1].step = step_table[ch[1].pitch];
		    ch[2].step = step_table[ch[2].pitch];
		    ch[3].step = step_table[ch[3].pitch];

		    for(bytes = 0; bytes < VSYNC; bytes++) {
#ifdef linux
			int byte[2] = { 0, 0 };
#else
			int byte = 0;
			unsigned char ulaw;
#endif

			curch = ch;
			for(channel = 0; channel < 4; channel++, curch++) {
			    if(!curch->samp || mute[channel])
				continue;

			    curv = &voices[(int) curch->samp];

			    /* test for end of sample */
			    if(curch->pointer >= curv->length)
				if(!curv->rep_end)
				    continue;
				else
				    curch->pointer += curv->rep_start;

			    /* mix samples.
			     * the sample is read and multiplied by the volume
			     */
			    if(curch->pointer < curv->length)
#ifdef linux
				/* in stereo, channels 1 & 3 and 2 & 4 are mixed
				 * seperately
				 */
				byte[channel & dsp_stereo] += (int)
					((curv->info[curch->pointer >> 16]) *
					((int) curch->volume << 2));
#else
				byte += (int)
					((curv->info[curch->pointer >> 16]) *
					((int) curch->volume << 2));
#endif
			    /* advance the sample pointer */
			    curch->pointer += curch->step;
			}

			/* output sample */
#ifdef linux
			if(dsp_samplesize == 8) {
			    if(dsp_stereo) {
				byte[0] >>= 9;
				audioput(byte[0] + 128);
				byte[1] >>= 9;
				audioput(byte[1] + 128);
			    } else
				audioput((byte[0] >> 10) + 128);
			} else {
			    if(dsp_stereo) {
				audioput16(byte[0] >> 1);
				audioput16(byte[1] >> 1);
			    } else
				audioput16(byte[0] >> 2);
			}
#else
			byte >>= 10;
			ulaw = (unsigned char) cvt(byte << 4);
			fputc(ulaw, audio);
#endif
		    }

		    /* Do end of vsync */
		    if(vsync == 0)
			continue;
		    curch = ch;
		    for(channel = 0; channel < 4; channel++, curch++) {
			if(mute[channel])
			    continue;
			if(curch->doarp)
			    curch->pitch = curch->arp[vsync % 3];
			else if(curch->doslide) {
			    curch->arp[0] += curch->slide;
			    curch->arp[0] = MIN(curch->arp[0], 1023);
			    curch->arp[0] = MAX(curch->arp[0], 113);
			    curch->pitch = curch->arp[0];
			} else if(curch->doporta) {
			    if(curch->arp[0] < curch->pitchgoal) {
				curch->arp[0] += curch->portarate;
				if(curch->arp[0] > curch->pitchgoal)
				    curch->arp[0] = curch->pitchgoal;
			    } else if(curch->arp[0] > curch->pitchgoal) {
				curch->arp[0] -= curch->portarate;
				if(curch->arp[0] < curch->pitchgoal)
				    curch->arp[0] = curch->pitchgoal;
			    }
			    curch->pitch = curch->arp[0];
			} else if(curch->dovib) {
			    if(curch->viboffs & 0x80)
				curch->pitch = curch->arp[0] - ((sin_table
					[(curch->viboffs >> 2) & 0x1F] *
					curch->vibamp) >> 6);
			    else
				curch->pitch = curch->arp[0] + ((sin_table
					[(curch->viboffs >> 2) & 0x1F] *
					curch->vibamp) >> 6);
			    curch->viboffs += curch->vibspeed;
			} else if(curch->doslidevol) {
			    curch->volume += curch->volslide;
			    if(curch->volume < 0)
				curch->volume = 0;
			    else if(curch->volume >= 64)
				curch->volume = 64;
			}
		    }
		}
	    }
	    if(end_pattern == 1)
		break;
	}
    }

    if(!quiet_mode)
	printf("\n");
}
