/***********************************************************************/ /* a mod player that will make your ears bleed */ /* corrected a bit and converted for interrupt based interface */ /* derives from an unfinished mod to Sparc raw player by */ /* Liam Corner - zenith@dcs.warwick.ac.uk */ /* Marc Espie - espie@dmi.ens.fr */ /***********************************************************************/ #include #include #include /**********************************************************/ /* 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) */ /* here we replay by default at 44100 Hz */ /**********************************************************/ #define uS ((1.0/44100)*1000000) #define VSYNC 882 #define AUDIO "./out.raw" #define MAX_CHANEL 16 #define MIN(A,B) ((A)<(B) ? (A) : (B)) #define MAX(A,B) ((A)>(B) ? (A) : (B)) typedef struct { /***********************************/ char name[256]; 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 { /**************************/ unsigned char sample [64][MAX_CHANEL]; /* Sample number */ unsigned char effect [64][MAX_CHANEL]; /* Effect number */ unsigned char params [64][MAX_CHANEL]; /* Effect parameters */ unsigned int period [64][MAX_CHANEL]; /* 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; /***********************************************/ typedef struct { int patnum; int curpat; int note; int vbl; int sample; int speed; } Playbuf; /****************************************************************************/ /* 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 step_table[1024]; /***********************************************************************/ /* 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. */ /***********************************************************************/ void init_step_table(void) { int loop; float dummy1, dummy2; step_table[0] = 0; for (loop = 1; loop < 1024; loop++) { dummy1 = 3575872 / loop; dummy2 = (dummy1 / (1000000 /uS) ) * 60000; step_table[loop] = (int)dummy2; } } /* song data */ char tune[128]; Voice voices[32]; Pattern patterns[128]; int chanel_nb=4; /* the playing status */ Channel ch[MAX_CHANEL]; Playbuf where; int type; /* module type: old or new */ int nvoices; char songlength; int load_module(char *filename) { FILE *fp; int loop; int notes, note, channel, vsync; int pat, pat_num; int byte, bytes; int speed=6; /* Default speed is 6 */ int end_pattern=0; char num_patterns=0; unsigned char ulaw; int effect; char signature[4+1]; type = NEW; if (type == OLD) nvoices = 15; else nvoices = 31; fp = fopen(filename, "rb"); if (fp == NULL) { fprintf(stderr, "unable to open tune file %s\n", filename); return 0; } /* check for signature at some obscure offset... */ fseek(fp,20+31*30+2+128,SEEK_SET); signature[0]=fgetc(fp); signature[1]=fgetc(fp); signature[2]=fgetc(fp); signature[3]=fgetc(fp); signature[4]='\0'; if (strcmp(signature,"M.K.")==0) { nvoices = 31; chanel_nb=4; } else if (strcmp(signature,"6CHN")==0) { nvoices = 31; chanel_nb=6; } else if (strcmp(signature,"8CHN")==0) { nvoices = 31; chanel_nb=8; } else { nvoices = 15; chanel_nb=4; } printf("Signature %s\n",signature); fseek(fp,0,SEEK_SET); /* read song name */ printf("Module : %s\n\n", getstring(fp, 20)); /* Reads in the sample-information tables */ for (loop = 1; loop <= nvoices; loop++) { sprintf(voices[loop].name,"%s",getstring(fp, 22)); // printf("%6d : %s\n", loop, voices[loop].name); 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 { 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 < chanel_nb; 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, "unable to allocate memory\n"); exit(1); } fread(voices[loop].info, 1, voices[loop].length, fp); } return -1; } void next_pattern() { int loop; where.curpat = tune[where.patnum]; for (loop = 0; loop < chanel_nb; loop++) { ch[loop].samp=0; ch[loop].pointer = 0; ch[loop].step = 0; ch[loop].volume = 0; ch[loop].pitch = 0; } // fprintf(stderr,"New pattern %X %X\n",where.patnum,where.curpat); } void next_note() { int samp, pitch, cmd, para; int channel; int end_pattern=0; for (channel = 0; channel < chanel_nb; channel++) { samp = patterns[where.curpat].sample[where.note][channel]; pitch = patterns[where.curpat].period[where.note][channel]; cmd = patterns[where.curpat].effect[where.note][channel]; para = patterns[where.curpat].params[where.note][channel]; if (samp) { ch[channel].samp = samp; /* load new instrument */ ch[channel].volume = voices[ch[channel].samp].volume; // fprintf(stderr,"New note %X %X\n",samp,pitch); } /* 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; /* Do effects */ switch(cmd) { case 0xF : if (para<=32) { where.speed = para; } else { where.speed = (750)/(para); } fprintf(stderr,"speed is %d\n",where.speed); 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 : where.patnum = (para & 0xF) + (10 * (para >> 4)); where.note=0; 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 0x3 : ch[channel].doporta = 1; if (para) ch[channel].portarate = para; if (pitch) ch[channel].pitchgoal = pitch; break; case 0x2 : ch[channel].doslide = 1; if (para) ch[channel].slide = para; break; case 0x1 : ch[channel].doslide = 1; if (para) ch[channel].slide = -para; break; case 0x0 : break; default : /* printf(" [%d][%d] ", cmd, para); */ break; } // of switch effect } // of for channel if (end_pattern) { where.note=63; } } void play_start(void) { int loop; for (loop = 0; loop < chanel_nb; 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; } init_step_table(); where.patnum=0; where.curpat=0; where.note=0; where.sample=0; where.vbl=0; where.speed=6; next_pattern(); next_note(); } void play_prev_pattern(void) { int loop; for (loop = 0; loop < chanel_nb; loop++) { ch[loop].samp=0; ch[loop].pointer = 0; ch[loop].step = 0; ch[loop].volume = 0; ch[loop].pitch = 0; } init_step_table(); if (where.patnum>0) { where.patnum--; } where.curpat=0; where.note=0; where.sample=0; where.vbl=0; next_pattern(); next_note(); } void play_next_pattern(void) { int loop; for (loop = 0; loop < chanel_nb; loop++) { ch[loop].samp=0; ch[loop].pointer = 0; ch[loop].step = 0; ch[loop].volume = 0; ch[loop].pitch = 0; } init_step_table(); if (where.patnum<127) { where.patnum++; } where.curpat=0; where.note=0; where.sample=0; where.vbl=0; next_pattern(); next_note(); } void next_vbl(void) { int channel; for (channel = 0; channel < chanel_nb; channel++) { if (ch[channel].doslide) { 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; } } } } void build_chunk(int size,int * buf) { int loop; int byte; int channel; for (loop=0;loop=where.speed) { where.vbl=0; where.note++; if (where.note==64) { where.note=0; where.patnum++; next_pattern(); } next_note(); } } where.sample++; byte = 0; for (channel = 0; channel < chanel_nb; 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 << 17; } } 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>128) byte=128; if (byte<-127) byte=-127; // byte=((byte+127)&0xFC)-127; buf[loop]=byte; } } static char song_text[128*12]; char * get_song_text(void) { int loop; char buf[256]; song_text[0]='\0'; for (loop=0;loop<128;loop++) { sprintf(buf,"%3d %3d \n",loop,tune[loop]); strcat(song_text,buf); } return song_text; } int get_song_pattern(int index) { if ((index<0) || (index>127)) return 0; return tune[index]; } static char voice_text[32*256]; char * get_voice_text(void) { int loop; char buf[256]; voice_text[0]='\0'; for (loop=0;loop<32;loop++) { sprintf(buf,"%3d %3d \n",loop,tune[loop]); strcat(voice_text,buf); } return voice_text; } /* period sample effect params */ /* C C# D D# E F F# G G# A A# B Octave 0:1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907 Octave 1: 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453 Octave 2: 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226 Octave 3: 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 Octave 4: 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57 */ char notesLabels[2000][5]; void init_notes_labels(void) { int loop; // by default (not real amiga notes) for (loop=0;loop<2000;loop++) { sprintf(notesLabels[loop],"%04d",loop); } strcpy(notesLabels[107],"C4 "); strcpy(notesLabels[101],"C#4 "); strcpy(notesLabels[95],"D4 "); strcpy(notesLabels[90],"D#4 "); strcpy(notesLabels[85],"E4 "); strcpy(notesLabels[80],"F4 "); strcpy(notesLabels[76],"F#4 "); strcpy(notesLabels[71],"G4 "); strcpy(notesLabels[67],"G#4 "); strcpy(notesLabels[64],"A4 "); strcpy(notesLabels[60],"A#4 "); strcpy(notesLabels[57],"B4 "); strcpy(notesLabels[214],"C3 "); strcpy(notesLabels[202],"C#3 "); strcpy(notesLabels[190],"D3 "); strcpy(notesLabels[180],"D#3 "); strcpy(notesLabels[170],"E3 "); strcpy(notesLabels[160],"F3 "); strcpy(notesLabels[151],"F#3 "); strcpy(notesLabels[143],"G3 "); strcpy(notesLabels[135],"G#3 "); strcpy(notesLabels[127],"A3 "); strcpy(notesLabels[120],"A#3 "); strcpy(notesLabels[113],"B3 "); strcpy(notesLabels[428],"C2 "); strcpy(notesLabels[404],"C#2 "); strcpy(notesLabels[381],"D2 "); strcpy(notesLabels[360],"D#2 "); strcpy(notesLabels[339],"E2 "); strcpy(notesLabels[320],"F2 "); strcpy(notesLabels[302],"F#2 "); strcpy(notesLabels[285],"G2 "); strcpy(notesLabels[269],"G#2 "); strcpy(notesLabels[254],"A2 "); strcpy(notesLabels[240],"A#2 "); strcpy(notesLabels[226],"B2 "); strcpy(notesLabels[856],"C1 "); strcpy(notesLabels[808],"C#1 "); strcpy(notesLabels[762],"D1 "); strcpy(notesLabels[720],"D#1 "); strcpy(notesLabels[678],"E1 "); strcpy(notesLabels[640],"F1 "); strcpy(notesLabels[604],"F#1 "); strcpy(notesLabels[570],"G1 "); strcpy(notesLabels[538],"G#1 "); strcpy(notesLabels[508],"A1 "); strcpy(notesLabels[480],"A#1 "); strcpy(notesLabels[453],"B1 "); strcpy(notesLabels[1712],"C0 "); strcpy(notesLabels[1616],"C#0 "); strcpy(notesLabels[1525],"D0 "); strcpy(notesLabels[1440],"D#0 "); strcpy(notesLabels[1357],"E0 "); strcpy(notesLabels[1281],"F0 "); strcpy(notesLabels[1209],"F#0 "); strcpy(notesLabels[1141],"G0 "); strcpy(notesLabels[1077],"G#0 "); strcpy(notesLabels[1017],"A0 "); strcpy(notesLabels[961 ],"A#0 "); strcpy(notesLabels[907 ],"B0 "); } char curNote[256]; char* get_pattern_label(int pat,int note,int channel) { int samp = patterns[pat].sample[note][channel]; int pitch = patterns[pat].period[note][channel]; int cmd = patterns[pat].effect[note][channel]; int para = patterns[pat].params[note][channel]; if ((samp == 0) && (cmd == 0) && (para == 0)) { strcpy(curNote," .. .. .."); return curNote; } sprintf(curNote,"%s %02d %02X %02X",notesLabels[pitch],samp,cmd,para); return curNote; } static char pattern_line_text[1024]; char * get_pattern_line(int patnum,int note) { int chanel; static int first=1; char buf[1024]; if (first) { init_notes_labels(); first=0; } pattern_line_text[0]='\0'; for (chanel=0;chanel0) { total++; } } return total; } int get_nb_sample(void) { return nvoices; } /* will range from -1.0 to 1.0 */ float* get_sample_preview(int index,int size,int step) { float tab[2048]; int loop; if (size>2048) return NULL; for (loop=0;loop