[ This technote is taken from the program "auplay" by Rick Miller. M. B. ]

[ Technical notes behind "auplay"
[ ===============================
[ The original source-code for what later became au-play, then auplay
[ was posted to comp.os.linux in a barely-intelligible form.  Here's my
[ revision of the information it contained.  -Rick

From: dvs@ze8.rz.uni-duesseldorf.de (Wolfgang R. Mueller)
Newsgroups: comp.os.linux
Subject: Re: HELP NEEDED - accessing the PC speaker from linux.
Date: Tue, 13 Apr 1993 13:23:18 GMT
Organization: Computing Centre, Heinrich-Heine-University, Duesseldorf, Germany
Message-ID: <dvs.97.734707398@ze8.rz.uni-duesseldorf.de>
References: <9309812.19114@mulga.cs.mu.OZ.AU>
NNTP-Posting-Host: mueller.rz.uni-duesseldorf.de

wcs@mundil.cs.mu.OZ.AU (Warren Carter SMITH) writes:
>I would be most grateful if someone could provide any clue to me
>at all of how to access the PC's speaker from linux.

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.
---------------------------------------------------------------------

[out-dated source-code omitted for sanity's sake]

---------------------------------------------------------------
Theory of Operation:
---------------------------------------------------------------
The speaker programming model on a PC consists of bits 0 and 1 of the
system control latch at ioaddr 0x61 and the third counter of an 8253 at
ioaddr 0x42 for count values and 0x43 for control (with most significant
bits 1,0). Bit 0 of port 0x61 serves as the gate input of the counter,
the clock input is 1.193MHz (=4.77MHz/4), the output is combined with
bit 1 of port 0x61 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-Width 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 0
at zero and controlling the speaker directly by bit 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 1 constantly to 1, and
at regular intervals load the count latch with appropriate analog
values and pulse bit 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. taking every two adjacently-
stored values and using their mean.

These methods are not my own idea, but were found in a (binary only)
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.

[The following are in some form of Pascal... I don't know which. -Rick]

program example1;
  uses crt;
  var a,b,c,d,e,i,j,k,l,m,n:byte;
  f:file;
  v,w:word;
  buf:array[1..24576] of byte;
begin
  assign(f,'mushroom.ovl');
  reset(f,1);
  b:=33;
  e:=port[$61] and $fc;
  blockread(f,buf,24576,w);
  while w>0 do begin
    for v:=1 to w do begin
      a:=b; b:=buf[v];
      for i:=0 to 1 do begin
        if odd(i) then c:=b
          else c:=(a+b) shr 1;
        port[$61]:=e+2;
        for j:=1 to c do;
        port[$61]:=e;
        for j:=c to 65 do;
      end;
    end;
    blockread(f,buf,24576,w);
  end;
  close(f);
end.


program example2;
  uses crt;
  var a,b,c,d,e,i,j,k,l,m,n:byte;
  f:file;
  v,w:word;
  buf:array[1..24576] of byte;
begin
  assign(f,'mushroom.ovl');
  reset(f,1);
  b:=33;
  e:=port[$61] or $03;
  port[$43]:=$92;
  port[$42]:=b;
  blockread(f,buf,24576,w);
  while w>0 do begin
    for v:=1 to w do begin
      a:=b;
      b:=buf[v];
      for i:=0 to 1 do begin
        port[$61]:=e;
        port[$61]:=e-1;
        for j:=1 to 80 do;
        if odd(i) then port[$42]:=b
          else port[$42]:=(a+b) shr 1;
      end;
    end;
    blockread(f,buf,24576,w);
  end;
  close(f);
end.


...and that's how "auplay" began.

[ end of text ]

ok, lets explain the driver by a little picture:


I        I-------I   I------------I       I--------I            I---I SPEAK on
I        I       I   I            I       I        I            I   I
I--------I       I---I            I-------I        I------------I   I--     off
^ this time is   ^                ^ this  ^
  constant by programming timer 0   time is variable by programming timer 2
  with the samplerate (18000)       as one-shot

the "analog wave" is produced by the dynamic of the speaker, i.e. the
timeintervall produced by timer 0 must be not too short and not too long.
