:TITLE[Audio];*Last edited: 30 January 1981 by Fiala

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

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 quadaligned long pointer to the 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.

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; 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, and 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. 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.
%

*JRAM starting constant is 160000b + audGoLoc
Set[audGoLoc,6600];
***Move these two defs to GlobalDefs
Set[audPage,15];

SetTask[TTask];

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

*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,4]];
RV[audIWord,Add[tRB,10]];
RV[audOWord,Add[tRB,11]];

*Initialize base registers and busy bits here.
audGo:
audTimer ← 50000C, At[audGoLoc];*Simple timer, 13d x 6.4 us,
audTimer ← (audTimer) or (322C);*slot 2
LoadTimer[audTimer];
T ← Stack&-1;
audBufhi ← T;
audBufhi ← (LSh[audBufhi,10]) + T + 1;
T ← Stack&-1;
audBuf ← T, LoadPage[audPage];
PFetch4[audBuf,audO1st,0];
OnPage[audPage];
audRet:
audCnt ← 101400C, Return;*Return to emulator JRAM return


%Timer dispatch comes here. Timing from timer wakeup = 17 cycles when
device isn’t ready yet, else 53 cycles on even iterations, 42 on odd,
plus 3 cycles on buffer wraparound.
%
audBuf, LoadPage[audPage], Skip[R Even], At[TimerTable,15]; *Slot 2
GotoP[audRet], At[TimerTable,17];*Termination
AddToTimer[audTimer], GotoP[.+1], At[TimerTable,16];
OnPage[audPage];
*Allow 1 mi after Printer← before ←Printer for signal propagation.
*** (1 mi = 200 ns seems marginal delay for this).
audCnt ← (Printer ← audCnt) + 1, Goto[aud0,R Odd];*Enable status
T ← audOPtr;
LU ← Printer, Skip[R Even];
audCnt ← (audCnt) - 1, Return;*Block til ready

**First two samples stored after start up are garbage
PFetch1[audBuf,audOWord];
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];

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

*Input next sample
TimerTemp ← 100400C;
T ← (Printer ← TimerTemp) - 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;

:END[Audio];