* spkr.c -- device driver for console speaker
* v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993
* modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su>
* $Id: spkr.c,v 1.3 1993/10/16 13:46:21 rgrimes Exp $
#include "machine/speaker.h"
/**************** MACHINE DEPENDENT PART STARTS HERE *************************
* This section defines a function tone() which causes a tone of given
* frequency and duration from the 80x86's console speaker.
* Another function endtone() is defined to force sound off, and there is
* also a rest() entry point to do pauses.
* Audible sound is generated using the Programmable Interval Timer (PIT) and
* Programmable Peripheral Interface (PPI) attached to the 80x86's speaker. The
* PPI controls whether sound is passed through at all; the PIT's channel 2 is
* used to generate clicks (a square wave) of whatever frequency is desired.
* PIT and PPI port addresses and control values
* Most of the magic is hidden in the TIMER_PREP value, which selects PIT
* channel 2, frequency LSB first, square-wave mode and binary encoding.
* The encoding is as follows:
* +----------+----------+---------------+-----+
* | 1 0 | 1 1 | 0 1 1 | 0 |
* | SC1 SC0 | RW1 RW0 | M2 M1 M0 | BCD |
* +----------+----------+---------------+-----+
* Counter Write Mode 3 Binary
* Channel 2 LSB first, (Square Wave) Encoding
#define PPI 0x61 /* port of Programmable Peripheral Interface */
#define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */
#define PIT_CTRL 0x43 /* PIT control address */
#define PIT_COUNT 0x42 /* PIT count address */
#define PIT_MODE 0xB6 /* set timer mode for sound generation */
* Magic numbers for timer control.
#define TIMER_CLK 1193180L /* corresponds to 18.2 MHz tick rate */
/* turn off the speaker, ending current tone */
wakeup((caddr_t
)endtone
);
outb(PPI
, inb(PPI
) & ~PPI_SPKR
);
static void tone(hz
, ticks
)
/* emit tone of frequency hz for given number of ticks */
unsigned int divisor
= TIMER_CLK
/ hz
;
(void) printf("tone: hz=%d ticks=%d\n", hz
, ticks
);
/* set timer to generate clicks at given frequency in Hertz */
outb(PIT_CTRL
, PIT_MODE
); /* prepare timer */
outb(PIT_COUNT
, (divisor
& 0xff)); /* send lo byte */
outb(PIT_COUNT
, (divisor
>> 8)); /* send hi byte */
/* turn the speaker on */
outb(PPI
, inb(PPI
) | PPI_SPKR
);
* Set timeout to endtone function, then give up the timeslice.
* This is so other processes can execute while the tone is being
timeout((caddr_t
)endtone
, (caddr_t
)NULL
, ticks
);
(void) sleep((caddr_t
)endtone
, PZERO
- 1);
wakeup((caddr_t
)endrest
);
/* rest for given number of ticks */
* Set timeout to endrest function, then give up the timeslice.
* This is so other processes can execute while the rest is being
(void) printf("rest: %d\n", ticks
);
timeout((caddr_t
)endrest
, (caddr_t
)NULL
, ticks
);
(void) sleep((caddr_t
)endrest
, PZERO
- 1);
/**************** PLAY STRING INTERPRETER BEGINS HERE **********************
* Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
* M[LNS] are missing; the ~ synonym and the _ slur mark and the octave-
* tracking facility are added.
* Requires tone(), rest(), and endtone(). String play is not interruptible
* except possibly at physical block boundaries.
#define toupper(c) ((c) - ' ' * (((c) >= 'a') && ((c) <= 'z')))
#define isdigit(c) (((c) >= '0') && ((c) <= '9'))
#define dtoi(c) ((c) - '0')
static int octave
; /* currently selected octave */
static int whole
; /* whole-note time at current tempo, in ticks */
static int value
; /* whole divisor for note time, quarter note = 1 */
static int fill
; /* controls spacing of notes */
static bool octtrack
; /* octave-tracking on? */
static bool octprefix
; /* override current octave-tracking state? */
* Magic number avoidance...
#define SECS_PER_MIN 60 /* seconds per minute */
#define WHOLE_NOTE 4 /* quarter notes per whole note */
#define MIN_VALUE 64 /* the most we can divide a note by */
#define DFLT_VALUE 4 /* default value (quarter-note) */
#define FILLTIME 8 /* for articulation, break note in parts */
#define STACCATO 6 /* 6/8 = 3/4 of note is filled */
#define NORMAL 7 /* 7/8ths of note interval is filled */
#define LEGATO 8 /* all of note interval is filled */
#define DFLT_OCTAVE 4 /* default octave */
#define MIN_TEMPO 32 /* minimum tempo */
#define DFLT_TEMPO 120 /* default tempo */
#define MAX_TEMPO 255 /* max tempo */
#define NUM_MULT 3 /* numerator of dot multiplier */
#define DENOM_MULT 2 /* denominator of dot multiplier */
/* letter to half-tone: A B C D E F G */
static int notetab
[8] = {9, 11, 0, 2, 4, 5, 7};
* This is the American Standard A440 Equal-Tempered scale with frequencies
* rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
* our octave 0 is standard octave 2.
#define OCTAVE_NOTES 12 /* semitones per octave */
/* C C# D D# E F F# G G# A A# B*/
/* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123,
/* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,
/* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
/* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,
/* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
/* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
/* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
whole
= (HZ
* SECS_PER_MIN
* WHOLE_NOTE
) / DFLT_TEMPO
;
octprefix
= TRUE
; /* act as though there was an initial O(n) */
static void playtone(pitch
, value
, sustain
)
/* play tone of proper duration for current rhythm signature */
int pitch
, value
, sustain
;
register int sound
, silence
, snum
= 1, sdenom
= 1;
/* this weirdness avoids floating-point arithmetic */
for (; sustain
; sustain
--)
/* See the BUGS section in the man page for discussion */
rest(whole
* snum
/ (value
* sdenom
));
sound
= (whole
* snum
) / (value
* sdenom
)
- (whole
* (FILLTIME
- fill
)) / (value
* FILLTIME
);
silence
= whole
* (FILLTIME
-fill
) * snum
/ (FILLTIME
* value
* sdenom
);
(void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
tone(pitchtab
[pitch
], sound
);
static void playstring(cp
, slen
)
/* interpret and play an item from a notation string */
int pitch
, oldfill
, lastpitch
= OCTAVE_NOTES
* DFLT_OCTAVE
;
#define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \
{v = v * 10 + (*++cp - '0'); slen--;}
int sustain
, timeval
, tempo
;
register char c
= toupper(*cp
);
(void) printf("playstring: %c (%x)\n", c
, c
);
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
pitch
= notetab
[c
- 'A'] + octave
* OCTAVE_NOTES
;
/* this may be followed by an accidental sign */
if (cp
[1] == '#' || cp
[1] == '+')
* If octave-tracking mode is on, and there has been no octave-
* setting prefix, find the version of the current letter note
* closest to the last regardless of octave.
if (octtrack
&& !octprefix
)
if (abs(pitch
-lastpitch
) > abs(pitch
+OCTAVE_NOTES
-lastpitch
))
if (abs(pitch
-lastpitch
) > abs((pitch
-OCTAVE_NOTES
)-lastpitch
))
/* ...which may in turn be followed by an override time value */
if (timeval
<= 0 || timeval
> MIN_VALUE
)
/* ...and/or sustain dots */
for (sustain
= 0; cp
[1] == '.'; cp
++)
/* ...and/or a slur mark */
/* time to emit the actual tone */
playtone(pitch
, timeval
, sustain
);
if (cp
[1] == 'N' || cp
[1] == 'n')
octprefix
= octtrack
= FALSE
;
else if (cp
[1] == 'L' || cp
[1] == 'l')
if (octave
>= sizeof(pitchtab
) / OCTAVE_NOTES
)
if (octave
< sizeof(pitchtab
) / OCTAVE_NOTES
- 1)
for (sustain
= 0; cp
[1] == '.'; cp
++)
playtone(pitch
- 1, value
, sustain
);
if (value
<= 0 || value
> MIN_VALUE
)
/* this may be followed by an override time value */
if (timeval
<= 0 || timeval
> MIN_VALUE
)
for (sustain
= 0; cp
[1] == '.'; cp
++)
playtone(-1, timeval
, sustain
);
if (tempo
< MIN_TEMPO
|| tempo
> MAX_TEMPO
)
whole
= (HZ
* SECS_PER_MIN
* WHOLE_NOTE
) / tempo
;
if (cp
[1] == 'N' || cp
[1] == 'n')
else if (cp
[1] == 'L' || cp
[1] == 'l')
else if (cp
[1] == 'S' || cp
[1] == 's')
/******************* UNIX DRIVER HOOKS BEGIN HERE **************************
* This section implements driver hooks to run playstring() and the tone(),
* endtone(), and rest() functions defined above.
static int spkr_active
= FALSE
; /* exclusion flag */
static struct buf
*spkr_inbuf
; /* incoming buf */
(void) printf("spkropen: entering with dev = %x\n", dev
);
(void) printf("spkropen: about to perform play initialization\n");
spkr_inbuf
= geteblk(DEV_BSIZE
);
printf("spkrwrite: entering with dev = %x, count = %d\n",
else if (uio
->uio_resid
> DEV_BSIZE
) /* prevent system crashes */
cp
= spkr_inbuf
->b_un
.b_addr
;
if (!(error
= uiomove(cp
, n
, uio
)))
(void) printf("spkrclose: entering with dev = %x\n", dev
);
int spkrioctl(dev
, cmd
, cmdarg
)
(void) printf("spkrioctl: entering with dev = %x, cmd = %x\n");
else if (cmd
== SPKRTONE
)
tone_t
*tp
= (tone_t
*)cmdarg
;
tone(tp
->frequency
, tp
->duration
);
else if (cmd
== SPKRTUNE
)
tone_t
*tp
= (tone_t
*)(*(caddr_t
*)cmdarg
);
error
= copyin(tp
, &ttp
, sizeof(tone_t
));
tone(ttp
.frequency
, ttp
.duration
);
#endif /* NSPEAKER > 0 */