
/*	spkrmidi.c
 *	Version 1.2
 *
 * Program to play MIDI or Mup files on computer's built-in speaker
 * under UNIX SV_R4 for x86 or Linux.
 * Since the simple ioctls for making tones on the speaker only allow
 * playing one note at a time,
 * it just plays one at a time. If more than one note occurs in a track at
 * the same time, it just plays the first one. 
 * On some systems at least, it must be run directly on the console to work--
 * it won't play anything if executed from an X window.
 * So this is obviously rather limited, but can still be useful for
 * listening to simple files for errors if you don't have a sound card.
 *
 * If the given file appears to be a MIDI file, it is played directly,
 * otherwise it is assumed to be a Mup input file and Mup is run on it to
 * produce a MIDI file. If that succeeds, the resulting MIDI file is played.
 * Obviously, Mup has to be in your PATH for this to work.
 */

/* arguments:
 *	[-b notebreak] [-t tempo] [-T track] file
 *
 * notebreak is how many milliseconds to shorten notes so they don't
 * run together. Default is 20.
 *
 * tempo is a factor by which to multiply the play speed.
 * 1.0 will play at the speed stated in the MIDI file,
 * 0.9 will make each note 0.9 times as long,
 * 1.5 will make each note 50% longer, etc.
 *
 * If a track is specified, only that track will be played, otherwise
 * each track will be played, one at a time. Tracks will be numbered
 * by the staff/voice. I.e., track 1 will be staff 1, voice 1. If there
 * are 2 voices on staff 1, track 2 will be staff 1 voice 2. Otherwise
 * track 2 will be staff 2 voice 1. Etc.
 *
 * Not everything in the MIDI standard is implemented in this program,
 * so arbitrary MIDI files may not work, but anything
 * produced by the MIDI option of Mup (other than arbitrary hex data)
 * should be recognized.
 *
 */

/* this program was derived from a more full-featured one for use with
 * the SoundBlaster Pro sound card, so it may contain a few artifacts from
 * that program. */


#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#ifdef linux
#include <sys/time.h>
#else
#include <stropts.h>
#include <poll.h>
#endif
#include <sys/wait.h>
#include <unistd.h>
#include <values.h>
#include <sys/kd.h>
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <string.h>
#include <stdlib.h>


#ifdef __STDC__
#define P(x)	x
#else
#define P(x)	()
#endif

#define BSIZE 32	/* buffer size */


/* macro to distinguish real-time MIDI commands */
#define IS_REAL_TIME(b)     ( (b & 0xff) >= 0xf8 )

/* how many milliseconds to leave between notes */
#define DFLT_NOTEBREAK	(20)


/* structure to keep track of tempo changes */
struct TEMPO {
	double	delta_adjust;	/* how to adjust clocks to milliseconds */
	long	when;		/* at what MIDI clock this is to take effect */
	struct TEMPO *next;	/* linked list */
};

struct TEMPO *Tempoinfolist_p;	/* list of tempo changes */
struct TEMPO **Tempo_insert_p_p = &Tempoinfolist_p;	/* where to insert into
				 * list of tempo changes */
struct TEMPO *Next_tempo_chg_p;	/* info about next change in tempo */
double Delta_adjust;		/* to translate MIDI clocks to milliseconds */

int Format;			/* MIDI file format */
int Ntracks;			/* number of tracks */
int Division;			/* MIDI division value */
unsigned char Buff[BSIZE];	/* input buffer */
long Length;			/* Length of header, track, etc */
int Curr_status = 0;		/* Current MIDI running status */
long Delta;			/* MIDI delta time of event */
double Tempo_factor = 1.0;	/* multiply times by this to adjust tempo */
int Notebreak = DFLT_NOTEBREAK;	/* time break between notes */
int Curr_note = -1;		/* midi value of current note. -1 == none */
int Timeval;			/* length of Curr_note */
int Now;			/* current time in MIDI clocks */

int get_midi_file P((int file));
void match P((unsigned char *str, char *expected_str));
void readheader P((int fd));
long getlong P((int fd));
int getshort P((int fd));
long getvarlen P((int fd, long *leng_p));
void Read P((int fd, unsigned char *b, int n));
int getm P((int fd, long *leng_p));
void err P((char *errmsg, ...));
void skiptrack P((int fd));
void do_event P((int fd, long *leng_p, int *status_p));
void system_message P((int fd, int b, long *leng_p));
void skip P((int fd, int leng, long *leng_p));
void databyte P((int fd, int b, long *leng_p, int *status_p));
void playnote P((int noteval));
void offnote P((int noteval));
int do_wait P((int millisecs));
void usage P((char *pname));
void xlatenote P((int noteval, int *pitch_p, int *sharp_p, int *octave_p));
void save_tempo_chg P((double delta_adj));
void delta_reset P((void));
double delta_adjust P((void));
int playtone P((int pitch, int accidental, int octave, int timeval));

