1 /*-
2 * spkr.c -- device driver for console speaker
3 *
4 * v1.4 by Eric S. Raymond ([email protected]) Aug 1993
5 * modified for FreeBSD by Andrew A. Chernov <[email protected]>
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
tone(unsigned int thz,unsigned int centisecs)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
rest(int centisecs)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
playinit(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
playtone(int pitch,int value,int sustain)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
playstring(char * cp,size_t slen)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
spkropen(struct cdev * dev,int flags,int fmt,struct thread * td)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
spkrwrite(struct cdev * dev,struct uio * uio,int ioflag)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
spkrclose(struct cdev * dev,int flags,int fmt,struct thread * td)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
spkrioctl(struct cdev * dev,unsigned long cmd,caddr_t cmdarg,int flags,struct thread * td)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
speaker_modevent(module_t mod,int type,void * data)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