spkr.c revision 1.1 1 /* $NetBSD: spkr.c,v 1.1 2016/12/09 04:32:39 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.1 2016/12/09 04:32:39 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
64 dev_type_open(spkropen);
65 dev_type_close(spkrclose);
66 dev_type_write(spkrwrite);
67 dev_type_ioctl(spkrioctl);
68
69 const struct cdevsw spkr_cdevsw = {
70 .d_open = spkropen,
71 .d_close = spkrclose,
72 .d_read = noread,
73 .d_write = spkrwrite,
74 .d_ioctl = spkrioctl,
75 .d_stop = nostop,
76 .d_tty = notty,
77 .d_poll = nopoll,
78 .d_mmap = nommap,
79 .d_kqfilter = nokqfilter,
80 .d_discard = nodiscard,
81 .d_flag = D_OTHER
82 };
83
84 static void playinit(void);
85 static void playtone(int, int, int);
86 static void playstring(char *, int);
87
88 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
89 *
90 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
91 * M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
92 * Requires spkr_tone(), spkr_rest(). String play is not interruptible
93 * except possibly at physical block boundaries.
94 */
95
96 #define dtoi(c) ((c) - '0')
97
98 static int octave; /* currently selected octave */
99 static int whole; /* whole-note time at current tempo, in ticks */
100 static int value; /* whole divisor for note time, quarter note = 1 */
101 static int fill; /* controls spacing of notes */
102 static bool octtrack; /* octave-tracking on? */
103 static bool octprefix; /* override current octave-tracking state? */
104
105 /*
106 * Magic number avoidance...
107 */
108 #define SECS_PER_MIN 60 /* seconds per minute */
109 #define WHOLE_NOTE 4 /* quarter notes per whole note */
110 #define MIN_VALUE 64 /* the most we can divide a note by */
111 #define DFLT_VALUE 4 /* default value (quarter-note) */
112 #define FILLTIME 8 /* for articulation, break note in parts */
113 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */
114 #define NORMAL 7 /* 7/8ths of note interval is filled */
115 #define LEGATO 8 /* all of note interval is filled */
116 #define DFLT_OCTAVE 4 /* default octave */
117 #define MIN_TEMPO 32 /* minimum tempo */
118 #define DFLT_TEMPO 120 /* default tempo */
119 #define MAX_TEMPO 255 /* max tempo */
120 #define NUM_MULT 3 /* numerator of dot multiplier */
121 #define DENOM_MULT 2 /* denominator of dot multiplier */
122
123 /* letter to half-tone: A B C D E F G */
124 static const int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
125
126 /*
127 * This is the American Standard A440 Equal-Tempered scale with frequencies
128 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
129 * our octave 0 is standard octave 2.
130 */
131 #define OCTAVE_NOTES 12 /* semitones per octave */
132 static const int pitchtab[] =
133 {
134 /* C C# D D# E F F# G G# A A# B*/
135 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123,
136 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
137 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
138 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
139 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
140 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
141 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
142 };
143 #define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES)
144
145 static void
146 playinit(void)
147 {
148 octave = DFLT_OCTAVE;
149 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
150 fill = NORMAL;
151 value = DFLT_VALUE;
152 octtrack = false;
153 octprefix = true; /* act as though there was an initial O(n) */
154 }
155
156 static void
157 playtone(int pitch, int val, int sustain)
158 /* play tone of proper duration for current rhythm signature */
159 {
160 int sound, silence, snum = 1, sdenom = 1;
161
162 /* this weirdness avoids floating-point arithmetic */
163 for (; sustain; sustain--)
164 {
165 snum *= NUM_MULT;
166 sdenom *= DENOM_MULT;
167 }
168
169 if (pitch == -1)
170 spkr_rest(whole * snum / (val * sdenom));
171 else
172 {
173 sound = (whole * snum) / (val * sdenom)
174 - (whole * (FILLTIME - fill)) / (val * FILLTIME);
175 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * val * sdenom);
176
177 #ifdef SPKRDEBUG
178 printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
179 pitch, sound, silence);
180 #endif /* SPKRDEBUG */
181
182 spkr_tone(pitchtab[pitch], sound);
183 if (fill != LEGATO)
184 spkr_rest(silence);
185 }
186 }
187
188 static void
189 playstring(char *cp, int slen)
190 /* interpret and play an item from a notation string */
191 {
192 int pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
193
194 #define GETNUM(cp, v) for(v=0; slen > 0 && isdigit(cp[1]); ) \
195 {v = v * 10 + (*++cp - '0'); slen--;}
196 for (; slen--; cp++)
197 {
198 int sustain, timeval, tempo;
199 char c = toupper(*cp);
200
201 #ifdef SPKRDEBUG
202 printf("playstring: %c (%x)\n", c, c);
203 #endif /* SPKRDEBUG */
204
205 switch (c)
206 {
207 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
208
209 /* compute pitch */
210 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
211
212 /* this may be followed by an accidental sign */
213 if (slen > 0 && (cp[1] == '#' || cp[1] == '+'))
214 {
215 ++pitch;
216 ++cp;
217 slen--;
218 }
219 else if (slen > 0 && cp[1] == '-')
220 {
221 --pitch;
222 ++cp;
223 slen--;
224 }
225
226 /*
227 * If octave-tracking mode is on, and there has been no octave-
228 * setting prefix, find the version of the current letter note
229 * closest to the last regardless of octave.
230 */
231 if (octtrack && !octprefix)
232 {
233 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
234 {
235 if (octave < NOCTAVES - 1) {
236 ++octave;
237 pitch += OCTAVE_NOTES;
238 }
239 }
240
241 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
242 {
243 if (octave > 0) {
244 --octave;
245 pitch -= OCTAVE_NOTES;
246 }
247 }
248 }
249 octprefix = false;
250 lastpitch = pitch;
251
252 /* ...which may in turn be followed by an override time value */
253 GETNUM(cp, timeval);
254 if (timeval <= 0 || timeval > MIN_VALUE)
255 timeval = value;
256
257 /* ...and/or sustain dots */
258 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
259 {
260 slen--;
261 sustain++;
262 }
263
264 /* time to emit the actual tone */
265 playtone(pitch, timeval, sustain);
266 break;
267
268 case 'O':
269 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
270 {
271 octprefix = octtrack = false;
272 ++cp;
273 slen--;
274 }
275 else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
276 {
277 octtrack = true;
278 ++cp;
279 slen--;
280 }
281 else
282 {
283 GETNUM(cp, octave);
284 if (octave >= NOCTAVES)
285 octave = DFLT_OCTAVE;
286 octprefix = true;
287 }
288 break;
289
290 case '>':
291 if (octave < NOCTAVES - 1)
292 octave++;
293 octprefix = true;
294 break;
295
296 case '<':
297 if (octave > 0)
298 octave--;
299 octprefix = true;
300 break;
301
302 case 'N':
303 GETNUM(cp, pitch);
304 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
305 {
306 slen--;
307 sustain++;
308 }
309 playtone(pitch - 1, value, sustain);
310 break;
311
312 case 'L':
313 GETNUM(cp, value);
314 if (value <= 0 || value > MIN_VALUE)
315 value = DFLT_VALUE;
316 break;
317
318 case 'P':
319 case '~':
320 /* this may be followed by an override time value */
321 GETNUM(cp, timeval);
322 if (timeval <= 0 || timeval > MIN_VALUE)
323 timeval = value;
324 for (sustain = 0; slen > 0 && cp[1] == '.'; cp++)
325 {
326 slen--;
327 sustain++;
328 }
329 playtone(-1, timeval, sustain);
330 break;
331
332 case 'T':
333 GETNUM(cp, tempo);
334 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
335 tempo = DFLT_TEMPO;
336 whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
337 break;
338
339 case 'M':
340 if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n'))
341 {
342 fill = NORMAL;
343 ++cp;
344 slen--;
345 }
346 else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l'))
347 {
348 fill = LEGATO;
349 ++cp;
350 slen--;
351 }
352 else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's'))
353 {
354 fill = STACCATO;
355 ++cp;
356 slen--;
357 }
358 break;
359 }
360 }
361 }
362
363 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
364 *
365 * This section implements driver hooks to run playstring() and the spkr_tone()
366 * and spkr_rest() functions defined above.
367 */
368
369 static int spkr_active; /* exclusion flag */
370 int spkr_attached;
371 static void *spkr_inbuf;
372
373 int
374 spkr_probe(device_t parent, cfdata_t match, void *aux)
375 {
376 return (!spkr_attached);
377 }
378
379 int
380 spkropen(dev_t dev, int flags, int mode, struct lwp *l)
381 {
382 #ifdef SPKRDEBUG
383 printf("spkropen: entering with dev = %"PRIx64"\n", dev);
384 #endif /* SPKRDEBUG */
385
386 if (minor(dev) != 0 || !spkr_attached)
387 return(ENXIO);
388 else if (spkr_active)
389 return(EBUSY);
390 else
391 {
392 playinit();
393 spkr_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
394 spkr_active = 1;
395 }
396 return(0);
397 }
398
399 int
400 spkrwrite(dev_t dev, struct uio *uio, int flags)
401 {
402 int n;
403 int error;
404 #ifdef SPKRDEBUG
405 printf("spkrwrite: entering with dev = %"PRIx64", count = %zu\n",
406 dev, uio->uio_resid);
407 #endif /* SPKRDEBUG */
408
409 if (minor(dev) != 0)
410 return(ENXIO);
411 else
412 {
413 n = min(DEV_BSIZE, uio->uio_resid);
414 error = uiomove(spkr_inbuf, n, uio);
415 if (!error)
416 playstring((char *)spkr_inbuf, n);
417 return(error);
418 }
419 }
420
421 int
422 spkrclose(dev_t dev, int flags, int mode, struct lwp *l)
423 {
424 #ifdef SPKRDEBUG
425 printf("spkrclose: entering with dev = %"PRIx64"\n", dev);
426 #endif /* SPKRDEBUG */
427
428 if (minor(dev) != 0)
429 return(ENXIO);
430 else
431 {
432 spkr_tone(0, 0);
433 free(spkr_inbuf, M_DEVBUF);
434 spkr_active = 0;
435 }
436 return(0);
437 }
438
439 int
440 spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
441 {
442 #ifdef SPKRDEBUG
443 printf("spkrioctl: entering with dev = %"PRIx64", cmd = %lx\n", dev, cmd);
444 #endif /* SPKRDEBUG */
445
446 if (minor(dev) != 0)
447 return(ENXIO);
448 else if (cmd == SPKRTONE)
449 {
450 tone_t *tp = (tone_t *)data;
451
452 if (tp->frequency == 0)
453 spkr_rest(tp->duration);
454 else
455 spkr_tone(tp->frequency, tp->duration);
456 }
457 else if (cmd == SPKRTUNE)
458 {
459 tone_t *tp = (tone_t *)(*(void **)data);
460 tone_t ttp;
461 int error;
462
463 for (; ; tp++) {
464 error = copyin(tp, &ttp, sizeof(tone_t));
465 if (error)
466 return(error);
467 if (ttp.duration == 0)
468 break;
469 if (ttp.frequency == 0)
470 spkr_rest(ttp.duration);
471 else
472 spkr_tone(ttp.frequency, ttp.duration);
473 }
474 }
475 else
476 return(EINVAL);
477 return(0);
478 }
479
480 int
481 spkr__modcmd(modcmd_t cmd, void *arg)
482 {
483 #ifdef _MODULE
484 devmajor_t bmajor, cmajor;
485 #endif
486 int error = 0;
487
488 #ifdef _MODULE
489 switch(cmd) {
490 case MODULE_CMD_INIT:
491 bmajor = cmajor = -1;
492 error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
493 &spkr_cdevsw, &cmajor);
494 if (error)
495 break;
496
497 error = config_init_component(cfdriver_ioconf_spkr,
498 cfattach_ioconf_spkr, cfdata_ioconf_spkr);
499 if (error) {
500 devsw_detach(NULL, &spkr_cdevsw);
501 }
502 break;
503
504 case MODULE_CMD_FINI:
505 if (spkr_active)
506 return EBUSY;
507 error = config_fini_component(cfdriver_ioconf_spkr,
508 cfattach_ioconf_spkr, cfdata_ioconf_spkr);
509 devsw_detach(NULL, &spkr_cdevsw);
510 break;
511 default:
512 error = ENOTTY;
513 break;
514 }
515 #endif
516
517 return error;
518 }
519