[comp.unix.xenix] uPort loudspeaker driver

mdg@uport.UUCP (10/23/87)

This is a loudspeaker device driver for Microport System V/AT UNIX. The
device has an interface that lets you play simple tunes by writing character
strings to the device. There are several files concatenated, including
documentation on installation. I would appreciate any and all comments,
questions, etc. that you might have, especially mods for the driver or
"song files" you may write. Thanks and enjoy!

Marc de Groot (KG6KF)	| UUCP: {hplabs, sun, ucbvax}!amdcad!uport!mdg
Microport Systems, Inc.	| AMATEUR PACKET RADIO: KG6KF @ N6IYA 
10 Victor Square	| DISCLAIMER: "..full of sound and fury, not 
Scotts Valley, CA 95066	|		necessarily agreeing with anyone.."
408 438 8649 Ext. 31	| MOTTO: "The future is almost over."

------------------------------CUT HERE---------------------------------------
spkr.doc follows
------------------------------CUT HERE---------------------------------------
Directions for installing the loudspeaker driver under Microport System V/AT

	- Make sure you have installed the link kit. If the directory
	  /usr/linkkit doesn't exist, you haven't installed it. Put
	  the link kit disk in the floppy drive and type 'installit' <Enter>.

	- Put the files spkr.c and spkr.h in the directory /usr/linkkit/io .
	  Then type:
	  cd /usr/linkkit/io
	  cc -c -Ml spkr.c
	  ar r ../lib2 spkr.o

	- Edit the file /usr/linkkit/cf/master .
	  Find the following piece of text:

*sxt	0	ocrwi	co	sxt	0	6	32
lp	37,39	ocwis	c	lp	0	7	3
*prf	0	rwi	co	prf	0	10	1
cm0	0	rw	co	cm0	0	8	1

	  Add the following line after the above:

spkr	0	ocrwi	c	sp	0	12	1
	
	- Edit the file /usr/linkkit/cf/dfile.wini .
	  Find the following piece of text:

*sxt	0	8
lp	0	3
*prf	0	1	
cm0	0	1

	  Add the following line after the above:

spkr	0	1

	- The following steps should be performed as super-user.

	- To create the device, type:
	  mknod /dev/spkr c 12 0 
	  chmod 666 /dev/spkr

	- Now, to link the kernel, type:
	  cd /usr/linkkit/cf
	  make wini

	- To copy the new kernel to the root directory, type:
	  cp ../system5 /

	  You may want to back up the old kernel first, by typing:
	  cp /system5 /system.old
	
	- At this point, bring down the system and reboot in the usual
	  way.

If all has gone well, the system will boot and run with the new device
driver incorporated into the UNIX kernel. You may now write to this device
like any other. The loudspeaker may be gotten to play simple musical tunes
by writing ASCII characters in a particular format to the loudspeaker. The
format is described in detail in the file spkr.c . There is a sample "song
file" called bugle; it may be played by typing:
cat bugle > /dev/spkr

Bugle was written by a colleague of mine at Microport, Rex Core.

It's possible that the system will NOT boot and run the new kernel. If you
have the new boot code that lets you specify the kernel name, *and* you
backed up the old kernel as described above, you may type system5.old in
response to the boot prompt, and the old kernel will boot. If you have the old
boot code, which boots /system5 without asking, you have to boot your system
from floppy and copy /system5 on the floppy to /system5 on the hard disk.
Ask your system administrator; it's beyond the scope of this doc. 

