1 /*- 2 * spkr.c -- device driver for console speaker 3 * 4 * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993 5 * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su> 6 * modified for PC98 by Kakefuda 7 */ 8 9 #include <sys/cdefs.h> 10 #include <sys/param.h> 11 #include <sys/systm.h> 12 #include <sys/kernel.h> 13 #include <sys/module.h> 14 #include <sys/uio.h> 15 #include <sys/conf.h> 16 #include <sys/ctype.h> 17 #include <sys/malloc.h> 18 #include <machine/clock.h> 19 #include <dev/speaker/speaker.h> 20 21 static d_open_t spkropen; 22 static d_close_t spkrclose; 23 static d_write_t spkrwrite; 24 static d_ioctl_t spkrioctl; 25 26 static struct cdevsw spkr_cdevsw = { 27 .d_version = D_VERSION, 28 .d_flags = D_NEEDGIANT, 29 .d_open = spkropen, 30 .d_close = spkrclose, 31 .d_write = spkrwrite, 32 .d_ioctl = spkrioctl, 33 .d_name = "spkr", 34 }; 35 36 static MALLOC_DEFINE(M_SPKR, "spkr", "Speaker buffer"); 37 38 /* 39 **************** MACHINE DEPENDENT PART STARTS HERE ************************* 40 * This section defines a function tone() which causes a tone of given 41 * frequency and duration from the ISA console speaker. 42 * Another function endtone() is defined to force sound off, and there is 43 * also a rest() entry point to do pauses. 44 * 45 * Audible sound is generated using the Programmable Interval Timer (PIT) and 46 * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The 47 * PPI controls whether sound is passed through at all; the PIT's channel 2 is 48 * used to generate clicks (a square wave) of whatever frequency is desired. 49 */ 50 51 #define SPKRPRI PSOCK 52 static char endtone, endrest; 53 54 static void tone(unsigned int thz, unsigned int centisecs); 55 static void rest(int centisecs); 56 static void playinit(void); 57 static void playtone(int pitch, int value, int sustain); 58 static void playstring(char *cp, size_t slen); 59 60 /* 61 * Emit tone of frequency thz for given number of centisecs 62 */ 63 static void 64 tone(unsigned int thz, unsigned int centisecs) 65 { 66 int timo; 67 68 if (thz <= 0) 69 return; 70 71 #ifdef DEBUG 72 (void) printf("tone: thz=%d centisecs=%d\n", thz, centisecs); 73 #endif /* DEBUG */ 74 75 /* set timer to generate clicks at given frequency in Hertz */ 76 if (timer_spkr_acquire()) { 77 /* enter list of waiting procs ??? */ 78 return; 79 } 80 disable_intr(); 81 timer_spkr_setfreq(thz); 82 enable_intr(); 83 84 /* 85 * Set timeout to endtone function, then give up the timeslice. 86 * This is so other processes can execute while the tone is being 87 * emitted. 88 */ 89 timo = centisecs * hz / 100; 90 if (timo > 0) 91 tsleep(&endtone, SPKRPRI | PCATCH, "spkrtn", timo); 92 timer_spkr_release(); 93 } 94 95 /* 96 * Rest for given number of centisecs 97 */ 98 static void 99 rest(int centisecs) 100 { 101 int timo; 102 103 /* 104 * Set timeout to endrest function, then give up the timeslice. 105 * This is so other processes can execute while the rest is being 106 * waited out. 107 */ 108 #ifdef DEBUG 109 (void) printf("rest: %d\n", centisecs); 110 #endif /* DEBUG */ 111 timo = centisecs * hz / 100; 112 if (timo > 0) 113 tsleep(&endrest, SPKRPRI | PCATCH, "spkrrs", timo); 114 } 115 116 /* 117 **************** PLAY STRING INTERPRETER BEGINS HERE ********************** 118 * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; 119 * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- 120 * tracking facility are added. 121 * Requires tone(), rest(), and endtone(). String play is not interruptible 122 * except possibly at physical block boundaries. 123 */ 124 125 #ifndef __bool_true_false_are_defined 126 typedef int bool; 127 #endif 128 #define TRUE 1 129 #define FALSE 0 130 131 #define dtoi(c) ((c) - '0') 132 133 static int octave; /* currently selected octave */ 134 static int whole; /* whole-note time at current tempo, in ticks */ 135 static int value; /* whole divisor for note time, quarter note = 1 */ 136 static int fill; /* controls spacing of notes */ 137 static bool octtrack; /* octave-tracking on? */ 138 static bool octprefix; /* override current octave-tracking state? */ 139 140 /* 141 * Magic number avoidance... 142 */ 143 #define SECS_PER_MIN 60 /* seconds per minute */ 144 #define WHOLE_NOTE 4 /* quarter notes per whole note */ 145 #define MIN_VALUE 64 /* the most we can divide a note by */ 146 #define DFLT_VALUE 4 /* default value (quarter-note) */ 147 #define FILLTIME 8 /* for articulation, break note in parts */ 148 #define STACCATO 6 /* 6/8 = 3/4 of note is filled */ 149 #define NORMAL 7 /* 7/8ths of note interval is filled */ 150 #define LEGATO 8 /* all of note interval is filled */ 151 #define DFLT_OCTAVE 4 /* default octave */ 152 #define MIN_TEMPO 32 /* minimum tempo */ 153 #define DFLT_TEMPO 120 /* default tempo */ 154 #define MAX_TEMPO 255 /* max tempo */ 155 #define NUM_MULT 3 /* numerator of dot multiplier */ 156 #define DENOM_MULT 2 /* denominator of dot multiplier */ 157 158 /* letter to half-tone: A B C D E F G */ 159 static int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; 160 161 /* 162 * This is the American Standard A440 Equal-Tempered scale with frequencies 163 * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... 164 * our octave 0 is standard octave 2. 165 */ 166 #define OCTAVE_NOTES 12 /* semitones per octave */ 167 static int pitchtab[] = 168 { 169 /* C C# D D# E F F# G G# A A# B*/ 170 /* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, 171 /* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 172 /* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 173 /* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 174 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, 175 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 176 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, 177 }; 178 179 static void 180 playinit(void) 181 { 182 octave = DFLT_OCTAVE; 183 whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; 184 fill = NORMAL; 185 value = DFLT_VALUE; 186 octtrack = FALSE; 187 octprefix = TRUE; /* act as though there was an initial O(n) */ 188 } 189 190 /* 191 * Play tone of proper duration for current rhythm signature 192 */ 193 static void 194 playtone(int pitch, int value, int sustain) 195 { 196 int sound, silence, snum = 1, sdenom = 1; 197 198 /* this weirdness avoids floating-point arithmetic */ 199 for (; sustain; sustain--) { 200 /* See the BUGS section in the man page for discussion */ 201 snum *= NUM_MULT; 202 sdenom *= DENOM_MULT; 203 } 204 205 if (value == 0 || sdenom == 0) 206 return; 207 208 if (pitch == -1) 209 rest(whole * snum / (value * sdenom)); 210 else { 211 sound = (whole * snum) / (value * sdenom) 212 - (whole * (FILLTIME - fill)) / (value * FILLTIME); 213 silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); 214 215 #ifdef DEBUG 216 (void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", 217 pitch, sound, silence); 218 #endif /* DEBUG */ 219 220 tone(pitchtab[pitch], sound); 221 if (fill != LEGATO) 222 rest(silence); 223 } 224 } 225 226 /* 227 * Interpret and play an item from a notation string 228 */ 229 static void 230 playstring(char *cp, size_t slen) 231 { 232 int pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; 233 234 #define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ 235 {v = v * 10 + (*++cp - '0'); slen--;} 236 for (; slen--; cp++) { 237 int sustain, timeval, tempo; 238 char c = toupper(*cp); 239 240 #ifdef DEBUG 241 (void) printf("playstring: %c (%x)\n", c, c); 242 #endif /* DEBUG */ 243 244 switch (c) { 245 case 'A': 246 case 'B': 247 case 'C': 248 case 'D': 249 case 'E': 250 case 'F': 251 case 'G': 252 /* compute pitch */ 253 pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; 254 255 /* this may be followed by an accidental sign */ 256 if (cp[1] == '#' || cp[1] == '+') { 257 ++pitch; 258 ++cp; 259 slen--; 260 } else if (cp[1] == '-') { 261 --pitch; 262 ++cp; 263 slen--; 264 } 265 266 /* 267 * If octave-tracking mode is on, and there has been no octave- 268 * setting prefix, find the version of the current letter note 269 * closest to the last regardless of octave. 270 */ 271 if (octtrack && !octprefix) { 272 if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES - 273 lastpitch)) { 274 ++octave; 275 pitch += OCTAVE_NOTES; 276 } 277 278 if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES) - 279 lastpitch)) { 280 --octave; 281 pitch -= OCTAVE_NOTES; 282 } 283 } 284 octprefix = FALSE; 285 lastpitch = pitch; 286 287 /* ...which may in turn be followed by an override time value */ 288 GETNUM(cp, timeval); 289 if (timeval <= 0 || timeval > MIN_VALUE) 290 timeval = value; 291 292 /* ...and/or sustain dots */ 293 for (sustain = 0; cp[1] == '.'; cp++) { 294 slen--; 295 sustain++; 296 } 297 298 /* ...and/or a slur mark */ 299 oldfill = fill; 300 if (cp[1] == '_') { 301 fill = LEGATO; 302 ++cp; 303 slen--; 304 } 305 306 /* time to emit the actual tone */ 307 playtone(pitch, timeval, sustain); 308 309 fill = oldfill; 310 break; 311 case 'O': 312 if (cp[1] == 'N' || cp[1] == 'n') { 313 octprefix = octtrack = FALSE; 314 ++cp; 315 slen--; 316 } else if (cp[1] == 'L' || cp[1] == 'l') { 317 octtrack = TRUE; 318 ++cp; 319 slen--; 320 } else { 321 GETNUM(cp, octave); 322 if (octave >= nitems(pitchtab) / OCTAVE_NOTES) 323 octave = DFLT_OCTAVE; 324 octprefix = TRUE; 325 } 326 break; 327 case '>': 328 if (octave < nitems(pitchtab) / OCTAVE_NOTES - 1) 329 octave++; 330 octprefix = TRUE; 331 break; 332 case '<': 333 if (octave > 0) 334 octave--; 335 octprefix = TRUE; 336 break; 337 case 'N': 338 GETNUM(cp, pitch); 339 for (sustain = 0; cp[1] == '.'; cp++) { 340 slen--; 341 sustain++; 342 } 343 oldfill = fill; 344 if (cp[1] == '_') { 345 fill = LEGATO; 346 ++cp; 347 slen--; 348 } 349 playtone(pitch - 1, value, sustain); 350 fill = oldfill; 351 break; 352 case 'L': 353 GETNUM(cp, value); 354 if (value <= 0 || value > MIN_VALUE) 355 value = DFLT_VALUE; 356 break; 357 case 'P': 358 case '~': 359 /* this may be followed by an override time value */ 360 GETNUM(cp, timeval); 361 if (timeval <= 0 || timeval > MIN_VALUE) 362 timeval = value; 363 for (sustain = 0; cp[1] == '.'; cp++) { 364 slen--; 365 sustain++; 366 } 367 playtone(-1, timeval, sustain); 368 break; 369 case 'T': 370 GETNUM(cp, tempo); 371 if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) 372 tempo = DFLT_TEMPO; 373 whole = (100 * SECS_PER_MIN * WHOLE_NOTE) / tempo; 374 break; 375 case 'M': 376 if (cp[1] == 'N' || cp[1] == 'n') { 377 fill = NORMAL; 378 ++cp; 379 slen--; 380 } else if (cp[1] == 'L' || cp[1] == 'l') { 381 fill = LEGATO; 382 ++cp; 383 slen--; 384 } else if (cp[1] == 'S' || cp[1] == 's') { 385 fill = STACCATO; 386 ++cp; 387 slen--; 388 } 389 break; 390 } 391 } 392 } 393 394 /* 395 * ****************** UNIX DRIVER HOOKS BEGIN HERE ************************** 396 * This section implements driver hooks to run playstring() and the tone(), 397 * endtone(), and rest() functions defined above. 398 */ 399 400 static int spkr_active = FALSE; /* exclusion flag */ 401 static char *spkr_inbuf; /* incoming buf */ 402 403 static int 404 spkropen(struct cdev *dev, int flags, int fmt, struct thread *td) 405 { 406 #ifdef DEBUG 407 (void) printf("spkropen: entering with dev = %s\n", devtoname(dev)); 408 #endif /* DEBUG */ 409 410 if (spkr_active) 411 return(EBUSY); 412 else { 413 #ifdef DEBUG 414 (void) printf("spkropen: about to perform play initialization\n"); 415 #endif /* DEBUG */ 416 playinit(); 417 spkr_inbuf = malloc(DEV_BSIZE, M_SPKR, M_WAITOK); 418 spkr_active = TRUE; 419 return(0); 420 } 421 } 422 423 static int 424 spkrwrite(struct cdev *dev, struct uio *uio, int ioflag) 425 { 426 #ifdef DEBUG 427 printf("spkrwrite: entering with dev = %s, count = %zd\n", 428 devtoname(dev), uio->uio_resid); 429 #endif /* DEBUG */ 430 431 if (uio->uio_resid > (DEV_BSIZE - 1)) /* prevent system crashes */ 432 return(E2BIG); 433 else { 434 unsigned n; 435 char *cp; 436 int error; 437 438 n = uio->uio_resid; 439 cp = spkr_inbuf; 440 error = uiomove(cp, n, uio); 441 if (!error) { 442 cp[n] = '\0'; 443 playstring(cp, n); 444 } 445 return(error); 446 } 447 } 448 449 static int 450 spkrclose(struct cdev *dev, int flags, int fmt, struct thread *td) 451 { 452 #ifdef DEBUG 453 (void) printf("spkrclose: entering with dev = %s\n", devtoname(dev)); 454 #endif /* DEBUG */ 455 456 wakeup(&endtone); 457 wakeup(&endrest); 458 free(spkr_inbuf, M_SPKR); 459 spkr_active = FALSE; 460 return(0); 461 } 462 463 static int 464 spkrioctl(struct cdev *dev, unsigned long cmd, caddr_t cmdarg, int flags, 465 struct thread *td) 466 { 467 #ifdef DEBUG 468 (void) printf("spkrioctl: entering with dev = %s, cmd = %lx\n", 469 devtoname(dev), cmd); 470 #endif /* DEBUG */ 471 472 if (cmd == SPKRTONE) { 473 tone_t *tp = (tone_t *)cmdarg; 474 475 if (tp->frequency == 0) 476 rest(tp->duration); 477 else 478 tone(tp->frequency, tp->duration); 479 return 0; 480 } else if (cmd == SPKRTUNE) { 481 tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); 482 tone_t ttp; 483 int error; 484 485 for (; ; tp++) { 486 error = copyin(tp, &ttp, sizeof(tone_t)); 487 if (error) 488 return(error); 489 490 if (ttp.duration == 0) 491 break; 492 493 if (ttp.frequency == 0) 494 rest(ttp.duration); 495 else 496 tone(ttp.frequency, ttp.duration); 497 } 498 return(0); 499 } 500 return(EINVAL); 501 } 502 503 static struct cdev *speaker_dev; 504 505 /* 506 * Module handling 507 */ 508 static int 509 speaker_modevent(module_t mod, int type, void *data) 510 { 511 int error = 0; 512 513 switch(type) { 514 case MOD_LOAD: 515 speaker_dev = make_dev(&spkr_cdevsw, 0, 516 UID_ROOT, GID_WHEEL, 0600, "speaker"); 517 break; 518 case MOD_SHUTDOWN: 519 case MOD_UNLOAD: 520 destroy_dev(speaker_dev); 521 break; 522 default: 523 error = EOPNOTSUPP; 524 } 525 return (error); 526 } 527 528 DEV_MODULE(speaker, speaker_modevent, NULL); 529