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