double atof();
long strtol();
extern int optind;
extern char *optarg;
long lseek();


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

{
	int fd;			/* file descriptor */
	int a;			/* argument */
	int trk;		/* current track */
	int track2play = -1;	/* which track to play. If -1, play all */
#ifdef linux
	char devtty[12];	/* for /dev/ttyN */
	int devtty_length;
	char *tty_name;
#endif


	/* process arguments */
	while ((a = getopt(argc, argv, "b:t:T:")) != EOF) {
		switch(a) {
		case 'b':
			Notebreak = atoi(optarg);
			if (Notebreak < 0) {
				Notebreak = 0;
			}
			break;
		case 't':
			Tempo_factor = atof(optarg);
			if (Tempo_factor < 0.1 || Tempo_factor > 20) {
				fprintf(stderr, "tempo factor out of range\n");
				exit(1);
			}
			break;
		case 'T':
			track2play = atoi(optarg);
			break;
		default:
			usage(argv[0]);
		}
	}

	if (argc != optind + 1) {
		usage(argv[0]);
	}

	/* open MIDI file */
	if ((fd = open(argv[optind], O_RDONLY)) < 0) {
		fprintf(stderr, "can't open %s\n", argv[optind]);
		exit(1);
	}

#ifdef linux
	/* Ted Merrill reported that under Linux,
	/* if ttyname doesn't return "/dev/tty?" then the
	 * ioctl to play tones won't work.
	 * He suggests fixing this by trying to open
	 * /dev/tty? ports hoping to find one on which we have permission.
	 * If one is found, use that as stdout. He notes that it may
	 * require root permission to actually open these ports without
	 * logging in... depending on system configuration.
	 * The code below is an adaptation of the fix he suggested.
	 */
	(void) sprintf(devtty, "/dev/tty");
	devtty_length = strlen(devtty);
	tty_name = ttyname(1);
	/* If ttyname returned null or something other than /dev/tty* then
	 * we'll try the workaround... */
	if (tty_name == 0 || strncmp(tty_name, devtty, devtty_length) != 0) {
		int n;			/* to loop through various ttys */
		int ttyfd;		/* file descriptor */

		for (n = 1; n < 10; n++) {
			(void) sprintf(devtty + devtty_length, "%d", n);
			if ((ttyfd = open(devtty, O_WRONLY, 0)) >= 0) {
				dup2(ttyfd, 1);
				close(ttyfd);
				break;
			}
		}
	}
#endif

	/* check if MIDI or Mup file. If Mup, generate MIDI from it first */
	fd = get_midi_file(fd);

	/* read MIDI header */
	readheader(fd);

	/* first track just gives time/key/tempo */
	playtrack(fd);

	/* read each track of MIDI data */
	for (trk = 1 ; trk < Ntracks; trk++) {
		if (track2play == trk || track2play == -1) {
			fprintf(stderr, "playing track %d...\n", trk);
			playtrack(fd);
		}
		else {
			skiptrack(fd);
		}
	}
}


/* print usage message and exit */

void
usage(pname)
char *pname;	/* program name */

{
	fprintf(stderr, "usage: %s [-b notebreak] [-t tempo] [-T track] file.mid\n", pname);
	exit(1);
}


/* Given an open file descriptor, determine if it is a MIDI file. If
 * it appears to be a MIDI file, just return the file descriptor as is.
 * If not, assume it's a Mup file and run "mup -m" on it, and return
 * the file descriptor of the resulting MIDI file, if one is successfully
 * generated.
 */

