/***********************************************************************/
/*                                                                     */
/* str.c - plays sound/noisetracker files on a SparcStation            */
/* modified for thomson 6bit D/A				       */
/* corrected core dump on                                              */
/* Authors  : Liam Corner - zenith@dcs.warwick.ac.uk                   */
/*            Marc Espie - espie@dmi.ens.fr                            */
/* Version  : 1.20 - 3 November 1991                                   */
/*                                                                     */
/*                                                                     */
/***********************************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>


/**********************************************************/
/* uS is the number of uSeconds that a byte is played for */
/* Sparc plays at 8000 bytes/sec  =>  1 byte = 125 uSec   */
/* VSYNC is the number of bytes played in 1/50 sec        */
/* ie 0.02/(uS * 10**-6)                                  */
/**********************************************************/
#define uS 125
#define VSYNC 160
#define AUDIO "./out.raw"
 
#define MIN(A,B) ((A)<(B) ? (A) : (B))
#define MAX(A,B) ((A)>(B) ? (A) : (B))
 
 
typedef struct {    /***********************************/
  signed char *info;       /* Sample                          */
  int length;       /* Length of sample                */
  float volume;     /* Fractional volume 0-1 (min-max) */
  int rep_start;    /* Byte offset of repeat start     */
  int rep_end;      /* Byte offset of repeat end       */
} Voice;            /***********************************/
 
 
typedef struct {                 /**************************/
  char sample [64][4];           /* Sample number          */
  char effect [64][4];           /* Effect number          */
  unsigned char params [64][4];  /* Effect parameters      */
  int period [64][4];            /* Period (pitch) of note */
} Pattern;                       /**************************/
 
 
typedef struct {         /***********************************************/
  char samp;             /* Sample number of current note               */
  int pitch;             /* Current channel pitch (index to step_table) */
  int slide;             /* Step size of pitch slide (if any)           */
  int doslide;
  unsigned int pointer;  /* Current sample position                     */
  unsigned int step;     /* Sample offset increment (gives pitch)       */
  float volume;          /* Fractional volume of current note           */
  float volslide;
  int doslidevol;
  int doporta;
  int pitchgoal;
  int portarate;
} Channel;               /***********************************************/
 

#define MX_CHUNKS_IN_SONG 80000
#define MX_CHUNKS_NB 80000
int song_chunks_index=0;
int song_chunks[MX_CHUNKS_IN_SONG]; 
int nb_chunk=0;
char chunks[MX_CHUNKS_NB][VSYNC];
int cur_chunk_index=0;
char cur_chunk[VSYNC*10];

int cmp_chunks(char *a, char *b, int size,int mask) {
	int i;
	int diff;
	// 1st pass ok if equal with mask
	for (i=0;i<size;i++) {
		if ((a[i]&mask) != (b[i]&mask)) break;
	}
	// 2nd pass... very lossy :)
	if (i==size) return 0;
	diff=0;
	for (i=0;i<size;i++) {
		if ((a[i]&mask) != (b[i]&mask)) diff++;
		if (diff>32) return -1;
	}
	
	return 0;
}

void insert_cur_chunk(void) {
	int i;
	for (i=0;i<nb_chunk;i++) {
//		if (memcmp(cur_chunk,chunks[i],VSYNC)==0) break;
		if (cmp_chunks(cur_chunk,chunks[i],VSYNC,0xFF)==0) break;
	}
	if (i==nb_chunk) {
		memcpy(chunks[i],cur_chunk,VSYNC);
		song_chunks[song_chunks_index]=i;
		song_chunks_index++;
		nb_chunk++;
		
	} else {
		song_chunks[song_chunks_index]=i;
		song_chunks_index++;		
		if (song_chunks_index>=MX_CHUNKS_NB)
		{
			fprintf(stderr,"Argh I'm dead... %d %d\n",song_chunks_index,i);
			exit(-1);
		} 
	}
}

void rebuild_from_chunks(char * fname) {
	FILE *f;
	int i,j;

	f=fopen(fname,"w");
	
	for (i=0;i<song_chunks_index;i++) {
		fwrite(chunks[song_chunks[i]],1,VSYNC,f);
	}

	fclose(f);
}

/*
 reconstruit des fichiers par banque de 16ko
 fichier 0 : patterns bank+offset (/128)
 fichiers 1 à n : chunks 
*/

