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