xref: /freebsd/sys/dev/speaker/spkr.c (revision 7f3dea244c40159a41ab22da77a434d7c5b5e85a)
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 
25 static	d_open_t	spkropen;
26 static	d_close_t	spkrclose;
27 static	d_write_t	spkrwrite;
28 static	d_ioctl_t	spkrioctl;
29 
30 #define CDEV_MAJOR 26
31 static 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
73 static char endtone, endrest;
74 
75 static void tone __P((unsigned int thz, unsigned int ticks));
76 static void rest __P((int ticks));
77 static void playinit __P((void));
78 static void playtone __P((int pitch, int value, int sustain));
79 static int abs __P((int n));
80 static void playstring __P((char *cp, size_t slen));
81 
82 /* emit tone of frequency thz for given number of ticks */
83 static void
84 tone(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 */
130 static void
131 rest(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 
155 typedef 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 
163 static int octave;	/* currently selected octave */
164 static int whole;	/* whole-note time at current tempo, in ticks */
165 static int value;	/* whole divisor for note time, quarter note = 1 */
166 static int fill;	/* controls spacing of notes */
167 static bool octtrack;	/* octave-tracking on? */
168 static 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 */
189 static 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 */
197 static 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 
209 static void
210 playinit()
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 */
221 static void
222 playtone(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 
257 static int
258 abs(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 */
268 static void
269 playstring(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 
465 static int spkr_active = FALSE; /* exclusion flag */
466 static struct buf *spkr_inbuf;  /* incoming buf */
467 
468 int
469 spkropen(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 
495 int
496 spkrwrite(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 
527 int
528 spkrclose(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 
550 int
551 spkrioctl(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 
597 static void
598 spkr_drvinit(void *unused)
599 {
600 	make_dev(&spkr_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "speaker");
601 }
602 
603 SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL)
604 
605 
606 #endif  /* NSPEAKER > 0 */
607 /* spkr.c ends here */
608