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