I would appreciate any comments on the device driver, especially song
files, additions, enhancements, bug fixes, etc. If the demand is great
enough I'll summarize and post to the news.
------------------------------CUT HERE---------------------------------------
bugle follows
------------------------------CUT HERE---------------------------------------
2qg 9qg
3qc 9qg
3ee 9eg
3ec 9eg
2qg 9qg
2qg 9qg
3qc 9qg
3ee 9eg
3ec 9eg
2qg 9qg
2qg 9qg
3qc 9qg
3ee 9eg
3ec 9eg
2qg 9qg
3qc 9qg
3he 9hg
3hc 9hg
------------------------------CUT HERE---------------------------------------
spkr.h follows
------------------------------CUT HERE---------------------------------------
/*
 * Header file for the speaker I/O driver - Marc de Groot
 */

 /************************** COPYRIGHT NOTICE **************************
 * This device driver is copyright (c) 1987 by Marc de Groot. Further  *
 * distribution of all or part of this device driver must include this *
 * copyright notice. Distribution of compiled object code derived from *
 * this device driver must be accompanied by the source code with this *
 * copyright notice included.                                          *
 ***********************************************************************/

/*
 * Port addresses
 */
#define SP_TIMER 0x40			/* speaker timer port base addr */
#define SP_PORTB 0x61			/* speaker gate port addr */

/*
 * ioctl function codes
 */
#define SP_SET_DIVISOR		(('S' << 8) | 0)
#define SP_TURN_ON		(('S' << 8) | 1)
#define SP_SET_TICKS		(('S' << 8) | 2)
#define SP_SET_FREQUENCY	(('S' << 8) | 3)
#define SP_SET_TEMPO		(('S' << 8) | 4)

/*
 * constants for the state machine
 */
#define SP_OCTAVE	20
#define SP_NOTE_LEN	21
#define SP_NOTENM1	22
#define SP_NOTENM2	23

/*
 * Character macros
 */
#define iswhite(x) ((x) == '\n' || (x) == '\t' || (x) == ' ')
#define toupper(x) ((x) >= 'a' && (x) <= 'z' ? (x) & 0xdf : (x))
#define ctoi(x) ((x) - 0x30)

/*
 * Debugging macro
 */
#define DBG(p) { if (sp_debug) {p} }
------------------------------CUT HERE---------------------------------------
spkr.c follows
------------------------------CUT HERE---------------------------------------
/*
 * Loudspeaker device driver for Microport System V/AT
 */

 /************************** COPYRIGHT NOTICE **************************
 * This device driver is copyright (c) 1987 by Marc de Groot. Further  *
 * distribution of all or part of this device driver must include this *
 * copyright notice. Distribution of compiled object code derived from *
 * this device driver must be accompanied by the source code with this *
 * copyright notice included.                                          *
 ***********************************************************************/

/*
 * Include files
 */
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/dir.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/user.h>
#include <sys/ioctl.h>
#include "spkr.h"

/*
 * Global variables
 */
int spkr_open = 0;			/* spkr_open != 0 means dev is open */
int sp_divisor;				/* Freq divisor. Loaded into timer. */
int sp_note_name;			/* ASCII name of the current note.  */
int sp_ticks;				/* # of ticks that the tone lasts.  */
int sp_tempo;				/* # of ticks in a whole note.	    */
int sp_octave;				/* How many times we shift divisor. */
int sp_symbol_type;			/* Next sym. state mach. will parse */
int sp_debug = 0;			/* sp_debug != 0 means print diags  */
					/* sp_debug may be changed with	    */
					/* /etc/patch			    */

long sp_divisors[] =			/* The chromatic scale.	*/
{
	61856,				/* A				*/
	58384,				/* A#				*/
	58384,				/* Bb				*/
	55107,				/* B				*/
	52014,				/* C				*/
	49095,				/* C#				*/
	49095,				/* Db				*/
	46340,				/* D				*/
	43739,				/* D#				*/
	43739,				/* Eb				*/
	41284,				/* E				*/
	38966,				/* F				*/
	36779,				/* F#				*/
	36779,				/* Gb				*/
	34715,				/* G				*/
	32767,				/* G#				*/
	32767				/* Ab				*/
};

