Commit | Line | Data |
---|---|---|
15637ed4 RG |
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" | |
dd18dc33 | 15 | #include "systm.h" |
15637ed4 RG |
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 | ||
61 | static 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 | ||
68 | static void tone(hz, ticks) | |
69 | /* emit tone of frequency hz for given number of ticks */ | |
70 | unsigned 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 | ||
98 | static int endrest() | |
99 | /* end a rest */ | |
100 | { | |
101 | wakeup((caddr_t)endrest); | |
102 | } | |
103 | ||
104 | static void rest(ticks) | |
105 | /* rest for given number of ticks */ | |
106 | int 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 | ||
128 | typedef 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 | ||
136 | static int octave; /* currently selected octave */ | |
137 | static int whole; /* whole-note time at current tempo, in ticks */ | |
138 | static int value; /* whole divisor for note time, quarter note = 1 */ | |
139 | static int fill; /* controls spacing of notes */ | |
140 | static bool octtrack; /* octave-tracking on? */ | |
141 | static 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 */ | |
162 | static 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 */ | |
170 | static 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 | ||
182 | static 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 | ||
192 | static void playtone(pitch, value, sustain) | |
193 | /* play tone of proper duration for current rhythm signature */ | |
194 | int 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 | ||
224 | static int abs(n) | |
225 | int n; | |
226 | { | |
227 | if (n < 0) | |
228 | return(-n); | |
229 | else | |
230 | return(n); | |
231 | } | |
232 | ||
233 | static void playstring(cp, slen) | |
234 | /* interpret and play an item from a notation string */ | |
235 | char *cp; | |
236 | size_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 | ||
411 | static int spkr_active; /* exclusion flag */ | |
412 | static struct buf *spkr_inbuf; /* incoming buf */ | |
413 | ||
414 | int spkropen(dev) | |
415 | dev_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 | ||
434 | int spkrwrite(dev, uio) | |
435 | dev_t dev; | |
436 | struct 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 | ||
459 | int spkrclose(dev) | |
460 | dev_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 | ||
477 | int spkrioctl(dev, cmd, cmdarg) | |
478 | dev_t dev; | |
479 | int cmd; | |
480 | caddr_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 */ |