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