:TITLE[Audio]; *Last edited: 4 November 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. 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. % IFE[WithMidas,1,ER[WARNING.Audio.can't.be.used.with.Midas,3]]; 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,4]]; RV[audIWord,Add[tRB,10]]; RV[audOWord,Add[tRB,11]]; :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; ****************************************** **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; 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 = 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]; *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; :END[Audio];e12(1795)\f5