Sound Blaster Unix Driver 2/2

Brian Smith brians at eecs.cs.pdx.edu
Fri Jun 7 03:22:05 AEST 1991


#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	apps/Makefile
#	apps/get_instr.1
#	apps/get_instr.c
#	apps/play_cleanup.1
#	apps/play_cleanup.c
#	apps/play_cmf.1
#	apps/play_cmf.c
#	apps/play_snd.1
#	apps/play_snd.c
#	apps/record_snd.1
#	apps/record_snd.c
#	apps/set_speed.1
#	apps/set_speed.c
#	apps/snd_norm.1
#	apps/snd_norm.c
#	apps/tst_fm_note.c
#	apps/tst_fm_open.c
#	apps/tst_instr.c
# This archive created: Thu Jun  6 08:33:44 1991
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'apps/Makefile'
then
	echo shar: "will not over-write existing file 'apps/Makefile'"
else
cat << \SHAR_EOF > 'apps/Makefile'
# 
#                 Programs using Sound Blaster(tm) Driver
#                     (Copyright 1991, Brian Smith)
#

SHELL=/bin/sh
CC= gcc -fpcc-struct-return
CFLAGS = # -O
LIBS= -lc_s

PROGRAMS= play_snd set_speed play_cmf record_snd snd_norm
TESTS= play_cleanup tst_fm_open tst_fm_note get_instr tst_instr

all: $(PROGRAMS) $(TESTS)

record_snd: record_snd.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o record_snd record_snd.c $(LIBS)

play_snd: play_snd.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o play_snd play_snd.c $(LIBS)

play_cleanup: play_cleanup.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o play_cleanup play_cleanup.c $(LIBS)

get_instr: get_instr.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o get_instr get_instr.c $(LIBS)

set_speed: set_speed.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o set_speed set_speed.c $(LIBS)

tst_fm_open: tst_fm_open.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o tst_fm_open tst_fm_open.c $(LIBS)

tst_fm_note: tst_fm_note.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o tst_fm_note tst_fm_note.c $(LIBS)

play_cmf: play_cmf.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o play_cmf play_cmf.c -linet $(LIBS)

tst_instr: tst_instr.c /usr/include/sys/sb.h
	$(CC) $(CFLAGS) -o tst_instr tst_instr.c $(LIBS)

snd_norm: snd_norm.c
	$(CC) $(CFLAGS) -o snd_norm snd_norm.c $(LIBS)

install: $(PROGRAMS)
	for i in $(PROGRAMS); do \
	mcs -d $$i ; \
	strip $$i ; \
	(echo $$i | cpio -pdlmv /usr/local/bin) ; \
	done

clean:
	/bin/rm -f $(PROGRAMS) $(TESTS)
SHAR_EOF
fi
if test -f 'apps/get_instr.1'
then
	echo shar: "will not over-write existing file 'apps/get_instr.1'"
else
cat << \SHAR_EOF > 'apps/get_instr.1'
.TH GET_INSTR 1 "3 June 1991"
.UC 4
.SH NAME
get_instr \- decodes and prints instrument info from CMF files
.SH SYNOPSIS
.B get_instr
[
.I file
]
.PP
The argument must be the CMF file to be examined.
.SH DESCRIPTION
.B get_instr
reads the portion of the CMF file detailing the instruments used, and
then prints out the breakdown of that information.
.SH AUTHOR
.PP
Brian Smith
SHAR_EOF
fi
if test -f 'apps/get_instr.c'
then
	echo shar: "will not over-write existing file 'apps/get_instr.c'"
else
cat << \SHAR_EOF > 'apps/get_instr.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/unistd.h>
#include <sys/sb.h>
#include <stdio.h>

