:TITLE[Audio];*Last edited: 4 November 1981 by Fiala

* January 7, 1983 4:21 PM van Melle
* added WithAudio switch July 2, 1982 3:44 PM
* Modified for Lisp - May 19, 1981 4:45 PM

%This code manages synchronous circular buffers for audio input and output,
using a full duplex connection to the printer interface with a timer for
scheduling. Because both the Audio driver and Midas use the Printer
interface, it does not make sense to debug this driver with Midas--this means
that a good choice for placement is on page 17, overwriting the Midas Kernel.

Input and output buffers have an identical form: They begin on a quadaligned
storage boundary where the header is used as follows:
word 0
unsigned offset to 1st output word (ge 4);
word 1
unsigned offset to last output word;
word 2
unsigned offset to next output word to be processed by
microcode;
word 3
displacement from 1st word of output buffer to 1st word of
input buffer (will be added to word 2 mod 64K; ordinarily
this requires (word 1 + word 3) < 64K).
Note that combined length of input and output buffers must be < 64K words,
or < 64K samples each. Also note that if the displacement from the 1st word
in the output buffer to the 1st word in the input buffer is 0, then the
buffers will be coincident and, since output is done before input, the input
will be played back after a time delay equal to .125*nsamples/buffer msec.

To start audio input/output, push a long pointer to the quadaligned buffer
onto the stack followed by the AudioGo constant, and do JRAM. The microcode
will then process both input and output continuously until terminated by
another AudioGo JRAM with an odd long pointer as its argument (Termination
will not actually occur until the Timer wakeup following this). Microcode
never waits for software, so software must keep ahead or nullify buffer
contents when it is about to get behind and must guarantee no page faults on
audio data structures. Page or write protect faults on other kinds of pages
would worsen the timing analysis below by about 9.0 us, so they aren’t
allowed either.

New samples are handled every 125 us with the "ready" condition of the audio
device determined by polling; for guaranteed results, the timer must expire
early enough for timer resolution (6.4 us), completion by one lower and all
higher priority tasks, all competing timers, and the audio ucode itself (from
the beginning to the last Printer← or ←Printer); also, samples must not be
handled during the last 8 (10?) us window before the next sample period.

No tasks are higher priority. For the Alto system, the worst lower priority
competitor is the UTVFC, which might require 200 to 250 cycles/wakeup;
refresh is the only competing timer, requiring up to 35 cycles/wakeup.
The audio microcode below uses 51 cycles through its final io operation, so a
timer interval of 125 - 8.0 - 6.4 - 25 - 5.1 - 3.5 = 77 us appears safe.

For the Pilot/Cedar system, the worst lower priority competitor again appears
to be the UTVFC (?Check this?), which might require 62 cycles/task; refresh
and ethernet timers are the only competitors, with refresh requiring 26
cycles/wakeup. The audio microcode below uses 51 cycles through its final
io operation, so a timer interval of 125 - 8.0 - 6.4 - 6.2 - 5.1 - 2.6 = 96.7
us appears safe.

At each wakeup, if the device is ready, the next io samples are processed;
else, if no buffers are setup, add to the timer and block.

A LoadTimer/AddToTimer must follow its predecessor by at least 6 cycles and
precede its successor by 8 cycles; the hardware requires separation by 14
cycles, enforced by this convention. Branch conditions are illegal with
LoadTimer and AddToTimer.
%


SetTask[TTask];

:IF[AltoMode]; ***********************************
Set[AudTimerC,50322];
*Simple timer, 13d x 6.4 us, slot 2

*These registers may be any in the 20b-77b range addressable by TTask.
RV[audTimer,30];
RV[audCnt,31];
RV2[audBuf,audBufhi,32];
RV[audTemp,43];

*These registers must be in the group of 20b registers reserved for TTask
*because they are sources or destinations for memory references.
Set[tRB,LShift[And[TTask,3],4]];
*Enforce RM allocation requirements
RV4[audO1st,audOLast,audOPtr,audInBufOffset,Add[tRB,10]];
RV[audIWord,Add[tRB,14]];
RV[audOWord,Add[tRB,15]];
:ELSE; *******************************************
Set[AudTimerC,50362];
*Simple timer, 15d x 6.4 us, slot 2

