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