From: Rick Miller (rick@ee.uwm.edu)
Date: 04/13/93


From: Rick Miller <rick@ee.uwm.edu>
Subject: *.au --> PC speaker (revised)
Date: 13 Apr 1993 16:23:50 GMT

I snagged the code for playing *.au files on the PC's internal speaker,
cleaned it up a bit, and adjusted it for my whimpy 386sx/20...

/*
Original code was written by:
Wolfgang R. Mueller <dvs@ze8.rz.uni-duesseldorf.de>,
Computing Centre, Heinrich-Heine-University, Duesseldorf, Germany.

Reformatted and readjusted by:
Rick Miller <rick@ee.uwm.edu> | <ricxjo@discus.mil.wi.us> Ricxjo Muelisto
Send a postcard, get one back! | Enposxtigu bildkarton kaj vi ricevos alion!
          RICK MILLER // 16203 WOODS // MUSKEGO, WIS. 53150 // USA
*/

/*
The following little source can play the Internet Talk Radio .au files.
It might need some adjustments in the delay loops for other than 386/40.
=====================================================================
*/
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/mm.h>

/* #define DELAY 60 /* original value for 386/40 */
#define DELAY 12 /* works for my 386s/20 */

static void inline port_out(char value, unsigned short port)
{
__asm__ volatile ("outb %0,%1"
                ::"a" ((char) value),"d" ((unsigned short) port));
}
static unsigned char inline port_in(unsigned short port)
{
        unsigned char _v;
__asm__ volatile ("inb %1,%0"
                :"=a" (_v):"d" ((unsigned short) port));
        return _v;
}

void main()
{
unsigned char a,b,e,i,j, ulaw[256];
short l;

if (ioperm(0x61,1,1)||ioperm(0x42,1,1)||ioperm(0x43,1,1)) {
    printf("can't get I/O permissions \n");
    exit(-1);
}
for(l=0;l<256;l++) {
    b=(l&0x7f)>>2;
    ulaw[l]=l&0x80?63-b:b;
}
b=0x33;
e=port_in(0x61)|0x03;
port_out(0x92,0x43);
port_out(b,0x42);
while(l!=EOF) {
    a=b;
    l=getchar();
    b=ulaw[l&0xff];
    for(j=1;j<DELAY;j++);
    port_out(e,0x61);
    port_out(e-1,0x61);
    port_out((a+b)>>1,0x42);
    for(j=1;j<DELAY;j++);
    port_out(e,0x61);
    port_out(e-1,0x61);
    port_out(b,0x42);
    for(j=1;j<DELAY;j++);
    port_out(e,0x61);
    port_out(e-1,0x61);
    port_out(b,0x42);
}

} /* main */
/*
===============================================================
For the theory of operation:
===============================================================
The speaker programming model on a PC consists of bits 2^0 and 2^1 of the
system control latch at ioaddr 61h and the third counter of a 8253 at
ioaddr 42h for count values and 43h for control (with most significant
bits 1,0). Bit 2^0 of port 61h serves as the gate input of the counter,
the clock input is 1.193MHz (=4.77MHz/4), the output is combined with
bit 2^1 of port 61h by an and gate feeding the speaker amplifier. Thus
only two discrete values can be sent to the speaker: pull-in, push-out.
Bios programs the counter as a frequency divider and sets both bits to
produce monophonic squarewave beeps.
To get something like an analog signal you must use a technique known
as pulse length modulation, i.e. change very often between pull-in and
push-out varying the relative duration of these states according to the
wanted analog value, then the inertia of speaker and human ear will
make you experience an am radio quality sound.
On very fast PCs (386/40 for example) you can do this by leaving bit
2^0 zero and controlling the speaker directly by bit 2^1 using suitable
delay loops between switchings (example 1 below).
On slower machines (even down to 4.77MHz XTs) you program the
counter as a retriggerable oneshot (mode 1, a 1 on the gate input
(re)starts the counter and sets the output to 0, expiration of the
count resets the output back to 1), set bit 2^1 constantly to 1, and
at regular intervals load the count latch with appropriate analog
values and pulse bit 2^0 (example 2 below).
These regular intervals can be generated by delay loops or by abusing
the first counter (normally giving the timer tics every 50 msec) by
lowering the divider to not much more than the maximum count value
used in the oneshot counter and doing the regular work as an
interrupt service routine for int 8 == irq 0 .
The amount of amplitude data required can be reduced significantly
by using twofold oversampling, i.e. using between every two adjacently
stored values their mean.
These methods are not my own idea, but were found in a (binary only
present) program named mushroom (perhaps after the music data
acompanying it).
This program awakened my interrest because of the surprising am radio
quality sound out of the normal PC hardware.
5+1 bits of amplitude imply a maximum count value of 65 or
1193180/65 = about 18000 amplitudes per second. Using twofold
oversampling then means 9KB per second audio data.
Please understand that I do not include the 270 KB data for a 30 sec
commercial about a device distributing good smells and having the
shape of a mushroom.