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