int
get_midi_file(int file)
{
	unsigned char fbuff[BUFSIZ];	/* buffer for copying from file */
	int n;				/* how many bytes read from file */
	int mup[2];			/* pipe to Mup */
	int retval;			/* exit status of Mup */
	char midifile[L_tmpnam];	/* MIDI file that Mup will generate */


	/* read first 4 bytes of file, then rewind it */
	Read (file, fbuff, 4);
	lseek(file, 0L, SEEK_SET);

	if (strncmp(fbuff, "MThd", 4) == 0) {
		/* It's a MIDI file; we can use it directly */
		return(file);
	}
	
	/* Assume it's a Mup file. Get a temp file name to put the
	 * MIDI into */
	tmpnam(midifile);

	/* Open a pipe between us and the Mup process we will spawn */
	if (pipe(mup) < 0) {
		perror("couldn't open pipe to Mup");
		exit(1);
	}

	/* Run Mup on the file */
	switch (fork()) {

	case 0:
		/* Set up plumbing to Mup and execute it */
		close(0);
		dup(mup[0]);
		close(mup[1]);
		execlp("mup", "mup", "-m", midifile, (char *) 0);
		/*FALLTHRU*/

	case -1:
		fprintf(stderr, "failed to exec mup\n");
		exit(1);

	default:
		/* parent only needs "write" end of the pipe */
		close(mup[0]);

		/* copy our input into Mup's stdin */
		while ((n = read(file, fbuff, sizeof(fbuff))) > 0) {
			write(mup[1], fbuff, n);
		}
		close(mup[1]);

		/* wait for Mup to get done and see whether it was successful */
		if ( (wait(&retval) < 0) || ( ! WIFEXITED(retval) )
					 || (WEXITSTATUS(retval) != 0) ) {
			/* Mup wasn't happy with the input */
			unlink(midifile);
			exit(WEXITSTATUS(retval));
		}
	}

	/* close our original input */
	close(file);

	/* open the temp file with the MIDI in it */
	if ((file = open(midifile, O_RDONLY, 0)) < 0) {
		fprintf(stderr, "Could not open temp MIDI file that Mup generated\n");
		/* will probably fail, but try to clean up... */
		unlink(midifile);
		exit(1);
	}

	/* remove the temp file so it will disappear as soon as this
	 * program gets done with it. */
	unlink(midifile);

	/* now return the MIDI file descriptor */
	return(file);
}



/* read info from MIDI file header */

void
readheader(fd)
int fd; 	/* MIDI file */

{
	Read(fd, Buff, 4);
	match(Buff, "MThd");
	Length = getlong(fd);
	if (Length != 6) {
		err("header length wrong");
	}

	Format = getshort(fd);
	Ntracks = getshort(fd);
	Division = getshort(fd);
}

/* if given str matches expected string, return. Otherwise, print error and
 * clean up */

void
match(str, expected_str)
unsigned char *str;
char *expected_str;

{
	if (strcmp((char *) str, expected_str) != 0) {
		err("didn't find expected string '%s'", expected_str);
	}
}


/* return a long read from fd, regardless of machine byte ordering */

long
getlong(fd)
int fd;		/* MIDI file */

{
	Read(fd, Buff, 4);
	return(  ((Buff[0] & 0xff) << 24)
		| ((Buff[1] & 0xff) << 16)
		| ((Buff[2] & 0xff) << 8)
		| (Buff[3] & 0xff)   );
}


/* return a short read from fd */

int
getshort(fd)
int fd;		/* MIDI file */

{
	Read(fd, Buff, 2);
	return(  ((Buff[0] & 0xff) << 8)
		| (Buff[1] & 0xff)   );
}



/* print error message (if non-null), clean up, and exit */

#ifdef __STDC__

void
err(char *errmsg, ...)

#else

void
err(errmsg, va_alist)
char *errmsg;
va_dcl

#endif

{
	va_list args;

	if (errmsg != (char *) 0) {
#ifdef __STDC__
		va_start(args, errmsg);
#else
		va_start(args);
#endif
		vfprintf( stderr, errmsg, args);
		fprintf(stderr, "\n");
		va_end(args);
	}
	exit(-1);
}


/* like read(), but clean up and exit on failure */

void
Read(fd, b, len)
int fd;			/* MIDI file */
unsigned char *b;	/* buffer to read into */
int len;		/* how many bytes to read */

{
	if (read(fd, b, len) != len) {
		err(errno == EINTR ? (char *) 0 : "read failed");
	}
}

/* read a character and decrement remaining length */

int
getm(fd, leng_p)
int fd;		/* MIDI file */
long *leng_p;	/* remaining length of track, to be updated  */

{
	Read(fd, Buff, 1);
	(*leng_p)--;
	return (Buff[0] & 0xff);
}

/* read a variable length number. High order bit of all but last byte set
 * to 1, use remaining 7 bits as 7 bits of the result. Adjust *leng_p to
 * be number of bytes remaining in track. */

