BABYL OPTIONS:
Version: 5
Labels:
Note: This is the header of an rmail file.
Note: If you are seeing it in rmail,
Note: it means the file has no messages in it.
0, unseen,,
*** EOOH ***
Newsgroups: comp.sys.ibm.pc.soundcard
From: Dmitry Boldyrev
Subject: Sources for simple .mod player. .mod file format (answer)
X-Xxmessage-Id:
X-Xxdate: Sun, 2 Feb 92 09:14:48 GMT
Organization: University of Utah
X-Useragent: Nuntius v1.1.1d17
Date: Tue, 2 Feb 93 19:11:54 GMT
Many people were asking me about .mod player: How it works? What is the
.mod file
format ?
I decided to *show* you one simple .mod player which can be easily
modified for
any computer, it can be Macintosh, PC or etc. The player was originally
written
for Sparc station, but as I told you before, can be modified for any
computer,
even for SoundBlaster, AdLib or any other sound card. What you have to
know is
initialization and how to write a byte..
Before I saw this source, I had a very poor notion of how it's works, but
two dwo
days with printed-out code made my notion very clear. So, if you're going
to write
a mod player and don't know how to start, try to start with reading the
source..
The code demonstrate how to read .mod files and how to playback them.'
Very clear and useful code..
/***********************************************************************/
/* */
/* str.c - plays sound/noisetracker files on a SparcStation */
/* */
/* Authors : Liam Corner - [email protected] */
/* Marc Espie - [email protected] */
/* Version : 1.20 - 3 November 1991 */
/* */
/* Usage : str32 */
/* [f|z]cat filename | str32 */
/* */
/***********************************************************************/
#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) */
/**********************************************************/
#define uS 125
#define VSYNC 160
#define AUDIO "/dev/audio"
#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);
}
/************************************************************************/
/* 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 / 2);
}
else if (ch < 96)
{
ch = 0xE0 | 15 - (ch - 32) / 4;
}
else if (ch < 224)
{
ch = 0xD0 | 15 - (ch - 96) / 8;
}
else if (ch < 480)
{
ch = 0xC0 | 15 - (ch - 224) / 16;
}
else if (ch < 992)
{
ch = 0xB0 | 15 - (ch - 480) / 32;
}
else if (ch < 2016)
{
ch = 0xA0 | 15 - (ch - 992) / 64;
}
else if (ch < 4064)
{
ch = 0x90 | 15 - (ch - 2016) / 128;
}
else if (ch < 8160)
{
ch = 0x80 | 15 - (ch - 4064) / 256;
}
else
{
ch = 0x80;
}
return (mask & ch);
}
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];
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");
exit(1);
}
if (type == OLD)
nvoices = 15;
else
nvoices = 31;
if (argc>2)
{
fprintf(stderr,"Usage: %s []\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].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_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 :
/* 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;
}
}
/* Divide by 4 to get the correct volume */
byte /= 4;
ulaw = (unsigned char) cvt(byte * 16);/* Convert byte
*/
fputc(ulaw, audio); /* and play the
note */
}
/* 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;
}
}
fclose(audio);
printf("\n");
return (0);
}
o=======================================o
I Dmitry Boldyrev. I
I Department of Chemistry, I
I University of Utah SLC Utah. I
I Inter. [email protected] I
o=======================================o