xref: /openbsd/sys/dev/isa/spkr.c (revision 404b540a)
1 /*	$OpenBSD: spkr.c,v 1.10 2006/03/09 22:35:23 miod Exp $	*/
2 /*	$NetBSD: spkr.c,v 1.1 1998/04/15 20:26:18 drochner Exp $	*/
3 
4 /*
5  * Copyright (c) 1990 Eric S. Raymond (esr@snark.thyrsus.com)
6  * Copyright (c) 1990 Andrew A. Chernov (ache@astral.msk.su)
7  * Copyright (c) 1990 Lennart Augustsson (lennart@augustsson.net)
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by Eric S. Raymond
21  * 4. The name of the author may not be used to endorse or promote products
22  *    derived from this software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
28  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 /*
38  * spkr.c -- device driver for console speaker on 80386
39  *
40  * v1.1 by Eric S. Raymond (esr@snark.thyrsus.com) Feb 1990
41  *      modified for 386bsd by Andrew A. Chernov <ache@astral.msk.su>
42  *      386bsd only clean version, all SYSV stuff removed
43  *      use hz value from param.c
44  */
45 
46 #include <sys/param.h>
47 #include <sys/systm.h>
48 #include <sys/kernel.h>
49 #include <sys/errno.h>
50 #include <sys/device.h>
51 #include <sys/malloc.h>
52 #include <sys/uio.h>
53 #include <sys/proc.h>
54 #include <sys/ioctl.h>
55 #include <sys/conf.h>
56 
57 #include <dev/isa/pcppivar.h>
58 
59 #include <dev/isa/spkrio.h>
60 
61 cdev_decl(spkr);
62 
63 int spkrprobe(struct device *, void *, void *);
64 void spkrattach(struct device *, struct device *, void *);
65 
66 struct cfattach spkr_ca = {
67 	sizeof(struct device), spkrprobe, spkrattach
68 };
69 
70 struct cfdriver spkr_cd = {
71 	NULL, "spkr", DV_DULL
72 };
73 
74 static pcppi_tag_t ppicookie;
75 
76 #define SPKRPRI (PZERO - 1)
77 
78 static void tone(u_int, u_int);
79 static void rest(int);
80 static void playinit(void);
81 static void playtone(int, int, int);
82 static void playstring(char *, int);
83 
84 /* emit tone of frequency hz for given number of ticks */
85 static void
86 tone(hz, ticks)
87 	u_int hz, ticks;
88 {
89 	pcppi_bell(ppicookie, hz, ticks, PCPPI_BELL_SLEEP);
90 }
91 
92 /* rest for given number of ticks */
93 static void
94 rest(ticks)
95 	int ticks;
96 {
97 	/*
98 	 * Set timeout to endrest function, then give up the timeslice.
99 	 * This is so other processes can execute while the rest is being
100 	 * waited out.
101 	 */
102 #ifdef SPKRDEBUG
103 	printf("rest: %d\n", ticks);
104 #endif /* SPKRDEBUG */
105 	if (ticks > 0)
106 		tsleep(rest, SPKRPRI | PCATCH, "rest", ticks);
107 }
108 
109 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
110  *
111  * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
112  * M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
113  * Requires tone(), rest(), and endtone(). String play is not interruptible
114  * except possibly at physical block boundaries.
115  */
116 
117 typedef int	bool;
118 #define TRUE	1
119 #define FALSE	0
120 
121 #define toupper(c)	((c) - ' ' * (((c) >= 'a') && ((c) <= 'z')))
122 #define isdigit(c)	(((c) >= '0') && ((c) <= '9'))
123 #define dtoi(c)		((c) - '0')
124 
125 static int octave;	/* currently selected octave */
126 static int whole;	/* whole-note time at current tempo, in ticks */
127 static int value;	/* whole divisor for note time, quarter note = 1 */
128 static int fill;	/* controls spacing of notes */
129 static bool octtrack;	/* octave-tracking on? */
130 static bool octprefix;	/* override current octave-tracking state? */
131 
132 /*
133  * Magic number avoidance...
134  */
135 #define SECS_PER_MIN	60	/* seconds per minute */
136 #define WHOLE_NOTE	4	/* quarter notes per whole note */
137 #define MIN_VALUE	64	/* the most we can divide a note by */
138 #define DFLT_VALUE	4	/* default value (quarter-note) */
139 #define FILLTIME	8	/* for articulation, break note in parts */
140 #define STACCATO	6	/* 6/8 = 3/4 of note is filled */
141 #define NORMAL		7	/* 7/8ths of note interval is filled */
142 #define LEGATO		8	/* all of note interval is filled */
143 #define DFLT_OCTAVE	4	/* default octave */
144 #define MIN_TEMPO	32	/* minimum tempo */
145 #define DFLT_TEMPO	120	/* default tempo */
146 #define MAX_TEMPO	255	/* max tempo */
147 #define NUM_MULT	3	/* numerator of dot multiplier */
148 #define DENOM_MULT	2	/* denominator of dot multiplier */
149 
150 /* letter to half-tone:  A   B  C  D  E  F  G */
151 static int notetab[8] = { 9, 11, 0, 2, 4, 5, 7 };
152 
153 /*
154  * This is the American Standard A440 Equal-Tempered scale with frequencies
155  * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
156  * our octave 0 is standard octave 2.
157  */
158 #define OCTAVE_NOTES	12	/* semitones per octave */
159 static int pitchtab[] =
160 {
161 /*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
162 /* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
163 /* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
164 /* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
165 /* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
166 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
167 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
168 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
169 };
170 #define NOCTAVES (sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES)
171 
172 static void
173 playinit()
174 {
175 	octave = DFLT_OCTAVE;
176 	whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
177 	fill = NORMAL;
178 	value = DFLT_VALUE;
179 	octtrack = FALSE;
180 	octprefix = TRUE;	/* act as though there was an initial O(n) */
181 }
182 
183 /* play tone of proper duration for current rhythm signature */
184 static void
185 playtone(pitch, value, sustain)
186 	int pitch, value, sustain;
187 {
188 	int sound, silence, snum = 1, sdenom = 1;
189 
190 	/* this weirdness avoids floating-point arithmetic */
191 	for (; sustain; sustain--) {
192 		snum *= NUM_MULT;
193 		sdenom *= DENOM_MULT;
194 	}
195 
196 	if (pitch == -1)
197 		rest(whole * snum / (value * sdenom));
198 	else if (pitch >= 0 &&
199 	    pitch < (sizeof(pitchtab) / sizeof(pitchtab[0]))) {
200 		sound = (whole * snum) / (value * sdenom) -
201 		    (whole * (FILLTIME - fill)) / (value * FILLTIME);
202 		silence = whole * (FILLTIME-fill) * snum /
203 		    (FILLTIME * value * sdenom);
204 
205 #ifdef SPKRDEBUG
206 		printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
207 		    pitch, sound, silence);
208 #endif /* SPKRDEBUG */
209 
210 		tone(pitchtab[pitch], sound);
211 		if (fill != LEGATO)
212 			rest(silence);
213 	}
214 }
215 
216 /* interpret and play an item from a notation string */
217 static void
218 playstring(cp, slen)
219 	char *cp;
220 	int slen;
221 {
222 	int pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
223 
224 #define GETNUM(cp, v) \
225 do { \
226 	for (v = 0; slen > 0 && isdigit(cp[1]); ) { \
227 		v = v * 10 + (*++cp - '0'); \
228 		slen--; \
229 	} \
230 } while (0)
231 
232 	for (; slen--; cp++) {
233 		int sustain, timeval, tempo;
234 		char c = toupper(*cp);
235 
236 #ifdef SPKRDEBUG
237 		printf("playstring: %c (%x)\n", c, c);
238 #endif /* SPKRDEBUG */
239 
240 		switch (c) {
241 		case 'A':
242 		case 'B':
243 		case 'C':
244 		case 'D':
245 		case 'E':
246 		case 'F':
247 		case 'G':
248 			/* compute pitch */
249 			pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
250 
251 			/* this may be followed by an accidental sign */
252 			if (slen > 0 && (cp[1] == '#' || cp[1] == '+')) {
253 				++pitch;
254 				++cp;
255 				slen--;
256 			} else if (slen > 0 && cp[1] == '-') {
257 				--pitch;
258 				++cp;
259 				slen--;
260 			}
261 
262 			/*
263 			 * If octave-tracking mode is on, and there has been
264 			 * no octave-setting prefix, find the version of the
265 			 * current letter note closest to the last regardless
266 			 * of octave.
267 			 */
268 			if (octtrack && !octprefix) {
269 				if (abs(pitch - lastpitch) >
270 				    abs(pitch + OCTAVE_NOTES - lastpitch)) {
271 					++octave;
272 					pitch += OCTAVE_NOTES;
273 				}
274 
275 				if (abs(pitch - lastpitch) >
276 				    abs(pitch - OCTAVE_NOTES - lastpitch)) {
277 					--octave;
278 					pitch -= OCTAVE_NOTES;
279 				}
280 			}
281 			octprefix = FALSE;
282 			lastpitch = pitch;
283 
284 			/*
285 			 * ...which may in turn be followed by an override
286 			 * time value
287 			 */
288 			GETNUM(cp, timeval);
289 			if (timeval <= 0 || timeval > MIN_VALUE)
290 				timeval = value;
291 
292 			/* ...and/or sustain dots */
293 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
294 				slen--;
295 				sustain++;
296 			}
297 
298 			/* time to emit the actual tone */
299 			playtone(pitch, timeval, sustain);
300 			break;
301 
302 		case 'O':
303 			if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
304 				octprefix = octtrack = FALSE;
305 				++cp;
306 				slen--;
307 			} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
308 				octtrack = TRUE;
309 				++cp;
310 				slen--;
311 			} else {
312 				GETNUM(cp, octave);
313 				if (octave >= NOCTAVES)
314 					octave = DFLT_OCTAVE;
315 				octprefix = TRUE;
316 			}
317 			break;
318 
319 		case '>':
320 			if (octave < NOCTAVES - 1)
321 				octave++;
322 			octprefix = TRUE;
323 			break;
324 
325 		case '<':
326 			if (octave > 0)
327 				octave--;
328 			octprefix = TRUE;
329 			break;
330 
331 		case 'N':
332 			GETNUM(cp, pitch);
333 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
334 				slen--;
335 				sustain++;
336 			}
337 			playtone(pitch - 1, value, sustain);
338 			break;
339 
340 		case 'L':
341 			GETNUM(cp, value);
342 			if (value <= 0 || value > MIN_VALUE)
343 				value = DFLT_VALUE;
344 			break;
345 
346 		case 'P':
347 		case '~':
348 			/* this may be followed by an override time value */
349 			GETNUM(cp, timeval);
350 			if (timeval <= 0 || timeval > MIN_VALUE)
351 				timeval = value;
352 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
353 				slen--;
354 				sustain++;
355 			}
356 			playtone(-1, timeval, sustain);
357 			break;
358 
359 		case 'T':
360 			GETNUM(cp, tempo);
361 			if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
362 				tempo = DFLT_TEMPO;
363 			whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
364 			break;
365 
366 		case 'M':
367 			if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
368 				fill = NORMAL;
369 				++cp;
370 				slen--;
371 			} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
372 				fill = LEGATO;
373 				++cp;
374 				slen--;
375 			} else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's')) {
376 				fill = STACCATO;
377 				++cp;
378 				slen--;
379 			}
380 			break;
381 		}
382 	}
383 }
384 
385 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
386  *
387  * This section implements driver hooks to run playstring() and the tone(),
388  * endtone(), and rest() functions defined above.
389  */
390 
391 static int spkr_active;	/* exclusion flag */
392 static void *spkr_inbuf;
393 
394 static int spkr_attached = 0;
395 
396 int
397 spkrprobe(parent, match, aux)
398 	struct device *parent;
399 	void *match;
400 	void *aux;
401 {
402 	return (!spkr_attached);
403 }
404 
405 void
406 spkrattach(parent, self, aux)
407 	struct device *parent;
408 	struct device *self;
409 	void *aux;
410 {
411 	printf("\n");
412 	ppicookie = ((struct pcppi_attach_args *)aux)->pa_cookie;
413 	spkr_attached = 1;
414 }
415 
416 int
417 spkropen(dev, flags, mode, p)
418 	dev_t dev;
419 	int flags;
420 	int mode;
421 	struct proc *p;
422 {
423 #ifdef SPKRDEBUG
424 	printf("spkropen: entering with dev = %x\n", dev);
425 #endif /* SPKRDEBUG */
426 
427 	if (minor(dev) != 0 || !spkr_attached)
428 		return (ENXIO);
429 	else if (spkr_active)
430 		return (EBUSY);
431 	else {
432 		playinit();
433 		spkr_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
434 		spkr_active = 1;
435 	}
436 	return (0);
437 }
438 
439 int
440 spkrwrite(dev, uio, flags)
441 	dev_t dev;
442 	struct uio *uio;
443 	int flags;
444 {
445 	int n;
446 	int error;
447 #ifdef SPKRDEBUG
448 	printf("spkrwrite: entering with dev = %x, count = %d\n",
449 	    dev, uio->uio_resid);
450 #endif /* SPKRDEBUG */
451 
452 	if (minor(dev) != 0)
453 		return (ENXIO);
454 	else {
455 		n = min(DEV_BSIZE, uio->uio_resid);
456 		error = uiomove(spkr_inbuf, n, uio);
457 		if (!error)
458 			playstring((char *)spkr_inbuf, n);
459 		return (error);
460 	}
461 }
462 
463 int
464 spkrclose(dev, flags, mode, p)
465 	dev_t dev;
466 	int flags;
467 	int mode;
468 	struct proc *p;
469 {
470 #ifdef SPKRDEBUG
471 	printf("spkrclose: entering with dev = %x\n", dev);
472 #endif /* SPKRDEBUG */
473 
474 	if (minor(dev) != 0)
475 		return (ENXIO);
476 	else {
477 		tone(0, 0);
478 		free(spkr_inbuf, M_DEVBUF);
479 		spkr_active = 0;
480 	}
481 	return (0);
482 }
483 
484 int
485 spkrioctl(dev, cmd, data, flag, p)
486 	dev_t dev;
487 	u_long cmd;
488 	caddr_t data;
489 	int flag;
490 	struct proc *p;
491 {
492 	tone_t *tp, ttp;
493 	int error;
494 
495 #ifdef SPKRDEBUG
496 	printf("spkrioctl: entering with dev = %x, cmd = %lx\n", dev, cmd);
497 #endif /* SPKRDEBUG */
498 
499 	if (minor(dev) != 0)
500 		return (ENXIO);
501 
502 	switch (cmd) {
503 	case SPKRTONE:
504 		tp = (tone_t *)data;
505 
506 		if (tp->frequency == 0)
507 			rest(tp->duration);
508 		else
509 			tone(tp->frequency, tp->duration);
510 		break;
511 	case SPKRTUNE:
512 		tp = (tone_t *)(*(caddr_t *)data);
513 
514 		for (; ; tp++) {
515 			error = copyin(tp, &ttp, sizeof(tone_t));
516 			if (error)
517 				return (error);
518 			if (ttp.duration == 0)
519 				break;
520 			if (ttp.frequency == 0)
521 				rest(ttp.duration);
522 			else
523 				tone(ttp.frequency, ttp.duration);
524 		}
525 		break;
526 	default:
527 		return (ENOTTY);
528 	}
529 
530 	return (0);
531 }
532