spkr.c revision 1.3 1 /* $NetBSD: spkr.c,v 1.3 2016/12/09 05:17:03 christos Exp $ */
2
3 /*
4 * Copyright (c) 1990 Eric S. Raymond (esr (at) snark.thyrsus.com)
5 * Copyright (c) 1990 Andrew A. Chernov (ache (at) astral.msk.su)
6 * Copyright (c) 1990 Lennart Augustsson (lennart (at) augustsson.net)
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed by Eric S. Raymond
20 * 4. The name of the author may not be used to endorse or promote products
21 * derived from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 /*
37 * spkr.c -- device driver for console speaker on 80386
38 *
39 * v1.1 by Eric S. Raymond (esr (at) snark.thyrsus.com) Feb 1990
40 * modified for 386bsd by Andrew A. Chernov <ache (at) astral.msk.su>
41 * 386bsd only clean version, all SYSV stuff removed
42 * use hz value from param.c
43 */
44
45 #include <sys/cdefs.h>
46 __KERNEL_RCSID(0, "$NetBSD: spkr.c,v 1.3 2016/12/09 05:17:03 christos Exp $");
47
48 #include <sys/param.h>
49 #include <sys/systm.h>
50 #include <sys/kernel.h>
51 #include <sys/errno.h>
52 #include <sys/device.h>
53 #include <sys/malloc.h>
54 #include <sys/module.h>
55 #include <sys/uio.h>
56 #include <sys/proc.h>
57 #include <sys/ioctl.h>
58 #include <sys/conf.h>
59
60 #include <sys/bus.h>
61
62 #include <dev/spkrio.h>
63 #include <dev/spkrvar.h>
64
65 dev_type_open(spkropen);
66 dev_type_close(spkrclose);
67 dev_type_write(spkrwrite);
68 dev_type_ioctl(spkrioctl);
69
70 const struct cdevsw spkr_cdevsw = {
71 .d_open = spkropen,
72 .d_close = spkrclose,
73 .d_read = noread,
74 .d_write = spkrwrite,
75 .d_ioctl = spkrioctl,
76 .d_stop = nostop,
77 .d_tty = notty,
78 .d_poll = nopoll,
79 .d_mmap = nommap,
80 .d_kqfilter = nokqfilter,
81 .d_discard = nodiscard,
82 .d_flag = D_OTHER
83 };
84
85 static void playinit(void);
86 static void playtone(int, int, int);
87 static void playstring(char *, int);
88
89 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
90 *
91 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
92 * M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
93 * Requires spkr_tone(), spkr_rest(). String play is not interruptible
94 * except possibly at physical block boundaries.
95 */
96
97 #define dtoi(c) ((c) - '0')
98
99 static int octave; /* currently selected octave */
100 static int whole; /* whole-note time at current tempo, in ticks */
101 static int value; /* whole divisor for note time, quarter note = 1 */
102 static int fill; /* controls spacing of notes */
103 static bool octtrack; /* octave-tracking on? */
104 static bool octprefix; /* override current octave-tracking state? */
105
106 /*
107 * Magic number avoidance...
108 */
109 #define SECS_PER_MIN 60 /* seconds per minute */
110 #define WHOLE_NOTE 4 /* quarter notes per whole note */
111 #define MIN_VALUE 64 /* the most we can divide a note by */
112 #define DFLT_VALUE 4 /* default value (quarter-note) */
113 #define FILLTIME 8 /* for articulation, break note in parts */
114 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */
115 #define NORMAL 7 /* 7/8ths of note interval is filled */
116 #define LEGATO 8 /* all of note interval is filled */
117 #define DFLT_OCTAVE 4 /* default octave */
118 #define MIN_TEMPO 32 /* minimum tempo */
119 #define DFLT_TEMPO 120 /* default tempo */
120 #define MAX_TEMPO 255 /* max tempo */
121 #define NUM_MULT 3 /* numerator of dot multiplier */
122 #define DENOM_MULT 2 /* denominator of dot multiplier */
123
124 /* letter to half-tone: A B C D E F G */
125 static const int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
126
127 /*
128 * This is the American Standard A440 Equal-Tempered scale with frequencies
129 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
130 * our octave 0 is standard octave 2.
131 */
132 #define OCTAVE_NOTES 12 /* semitones per octave */
133 static const int pitchtab[] =
134 {
135 /* C C# D D# E F F# G G# A A# B*/
136 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123,
137 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
138 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
139 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
140 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
141 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
142 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
143 };
144 #define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES)
145
146 static void
147 playinit(void)
148 {
149 octave = DFLT_OCTAVE;
150 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
151 fill = NORMAL;
152 value = DFLT_VALUE;
153 octtrack = false;
154 octprefix = true; /* act as though there was an initial O(n) */
155 }
156
157 static void
158 playtone(int pitch, int val, int sustain)
159 /* play tone of proper duration for current rhythm signature */
160 {
161 int sound, silence, snum = 1, sdenom = 1;
162
163 /* this weirdness avoids floating-point arithmetic */
164 for (; sustain; sustain--)
165 {
166 snum *= NUM_MULT;
167 sdenom *= DENOM_MULT;
168 }
169
170 if (pitch == -1)
171 spkr_rest(whole * snum / (val * sdenom));
172 else
173 {
174 sound = (whole * snum) / (val * sdenom)
175 - (whole * (FILLTIME - fill)) / (val * FILLTIME);
176 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * val * sdenom);
177
178 #ifdef SPKRDEBUG
179 printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
180 pitch, sound, silence);
181 #endif /* SPKRDEBUG */
182
183 spkr_tone(pitchtab[pitch], sound);
184 if (fill != LEGATO)
185 spkr_rest(silence);
186 }
187 }
188
189 static void
190 playstring(char *cp, int slen)
191 /* interpret and play an item from a notation string */
192 {
193 int pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
194
195 #define GETNUM(cp, v) for(v=0; slen > 0 && isdigit(cp[1]); ) \
196 {v = v * 10 + (*++cp - '0'); slen--;}
197 for (; slen--; cp++)
198 {
199 int sustain, timeval, tempo;
200 char c = toupper(*cp);
201
202 #ifdef SPKRDEBUG
203 printf("playstring: %c (%x)\n", c, c);
204 #endif /* SPKRDEBUG */
205
206 switch (c)
207 {
208 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
209
210 /* compute pitch */
211 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
212
213 /* this may be followed by an accidental sign */
214 if (slen > 0 && (cp[1] == '#' || cp[1] == '+'))
215 {
216 ++pitch;
217 ++cp;
218 slen--;
219 }
220 else if (slen > 0 && cp[1] == '-')
221 {
222 --pitch;
223 ++cp;
224 slen--;
225 }
226
227 /*
228 * If octave-tracking mode is on, and there has been no octave-
229 * setting prefix, find the version of the current letter note
230 * closest to the last regardless of octave.
231 */
232 if (octtrack && !octprefix)
233 {
234 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
235 {
236 if (octave < NOCTAVES - 1) {
237 ++octave;
238 pitch += OCTAVE_NOTES;
239 }
240 }
241
242 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
243 {
244 if (octave > 0) {
245 --octave;
246 pitch -= OCTAVE_NOTES;
247 }
248 }
249 }
250 octprefix = false;
251 lastpitch = pitch;
252
253 /* ...which may in turn be followed by an override time value */
254 GETNUM(cp, timeval);
255 if (timeval <= 0 || timeval > MIN_VALUE)
256 timeval = value;
257
258 /* ...and/or sustain dots */
259 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
260 {
261 slen--;
262 sustain++;
263 }
264
265 /* time to emit the actual tone */
266 playtone(pitch, timeval, sustain);
267 break;
268
269 case 'O':
270 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
271 {
272 octprefix = octtrack = false;
273 ++cp;
274 slen--;
275 }
276 else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
277 {
278 octtrack = true;
279 ++cp;
280 slen--;
281 }
282 else
283 {
284 GETNUM(cp, octave);
285 if (octave >= NOCTAVES)
286 octave = DFLT_OCTAVE;
287 octprefix = true;
288 }
289 break;
290
291 case '>':
292 if (octave < NOCTAVES - 1)
293 octave++;
294 octprefix = true;
295 break;
296
297 case '<':
298 if (octave > 0)
299 octave--;
300 octprefix = true;
301 break;
302
303 case 'N':
304 GETNUM(cp, pitch);
305 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
306 {
307 slen--;
308 sustain++;
309 }
310 playtone(pitch - 1, value, sustain);
311 break;
312
313 case 'L':
314 GETNUM(cp, value);
315 if (value <= 0 || value > MIN_VALUE)
316 value = DFLT_VALUE;
317 break;
318
319 case 'P':
320 case '~':
321 /* this may be followed by an override time value */
322 GETNUM(cp, timeval);
323 if (timeval <= 0 || timeval > MIN_VALUE)
324 timeval = value;
325 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
326 {
327 slen--;
328 sustain++;
329 }
330 playtone(-1, timeval, sustain);
331 break;
332
333 case 'T':
334 GETNUM(cp, tempo);
335 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
336 tempo = DFLT_TEMPO;
337 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
338 break;
339
340 case 'M':
341 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
342 {
343 fill = NORMAL;
344 ++cp;
345 slen--;
346 }
347 else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
348 {
349 fill = LEGATO;
350 ++cp;
351 slen--;
352 }
353 else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's'))
354 {
355 fill = STACCATO;
356 ++cp;
357 slen--;
358 }
359 break;
360 }
361 }
362 }
363
364 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
365 *
366 * This section implements driver hooks to run playstring() and the spkr_tone()
367 * and spkr_rest() functions defined above.
368 */
369
370 static int spkr_active; /* exclusion flag */
371 int spkr_attached;
372 static void *spkr_inbuf;
373
374 int
375 spkr_probe(device_t parent, cfdata_t match, void *aux)
376 {
377 return (!spkr_attached);
378 }
379
380 int
381 spkropen(dev_t dev, int flags, int mode, struct lwp *l)
382 {
383 #ifdef SPKRDEBUG
384 printf("spkropen: entering with dev = %"PRIx64"\n", dev);
385 #endif /* SPKRDEBUG */
386
387 if (minor(dev) != 0 || !spkr_attached)
388 return(ENXIO);
389 else if (spkr_active)
390 return(EBUSY);
391 else
392 {
393 playinit();
394 spkr_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
395 spkr_active = 1;
396 }
397 return(0);
398 }
399
400 int
401 spkrwrite(dev_t dev, struct uio *uio, int flags)
402 {
403 int n;
404 int error;
405 #ifdef SPKRDEBUG
406 printf("spkrwrite: entering with dev = %"PRIx64", count = %zu\n",
407 dev, uio->uio_resid);
408 #endif /* SPKRDEBUG */
409
410 if (minor(dev) != 0)
411 return(ENXIO);
412 else
413 {
414 n = min(DEV_BSIZE, uio->uio_resid);
415 error = uiomove(spkr_inbuf, n, uio);
416 if (!error)
417 playstring((char *)spkr_inbuf, n);
418 return(error);
419 }
420 }
421
422 int
423 spkrclose(dev_t dev, int flags, int mode, struct lwp *l)
424 {
425 #ifdef SPKRDEBUG
426 printf("spkrclose: entering with dev = %"PRIx64"\n", dev);
427 #endif /* SPKRDEBUG */
428
429 if (minor(dev) != 0)
430 return(ENXIO);
431 else
432 {
433 spkr_tone(0, 0);
434 free(spkr_inbuf, M_DEVBUF);
435 spkr_active = 0;
436 }
437 return(0);
438 }
439
440 int
441 spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
442 {
443 #ifdef SPKRDEBUG
444 printf("spkrioctl: entering with dev = %"PRIx64", cmd = %lx\n", dev, cmd);
445 #endif /* SPKRDEBUG */
446
447 if (minor(dev) != 0)
448 return(ENXIO);
449 else if (cmd == SPKRTONE)
450 {
451 tone_t *tp = (tone_t *)data;
452
453 if (tp->frequency == 0)
454 spkr_rest(tp->duration);
455 else
456 spkr_tone(tp->frequency, tp->duration);
457 }
458 else if (cmd == SPKRTUNE)
459 {
460 tone_t *tp = (tone_t *)(*(void **)data);
461 tone_t ttp;
462 int error;
463
464 for (; ; tp++) {
465 error = copyin(tp, &ttp, sizeof(tone_t));
466 if (error)
467 return(error);
468 if (ttp.duration == 0)
469 break;
470 if (ttp.frequency == 0)
471 spkr_rest(ttp.duration);
472 else
473 spkr_tone(ttp.frequency, ttp.duration);
474 }
475 }
476 else
477 return(EINVAL);
478 return(0);
479 }
480
481 #ifdef _MODULE
482 extern struct cfdriver spkr_cd;
483 #include "ioconf.c"
484 #endif
485
486 int
487 spkr__modcmd(modcmd_t cmd, void *arg)
488 {
489 #ifdef _MODULE
490 devmajor_t bmajor, cmajor;
491 int error = 0;
492
493 switch(cmd) {
494 case MODULE_CMD_INIT:
495 bmajor = cmajor = -1;
496 error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
497 &spkr_cdevsw, &cmajor);
498 if (error)
499 break;
500
501 error = config_init_component(cfdriver_ioconf_spkr,
502 cfattach_ioconf_spkr, cfdata_ioconf_spkr);
503 if (error) {
504 devsw_detach(NULL, &spkr_cdevsw);
505 }
506 break;
507
508 case MODULE_CMD_FINI:
509 if (spkr_active)
510 return EBUSY;
511 error = config_fini_component(cfdriver_ioconf_spkr,
512 cfattach_ioconf_spkr, cfdata_ioconf_spkr);
513 devsw_detach(NULL, &spkr_cdevsw);
514 break;
515 default:
516 error = ENOTTY;
517 break;
518 }
519
520 return error;
521 #else
522 return 0;
523 #endif
524 }
525