int sp_note_names[] =
{
	'A' << 8,
	'A' << 8 | '#',
	'B' << 8 | 'b',
	'B' << 8 ,
	'C' << 8,
	'C' << 8 | '#',
	'D' << 8 | 'b',
	'D' << 8,
	'D' << 8 | '#',
	'E' << 8 | 'b',
	'E' << 8,
	'F' << 8,
	'F' << 8 | '#',
	'G' << 8 | 'b',
	'G' << 8,
	'G' << 8 | '#',
	'A' << 8 | 'b'
};

/*
 * spopen - Open the speaker device 
 */
spopen(dev, flag)
int dev, flag;
{
	if(spkr_open)			/* If spkr already open		*/
	{
		u.u_error = ENXIO;	/* Report error			*/
		return;
	}
	spkr_open = 1;			/* Others can't use spkr	*/
	sp_tempo = 20;			/* Whole note = 1/3 second	*/
	sp_symbol_type = SP_OCTAVE;	/* Initialize state machine	*/
	sp_octave = 4;			/* Sane value for the octave	*/
	sp_note_name = 'A' << 8;	/* Sane note name		*/
	sp_ticks = 10;			/* Sane # of ticks ( 1/6 sec )	*/
}

/*
 * spclose - close the speaker device
 */
spclose(dev)
int dev;
{
	if(!spkr_open)
	{
		u.u_error = ENXIO;
		return;
	}
	spkr_open = 0;
}

/*
 * spread - speaker read routine
 */
spread(dev)
int dev;
{
}

/*
 * spwrite - speaker write routine
 *
 * Description of character output:
 * The spwrite routine incorporates a simple state machine which
 * interprets a representation of musical notes.  A whitespace
 * character causes a reset of the state machine to its initial
 * state, and plays the note set by the previous non-whitespace
 * character string. That character string is expected to consist
 * of:	odnn
 * where:	o is the octave number
 *		d is the duration of the note; one of
 *			w for whole note
 *			h for half note
 *			q for quarter note
 *			e for eighth note
 *			s for sixteenth note
 *			t for thirty-secondth note
 *			(Upper or lower case)
 *		nn is a one- or two-character string giving the note
 *		name. The first character is one of
 *			A B C D E F G  (Upper or lower case)
 *		The second character, if present, is one of
 *			b #  (To indicate flat or sharp, respectively)
 *
 * Example:
 * The string "3qbb 5wf " would play a B-flat quarter note in the third
 * octave, and then an F-natural whole note in the fifth octave.
 * 
 * Bugs:
 * There is no detection of erroneous note representations. Unexpected
 * characters will produce unpredictable results.
 */
spwrite(dev)
int dev;
{
	char c;
	unsigned int i;
	for(i = u.u_count; i--; u.u_base++)
	{
		copyin(u.u_base, &c, 1);
		state_machine(c);
	}
	u.u_count = 0;
}

/*
 * spioctl - speaker ioctl routine
 *
 * Functions supported:
 *
 * SP_SET_DIVISOR - Set the frequency divisor. The speaker output
 * frequency will be 1193180 / iarg Hertz.
 *
 * SP_TURN_ON - Turn on the loudspeaker for a certain number of
 * ticks of the system clock.
 *
 * SP_SET_FREQUENCY - The frequency divisor is set such that the
 * speaker output will be iarg Hertz the next time it is turned on.
 * This frequency is independent of the processor frequency.
 *
 * SP_SET_TICKS - Set the number of ticks of the system clock. The
 * speaker will be turned off after this number of ticks the next time
 * it is turned on.
 *
 * SP_SET_TEMPO - Set the length of a whole note to iarg ticks of the
 * system clock.
 */

spioctl(dev, cmd, arg)
int dev, cmd;
union ioctl_arg arg;
{
	switch(cmd)
	{
		case SP_SET_DIVISOR:
		{
			sp_divisor = arg.iarg;
			break;
		}			

		case SP_TURN_ON:
		{
			sp_setparms();
			sp_turn_on();
			break;
		}

		case SP_SET_FREQUENCY:
		{
			sp_divisor = 1193180L / (long)arg.iarg;
			break;
		}

		case SP_SET_TICKS:
		{
			sp_ticks = arg.iarg;
			break;
		}

		case SP_SET_TEMPO:
		{
			sp_tempo = arg.iarg;
			break;
		}
	}
}

