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