int main(argc, argv)
int argc;
char **argv;
{
    int cmf_fd;

    if (argc != 2)
    {
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* open cmf file */
    cmf_fd = open(argv[1], O_RDONLY);
    if (cmf_fd == -1)
    {
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* verify that file is a cmf file */
    if (!verify_cmf(cmf_fd))
    {
        printf("file was not a cmf file\n");
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* print out info on instruments in cmf file */
    print_instruments(cmf_fd);

    return(0);
}


/* check for "CTMF" in first four bytes of file */
int verify_cmf(fd)
int fd;
{
    char idbuf[5];

    /* get id */
    lseek(fd, 0, SEEK_SET);
    if (read(fd, idbuf, 4) != 4)
        return(FALSE);
    
    /* compare to standard id */
    idbuf[4] = (char)0;
    if (strcmp(idbuf, "CTMF") != 0)
        return(FALSE);
    
    return(TRUE);
}

int print_instruments(fd)
int fd;
{
    int offset;
    int num_instruments;
    int i;
    int j;
    unsigned char tmp_byte;
    unsigned char instrument_buf[16];

#define lobyte(X)   (((unsigned char *)&X)[0])
#define hibyte(X)   (((unsigned char *)&X)[1])

    /* get offset of instrument block */
    offset = 0;
    lseek(fd, 0x06, SEEK_SET);
    read(fd, &tmp_byte, 1);
    lobyte(offset) = tmp_byte;
    read(fd, &tmp_byte, 1);
    hibyte(offset) = tmp_byte;

    /* get number of instruments */
    num_instruments = 0;
    lseek(fd, 0x24, SEEK_SET);
    read(fd, &tmp_byte, 1);
    lobyte(num_instruments) = tmp_byte;
    read(fd, &tmp_byte, 1);
    hibyte(num_instruments) = tmp_byte;

    /* read each instrument */
    lseek(fd, offset, SEEK_SET);
    for (i=0; i< num_instruments; i++)
    {
        read(fd, instrument_buf, 16);
        printf("instrument: 0x%02x\n", i);

        for (j=0; j<16; j++)
            printf("0x%02x ", (unsigned int)instrument_buf[j]);
        printf("\n");

        /* byte 0 */
        printf("\tModulator: 0x%02x\n", (unsigned int)instrument_buf[0]);

        /* byte 1 */
        printf("\tCarrier Sound Characteristic\n");
        if (instrument_buf[1] & (1<<7))
            printf("\tPitch Vibrato: On\n");
        else
            printf("\tPitch Vibrato: Off\n");
        if (instrument_buf[1] & (1<<6))
            printf("\tAmplitude Vibrato: On\n");
        else
            printf("\tAmplitude Vibrato: Off\n");
        if (instrument_buf[1] & (1<<5))
            printf("\tSustaining Sound: On\n");
        else
            printf("\tSustaining Sound: Off\n");
        if (instrument_buf[1] & (1<<4))
            printf("\tEnvelope Scaling: On\n");
        else
            printf("\tEnvelope Scaling: Off\n");
        printf("\tFrequency Multiplier: 0x%02x\n", instrument_buf[1] & 0x0F);

        /* byte 2 */
        printf("\tModulator Level Scaling: 0x%02x\n", instrument_buf[2] >> 4);
        printf("\tModulator Output Level: 0x%02x\n", instrument_buf[2] & 0x3f);

        /* byte 3 */
        printf("\tCarrier Level Scaling: 0x%02x\n", instrument_buf[3] >> 4);
        printf("\tCarrier Output Level: 0x%02x\n", instrument_buf[3] & 0x3f);

        /* byte 4 */
        printf("\tModulator Attack Rate 0x%02x\n", instrument_buf[5] >> 4);
        printf("\tModulator Decay Rate 0x%02x\n", instrument_buf[5] & 0xF);

        /* byte 5 */
        printf("\tCarrier Attack Rate 0x%02x\n", instrument_buf[5] >> 4);
        printf("\tCarrier Decay Rate 0x%02x\n", instrument_buf[5] & 0xF);

        /* byte 6 */
        printf("\tModulator Sustain Level 0x%02x\n", instrument_buf[6] >> 4);
        printf("\tModulator Release Level 0x%02x\n", instrument_buf[6] >> 4);

        /* byte 7 */
        printf("\tCarrier Sustain Level 0x%02x\n", instrument_buf[7] >> 4);
        printf("\tCarrier Release Level 0x%02x\n", instrument_buf[7] >> 4);

        /* byte 8 */
        printf("\tModulator Wave Select 0x%02x\n", instrument_buf[8] & 0x03);

        /* byte 9 */
        printf("\tCarrier Wave Select 0x%02x\n", instrument_buf[9] & 0x03);

        /* byte A */
        printf("\tModulator FeedBack 0x%02x\n",
            (instrument_buf[0xA] >> 1) & 0x07);
    }

    return(0);
}
SHAR_EOF
fi
if test -f 'apps/play_cleanup.1'
then
	echo shar: "will not over-write existing file 'apps/play_cleanup.1'"
else
cat << \SHAR_EOF > 'apps/play_cleanup.1'
.TH PLAY_CLEANUP 1 "3 June 1991"
.UC 4
.SH NAME
play_cleanup \- Cleans up after a very abnormal exit from play_snd
.SH SYNOPSIS
.B play_cleanup
.PP
No arguments are necessary.
.SH DESCRIPTION
If play_snd exits without destroying the shared memory segment,
further invocations will fail until the segment is destroyed.  This
program destroys that memory segment.  This should not be needed,
because play_snd cleans up after getting eof and SIGINT.  It cannot,
however, catch SIGKILL.
.B play_cleanup
.SH AUTHOR
.PP
Brian Smith
SHAR_EOF
fi
if test -f 'apps/play_cleanup.c'
then
	echo shar: "will not over-write existing file 'apps/play_cleanup.c'"
else
cat << \SHAR_EOF > 'apps/play_cleanup.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define NUMBUFS     2
#define SHM_BUFSIZ  (8*4096)
#define SHM_KEY     1796

typedef struct {
    int     not_last_segment[NUMBUFS];
    int     unlocked[NUMBUFS];
    char    buf[NUMBUFS*SHM_BUFSIZ];
}   buf_struct;


main()
{
    int rc;
    int shmid;

    shmid = shmget(SHM_KEY, sizeof(buf_struct), IPC_CREAT | 0644);
    if (shmid == -1)
    {
        perror("creating shared-mem buffer");
        exit(-1);
    }

    rc = shmctl(shmid, IPC_RMID, 0);
    if (rc == -1)
        perror("removing segment");

    exit(0);
}
SHAR_EOF
fi
if test -f 'apps/play_cmf.1'
then
	echo shar: "will not over-write existing file 'apps/play_cmf.1'"
else
cat << \SHAR_EOF > 'apps/play_cmf.1'
.TH PLAY_CMF 1 "3 June 1991"
.UC 4
.SH NAME
play_cmf \- decodes and plays a CMF music file.
.SH SYNOPSIS
.B play_cmf
[
.I file
]
.PP
The argument must be the CMF file to be played.
.SH DESCRIPTION
.B play_cmf
Attempts to play a CMF file.
.SH BUGS
Many CMF files attempt to use Control Change events not understood by the
author.  Therefore, the program does not implement whatever these
control changes are supposed to do.
.SH AUTHOR
.PP
Brian Smith
SHAR_EOF
fi
if test -f 'apps/play_cmf.c'
then
	echo shar: "will not over-write existing file 'apps/play_cmf.c'"
else
cat << \SHAR_EOF > 'apps/play_cmf.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/unistd.h>
#include <sys/sb.h>
#include <sys/time.h>
#include <stdio.h>

#define lobyte(X)   (((unsigned char *)&X)[0])
#define hibyte(X)   (((unsigned char *)&X)[1])

/* Globals */
int fm_herz;        /* clock ticks per second */
int tempo;          /* clock ticks per quarter note */
int fm_fd;
int note_on[16];
char **instrument_table;
int note_table[12] = {
    343,
    363,
    385,
    408,
    432,
    458,
    485,
    514,
    544,
    577,
    611,
    647
    };


int main(argc, argv)
int argc;
char **argv;
{
    int cmf_fd;

    if (argc != 2)
    {
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* open cmf file */
    cmf_fd = open(argv[1], O_RDONLY);
    if (cmf_fd == -1)
    {
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* verify that file is a cmf file */
    if (!verify_cmf(cmf_fd))
    {
        printf("file was not a cmf file\n");
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* read and set instruments from cmf file */
    get_instruments(cmf_fd);

    /* get timing */
    set_timing(cmf_fd);

    /* open soundblaster fm chips */
    fm_fd = open("/dev/sbfm", O_WRONLY);
    if (fm_fd == -1)
    {
        perror("opening fm chips");
        exit(-1);
    }

    /* play song */
    play_song(cmf_fd);

    return(0);
}


/* check for "CTMF" in first four bytes of file */
int verify_cmf(fd)
int fd;
{
    char idbuf[5];

    /* get id */
    lseek(fd, 0, SEEK_SET);
    if (read(fd, idbuf, 4) != 4)
        return(FALSE);
    
    /* compare to standard id */
    idbuf[4] = (char)0;
    if (strcmp(idbuf, "CTMF") != 0)
        return(FALSE);
    
    return(TRUE);
}

int get_instruments(fd)
int fd;
{
    int offset;
    int num_instruments;
    int i;
    int rc;
    int fnum, block, note;
    unsigned char tmp_byte;
    sb_fm_character note_character;

    /* get offset of instrument block */
    offset = 0;
    lseek(fd, 0x06, SEEK_SET);
    read(fd, &tmp_byte, 1);
    lobyte(offset) = tmp_byte;
    read(fd, &tmp_byte, 1);
    hibyte(offset) = tmp_byte;

    /* get number of instruments */
    num_instruments = 0;
    lseek(fd, 0x24, SEEK_SET);
    read(fd, &tmp_byte, 1);
    lobyte(num_instruments) = tmp_byte;
    read(fd, &tmp_byte, 1);
    hibyte(num_instruments) = tmp_byte;

    /* allocate space */
    instrument_table = (char **)malloc(sizeof(int *) * num_instruments);

    /* read each instrument */
    lseek(fd, offset, SEEK_SET);
    for (i=0; i< num_instruments; i++)
    {
        /* allocate space */
        instrument_table[i] = (char *)malloc(16);

        /* set instrument characteristics */
        read(fd, instrument_table[i], 16);
    }

    return(0);
}


/*
 * get and set timing parameters
 */
int set_timing(fd)
int fd;
{
    unsigned char tmp_byte;

    /* get tempo */
    tempo = 0;
    lseek(fd, 0x0C, SEEK_SET);
    read(fd, &tmp_byte, 1);
    tempo = (unsigned int)tmp_byte;
    read(fd, &tmp_byte, 1);
    tempo += (unsigned int)tmp_byte << 8;

    /* get herz of timing clock */
    fm_herz = 0;
    lseek(fd, 0x0C, SEEK_SET);
    read(fd, &tmp_byte, 1);
    fm_herz = (unsigned int)tmp_byte;
    read(fd, &tmp_byte, 1);
    fm_herz += (unsigned int)tmp_byte << 8;
    
    return(0);
}


/*
 * seek to the midi stream and handle midi events for the song
 */
int play_song(fd)
int fd;
{
    int offset;
    unsigned char tmp_byte;
    int delta;

    /* get offset of music stream */
    lseek(fd, 8, SEEK_SET);
    read(fd, &tmp_byte, 1);
    offset = (unsigned int)tmp_byte;
    read(fd, &tmp_byte, 1);
    offset += (unsigned int)tmp_byte << 8;
    lseek(fd, offset, SEEK_SET);

    /* process till EOF */
    while(1)
    {
        /* get delta time */
        delta = ReadVarLen(fd);
        if (delta == -1)
            break;

        /* wait delta */
        if (delta > 0)
            high_res_sleep((double)delta/(double)fm_herz);

        /* process midi event */
        process_event(fd, delta);
    }


    return(0);
}


/*
 * read a variable length scalar in MIDI format
 */
int ReadVarLen(fd)
int fd;
{
    int value;
    unsigned char tmp_byte;

    if (read(fd, &tmp_byte, 1) == 0)
        return(-1);
    value = (int)tmp_byte;
    if (tmp_byte & 0x80)
    {
        value &= 0x7F;
        do
        {
            if (read(fd, &tmp_byte, 1) == 0)
                return(-1);
            value = (value << 7) + (tmp_byte & 0x7F);
        } while (tmp_byte & 0x80);
    }

    return(value);
}


/*
 * process a midi event
 */
int process_event(fd, delta)
int fd;
{
    int rc;
    unsigned char tmp_byte;
    static int status = -1;
    sb_fm_note note;

    /* get status byte */
    read(fd, &tmp_byte, 1);
    if (tmp_byte & 0x80)
    {
        status = (unsigned int)tmp_byte;
    }
    else
    {
        /* running status, so back up one */
        if (status == -1)
        {
            printf("ERROR in cmf file. Running status at beginning of file\n");
            exit(-1);
        }
        lseek(fd, -1, SEEK_CUR);
    }
    
    /* switch different events */
    switch (status & 0xF0)
    {
        case 0x80:
            /* turn note off */
            ioctl(fm_fd, FM_IOCTL_NOTE_OFF, status & 0x0F);
            note_on[status&0x0F] = 0;
            /* waste two bytes */
            read(fd, &tmp_byte, 1);
            read(fd, &tmp_byte, 1);
            break;
        case 0x90:
            /* get note */
            read(fd, &tmp_byte, 1);
            /* determine note */
            note_num(note) = status & 0x0F;
            fnum_low(note) = note_table[tmp_byte%12] & 0xFF;
            fnum_low(note) = note_table[tmp_byte%12] & 0xFF;
            keyon_blk_fnum(note) = 0;
            keyon_blk_fnum(note) |= 1<<5;
            keyon_blk_fnum(note) |= ((tmp_byte/12) & 7) << 2;
            keyon_blk_fnum(note) |= (note_table[tmp_byte%12] & 0x3FF) >> 8;

            /* turn note on */
            if (note_on[status&0x0F])
                ioctl(fm_fd, FM_IOCTL_NOTE_OFF, status&0x0F);
            ioctl(fm_fd, FM_IOCTL_NOTE_ON, note);
            note_on[status&0x0F] = 1;

            /* waste a bytes */
            read(fd, &tmp_byte, 1);
            break;
        case 0xA0:
            printf("polyphonic key pressure: not handled\n");
            /* waste two bytes */
            read(fd, &tmp_byte, 1);
            read(fd, &tmp_byte, 1);
            break;
        case 0xB0:
            printf("control change: not handled\n");
            /* waste two bytes */
            read(fd, &tmp_byte, 1);
            read(fd, &tmp_byte, 1);
            break;
        case 0xC0:
            /* change the instrument on a channel */
            read(fd, &tmp_byte, 1);
            load_instrument(status&0x0F, tmp_byte & 0x0F);
            break;
        case 0xD0:
            printf("Channel Pressure: not handled\n");
            /* waste a byte */
            read(fd, &tmp_byte, 1);
            break;
        case 0xE0:
            printf("Pitch Wheel Change: not handled\n");
            /* waste two bytes */
            read(fd, &tmp_byte, 1);
            read(fd, &tmp_byte, 1);
            break;
        case 0xF0:
            printf("System Exclusive: not handled\n");
            /* waste two bytes */
            read(fd, &tmp_byte, 1);
            read(fd, &tmp_byte, 1);
            break;
        default:
            printf("internal program error\n");
            /* waste two bytes */
            read(fd, &tmp_byte, 1);
            read(fd, &tmp_byte, 1);
            break;
    }


    return(0);
}


/*
 * load an instrument from the instrument table into the SoundBlaster
 */
int load_instrument(channel, instrument)
{
    int rc;
    sb_fm_character note_character;

    /* error check! */
    if ((channel <0) || (channel >= 9))
        return;

    /* abort instrument if being loaded */
    if (note_on[channel])
        ioctl(fm_fd, FM_IOCTL_NOTE_OFF, channel);

    /* set instrument characteristics */
    note_character.voice_num = channel;
    memcpy(note_character.data, instrument_table[instrument], 16);
    rc = ioctl(fm_fd, FM_IOCTL_SET_VOICE, (int)&note_character);
    if (rc == -1)
    {
        perror("fm chips voice set");
        exit(-1);
    }

    return(0);
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Higher-resolution sleep
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int high_res_sleep(seconds)
    double      seconds;
{
    int         fds = 0;
    struct timeval timeout;

    timeout.tv_sec = seconds;
    timeout.tv_usec = (seconds - timeout.tv_sec) * 1000000.0;
    select(0, &fds, &fds, &fds, &timeout);
}

SHAR_EOF
fi
if test -f 'apps/play_snd.1'
then
	echo shar: "will not over-write existing file 'apps/play_snd.1'"
else
cat << \SHAR_EOF > 'apps/play_snd.1'
.TH PLAY_SND 1 "3 June 1991"
.UC 4
.SH NAME
play_snd \- plays a raw 8-bit sound file
.SH SYNOPSIS
.B play_snd
[
.I file
]
.PP
The argument must be the sound file to be played.
.SH DESCRIPTION
.B play_snd
Plays raw 8-bit sound files, using shared memory and two processes
to double buffer reads and writes.  Feel free to use SIGINT (usually
control-C) to interrupt the play.  Do NOT, however, use SIGKILL or any
other signal to terminate the program.  This leaves unwanted
left-overs, in the form of a shared memory segment.  Use play_cleanup
if this happens accidentally.

Use set_speed to change the playing rate of the samples.
.SH AUTHOR
.PP
Brian Smith
SHAR_EOF
fi
if test -f 'apps/play_snd.c'
then
	echo shar: "will not over-write existing file 'apps/play_snd.c'"
else
cat << \SHAR_EOF > 'apps/play_snd.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sb.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>

#define NUMBUFS     4
#define SHM_BUFSIZ  (8*4096)
#define SHM_KEY     1796

typedef struct {
    char    buf[NUMBUFS*SHM_BUFSIZ];
    int     write_waiting;
    int     read_waiting;
    int     locked[NUMBUFS];
    int     length[NUMBUFS];
}   buf_struct;


/* GLOBALS */
int shmid;
buf_struct *buffers;
int sound_fd;

void sigusr_handler()
{
    return;
}


void cleanup()
{
    shmctl(shmid, IPC_RMID, 0);
    exit(0);
}

void detach()
{
    shmdt(buffers);
    exit(0);
}


int main(argc, argv)
int argc;
char **argv;
{
    int infile;
    int child_pid;
    int buf_num;
    int rc;

    if (argc != 2)
    {
        printf("usage: %s <sound file>\n", argv[0]);
        printf("\tif <sound file> is \"-\", then %s will play from stdin\n",
            argv[0]);
        exit(-1);
    }

    if (strcmp(argv[1], "-") == 0)
        infile = 0;
    else
    {
        infile = open(argv[1], O_RDONLY);
        if (infile == -1)
        {
            perror("opening data file");
            printf("usage: %s <sound file>\n", argv[0]);
            printf("\tif <sound file> is \"-\", then %s will play from stdin\n",
                argv[0]);
            exit(-1);
        }
    }

    /* open device */
    sound_fd = open("/dev/sbdsp", O_WRONLY);
    if (sound_fd == -1)
    {
        perror("opening SoundBlaster device");
        exit(-1);
    }

    /* create shared memory segment */
    shmid = shmget(SHM_KEY, sizeof(buf_struct), IPC_CREAT | IPC_EXCL | 0644);
    if (shmid == -1)
    {
        perror("creating shared-mem buffer");
        exit(-1);
    }

    /* attach handler for signal */
    sigset(SIGUSR1, sigusr_handler);
    sigset(SIGINT, cleanup);
    sigset(SIGHUP, cleanup);

    /* start read process */
    child_pid = fork();
    switch (child_pid)
    {
        case 0:
            start_read(infile);
            exit(0);
        case -1:
            perror("forking reader process");
            cleanup();
    }

    /* attach shared memory segment */
    buffers = (buf_struct *)shmat(shmid, 0, 0);
    if (buffers == (buf_struct *)-1)
    {
        perror("attaching shared memory");
        if (buffers->read_waiting)
            kill(child_pid, SIGKILL);
        cleanup();
    }

    /* start writing stuff in buffers */
    while(1)
    {
        /* wait until buffer is locked for us, or flush and break on eof */
        if (!buffers->locked[buf_num])
        {
            buffers->write_waiting = 1;
            sigpause(SIGUSR1);
            continue;
        }

        /* not waiting now */
        buffers->write_waiting = 0;

        /* eof check */
        if (buffers->length[buf_num] <= 0)
            break;

        /* write out data in buffer */
        rc = write(sound_fd, buffers->buf + (buf_num*SHM_BUFSIZ),
            buffers->length[buf_num]);
        if (rc != buffers->length[buf_num])
        {
            if ((errno == EINTR) || (errno == 0))
                continue;

            perror("writing to sound blaster");
            kill(child_pid, SIGKILL);
            cleanup();
        }

        /* unlock buffer for child's use */
        buffers->locked[buf_num] = 0;
        if (buffers->read_waiting)
            kill(child_pid, SIGUSR1);

        /* go to next buffer */
        buf_num++;
        buf_num %= NUMBUFS;
    }


    cleanup();
    return(0);
}

int start_read(infile)
int infile;
{
    buf_struct *buffers;
    int buf_num = 0;

    /* attach handler for signal */
    sigset(SIGUSR1, sigusr_handler);
    sigset(SIGINT, detach);
    sigset(SIGHUP, detach);

    /* attach shared memory */
    buffers = (buf_struct *)shmat(shmid, 0, 0);
    if (buffers == (buf_struct *)-1)
    {
        perror("attaching shared memory");
        exit(0);
    }

    /* init shared mem stuff */
    buffers->read_waiting = 0;
    for (buf_num=0; buf_num < NUMBUFS; buf_num++)
        buffers->locked[buf_num] = 0;

    /* start reading into buffers */
    buf_num = 0;
    sleep(1);
    while(1)
    {
        /* wait for current buffer to become unlocked */
        if (buffers->locked[buf_num])
        {
            buffers->read_waiting = 1;
            sigpause(SIGUSR1);
            continue;
        }

        /* not waiting any more */
        buffers->read_waiting = 0;

        /* actually read data */
        buffers->length[buf_num] =
            read(infile, buffers->buf + (buf_num*SHM_BUFSIZ), SHM_BUFSIZ);
        if (buffers->length[buf_num] == -1) 
        {
            if (errno == EINTR)
                continue;
            else
            {
                perror("reading from input file\n");
                detach();
            }
        }
        else if (buffers->length[buf_num] == 0)
        {
            buffers->locked[buf_num] = 1;

            /* wake up parent */
            if (buffers->write_waiting)
                kill(getppid(), SIGUSR1);

            break;
        }

        /* lock buffer for parent's use */
        buffers->locked[buf_num] = 1;
                
        /* wake up parent */
        if (buffers->write_waiting)
            kill(getppid(), SIGUSR1);

        /* go to next buffer */
        buf_num++;
        buf_num %= NUMBUFS;
    }
        

    /* wake up parent */
    if (buffers->write_waiting)
        kill(getppid(), SIGUSR1);

    /* detach shared memory */
    shmdt(buffers);

    return(0);
}
SHAR_EOF
fi
if test -f 'apps/record_snd.1'
then
	echo shar: "will not over-write existing file 'apps/record_snd.1'"
else
cat << \SHAR_EOF > 'apps/record_snd.1'
.TH RECORD_SND 1 "3 June 1991"
.UC 4
.SH NAME
record_snd \- records a raw 8-bit sound file
.SH SYNOPSIS
.B record_snd
[
.I file
]
.PP
The argument must be the sound file to contain the recording.
.SH DESCRIPTION
.B record_snd
Records raw 8-bit sound files, using shared memory and two processes
to double buffer reads and writes.  Use SIGINT (usually control-C) to
stop the recording.  Do NOT, however, use SIGKILL or any other signal
to terminate the program.  This leaves unwanted left-overs, in the
form of a shared memory segment.  Use play_cleanup if this happens
accidentally.

Use set_speed to change the recording rate of the samples.
.SH AUTHOR
.PP
Brian Smith
SHAR_EOF
fi
if test -f 'apps/record_snd.c'
then
	echo shar: "will not over-write existing file 'apps/record_snd.c'"
else
cat << \SHAR_EOF > 'apps/record_snd.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sb.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>

#define NUMBUFS     4
#define SHM_BUFSIZ  (8*4096)
#define SHM_KEY     1796

typedef struct {
    char    buf[NUMBUFS*SHM_BUFSIZ];
    int     write_waiting;
    int     read_waiting;
    int     locked[NUMBUFS];
    int     length[NUMBUFS];
}   buf_struct;


/* GLOBALS */
int shmid;
buf_struct *buffers;

void sigusr_handler()
{
    return;
}


void cleanup()
{
    printf("cleanup\n");
    shmctl(shmid, IPC_RMID, 0);
    exit(0);
}

void detach()
{
    printf("detaching\n");
    shmdt(buffers);
    exit(0);
}


int main(argc, argv)
int argc;
char **argv;
{
    int outfile;
    int sound_fd;
    int child_pid;
    int buf_num;
    int rc;

    if (argc != 2)
    {
        printf("usage: %s <new sound file>\n", argv[0]);
        printf("\tif <sound file> is \"-\", then %s will record to stdout\n",
            argv[0]);
        exit(-1);
    }

    if (strcmp(argv[1], "-") == 0)
        outfile = 1;
    else
    {
        outfile = open(argv[1], O_WRONLY | O_CREAT, 0666);
        if (outfile == -1)
        {
            perror("opening output file");
            printf("usage: %s <output sound file>\n", argv[0]);
            printf("\tif <sound file> is \"-\", %s will record to stdout\n",
                argv[0]);
            exit(-1);
        }
    }

    /* open device */
    sound_fd = open("/dev/sbdsp", O_RDONLY);
    if (sound_fd == -1)
    {
        perror("opening SoundBlaster device");
        exit(-1);
    }
    if (ioctl(sound_fd, DSP_IOCTL_RESET) == -1)
    {
        perror("trying to reset DSP");
        exit(-1);
    }
    if (ioctl(sound_fd, DSP_IOCTL_VOICE, 0) == -1)
    {
        perror("trying to set voice on");
        exit(-1);
    }

    /* create shared memory segment */
    shmid = shmget(SHM_KEY, sizeof(buf_struct), IPC_CREAT | IPC_EXCL | 0644);
    if (shmid == -1)
    {
        perror("creating shared-mem buffer");
        exit(-1);
    }

    /* attach handler for signal */
    sigset(SIGUSR1, sigusr_handler);
    sigset(SIGINT, cleanup);
    sigset(SIGHUP, cleanup);

    /* start read process */
    child_pid = fork();
    switch (child_pid)
    {
        case 0:
            start_read(sound_fd);
            exit(0);
        case -1:
            perror("forking read process");
            cleanup();
    }

    /* attach shared memory segment */
    buffers = (buf_struct *)shmat(shmid, 0, 0);
    if (buffers == (buf_struct *)-1)
    {
        perror("attaching shared memory");
        if (buffers->read_waiting)
            kill(child_pid, SIGKILL);
        cleanup();
    }

    /* start writing stuff from buffers */
    while(1)
    {
        /* wait until buffer is locked for us, or flush and break on eof */
        if (!buffers->locked[buf_num])
        {
            buffers->write_waiting = 1;
            sigpause(SIGUSR1);
            continue;
        }

        /* not waiting now */
        buffers->write_waiting = 0;

        /* eof check */
        if (buffers->length[buf_num] <= 0)
            break;

        /* write out data in buffer */
        rc = write(outfile, buffers->buf + (buf_num*SHM_BUFSIZ),
            buffers->length[buf_num]);
        if (rc != buffers->length[buf_num])
        {
            if ((errno == EINTR) || (errno == 0))
                continue;

            perror("writing to output file");
            kill(child_pid, SIGKILL);
            cleanup();
        }

        /* unlock buffer for child's use */
        buffers->locked[buf_num] = 0;
        if (buffers->read_waiting)
            kill(child_pid, SIGUSR1);

        /* go to next buffer */
        buf_num++;
        buf_num %= NUMBUFS;
    }


    printf("done\n");
    cleanup();
    return(0);
}

int start_read(sound_fd)
int sound_fd;
{
    buf_struct *buffers;
    int buf_num = 0;

    /* attach handler for signal */
    sigset(SIGUSR1, sigusr_handler);
    sigset(SIGINT, detach);
    sigset(SIGHUP, detach);

    /* attach shared memory */
    buffers = (buf_struct *)shmat(shmid, 0, 0);
    if (buffers == (buf_struct *)-1)
    {
        perror("attaching shared memory");
        exit(0);
    }

    for (buf_num=0; buf_num < NUMBUFS; buf_num++)
        buffers->locked[buf_num] = 0;

    /* start reading into buffers */
    buf_num = 0;
    sleep(1);
    while(1)
    {
        /* wait for current buffer to become unlocked */
        if (buffers->locked[buf_num])
        {
            buffers->read_waiting = 1;
            sigpause(SIGUSR1);
            continue;
        }

        /* not waiting any more */
        buffers->read_waiting = 1;

        /* actually read data */
        buffers->length[buf_num] =
            read(sound_fd, buffers->buf + (buf_num*SHM_BUFSIZ), SHM_BUFSIZ);
        if (buffers->length[buf_num] == -1) 
        {
            if (errno == EINTR)
                continue;
            else
            {
                perror("reading from SoundBlaster\n");
                detach();
            }
        }
        else if (buffers->length[buf_num] == 0)
        {
            buffers->locked[buf_num] = 1;

            /* wake up parent */
            if (buffers->write_waiting)
                kill(getppid(), SIGUSR1);

            break;
        }

        /* lock buffer for parent's use */
        buffers->locked[buf_num] = 1;
                
        /* wake up parent */
        if (buffers->write_waiting)
            kill(getppid(), SIGUSR1);

        /* go to next buffer */
        buf_num++;
        buf_num %= NUMBUFS;
    }
        

    /* wake up parent */
    kill(getppid(), SIGUSR1);

    /* detach shared memory */
    shmdt(buffers);

    return(0);
}
SHAR_EOF
fi
if test -f 'apps/set_speed.1'
then
	echo shar: "will not over-write existing file 'apps/set_speed.1'"
else
cat << \SHAR_EOF > 'apps/set_speed.1'
.TH SET_SPEED 1 "3 June 1991"
.UC 4
.SH NAME
set_speed \- changes the speed of recording and playing on Sound
Blaster
.SH SYNOPSIS
.B set_speed
]
.I speed
]
.PP
The argument must be the speed (in HZ) for sampling or playing.
.SH DESCRIPTION
.B set_speed
Sets the speed of the sampling and playing of the Sound Blaster
driver.  The speed must be greater than (or equal) to 4000Hz, and less
than 24000Hz.  Speeds in excess of 13000Hz will be truncated to
approximately 13000Hz by any recording.
.SH AUTHOR
.PP
Brian Smith
SHAR_EOF
fi
if test -f 'apps/set_speed.c'
then
	echo shar: "will not over-write existing file 'apps/set_speed.c'"
else
cat << \SHAR_EOF > 'apps/set_speed.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/sb.h>

int main(argc, argv)
int argc;
char **argv;
{
    int fd;
    int speed;

    if (argc != 2)
    {
        printf("usage: %s <speed>\n", argv[0]);
        exit(-1);
    }
    speed = atoi(argv[1]);
    if ((speed < 4000) || (speed > 24000))
    {
        printf("usage: %s <speed>\n", argv[0]);
        printf("\tspeed must be between 4000 and 24000");
        exit(-1);
    }

    fd = open("/dev/sbdsp", O_RDONLY);
    if (fd == -1)
    {
        perror("opening SoundBlaster");
        exit(-1);
    }

    ioctl(fd, DSP_IOCTL_SPEED, speed);

    exit(0);
}
SHAR_EOF
fi
if test -f 'apps/snd_norm.1'
then
	echo shar: "will not over-write existing file 'apps/snd_norm.1'"
else
cat << \SHAR_EOF > 'apps/snd_norm.1'
.TH SND_NORM 1 "3 June 1991"
.UC 4
.SH NAME
snd_norm \- normalizes a sound file
.SH SYNOPSIS
.B snd_norm
[
.I old file
]
[
.I new file
]
.PP
The arguments are the input and output of the normalization.
.SH DESCRIPTION
.B snd_norm
The input file is normalized, such that the larget amplitude 127, thus
obtaining the larget volume from a file (for a given volume setting on
the actual Sound Blaster).  Usefull for post-processing recordings
which are barely audible.
.SH AUTHOR
.PP
Brian Smith
SHAR_EOF
fi
if test -f 'apps/snd_norm.c'
then
	echo shar: "will not over-write existing file 'apps/snd_norm.c'"
else
cat << \SHAR_EOF > 'apps/snd_norm.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <stdio.h>
#include <sys/fcntl.h>
#include <sys/unistd.h>

main(argc, argv)
int argc;
char **argv;
{
    int fd, new_fd, rc, max_wave;
    unsigned char tmp_byte;
    int tmp_int;
    int i;
    double augmentation;

    if (argc != 3)
    {
        printf("usage: %s <oldfile> <newfile>\n", argv[0]);
        exit(-1);
    }

    fd = open(argv[1], O_RDONLY);
    if (fd == -1)
    {
        perror("opening oldfile");
        exit(-1);
    }

    new_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (new_fd == -1)
    {
        perror("opening newfile");
        exit(-1);
    }

    printf("normalizing from input file..");
    fflush(stdout);
    i = 0;
    max_wave = 0;
    while(1)
    {
        i++ ; i &= 0x7FFF;
        if (!i)
            write(1, ".", 1);

        rc = read(fd, &tmp_byte, 1);
        if (rc == 0)
            break;
        tmp_int = tmp_byte - 128;
        if (abs(tmp_int) > max_wave)
            max_wave = abs(tmp_int);
    }
    write(1, "\n", 1);

    augmentation = (double)0x80 / (double)max_wave;
    printf("max_wave\t%d\n", max_wave);
    printf("augmentation\t%f\n", augmentation);


    /* write out new file */
    lseek(fd, 0, SEEK_SET);
    while (1)
    {
        rc = read(fd, &tmp_byte, 1);
        if (rc == 0)
            break;

        tmp_int = tmp_byte - 128;
        tmp_int *= augmentation;
        tmp_int += 128;

        tmp_byte = tmp_int;

        rc = write(new_fd, &tmp_byte, 1);
        if (rc != 1)
        {
            perror("writing to new_fd");
            exit(-1);
        }
    }
        

    return(0);
}
SHAR_EOF
fi
if test -f 'apps/tst_fm_note.c'
then
	echo shar: "will not over-write existing file 'apps/tst_fm_note.c'"
else
cat << \SHAR_EOF > 'apps/tst_fm_note.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/signal.h>
#include <sys/sb.h>
#include <stdio.h>
#include <memory.h>

void handler()
{
    return;
}

int main(argc, argv)
int argc;
char **argv;
{
    int fd;
    int rc;
    int i;
    int note;
    int fnum;
    int block;
    static unsigned char instrument_buf[16] = {
        0x11, 0x01, 0x8a, 0x40,
        0xf1, 0xf1, 0x11, 0xb3,
        0x00, 0x00, 0x06, 0x00,
        0x00, 0x00, 0x00, 0x00 
    };
    sb_fm_character note_character;

    sigset(SIGINT, handler);

    fd = open("/dev/sbfm", O_WRONLY);
    if (fd == -1)
    {
        perror("opening fm device");
        return(-1);
    }

    /* test reset */
    rc = ioctl(fd, FM_IOCTL_RESET);
    if (rc == -1)
    {
        perror("fm chips reset");
        exit(-1);
    }

    /* test setting an instrument */
    for (i=0 ; i<9; i++)
    {
        /* set instrument characteristics */
        note_character.voice_num = i;
        memset(note_character.data, (char)0, 16);
        memcpy(note_character.data, instrument_buf, 16);
        rc = ioctl(fd, FM_IOCTL_SET_VOICE, (int)&note_character);
        if (rc == -1)
        {
            perror("fm chips voice set");
            exit(-1);
        }

        /* set note to play */
        fnum = 686;
        block = i;
        note_num(note) = i;
        fnum_low(note) = fnum & 0xFF;
        keyon_blk_fnum(note) = 0;
        keyon_blk_fnum(note) |= 1<<5;                   /* KEYON bit */
        keyon_blk_fnum(note) |= (block & 7) << 2;       /* block/octave */
        keyon_blk_fnum(note) |= (fnum & 0x3FF) >> 8;    /* top 2 bits of fnum */

        /* test note on/off */
        rc = ioctl(fd, FM_IOCTL_NOTE_ON, note);
        if (rc == -1)
        {
            perror("fm chips voice on");
            exit(-1);
        }
    }

    /* wait for ^C */
    sigpause(SIGINT);
    close(fd);
    return(0);
}
SHAR_EOF
fi
if test -f 'apps/tst_fm_open.c'
then
	echo shar: "will not over-write existing file 'apps/tst_fm_open.c'"
else
cat << \SHAR_EOF > 'apps/tst_fm_open.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/sb.h>

int main(argc, argv)
int argc;
char **argv;
{
    int fd;

    fd = open("/dev/sbfm", O_WRONLY);
    if (fd == -1)
        perror("opening fm device");
    else
        close(fd);

    return(0);
}
SHAR_EOF
fi
if test -f 'apps/tst_instr.c'
then
	echo shar: "will not over-write existing file 'apps/tst_instr.c'"
else
cat << \SHAR_EOF > 'apps/tst_instr.c'
/*
 * Copyrighted as an unpublished work.
 * (c) Copyright 1991 Brian Smith
 * All rights reserved.
 *
 * Read the LICENSE file for details on distribution and use.
 *
 */

#include <sys/fcntl.h>
#include <sys/unistd.h>
#include <sys/sb.h>
#include <stdio.h>

#define lobyte(X)   (((unsigned char *)&X)[0])
#define hibyte(X)   (((unsigned char *)&X)[1])

/* Globals */
int fm_herz;        /* clock ticks per second */
int tempo;          /* clock ticks per quarter note */
int fm_fd;

int main(argc, argv)
int argc;
char **argv;
{
    int cmf_fd;

    if (argc != 2)
    {
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* open cmf file */
    cmf_fd = open(argv[1], O_RDONLY);
    if (cmf_fd == -1)
    {
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* verify that file is a cmf file */
    if (!verify_cmf(cmf_fd))
    {
        printf("file was not a cmf file\n");
        printf("usage: %s <cmf file>\n", argv[0]);
        exit(-1);
    }

    /* read and set instruments from cmf file */
    set_instruments(cmf_fd);

    /* play song */
    play_song(cmf_fd);

    return(0);
}


/* check for "CTMF" in first four bytes of file */
int verify_cmf(fd)
int fd;
{
    char idbuf[5];

    /* get id */
    lseek(fd, 0, SEEK_SET);
    if (read(fd, idbuf, 4) != 4)
        return(FALSE);
    
    /* compare to standard id */
    idbuf[4] = (char)0;
    if (strcmp(idbuf, "CTMF") != 0)
        return(FALSE);
    
    return(TRUE);
}

int set_instruments(fd)
int fd;
{
    int offset;
    int num_instruments;
    int i;
    int rc;
    int fnum, block, note;
    unsigned char tmp_byte;
    sb_fm_character note_character;

    /* open soundblaster fm chips */
    fm_fd = open("/dev/sbfm", O_WRONLY);
    if (fm_fd == -1)
    {
        perror("opening fm chips");
        exit(-1);
    }

    /* get offset of instrument block */
    offset = 0;
    lseek(fd, 0x06, SEEK_SET);
    read(fd, &tmp_byte, 1);
    lobyte(offset) = tmp_byte;
    read(fd, &tmp_byte, 1);
    hibyte(offset) = tmp_byte;

    /* get number of instruments */
    num_instruments = 0;
    lseek(fd, 0x24, SEEK_SET);
    read(fd, &tmp_byte, 1);
    lobyte(num_instruments) = tmp_byte;
    read(fd, &tmp_byte, 1);
    hibyte(num_instruments) = tmp_byte;

    if (num_instruments > 9)
        num_instruments = 9;
    printf("loading %d instruments\n", num_instruments);

    /* read each instrument */
    lseek(fd, offset, SEEK_SET);
    for (i=0; i< num_instruments; i++)
    {
        /* set instrument characteristics */
        note_character.voice_num = i;
        read(fd, note_character.data, 16);
        printf("loading instrument: 0x%02x\n", i);
        rc = ioctl(fm_fd, FM_IOCTL_SET_VOICE, (int)&note_character);
        if (rc == -1)
        {
            perror("fm chips voice set");
            exit(-1);
        }

        /* set note to regular C note */
        fnum = 686;
        block = 4;
        note_num(note) = i;
        fnum_low(note) = fnum & 0xFF;
        keyon_blk_fnum(note) = 0;
        keyon_blk_fnum(note) |= 1<<5;                   /* KEYON bit */
        keyon_blk_fnum(note) |= (block & 7) << 2;       /* block/octave */
        keyon_blk_fnum(note) |= (fnum & 0x3FF) >> 8;    /* top 2 bits of fnum */
        ioctl(fm_fd, FM_IOCTL_NOTE_ON, note);
        sleep(1);
        ioctl(fm_fd, FM_IOCTL_NOTE_ON, i);
    }

    return(0);
}


/*
 * get and set timing parameters
 */
void set_timing(fd)
int fd;
{
    unsigned char tmp_byte;

    /* get tempo */
    tempo = 0;
    lseek(fd, 0x0C, SEEK_SET);
    read(fd, &tmp_byte, 1);
    tempo = (unsigned int)tmp_byte;
    read(fd, &tmp_byte, 1);
    tempo += (unsigned int)tmp_byte << 8;

    /* get herz of timing clock */
    fm_herz = 0;
    lseek(fd, 0x0C, SEEK_SET);
    read(fd, &tmp_byte, 1);
    tempo = (unsigned int)fm_herz;
    read(fd, &tmp_byte, 1);
    tempo += (unsigned int)fm_herz << 8;
    
    return;
}


/*
 * seek to the midi stream and handle midi events for the song
 */
int play_song(fd)
int fd;
{
    return(0);
}
SHAR_EOF
fi
exit 0
#	End of shell archive



More information about the Alt.sources mailing list