/*
 * Supporting subroutines follow
 */

/*
 * sp_setparms - Set up the 8253 timer for sound
 */
sp_setparms()
{
	outb(SP_TIMER + 3, 0xb6);	/* Timer 2, lsb, msb, binary */
	outb(SP_TIMER + 2, sp_divisor & 0xff);	/* Output lo byte    */
	outb(SP_TIMER + 2, sp_divisor >> 8 & 0xff); /* Output hi byte    */
}

/*
 * sp_turn_on() - Turn on the loudspeaker gate, delay for a number of
 * system ticks, and turn the loudspeaker back off.
 */
sp_turn_on()
{
	outb(SP_PORTB, inb(SP_PORTB) | 3);	/* Gate timer 2, turn sp on */
	delay(sp_ticks);			/* Wait sp_ticks/HZ seconds */
	outb(SP_PORTB, inb(SP_PORTB) & ~3);	/* Gate timer 2, turn sp off*/
}

/*
 * state_machine - The note-playing state machine
 */
state_machine(c)
char c;
{
	DBG(printf("state_machine(0x%x) ", c);)
	if(iswhite(c))			/* Whitespace inits state machine */
	{
		if(sp_symbol_type != SP_OCTAVE)
		{
			sp_cnvt_note();	/* Cnvrt sp_note_name to sp_divisor */
			sp_setparms();	/* Load sp_divisor into timer hdwe. */
			sp_turn_on();   /* Load timer and turn on speaker */
		}
		sp_symbol_type = SP_OCTAVE; /* Get set up for next note */
		DBG(printf("EXIT1 ");)
		return;
	}
	DBG(printf("sp_symbol_type=%d ", sp_symbol_type);)
	switch(sp_symbol_type)
	{
		case SP_OCTAVE:
			sp_octave = ctoi(c);
			sp_symbol_type = SP_NOTE_LEN;
			DBG(printf("sp_octave=%d ",sp_octave);)
			break;

		case SP_NOTE_LEN:
			sp_symbol_type = SP_NOTENM1;
			switch(c)
			{
				case 'W':
				case 'w':
					sp_ticks = sp_tempo;
					break;
				case 'H':
				case 'h':
					sp_ticks = sp_tempo / 2;
					break;
				case 'Q':
				case 'q':
					sp_ticks = sp_tempo / 4;
					break;
				case 'E':
				case 'e':
					sp_ticks = sp_tempo / 8;
					break;
				case 'S':
				case 's':
					sp_ticks = sp_tempo / 16;
					break;
				case 'T':
				case 't':
					sp_ticks = sp_tempo / 32;
					break;
			}
			DBG(printf("sp_ticks=%d ", sp_ticks);)
			break;
		case SP_NOTENM1:
			sp_note_name = toupper(c) << 8;	
			sp_symbol_type = SP_NOTENM2;
			DBG(printf("1st:sp_note_name=0x%x ",sp_note_name);)
			break;
		case SP_NOTENM2:
			if(c == '#' || c == 'b')
			{
				sp_note_name |= c;
			}
			DBG(printf("2nd:sp_note_name=%d ",sp_note_name);)
			break;
	}
	DBG(printf("EXIT2 ");)
}

/*
 * sp_cnvt_note - Given a note designator, convert to a frequency
 *		  divisor and a number of ticks.
 */
sp_cnvt_note()
{
	int i;
	DBG(printf("sp_cnvt_note() ");)
	for(i = 0; i < 17; i++)
	{
		if(sp_note_names[i] == sp_note_name)
		{
			sp_divisor = sp_divisors[i] >> sp_octave;
			DBG(printf("sp_divisor=%d, EXIT1 ", sp_divisor);)
			return;
		}
	}
	DBG(printf("EXIT2 ");)
}