void build_outfiles(char * base) {
	int ficnum,i,j,k;
	FILE *f;
	char str[1024];
	
	ficnum=0;

	sprintf(str,"%s%02d.BIN",base,ficnum);
	f=fopen(str,"w");
	
	for (j=0;j<song_chunks_index;j++) {
		fputc((song_chunks[j]/64)+6,f);
		fputc(song_chunks[j]&0x3F,f);
	}

	fclose(f);

	ficnum++;

	for (j=0;j<(nb_chunk/64)+1;j++) {
	sprintf(str,"%s%02d.BIN",base,ficnum);
	f=fopen(str,"w");
		for (k=0;k<64;k++) {
			for (i=0;i<160;i++) {
					char c1;
					c1=chunks[j*64+k][i];
					fputc(c1,f);
			}
			for (i=160;i<256;i++) {
				fputc(0,f);
			}
		} // of for k
	
	fclose(f);
	ficnum++;	
	} // of for j
}

/****************************************************************************/
/* Skips the next 'n' input bytes - because fseek won't work on stdin       */
/****************************************************************************/
void byteskip (fp, bytes)
FILE *fp;
int bytes;
    {
    int loop;
 
    for (loop = 0; loop < bytes; loop++)
        getc(fp);
    }
 
 
 
char *getstring(f, len)
FILE *f;
int len;
    {
    static char s[150];
    int i;
 
    for (i = 0; i < len; i++)
        s[i] = fgetc(f);
    s[len] = '\0';
    return s;
    }
 
#define OLD 0
#define NEW 1
 
