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