xref: /freebsd/sys/dev/speaker/spkr.c (revision c83d8320)
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
tone(unsigned int thz,unsigned int centisecs)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
rest(int centisecs)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 #define dtoi(c)		((c) - '0')
125 
126 static int octave;	/* currently selected octave */
127 static int whole;	/* whole-note time at current tempo, in ticks */
128 static int value;	/* whole divisor for note time, quarter note = 1 */
129 static int fill;	/* controls spacing of notes */
130 static bool octtrack;	/* octave-tracking on? */
131 static bool octprefix;	/* override current octave-tracking state? */
132 
133 /*
134  * Magic number avoidance...
135  */
136 #define SECS_PER_MIN	60	/* seconds per minute */
137 #define WHOLE_NOTE	4	/* quarter notes per whole note */
138 #define MIN_VALUE	64	/* the most we can divide a note by */
139 #define DFLT_VALUE	4	/* default value (quarter-note) */
140 #define FILLTIME	8	/* for articulation, break note in parts */
141 #define STACCATO	6	/* 6/8 = 3/4 of note is filled */
142 #define NORMAL		7	/* 7/8ths of note interval is filled */
143 #define LEGATO		8	/* all of note interval is filled */
144 #define DFLT_OCTAVE	4	/* default octave */
145 #define MIN_TEMPO	32	/* minimum tempo */
146 #define DFLT_TEMPO	120	/* default tempo */
147 #define MAX_TEMPO	255	/* max tempo */
148 #define NUM_MULT	3	/* numerator of dot multiplier */
149 #define DENOM_MULT	2	/* denominator of dot multiplier */
150 
151 /* letter to half-tone:  A   B  C  D  E  F  G */
152 static int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
153 
154 /*
155  * This is the American Standard A440 Equal-Tempered scale with frequencies
156  * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
157  * our octave 0 is standard octave 2.
158  */
159 #define OCTAVE_NOTES	12	/* semitones per octave */
160 static int pitchtab[] =
161 {
162 /*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
163 /* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
164 /* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
165 /* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
166 /* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
167 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
168 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
169 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
170 };
171 
172 static void
playinit(void)173 playinit(void)
174 {
175     octave = DFLT_OCTAVE;
176     whole = (100 * 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 /*
184  * Play tone of proper duration for current rhythm signature
185  */
186 static void
playtone(int pitch,int value,int sustain)187 playtone(int pitch, int value, int sustain)
188 {
189 	int sound, silence, snum = 1, sdenom = 1;
190 
191 	/* this weirdness avoids floating-point arithmetic */
192 	for (; sustain; sustain--) {
193 		/* See the BUGS section in the man page for discussion */
194 		snum *= NUM_MULT;
195 		sdenom *= DENOM_MULT;
196 	}
197 
198 	if (value == 0 || sdenom == 0)
199 		return;
200 
201 	if (pitch == -1)
202 		rest(whole * snum / (value * sdenom));
203 	else {
204 		sound = (whole * snum) / (value * sdenom)
205 			- (whole * (FILLTIME - fill)) / (value * FILLTIME);
206 		silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom);
207 
208 #ifdef DEBUG
209 		(void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
210 			pitch, sound, silence);
211 #endif /* DEBUG */
212 
213 		tone(pitchtab[pitch], sound);
214 		if (fill != LEGATO)
215 			rest(silence);
216 	}
217 }
218 
219 /*
220  * Interpret and play an item from a notation string
221  */
222 static void
playstring(char * cp,size_t slen)223 playstring(char *cp, size_t slen)
224 {
225 	int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
226 
227 #define GETNUM(cp, v)	for(v=0; isdigit(cp[1]) && slen > 0; ) \
228 				{v = v * 10 + (*++cp - '0'); slen--;}
229 	for (; slen--; cp++) {
230 		int sustain, timeval, tempo;
231 		char c = toupper(*cp);
232 
233 #ifdef DEBUG
234 		(void) printf("playstring: %c (%x)\n", c, c);
235 #endif /* DEBUG */
236 
237 		switch (c) {
238 		case 'A':
239 		case 'B':
240 		case 'C':
241 		case 'D':
242 		case 'E':
243 		case 'F':
244 		case 'G':
245 			/* compute pitch */
246 			pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
247 
248 			/* this may be followed by an accidental sign */
249 			if (cp[1] == '#' || cp[1] == '+') {
250 				++pitch;
251 				++cp;
252 				slen--;
253 			} else if (cp[1] == '-') {
254 				--pitch;
255 				++cp;
256 				slen--;
257 			}
258 
259 			/*
260 			 * If octave-tracking mode is on, and there has been no octave-
261 			 * setting prefix, find the version of the current letter note
262 			 * closest to the last regardless of octave.
263 			 */
264 			if (octtrack && !octprefix) {
265 				if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES -
266 					lastpitch)) {
267 					++octave;
268 					pitch += OCTAVE_NOTES;
269 				}
270 
271 				if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES) -
272 					lastpitch)) {
273 					--octave;
274 					pitch -= OCTAVE_NOTES;
275 				}
276 			}
277 			octprefix = false;
278 			lastpitch = pitch;
279 
280 			/* ...which may in turn be followed by an override time value */
281 			GETNUM(cp, timeval);
282 			if (timeval <= 0 || timeval > MIN_VALUE)
283 				timeval = value;
284 
285 			/* ...and/or sustain dots */
286 			for (sustain = 0; cp[1] == '.'; cp++) {
287 				slen--;
288 				sustain++;
289 			}
290 
291 			/* ...and/or a slur mark */
292 			oldfill = fill;
293 			if (cp[1] == '_') {
294 				fill = LEGATO;
295 				++cp;
296 				slen--;
297 			}
298 
299 			/* time to emit the actual tone */
300 			playtone(pitch, timeval, sustain);
301 
302 			fill = oldfill;
303 			break;
304 		case 'O':
305 			if (cp[1] == 'N' || cp[1] == 'n') {
306 				octprefix = octtrack = false;
307 				++cp;
308 				slen--;
309 			} else if (cp[1] == 'L' || cp[1] == 'l') {
310 				octtrack = true;
311 				++cp;
312 				slen--;
313 			} else {
314 				GETNUM(cp, octave);
315 				if (octave >= nitems(pitchtab) / OCTAVE_NOTES)
316 					octave = DFLT_OCTAVE;
317 				octprefix = true;
318 			}
319 			break;
320 		case '>':
321 			if (octave < nitems(pitchtab) / OCTAVE_NOTES - 1)
322 				octave++;
323 			octprefix = true;
324 			break;
325 		case '<':
326 			if (octave > 0)
327 				octave--;
328 			octprefix = true;
329 			break;
330 		case 'N':
331 			GETNUM(cp, pitch);
332 			for (sustain = 0; cp[1] == '.'; cp++) {
333 				slen--;
334 				sustain++;
335 			}
336 			oldfill = fill;
337 			if (cp[1] == '_') {
338 				fill = LEGATO;
339 				++cp;
340 				slen--;
341 			}
342 			playtone(pitch - 1, value, sustain);
343 			fill = oldfill;
344 			break;
345 		case 'L':
346 			GETNUM(cp, value);
347 			if (value <= 0 || value > MIN_VALUE)
348 				value = DFLT_VALUE;
349 			break;
350 		case 'P':
351 		case '~':
352 			/* this may be followed by an override time value */
353 			GETNUM(cp, timeval);
354 			if (timeval <= 0 || timeval > MIN_VALUE)
355 				timeval = value;
356 			for (sustain = 0; cp[1] == '.'; cp++) {
357 				slen--;
358 				sustain++;
359 			}
360 			playtone(-1, timeval, sustain);
361 			break;
362 		case 'T':
363 			GETNUM(cp, tempo);
364 			if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
365 				tempo = DFLT_TEMPO;
366 			whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / tempo;
367 			break;
368 		case 'M':
369 			if (cp[1] == 'N' || cp[1] == 'n') {
370 				fill = NORMAL;
371 				++cp;
372 				slen--;
373 			} else if (cp[1] == 'L' || cp[1] == 'l') {
374 				fill = LEGATO;
375 				++cp;
376 				slen--;
377 			} else if (cp[1] == 'S' || cp[1] == 's') {
378 				fill = STACCATO;
379 				++cp;
380 				slen--;
381 			}
382 			break;
383 		}
384 	}
385 }
386 
387 /*
388  * ****************** UNIX DRIVER HOOKS BEGIN HERE **************************
389  * This section implements driver hooks to run playstring() and the tone(),
390  * endtone(), and rest() functions defined above.
391  */
392 
393 static bool spkr_active = false; /* exclusion flag */
394 static char *spkr_inbuf;  /* incoming buf */
395 
396 static int
spkropen(struct cdev * dev,int flags,int fmt,struct thread * td)397 spkropen(struct cdev *dev, int flags, int fmt, struct thread *td)
398 {
399 #ifdef DEBUG
400 	(void) printf("spkropen: entering with dev = %s\n", devtoname(dev));
401 #endif /* DEBUG */
402 
403 	if (spkr_active)
404 		return(EBUSY);
405 	else {
406 #ifdef DEBUG
407 		(void) printf("spkropen: about to perform play initialization\n");
408 #endif /* DEBUG */
409 		playinit();
410 		spkr_inbuf = malloc(DEV_BSIZE, M_SPKR, M_WAITOK);
411 		spkr_active = true;
412 		return(0);
413     	}
414 }
415 
416 static int
spkrwrite(struct cdev * dev,struct uio * uio,int ioflag)417 spkrwrite(struct cdev *dev, struct uio *uio, int ioflag)
418 {
419 #ifdef DEBUG
420 	printf("spkrwrite: entering with dev = %s, count = %zd\n",
421 		devtoname(dev), uio->uio_resid);
422 #endif /* DEBUG */
423 
424 	if (uio->uio_resid > (DEV_BSIZE - 1))     /* prevent system crashes */
425 		return(E2BIG);
426 	else {
427 		unsigned n;
428 		char *cp;
429 		int error;
430 
431 		n = uio->uio_resid;
432 		cp = spkr_inbuf;
433 		error = uiomove(cp, n, uio);
434 		if (!error) {
435 			cp[n] = '\0';
436 			playstring(cp, n);
437 		}
438 	return(error);
439 	}
440 }
441 
442 static int
spkrclose(struct cdev * dev,int flags,int fmt,struct thread * td)443 spkrclose(struct cdev *dev, int flags, int fmt, struct thread *td)
444 {
445 #ifdef DEBUG
446 	(void) printf("spkrclose: entering with dev = %s\n", devtoname(dev));
447 #endif /* DEBUG */
448 
449 	wakeup(&endtone);
450 	wakeup(&endrest);
451 	free(spkr_inbuf, M_SPKR);
452 	spkr_active = false;
453 	return(0);
454 }
455 
456 static int
spkrioctl(struct cdev * dev,unsigned long cmd,caddr_t cmdarg,int flags,struct thread * td)457 spkrioctl(struct cdev *dev, unsigned long cmd, caddr_t cmdarg, int flags,
458     struct thread *td)
459 {
460 #ifdef DEBUG
461 	(void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n",
462     		devtoname(dev), cmd);
463 #endif /* DEBUG */
464 
465 	if (cmd == SPKRTONE) {
466 		tone_t	*tp = (tone_t *)cmdarg;
467 
468 		if (tp->frequency == 0)
469 			rest(tp->duration);
470 		else
471 			tone(tp->frequency, tp->duration);
472 		return 0;
473 	} else if (cmd == SPKRTUNE) {
474 		tone_t  *tp = (tone_t *)(*(caddr_t *)cmdarg);
475 		tone_t ttp;
476 		int error;
477 
478 		for (; ; tp++) {
479 			error = copyin(tp, &ttp, sizeof(tone_t));
480 			if (error)
481 				return(error);
482 
483 			if (ttp.duration == 0)
484 				break;
485 
486 			if (ttp.frequency == 0)
487 				rest(ttp.duration);
488 			else
489 				tone(ttp.frequency, ttp.duration);
490 		}
491 		return(0);
492 	}
493 	return(EINVAL);
494 }
495 
496 static struct cdev *speaker_dev;
497 
498 /*
499  * Module handling
500  */
501 static int
speaker_modevent(module_t mod,int type,void * data)502 speaker_modevent(module_t mod, int type, void *data)
503 {
504 	int error = 0;
505 
506 	switch(type) {
507 	case MOD_LOAD:
508 		speaker_dev = make_dev(&spkr_cdevsw, 0,
509 		    UID_ROOT, GID_WHEEL, 0600, "speaker");
510 		break;
511 	case MOD_SHUTDOWN:
512 	case MOD_UNLOAD:
513 		destroy_dev(speaker_dev);
514 		break;
515 	default:
516 		error = EOPNOTSUPP;
517 	}
518 	return (error);
519 }
520 
521 DEV_MODULE(speaker, speaker_modevent, NULL);
522