int main (argc, argv)
int argc;
char **argv;
    {
    FILE *fp, *audio;
    int loop;
    int notes, note, channel, vsync;
    int pat, pat_num;
    int byte, bytes;
    int step_table[1024];
    int speed=6;                      /* Default speed is 6 */
    int end_pattern=0;
    char songlength;
    char tune[128];
    char num_patterns=0;
    unsigned char ulaw;
    float dummy1, dummy2;
    Voice voices[32];
    Pattern patterns[64];
    Channel ch[4];
    int nvoices;
    int effect;
 
    int type;   /* module type: old or new */
    char *command;  /* the actual command name used */
 
    command = argv[0];

        type = NEW;

 
    if (type == OLD)
        nvoices = 15;
    else
        nvoices = 31;
 
    if (argc>2)
        {
        fprintf(stderr,"Usage: %s [<filename>]\n", command);
        exit(1);
        }
 
/***********************************************************************/
/* Creates a table of the byte_step << 16 for a given pitch            */
/* The step and pointer are stored << 16 to get accuracy without floats*/
/* eg to get double pitch only play every other byte                   */
/* so step of 0x10000 is normal pitch, 0x8000 is half,                 */
/* 0x20000 is double.  Pointer is >> 16 when accessed,                 */
/* so 0x10000 is 1st byte, 0x20000 2nd etc                             */
/* I have no idea where the other numbers are from, I copied them from */
/* a SoundTracker player for the Acorn Archimedes                      */
/*                                                                     */
/* Actually, these other numbers are highly dependent on the amiga hw. */
/***********************************************************************/
    step_table[0] = 0;
    for (loop = 1; loop < 1024; loop++)
        {
        dummy1 = 3575872 / loop;
        dummy2 = (dummy1 / (1000000 /uS) ) * 60000;
        step_table[loop] = (int)dummy2;
        }
 
    if (argc < 2)
        fp = stdin;
    else
        fp = fopen(argv[1], "r");
    if (fp == NULL)
        {
        fprintf(stderr, "%s: unable to open tune file %s\n",
            command, argv[1]);
        exit(1);
        }
 
        /* read song name */
    printf("Module : %s\n\n", getstring(fp, 20));
 
        /* Reads in the sample-information tables */
    for (loop = 1; loop <= nvoices; loop++)
        {
        printf("%6d : %s\n", loop, getstring(fp, 22));
        voices[loop].length = ( (getc(fp) << 8) | getc(fp) ) * 2;
        getc(fp);
        voices[loop].volume = getc(fp);
        voices[loop].volume = MIN(voices[loop].volume, 64);
        voices[loop].volume /= 64;   /* Volume is a fraction */
        voices[loop].rep_start = ( (getc(fp) << 8) | getc(fp) ) * 2;
        voices[loop].rep_end = ( (getc(fp) << 8) | getc(fp) ) * 2;
        if (voices[loop].rep_end <= 4)
            voices[loop].rep_end = 0;
        else
            {
                /* If there is a repeat then end=start+length, but must be */
                /* less than the sample length.  Not sure if this is 100%  */
                /* correct, but it seems to work OK :-)                    */
            if (voices[loop].rep_end + voices[loop].rep_start - 1
                > voices[loop].length)
                voices[loop].rep_start >>= 1;
            voices[loop].rep_end += voices[loop].rep_start;
            voices[loop].rep_end = MIN(voices[loop].rep_end,
                voices[loop].length);
            }
        }
    voices[0].length = 0;
 
    songlength = getc(fp);
    byteskip(fp, 1);
 
        /* Reads in the tune */
    for (loop = 0; loop < 128; loop++)
        {
        tune[loop] = getc(fp);
        if (tune[loop] > num_patterns)
            num_patterns = tune[loop];
        }
    num_patterns++;
 
        /* skip over sig (usually M.K.) */
    if (type == NEW)
        byteskip(fp,4);
 
        /* Reads in the patterns */
    for (pat_num = 0; pat_num < num_patterns; pat_num++)
        {
            /* 64 notes per pattern  */
        for (notes = 0; notes < 64; notes++)
            {
                /* 4 channels per note   */
            for (channel = 0; channel < 4; channel++)
                {
                note = (getc(fp) << 24) | (getc(fp) << 16) |
                    (getc(fp) << 8) | getc(fp);
                (patterns[pat_num]).effect[notes][channel] =
                    (note & 0xF00) >> 8;
                (patterns[pat_num]).params[notes][channel] = note & 0xFF;
                (patterns[pat_num]).sample[notes][channel] =
                    ( (note & 0xF000) >> 12) | ( (note >> 24) & 0x10);
                (patterns[pat_num]).period[notes][channel] =
                    MIN( (note & 0xFFF0000) >> 16, 1023);
                }
            }
        }
 
        /* Stores the samples voices as an array of char */
    for (loop = 1; loop <= nvoices; loop++)
        {
        voices[loop].info = malloc(voices[loop].length);
        if (voices[loop].info == NULL)
            {
            fprintf(stderr, "%s: unable to allocate memory\n", command);
            exit(1);
            }
        fread(voices[loop].info, 1, voices[loop].length, fp);
        }
    // audio = fopen(AUDIO, "w");
    // if (audio == NULL)
    //    {
    //    fprintf(stderr, "%s: unable to access %s\n", command, AUDIO);
    //    exit(1);
    //    }
 
    for (loop = 0; loop < 4; loop++)
        {
	ch[loop].samp=0; /* GFE : added this to avoid nasty core dumps :) */
        ch[loop].pointer = 0;
        ch[loop].step = 0;
        ch[loop].volume = 0;
        ch[loop].pitch = 0;
        }
 
    printf("\nPosition (%d):", songlength);
    fflush(stdout);
 
    for (pat_num = 0; pat_num < songlength; pat_num++)
        {
        printf("\r\t\t%3d", pat_num);
        fflush(stdout);
        pat = tune[pat_num];
        end_pattern = 0;
        for (notes = 0; notes < 64; notes++)
            {
            for (channel = 0; channel < 4; channel++)
                {
                int samp, pitch, cmd, para;

                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 (samp)
                    {
                    ch[channel].samp = samp;
                        /* load new instrument */
                    ch[channel].volume = voices[ch[channel].samp].volume;
                    }
                        /* If sample number=0 and no new period */
                        /* continue last note */
                if (pitch && cmd != 3)
                    {
                    ch[channel].pointer = 0;
                    ch[channel].step = step_table[pitch];
                    ch[channel].pitch = pitch;
                    }
                ch[channel].doslide = 0;
                ch[channel].doslidevol = 0;
                ch[channel].doporta = 0;
                switch(cmd)  /* Do effects */
                    {
                case 0xF :
                    speed = para;
                    break;
                case 0xD :
                    end_pattern = 1;
                    break;
                case 0xC :
                    ch[channel].volume= MIN(para, 64);
                    ch[channel].volume /= 64;
                    break;
                    /* volume_slicur_chunk_indexde */
  case 0xB :
      pat_num = (para & 0xF) + (10 * (para >> 4));
      break;
                case 0xA :
                    ch[channel].doslidevol = 1;
                    if (para)
                        {
                        if (para & 15)
                            ch[channel].volslide = - para / 64;
                        else
                            ch[channel].volslide = (para >> 4)/64;
                        }
                    break;
                case 3   :
                    ch[channel].doporta = 1;
                    if (para)
                        ch[channel].portarate = para;
                    if (pitch)
                        ch[channel].pitchgoal = pitch;
                    break;
                case 2   :
                    ch[channel].doslide = 1;
                    if (para)
                        ch[channel].slide = para;
                    break;
                case 1   :
                    ch[channel].doslide = 1;
                    if (para)
                        ch[channel].slide = -para;
                    break;
                case 0   :
                    break;
                default  :
                    /* printf(" [%d][%d] ", cmd, para); */
                    break;
                    }
                }
                /* 1 vsync = 0.02 sec */
            for (vsync = 0; vsync < speed; vsync++)
                {
                    /* 160*125uSec = 0.02 */
                for (bytes = 0; bytes < VSYNC; bytes++)
                    {
                    byte = 0;
                    for (channel = 0; channel < 4; channel++)
                        {
                        if (ch[channel].samp == 0)
                            continue;
                            /* If at end of sample jump to rep_start position */
                        if (voices[ch[channel].samp].rep_end)
                            {
                            if ((ch[channel].pointer >> 16) >=
                                voices[ch[channel].samp].rep_end)
                                    ch[channel].pointer +=
                                    (voices[ch[channel].samp].rep_start -
                                    voices[ch[channel].samp].length)<< 16;
                            }
                        else
                            if ((ch[channel].pointer >> 16) >=
                                voices[ch[channel].samp].length)
                                continue;
                        /* byte = sum of (sample byte * volume) for each */
                        /* of 4 channels which mixes the sounds          */
                        if ((ch[channel].pointer >> 16) <
                            voices[ch[channel].samp].length)
                            {
                            byte += (int) ( (voices[ch[channel].samp]
                                .info[ch[channel].pointer >> 16])
                                * (ch[channel].volume));
                            ch[channel].pointer += ch[channel].step;
                            }
                        } // of for channel

					// clip
                    if (byte>127) byte=127;
                    if (byte<-128) byte=-128;
                    
                    // make unsigned
                    byte+=128;
                    byte /= 4;

                    // fputc(byte, audio);                /* and play the note */
		    cur_chunk[cur_chunk_index]=byte;	    
	            cur_chunk_index++;
                    } // of for bytes
	            insert_cur_chunk();
		    cur_chunk_index=0;
                    /* Do end of vsync */
                if (vsync == 0)
                    continue;
                for (channel = 0; channel < 4; channel++)
                    {
                    if (ch[channel].doslide)             /* effects */
                        {
                        ch[channel].pitch += ch[channel].slide;
                        ch[channel].pitch = MIN(ch[channel].pitch, 1023);
                        ch[channel].pitch = MAX(ch[channel].pitch, 113);
                        ch[channel].step = step_table[ch[channel].pitch];
                        }
                    if (ch[channel].doslidevol)
                        {
                        ch[channel].volume += ch[channel].volslide;
                        if (ch[channel].volume < 0.0)
                            ch[channel].volume = 0.0;
                        else if (ch[channel].volume >= 1.0)
                            ch[channel].volume = 1.0;
                        }
                    if (ch[channel].doporta)
                        {
                        if (ch[channel].pitch < ch[channel].pitchgoal)
                            {
                            ch[channel].pitch += ch[channel].portarate;
                            if (ch[channel].pitch > ch[channel].pitchgoal)
                                ch[channel].pitch = ch[channel].pitchgoal;
                            }
                        else if (ch[channel].pitch > ch[channel].pitchgoal)
                            {
                            ch[channel].pitch -= ch[channel].portarate;
                            if (ch[channel].pitch < ch[channel].pitchgoal)
                                ch[channel].pitch = ch[channel].pitchgoal;
                            }
                        }
                    }
                }
            if (end_pattern == 1)
                break;
            } // of for notes
    		    for (loop = 0; loop < 4; loop++)
        	    {
			ch[loop].samp=0; /* GFE : added this to avoid nasty core dumps :) */
        		ch[loop].pointer = 0;
        		ch[loop].step = 0;
        		ch[loop].volume = 0;
        		ch[loop].pitch = 0;
        	    }
        } // of for pattern

    // fclose(audio);
    rebuild_from_chunks("out2.raw");
    build_outfiles("MOD");
    printf("\n");
    fprintf(stdout,"%d chunks %d chunks in song\n",nb_chunk,song_chunks_index);
    return (0);
    }
