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.