Set[tRB,LShift[And[TTask,3],4]];
*Enforce RM allocation requirements

*These registers may be any in the 20b-77b range addressable by TTask.
RV[audTimer,34];
RV[audCnt,35];
RV2[audBuf,audBufhi,36];
RV[audTemp,Add[tRB,6]];

*These registers must be in the group of 20b registers reserved for TTask
*because they are sources or destinations for memory references.
RV4[audO1st,audOLast,audOPtr,audInBufOffset,Add[tRB,0]];
RV[audIWord,Add[tRB,4]];
RV[audOWord,Add[tRB,5]];
:ENDIF; ******************************************

SETTASK[0];
:IF[WithAudio];
IFE[WithMidas,1,ER[WARNING.Audio.can’t.be.used.with.Midas,3]];

@Audio:lspLN ← HiA[audGoLoc, TTask], call[AudA], opcode[177];
goto[nxiLBL];

AudA:
lspLN ← (lspLN) or (LoA[audGoLoc]);
APCTask&APC ← lspLN, goto[retLBL];

SETTASK[TTask];
**JRAM starting constant is 160000b + audGoLoc.
*Initialize base registers and busy bits here.
audGo:
audTimer ← HiA[AudTimerC], At[audGoLoc];
audTimer ← (audTimer) or (LoA[AudTimerC]);
LoadTimer[audTimer];
T ← Stack&-1;
audBuf ← T;
T ← rhmask[Stack&+1];
audBufhi ← T, LoadPage[audPage];
audBufhi ← (LSh[audBufhi,10]) + T + 1;
OnPage[audPage];
nop;* Wait for Base Hi to get written!!!
PFetch4[audBuf,audO1st,0];
audRet:
audCnt ← 101400C, Return;*Return to emulator JRAM return


%Timer dispatch comes here. Timing from timer wakeup = 20 cycles when
device isn’t ready yet, else 55 cycles on even iterations, 44 on odd,
plus 3 cycles on buffer wraparound.
%
audBuf, Skip[R Even], At[TimerTable,15]; *Slot 2
Return;*Termination
T ← audOPtr, LoadPage[audPage];*LoadPage must be no earlier
AddToTimer[audTimer], gotop[.+1];*AddToTimer at 5th mi after wakeup
OnPage[audPage];
audCnt ← (Printer ← audCnt) + 1, GoTo[aud0,R Odd];*Enable status
*Allow 1 mi after Printer← before ←Printer for signal propagation.
*** (1 mi = 200 ns seems marginal delay for this).
PFetch1[audBuf,audOWord];
LU ← Printer, Skip[R Even];
audCnt ← (audCnt) - 1, Return;*Block til ready
*Return at 4th mi after AddToTimer

**First two samples stored after start up are garbage
audOPtr ← (audOPtr) + 1;
audIWord ← (audIWord) xnor (0C);*Invert
LU ← (audOLast) - T;
T ← (audInBufOffset) + T, FreezeResult;
PStore1[audBuf,audIWord], GoTo[.+3,ALU#0];
T ← audO1st;
audOPtr ← T;
audOWord ← (audOWord) xnor (0C);*Invert
*Output next sample
T ← RSh[audOWord,10], GoTo[aud1];

*audCnt alternates between 101400b and 101401b.
aud0:
audCnt ← (audCnt) - (2C);
LU ← Printer, Skip[R Even];
audCnt ← (audCnt) + 1, Return;
PStore1[audBuf,audOPtr,2];*Store position for software
*Output next sample
T ← RHMask[audOWord];

*audTemp ← 100000b or sample
aud1:
audTemp ← T;
audTemp ← (audTemp) or (100000C);
T ← 1000C;
audTemp ← (Printer ← audTemp) or T;
audTemp ← (Printer ← audTemp) and not T;
Printer ← audTemp;

*Input next sample
audTemp ← 100400C;
T ← (Printer ← audTemp) - 1;*Enable data read
T ← 377C;
*←Printer appears to be always negative here
T ← (Printer) and T;
*LH=previous, RH=new sample
audIWord ← (LSh[audIWord,10]) or T, Return;

:ELSE;
@Audio:lspUFN← 177c, goto[lspUfnxP5], opcode[177];
:ENDIF;

:END[Audio];