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