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 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 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 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 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 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 388 spkrprobe(struct device *parent, void *match, void *aux) 389 { 390 return (!spkr_attached); 391 } 392 393 void 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 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 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 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 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