spkr.c revision 50254
1/*
2 * spkr.c -- device driver for console speaker
3 *
4 * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993
5 * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su>
6 *
7 *    $Id: spkr.c,v 1.39 1999/08/23 20:35:17 bde Exp $
8 */
9
10#include "speaker.h"
11
12#if NSPEAKER > 0
13
14#include <sys/param.h>
15#include <sys/systm.h>
16#include <sys/kernel.h>
17#include <sys/buf.h>
18#include <sys/uio.h>
19#include <sys/conf.h>
20#include <i386/isa/isa.h>
21#include <i386/isa/timerreg.h>
22#include <machine/clock.h>
23#include <machine/speaker.h>
24
25static	d_open_t	spkropen;
26static	d_close_t	spkrclose;
27static	d_write_t	spkrwrite;
28static	d_ioctl_t	spkrioctl;
29
30#define CDEV_MAJOR 26
31static struct cdevsw spkr_cdevsw = {
32	/* open */	spkropen,
33	/* close */	spkrclose,
34	/* read */	noread,
35	/* write */	spkrwrite,
36	/* ioctl */	spkrioctl,
37	/* stop */	nostop,
38	/* reset */	noreset,
39	/* devtotty */	nodevtotty,
40	/* poll */	nopoll,
41	/* mmap */	nommap,
42	/* strategy */	nostrategy,
43	/* name */	"spkr",
44	/* parms */	noparms,
45	/* maj */	CDEV_MAJOR,
46	/* dump */	nodump,
47	/* psize */	nopsize,
48	/* flags */	0,
49	/* maxio */	0,
50	/* bmaj */	-1
51};
52
53/**************** MACHINE DEPENDENT PART STARTS HERE *************************
54 *
55 * This section defines a function tone() which causes a tone of given
56 * frequency and duration from the ISA console speaker.
57 * Another function endtone() is defined to force sound off, and there is
58 * also a rest() entry point to do pauses.
59 *
60 * Audible sound is generated using the Programmable Interval Timer (PIT) and
61 * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The
62 * PPI controls whether sound is passed through at all; the PIT's channel 2 is
63 * used to generate clicks (a square wave) of whatever frequency is desired.
64 */
65
66/*
67 * PPI control values.
68 * XXX should be in a header and used in clock.c.
69 */
70#define PPI_SPKR	0x03	/* turn these PPI bits on to pass sound */
71
72#define SPKRPRI PSOCK
73static char endtone, endrest;
74
75static void tone __P((unsigned int thz, unsigned int ticks));
76static void rest __P((int ticks));
77static void playinit __P((void));
78static void playtone __P((int pitch, int value, int sustain));
79static int abs __P((int n));
80static void playstring __P((char *cp, size_t slen));
81
82/* emit tone of frequency thz for given number of ticks */
83static void
84tone(thz, ticks)
85	unsigned int thz, ticks;
86{
87    unsigned int divisor;
88    int sps;
89
90    if (thz <= 0)
91	return;
92
93    divisor = timer_freq / thz;
94
95#ifdef DEBUG
96    (void) printf("tone: thz=%d ticks=%d\n", thz, ticks);
97#endif /* DEBUG */
98
99    /* set timer to generate clicks at given frequency in Hertz */
100    sps = splclock();
101
102    if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) {
103	/* enter list of waiting procs ??? */
104	splx(sps);
105	return;
106    }
107    splx(sps);
108    disable_intr();
109    outb(TIMER_CNTR2, (divisor & 0xff));	/* send lo byte */
110    outb(TIMER_CNTR2, (divisor >> 8));	/* send hi byte */
111    enable_intr();
112
113    /* turn the speaker on */
114    outb(IO_PPI, inb(IO_PPI) | PPI_SPKR);
115
116    /*
117     * Set timeout to endtone function, then give up the timeslice.
118     * This is so other processes can execute while the tone is being
119     * emitted.
120     */
121    if (ticks > 0)
122	tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks);
123    outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR);
124    sps = splclock();
125    release_timer2();
126    splx(sps);
127}
128
129/* rest for given number of ticks */
130static void
131rest(ticks)
132	int	ticks;
133{
134    /*
135     * Set timeout to endrest function, then give up the timeslice.
136     * This is so other processes can execute while the rest is being
137     * waited out.
138     */
139#ifdef DEBUG
140    (void) printf("rest: %d\n", ticks);
141#endif /* DEBUG */
142    if (ticks > 0)
143	tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks);
144}
145
146/**************** PLAY STRING INTERPRETER BEGINS HERE **********************
147 *
148 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
149 * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave-
150 * tracking facility are added.
151 * Requires tone(), rest(), and endtone(). String play is not interruptible
152 * except possibly at physical block boundaries.
153 */
154
155typedef int	bool;
156#define TRUE	1
157#define FALSE	0
158
159#define toupper(c)	((c) - ' ' * (((c) >= 'a') && ((c) <= 'z')))
160#define isdigit(c)	(((c) >= '0') && ((c) <= '9'))
161#define dtoi(c)		((c) - '0')
162
163static int octave;	/* currently selected octave */
164static int whole;	/* whole-note time at current tempo, in ticks */
165static int value;	/* whole divisor for note time, quarter note = 1 */
166static int fill;	/* controls spacing of notes */
167static bool octtrack;	/* octave-tracking on? */
168static bool octprefix;	/* override current octave-tracking state? */
169
170/*
171 * Magic number avoidance...
172 */
173#define SECS_PER_MIN	60	/* seconds per minute */
174#define WHOLE_NOTE	4	/* quarter notes per whole note */
175#define MIN_VALUE	64	/* the most we can divide a note by */
176#define DFLT_VALUE	4	/* default value (quarter-note) */
177#define FILLTIME	8	/* for articulation, break note in parts */
178#define STACCATO	6	/* 6/8 = 3/4 of note is filled */
179#define NORMAL		7	/* 7/8ths of note interval is filled */
180#define LEGATO		8	/* all of note interval is filled */
181#define DFLT_OCTAVE	4	/* default octave */
182#define MIN_TEMPO	32	/* minimum tempo */
183#define DFLT_TEMPO	120	/* default tempo */
184#define MAX_TEMPO	255	/* max tempo */
185#define NUM_MULT	3	/* numerator of dot multiplier */
186#define DENOM_MULT	2	/* denominator of dot multiplier */
187
188/* letter to half-tone:  A   B  C  D  E  F  G */
189static int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
190
191/*
192 * This is the American Standard A440 Equal-Tempered scale with frequencies
193 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
194 * our octave 0 is standard octave 2.
195 */
196#define OCTAVE_NOTES	12	/* semitones per octave */
197static int pitchtab[] =
198{
199/*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
200/* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
201/* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
202/* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
203/* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
204/* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
205/* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
206/* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
207};
208
209static void
210playinit()
211{
212    octave = DFLT_OCTAVE;
213    whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
214    fill = NORMAL;
215    value = DFLT_VALUE;
216    octtrack = FALSE;
217    octprefix = TRUE;	/* act as though there was an initial O(n) */
218}
219
220/* play tone of proper duration for current rhythm signature */
221static void
222playtone(pitch, value, sustain)
223	int	pitch, value, sustain;
224{
225    register int	sound, silence, snum = 1, sdenom = 1;
226
227    /* this weirdness avoids floating-point arithmetic */
228    for (; sustain; sustain--)
229    {
230	/* See the BUGS section in the man page for discussion */
231	snum *= NUM_MULT;
232	sdenom *= DENOM_MULT;
233    }
234
235    if (value == 0 || sdenom == 0)
236	return;
237
238    if (pitch == -1)
239	rest(whole * snum / (value * sdenom));
240    else
241    {
242	sound = (whole * snum) / (value * sdenom)
243		- (whole * (FILLTIME - fill)) / (value * FILLTIME);
244	silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom);
245
246#ifdef DEBUG
247	(void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
248			pitch, sound, silence);
249#endif /* DEBUG */
250
251	tone(pitchtab[pitch], sound);
252	if (fill != LEGATO)
253	    rest(silence);
254    }
255}
256
257static int
258abs(n)
259	int n;
260{
261    if (n < 0)
262	return(-n);
263    else
264	return(n);
265}
266
267/* interpret and play an item from a notation string */
268static void
269playstring(cp, slen)
270	char	*cp;
271	size_t	slen;
272{
273    int		pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
274
275#define GETNUM(cp, v)	for(v=0; isdigit(cp[1]) && slen > 0; ) \
276				{v = v * 10 + (*++cp - '0'); slen--;}
277    for (; slen--; cp++)
278    {
279	int		sustain, timeval, tempo;
280	register char	c = toupper(*cp);
281
282#ifdef DEBUG
283	(void) printf("playstring: %c (%x)\n", c, c);
284#endif /* DEBUG */
285
286	switch (c)
287	{
288	case 'A':  case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
289
290	    /* compute pitch */
291	    pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
292
293	    /* this may be followed by an accidental sign */
294	    if (cp[1] == '#' || cp[1] == '+')
295	    {
296		++pitch;
297		++cp;
298		slen--;
299	    }
300	    else if (cp[1] == '-')
301	    {
302		--pitch;
303		++cp;
304		slen--;
305	    }
306
307	    /*
308	     * If octave-tracking mode is on, and there has been no octave-
309	     * setting prefix, find the version of the current letter note
310	     * closest to the last regardless of octave.
311	     */
312	    if (octtrack && !octprefix)
313	    {
314		if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
315		{
316		    ++octave;
317		    pitch += OCTAVE_NOTES;
318		}
319
320		if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
321		{
322		    --octave;
323		    pitch -= OCTAVE_NOTES;
324		}
325	    }
326	    octprefix = FALSE;
327	    lastpitch = pitch;
328
329	    /* ...which may in turn be followed by an override time value */
330	    GETNUM(cp, timeval);
331	    if (timeval <= 0 || timeval > MIN_VALUE)
332		timeval = value;
333
334	    /* ...and/or sustain dots */
335	    for (sustain = 0; cp[1] == '.'; cp++)
336	    {
337		slen--;
338		sustain++;
339	    }
340
341	    /* ...and/or a slur mark */
342	    oldfill = fill;
343	    if (cp[1] == '_')
344	    {
345		fill = LEGATO;
346		++cp;
347		slen--;
348	    }
349
350	    /* time to emit the actual tone */
351	    playtone(pitch, timeval, sustain);
352
353	    fill = oldfill;
354	    break;
355
356	case 'O':
357	    if (cp[1] == 'N' || cp[1] == 'n')
358	    {
359		octprefix = octtrack = FALSE;
360		++cp;
361		slen--;
362	    }
363	    else if (cp[1] == 'L' || cp[1] == 'l')
364	    {
365		octtrack = TRUE;
366		++cp;
367		slen--;
368	    }
369	    else
370	    {
371		GETNUM(cp, octave);
372		if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES)
373		    octave = DFLT_OCTAVE;
374		octprefix = TRUE;
375	    }
376	    break;
377
378	case '>':
379	    if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1)
380		octave++;
381	    octprefix = TRUE;
382	    break;
383
384	case '<':
385	    if (octave > 0)
386		octave--;
387	    octprefix = TRUE;
388	    break;
389
390	case 'N':
391	    GETNUM(cp, pitch);
392	    for (sustain = 0; cp[1] == '.'; cp++)
393	    {
394		slen--;
395		sustain++;
396	    }
397	    oldfill = fill;
398	    if (cp[1] == '_')
399	    {
400		fill = LEGATO;
401		++cp;
402		slen--;
403	    }
404	    playtone(pitch - 1, value, sustain);
405	    fill = oldfill;
406	    break;
407
408	case 'L':
409	    GETNUM(cp, value);
410	    if (value <= 0 || value > MIN_VALUE)
411		value = DFLT_VALUE;
412	    break;
413
414	case 'P':
415	case '~':
416	    /* this may be followed by an override time value */
417	    GETNUM(cp, timeval);
418	    if (timeval <= 0 || timeval > MIN_VALUE)
419		timeval = value;
420	    for (sustain = 0; cp[1] == '.'; cp++)
421	    {
422		slen--;
423		sustain++;
424	    }
425	    playtone(-1, timeval, sustain);
426	    break;
427
428	case 'T':
429	    GETNUM(cp, tempo);
430	    if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
431		tempo = DFLT_TEMPO;
432	    whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
433	    break;
434
435	case 'M':
436	    if (cp[1] == 'N' || cp[1] == 'n')
437	    {
438		fill = NORMAL;
439		++cp;
440		slen--;
441	    }
442	    else if (cp[1] == 'L' || cp[1] == 'l')
443	    {
444		fill = LEGATO;
445		++cp;
446		slen--;
447	    }
448	    else if (cp[1] == 'S' || cp[1] == 's')
449	    {
450		fill = STACCATO;
451		++cp;
452		slen--;
453	    }
454	    break;
455	}
456    }
457}
458
459/******************* UNIX DRIVER HOOKS BEGIN HERE **************************
460 *
461 * This section implements driver hooks to run playstring() and the tone(),
462 * endtone(), and rest() functions defined above.
463 */
464
465static int spkr_active = FALSE; /* exclusion flag */
466static struct buf *spkr_inbuf;  /* incoming buf */
467
468int
469spkropen(dev, flags, fmt, p)
470	dev_t		dev;
471	int		flags;
472	int		fmt;
473	struct proc	*p;
474{
475#ifdef DEBUG
476    (void) printf("spkropen: entering with dev = %s\n", devtoname(dev));
477#endif /* DEBUG */
478
479    if (minor(dev) != 0)
480	return(ENXIO);
481    else if (spkr_active)
482	return(EBUSY);
483    else
484    {
485#ifdef DEBUG
486	(void) printf("spkropen: about to perform play initialization\n");
487#endif /* DEBUG */
488	playinit();
489	spkr_inbuf = geteblk(DEV_BSIZE);
490	spkr_active = TRUE;
491	return(0);
492    }
493}
494
495int
496spkrwrite(dev, uio, ioflag)
497	dev_t		dev;
498	struct uio	*uio;
499	int		ioflag;
500{
501#ifdef DEBUG
502    printf("spkrwrite: entering with dev = %s, count = %d\n",
503		devtoname(dev), uio->uio_resid);
504#endif /* DEBUG */
505
506    if (minor(dev) != 0)
507	return(ENXIO);
508    else if (uio->uio_resid > (DEV_BSIZE - 1))     /* prevent system crashes */
509	return(E2BIG);
510    else
511    {
512	unsigned n;
513	char *cp;
514	int error;
515
516	n = uio->uio_resid;
517	cp = spkr_inbuf->b_data;
518	error = uiomove(cp, n, uio);
519	if (!error) {
520		cp[n] = '\0';
521		playstring(cp, n);
522	}
523	return(error);
524    }
525}
526
527int
528spkrclose(dev, flags, fmt, p)
529	dev_t		dev;
530	int		flags;
531	int		fmt;
532	struct proc	*p;
533{
534#ifdef DEBUG
535    (void) printf("spkrclose: entering with dev = %s\n", devtoname(dev));
536#endif /* DEBUG */
537
538    if (minor(dev) != 0)
539	return(ENXIO);
540    else
541    {
542	wakeup((caddr_t)&endtone);
543	wakeup((caddr_t)&endrest);
544	brelse(spkr_inbuf);
545	spkr_active = FALSE;
546	return(0);
547    }
548}
549
550int
551spkrioctl(dev, cmd, cmdarg, flags, p)
552	dev_t		dev;
553	unsigned long	cmd;
554	caddr_t		cmdarg;
555	int		flags;
556	struct proc	*p;
557{
558#ifdef DEBUG
559    (void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n",
560    	devtoname(dev), cmd);
561#endif /* DEBUG */
562
563    if (minor(dev) != 0)
564	return(ENXIO);
565    else if (cmd == SPKRTONE)
566    {
567	tone_t	*tp = (tone_t *)cmdarg;
568
569	if (tp->frequency == 0)
570	    rest(tp->duration);
571	else
572	    tone(tp->frequency, tp->duration);
573	return 0;
574    }
575    else if (cmd == SPKRTUNE)
576    {
577	tone_t  *tp = (tone_t *)(*(caddr_t *)cmdarg);
578	tone_t ttp;
579	int error;
580
581	for (; ; tp++) {
582	    error = copyin(tp, &ttp, sizeof(tone_t));
583	    if (error)
584		    return(error);
585	    if (ttp.duration == 0)
586		    break;
587	    if (ttp.frequency == 0)
588		 rest(ttp.duration);
589	    else
590		 tone(ttp.frequency, ttp.duration);
591	}
592	return(0);
593    }
594    return(EINVAL);
595}
596
597static void
598spkr_drvinit(void *unused)
599{
600	make_dev(&spkr_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "speaker");
601}
602
603SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL)
604
605
606#endif  /* NSPEAKER > 0 */
607/* spkr.c ends here */
608