long
getvarlen(fd, leng_p)
int fd;		/* MIDI file */
long *leng_p;	/* remaining bytes in track, to be updated */

{
	long n = 0;
	int b;

	do {
		n <<= 7;
		n |= (b = getm(fd, leng_p)) & 0x7f;
	} while ((b & 0x80) == 0x80);
	return(n);
}


/* play one track. Read the header, then do each MIDI event in the track */

playtrack(fd)
int fd;		/* MIDI file */

{
	Read(fd, Buff, 4);
	match(Buff, "MTrk");
	Length = getlong(fd);


	/* initialize time values */
	Now = 0;
	delta_reset();
	Timeval = 0;

	/* process all event in track */
	while (Length > 0) {
		/* figure out when next event happens */
		Delta = getvarlen(fd, &Length);
		Now += Delta;
		Delta = (int) (Delta * Tempo_factor * delta_adjust());
		Timeval += Delta;

		/* handle the event */
		do_event(fd, &Length, &Curr_status);
	}
}

/* if a track is to be skipped, just jump ahead in the file to the next track */

void
skiptrack(fd)
int fd;		/* MIDI file */

{
	Read(fd, Buff, 4);
	match(Buff, "MTrk");
	Length = getlong(fd);
	lseek(fd, Length, SEEK_CUR);
}


/* do a MIDI event. Update the remaining length of the track and the running
 * status */

void
do_event(fd, leng_p, status_p)
int fd;			/* MIDI file */
long *leng_p;		/* remaining length of track */
int *status_p;		/* running status */

{
	int b;


	b = getm(fd, leng_p);
	if ( (b & 0x80) == 0x80) {
		if (statusbyte(fd, b, leng_p, status_p) == 0) {
			/* completely handled--no data bytes */
			return;
		}
		else {
			/* read the first data byte */
			b = getm(fd, leng_p);
		}
	}
	databyte(fd, b, leng_p, status_p);
}


/* handle a status byte. If has data bytes, return 1, else return 0 */

int
statusbyte(fd, b, leng_p, status_p)
int fd;		/* MIDI file */
int b;		/* status byte */
long *leng_p;	/* remaining length of track, to be updated */
int *status_p;	/* running status, to be updated */

{
	switch (b & 0xf0) {
	case 0x80:
	case 0x90:
	case 0xa0:
	case 0xb0:
	case 0xc0:
	case 0xd0:
	case 0xe0:
		*status_p = b;
		return(1);
	case 0xf0:
		system_message(fd, b, leng_p);
		return(0);
	default:
		err("bug detected while in statusbyte()");
		break;
	}
}


/* handle a system message. Just skip over the proper number of bytes */

void
system_message(fd, b, leng_p)
int fd;		/* MIDI file */
int b;		/* data byte */
long *leng_p;	/* remaining length of track, to be updated */

{
	long leng;
	int eventtype;


	switch (b) {
	case 0xf0:
		/* sysex message. read as many bytes as specified */
		leng = getvarlen(fd, leng_p);
		skip(fd, leng, leng_p);
		break;

	case 0xf2:
		/* song position, two 7-bit data bytes */
		getm(fd, leng_p);
		getm(fd, leng_p);
		break;

	case 0xf3:
		/* song select, one 7-bit data byte */
		getm(fd, leng_p);
		break;

	case 0xf1:
	case 0xf4:
	case 0xf5:
	case 0xf9:
	case 0xfd:
		err("undefined system message 0x%x", b & 0xff);
		break;

	case 0xf6:
		/* tune request. no data */
		break;

	case 0xf7:
		/* end of system exclusive */
		break;

	case 0xf8:
	case 0xfa:
	case 0xfb:
	case 0xfc:
	case 0xfe:
		/* real time stuff, ignore */
		break;

	case 0xff:
		/* meta event */
		eventtype = getm(fd, leng_p) & 0xff;
		leng = getvarlen(fd, leng_p);
		if (eventtype == 0x51) {
			long usec_per_quarter;

			/* tempo change */
			if (leng != 3) {
				err("tempo change in wrong format");
			}
			usec_per_quarter = (getm(fd, leng_p) & 0xff) << 16;
			usec_per_quarter |= (getm(fd, leng_p) & 0xff) << 8;
			usec_per_quarter |= (getm(fd, leng_p) & 0xff);
			Delta_adjust = ((double) usec_per_quarter /
					(double) (1000 * Division));
			save_tempo_chg(Delta_adjust);
		}
		else if (eventtype == 0x59) {
			/* key signature. Read here rather than skip so if
			 * there is negative number of sharps, it isn't
			 * interpreted as real time message */
			for (  ; leng > 0; leng--) {
				getm(fd, leng_p);
			}
		}
		else if (eventtype == 0x0) {
			/* sequence number. Not variable length. Could have
			 * high bit set in argument */
			for (  ; leng > 0; leng--) {
				getm(fd, leng_p);
			}
		}
		else {
			/* skip everything else for now */
			skip(fd, leng, leng_p);
		}
		break;

	default:
		err("bug detected while in system_message()");
		break;
	}
}


