This is str.c in view mode; [Download] [Up]
/* The following was written long ago on a NeXT Cube. I found it in one of my old archives and since quite a lot poeple asked for it... Here it is: a modplayer for black AND white Caveats: It is a hack, based on a very old version of sparctracker and is completely unsupported. The quality is not very good and on slow machines it does not work at all. Untested on a Gecko *GRIN* Mark Espie & Liam Corner don't know anything about this hack, so don't blame them if it does not work As always you are working at your own risk. Tested on: NeXT Cube 25 MHz 16 MB, 486DX2 EISA 32MB both NS 3.1 Compile: cc str.c -O -o str32; ln -s str32 str15 No copyrights from my side apply, ask Mark Espie if you want to use it in your commercial apps 8-). In case someone want to improve it I strongly recommend to get a newer version of the tracker code from the archives. "It was long ago and it was far away and it was so much better than it is today" -Meatloaf, Paradise by the dashboard light Frank Siegert u288869@uebung3.rz.fh-reutlingen.de (until 7. March 94) */ /*****************************************************************/ /* */ /* str.c - plays sound/noisetracker files on a NeXT */ /* "A Q&D hack is better than nothing!" */ /* NeXT code by Frank Siegert 1992 */ /* */ /* */ /* all other code from the sound/noisetracker for a SparcStation */ /* by: Liam Corner - zenith@dcs.warwick.ac.uk */ /* Marc Espie - espie@dmi.ens.fr */ /* Version : 1.00 - 6 April 1992 */ /* */ /* Usage : str32 <filename> */ /* [f|z]cat filename | str32 */ /* */ /*****************************************************************/ #include <stdio.h> #include <stdlib.h> #include <sound/sound.h> #include <sys/resource.h> /**********************************************************/ /* Use HALFFREQ on slow machines, quality goes down but */ /* the tracker has more time to calculate the samples */ /* #define HALFFREQ */ /**********************************************************/ /* uS is the number of uSeconds that a byte is played for */ /* Sparc plays at 8000 bytes/sec => 1 byte = 125 uSec */ /* we play at 22050/2 kHz, so we have to change this value */ /* to 22055/sec *2 => approx. 90 uSec */ /* VSYNC is the number of bytes played in 1/50 sec */ /* ie 0.02/(uS * 10**-6) */ /**********************************************************/ #ifdef HALFFREQ #define uS 100 #define VSYNC 218 /* 11025 Hz */ #else #define uS 50 #define VSYNC 435 /* 22050 Hz */ #endif /* defined for the NeXT */ #define MAXSOUND 4 /* number of independent sound frames */ #define MAXSAMPLE 32*1024 /* size of a frame 32kByte*/ #define MARKEDFREE 0 /* marker for unused frame */ #define MARKEDBUSY 1 /* marker for used frame */ SNDSoundStruct *blubber[MAXSOUND]; /* array of sound frames */ char *fillSound; /* current fill point */ int fillCount; /* fill frame counter */ int actualSample; /* frame to be filled up */ int played; /* to keep track of running sounds in queue */ int lastFreed; /* last sample freed */ int DEBUG; /* Debugging on/off */ /* End NeXT defines */ #define MIN(A,B) ((A)<(B) ? (A) : (B)) #define MAX(A,B) ((A)>(B) ? (A) : (B)) typedef struct { /***********************************/ 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; /***********************************************/ /*****************************************************************************/ /* 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); } /*****************************************************************************/ /* soundEnd is called by the soundplayer when a sound finished playing */ /*****************************************************************************/ int soundEnd(SNDSoundStruct *s, int tag, int err) { lastFreed=tag; s->info[0]=MARKEDFREE; if (DEBUG) printf("Released %d\n",tag); played--; return (0); } /*****************************************************************************/ /* doSound is called to put a note in the current frame and play this frame */ /* must be called with the parameter All==TRUE, if the current frame should */ /* be played at the end */ /*****************************************************************************/ /* play is called by nextSample to put the current frame to the sound queue */ /*****************************************************************************/ void play(SNDSoundStruct *theSound, int SoundTag) { int err; theSound ->info[0]=MARKEDBUSY; played++; err = SNDStartPlaying(theSound,SoundTag,5,0,0,(SNDNotificationFun)soundEnd); if (err) { fprintf(stderr,"Error: Unable to call SNDStartPlaying\n"); exit(1); } return; } /*****************************************************************************/ /* nextSample plays the current Sample and tries to get a unused one, */ /* it waits until a frame is avaiable */ /*****************************************************************************/ int nextSample() { play(blubber[actualSample],actualSample); /* play current */ actualSample++; /* next frame */ if (actualSample>=MAXSOUND) { /* reached end of frames */ actualSample=0; /* back to the beginning */ } while (blubber[actualSample]->info[0]==MARKEDBUSY) ; /* running around and wait for free */ if (DEBUG) printf("Got %d\n", actualSample); return 0; } 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; register 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 */ int lastbyte=0; char *command; /* the actual command name used */ command = argv[0]; if (strcmp(argv[0], "str32") == 0) type = NEW; else if (strcmp(argv[0], "str15") == 0) type = OLD; else { fprintf(stderr, "Error: command should be named either str15 or str32\n"); type = NEW; } if (type == OLD) nvoices = 15; else nvoices = 31; if (argc==3) { if (strcmp(argv[2],"DEBUG")==0) { DEBUG=TRUE; } } if (argc>3) { fprintf(stderr,"Usage: %s [<filename>] [DEBUG]\n", command); exit(1); } /***********************************************************************/ /* create some Sound structures in mem big enougth to hold a */ /* a significant amount of sound data */ for (loop=0;loop<MAXSOUND;loop++) { blubber[loop]=(SNDSoundStruct*) malloc(sizeof(SNDSoundStruct)+MAXSAMPLE+16); /* must be more than MAXSAMPLE */ blubber[loop]->magic=SND_MAGIC; blubber[loop]->dataLocation=sizeof(SNDSoundStruct); blubber[loop]->dataSize=0; blubber[loop]->dataFormat=SND_FORMAT_LINEAR_8; /* 8 bit linear */ blubber[loop]->samplingRate=SND_RATE_LOW; /* 22 kHz */ blubber[loop]->channelCount=1; blubber[loop]->info[0]=MARKEDFREE; } fillCount=0; actualSample=0; played=0; lastFreed=0; fillSound=(char *)((int)blubber[actualSample]+sizeof(SNDSoundStruct)); /* start of data area */ /***********************************************************************/ /* 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; task_priority(task_self(), 18, TRUE); /* set our priority to high */ SNDSetFilter(1); /* low pass filter ON */ 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); } for (loop = 0; loop < 4; loop++) { ch[loop].pointer = 0; ch[loop].step = 0; ch[loop].volume = 0; ch[loop].pitch = 0; } for (pat_num = 0; pat_num < songlength; pat_num++) { 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_slide */ 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 : 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++) { register char R_sample; register unsigned int R_pointer; if ((R_sample=ch[channel].samp) == 0) continue; /* If at end of sample jump to rep_start position */ R_pointer=ch[channel].pointer; if (voices[R_sample].rep_end) { if ((R_pointer >> 16) >= voices[R_sample].rep_end) R_pointer += (voices[R_sample].rep_start - voices[R_sample].length)<< 16; ch[channel].pointer =R_pointer; } else if ((R_pointer >> 16) >= voices[R_sample].length) continue; /* byte = sum of (sample byte * volume) for each */ /* of 4 channels which mixes the sounds */ if (R_pointer>> 16 < voices[R_sample].length) { byte += (int) ( (voices[R_sample].info[R_pointer >> 16]) * (ch[channel].volume)); ch[channel].pointer =R_pointer+ ch[channel].step; } } /* Divide by 8 to get the correct volume */ byte /= 8; #ifdef HALFFREQ *fillSound++ = ((byte<<2)+lastbyte)/3; /* write to sndbuffer */ fillCount++; #endif *fillSound++ = byte; /* write to sndbuffer */ fillCount++; lastbyte=byte; /* for evaluation of mean value in case 11 kHz is emulated by 22 kHz */ if (fillCount>MAXSAMPLE) /* is frame filled up? */ { blubber[actualSample]->dataSize=fillCount-1; nextSample(); fillCount=0; fillSound=(char*)((int)blubber[actualSample]+sizeof(SNDSoundStruct)); /* start of data area */ } /* 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; } } } if (fillCount>0) { blubber[actualSample]->dataSize=fillCount-1; /* play last pending frame */ nextSample(); } while (played) { /* wait for end of sndplay */ sleep(1); } /***********************************************************************/ /* free the Sound structures */ for (loop=0;loop<MAXSOUND;loop++) free(blubber[loop]); if (DEBUG) printf("\ndone\n"); exit(0); }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.