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