/* handle data bytes. One byte has already been read and is passed in as b.
 * If current status requires reading more than one, read them here. In any
 * case, do whatever is implied by the data */

void
databyte(fd, b, leng_p, status_p)
int fd;		/* MIDI file */
int b;		/* data byte */
long *leng_p;	/* remaining length of track, to be updated */
int *status_p;	/* running status */

{
	int velocity;


	switch (*status_p & 0xf0) {
	case 0x80:
		/* note off */
		velocity = getm(fd, leng_p);
		offnote(b);
		break;
	case 0x90:
		/* note on */
		velocity = getm(fd, leng_p);
		if (velocity != 0) {
			playnote(b);
		}
		else {
			offnote(b);
		}
		break;
	case 0xa0:
		/* key pressure */
		getm(fd, leng_p);
		break;
	case 0xb0:
		/* parameter */
		getm(fd, leng_p);
		break;
	case 0xc0:
		/* program */
		break;
	case 0xd0:
		/* channel pressure */
		break;
	case 0xe0:
		/* pitch wheel */
		getm(fd, leng_p);
		break;
	default:
		err("bad status of 0x%x", *status_p);
		break;
	}
}

/* skip specified number of bytes (leng) and decrement variable pointed to
 * by leng_p by that amount */

void
skip(fd, leng, leng_p)
int fd;		/* MIDI file */
int leng;	/* how much to skip */
long *leng_p;	/* remaining length of track, to be updated */

{
	int b;

	for (  ; leng > 0; leng--) {
		b = getm(fd, leng_p);
		/* real time messages don't count */
		if ( IS_REAL_TIME(b) ) {
			leng++;
		}
	}
}


/* play a note. Actually just save info to be able to play it */

void
playnote(noteval)
int noteval;		/* which MIDI note to play */

{
	/* don't actually play now, just save till we find the end to know how
	 * long to make it */
	if (Curr_note == -1) {
		Curr_note = noteval;

		/* if any accumulated time, must be for a rest */
		do_wait(Timeval);

		Timeval = 0;
	}
}


/* play a note then turn it off */

void
offnote(noteval)
int noteval;		/* which MIDI note */

{
	int pitch, sharp, octave;
	int note_on_time;	/* how long to leave note turned on */


	xlatenote(noteval, &pitch, &sharp, &octave);

	/* shorten note slightly so notes don't run together. Shorten by
	 * Notebreak, unless than would make it too short, in which case,
	 * shorten by half its length */
	if (Timeval < 2 * Notebreak) {
		note_on_time = Timeval / 2;
	}
	else {
		note_on_time = Timeval - Notebreak;
	}

	playtone(pitch, sharp ? '#' : ' ', octave, note_on_time);
	do_wait(Timeval);

	Timeval = 0;
	Curr_note = -1;
}

/* given a MIDI note number, return (via pointers) its pitch letter, a 0 or
 * 1 depending on whether it has a sharp or not, and the octave. This function
 * will alway use # for black notes. */

void
xlatenote(noteval, pitch_p, sharp_p, octave_p)
int noteval;		/* which MIDI note */
int *pitch_p;		/* pitch returned here */
int *sharp_p;		/* return 1 if sharp, 0 if not */
int *octave_p;		/* return octave number */

{
	*octave_p = (noteval / 12) - 1;
	switch (noteval % 12) {
	case 0:
		*pitch_p = 'c';
		*sharp_p = 0;
		break;
	case 1:
		*pitch_p = 'c';
		*sharp_p = 1;
		break;
	case 2:
		*pitch_p = 'd';
		*sharp_p = 0;
		break;
	case 3:
		*pitch_p = 'd';
		*sharp_p = 1;
		break;
	case 4:
		*pitch_p = 'e';
		*sharp_p = 0;
		break;
	case 5:
		*pitch_p = 'f';
		*sharp_p = 0;
		break;
	case 6:
		*pitch_p = 'f';
		*sharp_p = 1;
		break;
	case 7:
		*pitch_p = 'g';
		*sharp_p = 0;
		break;
	case 8:
		*pitch_p = 'g';
		*sharp_p = 1;
		break;
	case 9:
		*pitch_p = 'a';
		*sharp_p = 0;
		break;
	case 10:
		*pitch_p = 'a';
		*sharp_p = 1;
		break;
	case 11:
		*pitch_p = 'b';
		*sharp_p = 0;
		break;
	}
}


