: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];e12(1795)