/* pause for given number of milliseconds (will be rounded up to next multiple
 * of 10), or until there is something to read from stdin. Returns 1 if
 * something to read from stdin, 0 if timer expired, or -1 if something
 * went wrong. */

int
do_wait(millisecs)
int millisecs;

{
#ifdef linux
	struct timeval tv;

	if (millisecs > 0) {
		tv.tv_sec = millisecs / 1000;
		tv.tv_usec = (millisecs * 1000) % 1000000;	
		return(select(0, 0, 0, 0, &tv));
	}
	else {
		return(0);
	}
#else
	struct pollfd pollinfo;

	if (millisecs > 0) {
		pollinfo.fd = 0;
		pollinfo.events = POLLIN;
		return(poll( &pollinfo, 1, millisecs));
	}
	else {
		return(0);
	}
#endif
}


/* save information about tempo change */

void
save_tempo_chg(delta_adj)
double delta_adj;

{
	struct TEMPO *new_p;


	/* malloc save to save tempo information */
	if ((new_p = (struct TEMPO *) malloc(sizeof(struct TEMPO))) ==
					(struct TEMPO *) 0) {
		err("memory allocation failed");
	}

	/* fill in information and link onto linked list */
	new_p->delta_adjust = delta_adj;
	new_p->when = Now;
	*Tempo_insert_p_p = new_p;
	new_p->next = (struct TEMPO *) 0;
	Tempo_insert_p_p = &(new_p->next);
}


/* reset tempo delta information for next track */

void
delta_reset()

{
	Delta_adjust = (500000 / (1000.0 * (double) Division));
	Next_tempo_chg_p = Tempoinfolist_p;
}

	
/* set the current Delta_adjust value and return it */

double
delta_adjust()

{
	/* do any tempo changes that have happened */
	while (Next_tempo_chg_p != (struct TEMPO *) 0 &&
			Now > Next_tempo_chg_p->when) {
		Delta_adjust = Next_tempo_chg_p->delta_adjust;
		Next_tempo_chg_p = Next_tempo_chg_p->next;
	}
	return(Delta_adjust);
}


/* sound a tone. Return 1 on success, 0 on parameter error */

int
playtone(pitch, accidental, octave, timeval)
int pitch;		/* which pitch to sound, 'a', through 'g' */
int accidental;		/* if '#', raise 1/2 step. If '&', lower 1/2 step.
			 * If anything else, ignore. */
int octave;		/* which octave to use, 1-9 */
int timeval;		/* how long to sound note, in milliseconds */

{
	double val;	/* what to send to tone generator */
	int factor;	/* offset from 'c' */


	/* figure out what number to send to sound generator */
	switch(pitch) {
	case 'c':
		factor = 0;
		break;
	case 'd':
		factor = 2;
		break;
	case 'e':
		factor = 4;
		break;
	case 'f':
		factor = 5;
		break;
	case 'g':
		factor = 7;
		break;
	case 'a':
		factor = 9;
		break;
	case 'b':
		factor = 11;
		break;
	default:
		return(0);
	}

	switch (accidental) {
	case '#':
		factor++;
		break;
	case '&':
		factor--;
		break;
	}

	/* start with value for middle C, which happens to be magic cookie of
	 * 4550. Find proper value by multiplying by the
 	 * appropriate power of the reciprocal of the 12th root of 2. */
	val = 4550.0;
	for (   ; factor > 0; factor --) {
		val *= 0.943874313;
	}

	/* adjust for octave */
	if (octave < 1 || octave > 9) {
		return(0);
	}
	for (  ; octave > 4; octave--) {
		val /= 2.0;
	}
	for (  ; octave < 4; octave++) {
		val *= 2.0;
	}

	/* play the tone */
	if (ioctl(1, KDMKTONE, (timeval << 16) + (int) val) < 0) {
		fprintf(stderr, "unable to play tone\n");
		exit(1);
	}
	return(1);
}
