DIRECTORY FS USING [Read, Write], IO USING [int, PutFR, STREAM], Jukebox USING [bytesPerChirp, CloseTune, EOF, Error, FindChirp, MissingChirp, OpenTune, TuneSize, RunComponent, singlePktLength, RunElementType, RunArrayRange, Tune], Process USING [Detach, Priority, priorityNormal, SetPriority], Rope USING [ROPE], RuntimeError USING [ UNCAUGHT ], VM USING [AddressForPageNumber], VoiceStream; VoiceStreamServerImpl: MONITOR LOCKS VoiceStream.Lock IMPORTS FS, IO, Jukebox, Process, RuntimeError, VM, VoiceStream EXPORTS VoiceStream = BEGIN Foo: SIGNAL = CODE; debugRuns: BOOL _ FALSE; serverPriority: Process.Priority _ Process.priorityNormal; GetServerBuffer: ENTRY PROC RETURNS [handle: VoiceStream.Handle] = { ENABLE UNWIND => NULL; buffer: REF VoiceStream.Buffer; WHILE TRUE DO handle _ VoiceStream.vSList; WHILE handle # NIL DO IF (handle.errorRope = NIL) THEN { IF (handle.firstServerBuffer # NIL) THEN { buffer _ handle.firstServerBuffer; handle.firstServerBuffer _ buffer.next; buffer.next _ handle.firstIdleBuffer; handle.firstIdleBuffer _ buffer; RETURN [handle]; } ELSE IF (handle.piece # NIL) THEN { IF handle.firstClientBuffer = NIL THEN RETURN [handle]; IF (NOT handle.piece.flush AND (handle.piece.intervalSpec.length > 0) AND (handle.firstIdleBuffer # NIL)) THEN RETURN [handle]; }; }; handle _ handle.next; ENDLOOP; BROADCAST VoiceStream.closeCondition; WAIT VoiceStream.serverCondition; ENDLOOP; }; GiveClientBuffer: ENTRY PROC [handle: VoiceStream.Handle] = { ENABLE UNWIND => NULL; buffer, b2: REF VoiceStream.Buffer; buffer _ handle.firstIdleBuffer; handle.firstIdleBuffer _ buffer.next; buffer.next _ NIL; IF handle.firstClientBuffer = NIL THEN { handle.firstClientBuffer _ buffer; NOTIFY handle.newPiece; } ELSE { b2 _ handle.firstClientBuffer; WHILE b2.next # NIL DO b2 _ b2.next ENDLOOP; b2.next _ buffer; }; BROADCAST VoiceStream.client; }; SetError: PUBLIC ENTRY PROC [handle: VoiceStream.Handle, rope: Rope.ROPE, code: VoiceStream.ErrorCode] = { ENABLE UNWIND => NULL; handle.errorRope _ rope; handle.errorCode _ code; BROADCAST VoiceStream.client; BROADCAST VoiceStream.waitCondition; }; GetPiece: ENTRY PROC [handle: VoiceStream.Handle] RETURNS [VoiceStream.Piece] = { ENABLE UNWIND => NULL; piece: VoiceStream.Piece; tune: Jukebox.Tune_NIL; WHILE TRUE DO piece _ handle.piece; IF piece = NIL THEN { handle.pieceInProgress _ FALSE; RETURN [NIL]; }; IF piece.flush THEN piece.intervalSpec.length _ 0; IF handle.pieceInProgress THEN { nxP: VoiceStream.Piece _ piece.next; IF piece.intervalSpec.length > 0 THEN RETURN [piece]; tune _ piece.tune; IF (handle.firstClientBuffer # NIL) OR (handle.firstServerBuffer # NIL) THEN RETURN [NIL]; -- No buffer-ahead IF handle.proc#NIL THEN -- Report completion of a piece handle.proc[handle, $finished, piece.clientData]; IF tune # NIL AND (nxP = NIL OR nxP.intervalSpec.tuneID # piece.intervalSpec.tuneID) THEN { Jukebox.CloseTune[handle.jukebox, tune]; tune _ NIL; }; handle.piece _ piece _ piece.next; }; handle.pieceInProgress _ TRUE; IF piece = NIL THEN { BROADCAST VoiceStream.client; LOOP; }; IF tune=NIL THEN tune _ Jukebox.OpenTune[handle.jukebox, piece.intervalSpec.tuneID, ~piece.playback]; piece.tune _ tune; IF handle.proc#NIL THEN -- Report start of next piece (see AddPiece for report of first) handle.proc[handle, $started, handle.piece.clientData]; ENDLOOP; RETURN [NIL]; }; Server: PROC [] = { handle: VoiceStream.Handle; buffer: REF VoiceStream.Buffer; ourSize: INT; skipLeading: INT; piece: VoiceStream.Piece; noChirp: BOOLEAN; Process.SetPriority[serverPriority]; WHILE TRUE DO ENABLE { Jukebox.Error => { SetError[handle: handle, code: BadPiece, rope: IO.PutFR["Piece for tune id %d starting at byte %d can't be accessed.", IO.int[piece.intervalSpec.tuneID], IO.int[piece.intervalSpec.start]]]; CONTINUE; }; Jukebox.EOF => { piece.intervalSpec.length _ 0; CONTINUE; }; RuntimeError.UNCAUGHT => { SetError[handle: handle, code: Bug, rope: "Unknown error in voice stream demon."]; REJECT; }; }; handle _ GetServerBuffer[]; buffer _ handle.firstIdleBuffer; IF buffer.toJukebox AND buffer.valid THEN { buffer.valid _ FALSE; buffer.window _ Jukebox.FindChirp[self: handle.jukebox, tune: handle.piece.tune, chirp: buffer.chirp, signalMissingChirp: FALSE, signalEOF: FALSE]; IF buffer.bytesAccountedFor < Jukebox.bytesPerChirp THEN { IF buffer.runData.runArray[buffer.runIndex].elementType ~= silence THEN { buffer.runIndex _ buffer.runIndex + (IF buffer.runData.runArray[buffer.runIndex].elementType = soundEnergy THEN 2 ELSE 1); buffer.runData.runArray[buffer.runIndex] _ [silence[0]]; }; buffer.runData.runArray[buffer.runIndex]_[ silence[ NARROW[buffer.runData.runArray[buffer.runIndex], Jukebox.RunComponent[silence]].length +(Jukebox.bytesPerChirp - buffer.bytesAccountedFor)] ]; buffer.bytesAccountedFor _ Jukebox.bytesPerChirp -- just for purity }; CheckRunData[buffer]; FS.Write[file: buffer.window.file, to: buffer.window.base, nPages: buffer.chirpSpace.count, from: VM.AddressForPageNumber[buffer.chirpSpace.page]]; }; piece _ GetPiece[handle]; IF piece = NIL THEN LOOP; buffer.keyIndex _ piece.intervalSpec.keyIndex; IF piece.tune = NIL OR NOT (piece.playback OR piece.tune.writable) THEN ERROR; IF piece.intervalSpec.start < 0 THEN piece.intervalSpec.start _ Jukebox.TuneSize[piece.tune] * Jukebox.bytesPerChirp; buffer.chirp _ piece.intervalSpec.start / Jukebox.bytesPerChirp; skipLeading _ piece.intervalSpec.start MOD Jukebox.bytesPerChirp; buffer.bytesAccountedFor _ skipLeading; ourSize _ Jukebox.bytesPerChirp - skipLeading; IF piece.intervalSpec.length < ourSize THEN { IF piece.playback THEN ourSize _ piece.intervalSpec.length ELSE piece.intervalSpec.length _ ourSize; IF piece.playback THEN buffer.bytesAccountedFor _ Jukebox.bytesPerChirp - ourSize; }; piece.intervalSpec.length _ piece.intervalSpec.length - ourSize; piece.intervalSpec.start _ piece.intervalSpec.start + ourSize; buffer.runIndex _ 0; buffer.playedBytes _ 0; IF (ourSize # Jukebox.bytesPerChirp) OR piece.playback THEN { noChirp _ FALSE; buffer.window _ Jukebox.FindChirp[self: handle.jukebox, tune: piece.tune, chirp: buffer.chirp, signalEOF: piece.playback, signalMissingChirp: TRUE ! Jukebox.MissingChirp => {noChirp _ TRUE; CONTINUE}]; IF noChirp THEN { IF piece.playback THEN { buffer.block.startIndex _ 0; buffer.block.stopIndexPlusOne _ 0; buffer.runData.runArray[0] _ [silence[ourSize]]; } ELSE { -- build a fake chirp with the right structure buffer.block.startIndex _ 0; buffer.block.stopIndexPlusOne _ ourSize; buffer.runData.runArray[0] _ [silence[skipLeading]]; }; } ELSE { FS.Read[file: buffer.window.file, from: buffer.window.base, nPages: buffer.chirpSpace.count, to: VM.AddressForPageNumber[buffer.chirpSpace.page]]; IF skipLeading > 0 THEN { runSize: CARDINAL; buffer.block.startIndex _ 0; DO WITH curr: buffer.runData.runArray[buffer.runIndex] SELECT FROM silence => runSize _ curr.length; singlePkt => runSize _ Jukebox.singlePktLength; soundEnergy => runSize _ runSize _ NARROW[buffer.runData.runArray[buffer.runIndex+1], Jukebox.RunComponent[soundLength]].length; ENDCASE => ERROR; IF skipLeading >= runSize THEN { skipLeading _ skipLeading - runSize; IF buffer.runData.runArray[buffer.runIndex].elementType # silence THEN buffer.block.startIndex _ buffer.block.startIndex + runSize; buffer.runIndex _ buffer.runIndex + (IF buffer.runData.runArray[buffer.runIndex].elementType=soundEnergy THEN 2 ELSE 1); } ELSE { -- last run: WITH curr: buffer.runData.runArray[buffer.runIndex] SELECT FROM silence => curr.length _ runSize - skipLeading; soundEnergy => buffer.runData.runArray[buffer.runIndex+1] _ [soundLength[IF piece.playback THEN NARROW[buffer.runData.runArray[buffer.runIndex+1], Jukebox.RunComponent[soundLength]].length - skipLeading ELSE skipLeading]]; -- but on record this is what is already there singlePkt => IF piece.playback THEN buffer.playedBytes _ skipLeading ELSE {buffer.runData.runArray[buffer.runIndex] _ [soundEnergy[NARROW[buffer.runData.runArray[buffer.runIndex], Jukebox.RunComponent[soundEnergy]].energy]]; buffer.runData.runArray[buffer.runIndex+1] _ [soundLength[skipLeading]]; }; ENDCASE; EXIT; }; ENDLOOP; buffer.block.stopIndexPlusOne _ buffer.block.startIndex + ourSize; } ELSE { buffer.block.startIndex _ 0; buffer.block.stopIndexPlusOne _ ourSize; }; }; } ELSE { -- whole chirp is involved and it is record buffer.block.startIndex _ 0; buffer.block.stopIndexPlusOne _ ourSize; buffer.runData.runArray[0] _ [silence[0]] }; buffer.toJukebox _ NOT piece.playback; IF buffer.toJukebox THEN buffer.runData.ambientLevel _ piece.intervalSpec.ambientLevel; GiveClientBuffer[handle]; ENDLOOP; }; CheckRunData: PROC [buffer: REF VoiceStream.Buffer] = { total: CARDINAL _ 0; run: CARDINAL; currElement: Jukebox.RunElementType _ silence; -- must simply start as ~soundEnergy FOR ri: Jukebox.RunArrayRange IN [0..95) DO IF currElement = soundEnergy -- acutally it is prevElement is this context THEN IF buffer.runData.runArray[ri].elementType = soundLength THEN {currElement _soundLength; LOOP} ELSE ERROR; currElement _ buffer.runData.runArray[ri].elementType; WITH curr: buffer.runData.runArray[ri] SELECT FROM silence => run _ curr.length; singlePkt => run _ Jukebox.singlePktLength; soundEnergy => run _ NARROW[buffer.runData.runArray[ri+1], Jukebox.RunComponent[soundLength]].length; ENDCASE => ERROR; total _ total + run; IF total > Jukebox.bytesPerChirp THEN { WITH curr: buffer.runData.runArray[ri] SELECT FROM silence => curr.length _ run - (total - Jukebox.bytesPerChirp); soundEnergy => buffer.runData.runArray[ri+1] _ [soundLength[run - (total - Jukebox.bytesPerChirp)]]; singlePkt => { -- since this is last valid entry in chirp, okay to trample 'nextElement' buffer.runData.runArray[buffer.runIndex]_ [soundEnergy[NARROW[buffer.runData.runArray[buffer.runIndex], Jukebox.RunComponent[singlePkt]].energy]]; buffer.runData.runArray[ri+1] _ [soundLength[Jukebox.singlePktLength - (total - Jukebox.bytesPerChirp)]] }; ENDCASE; IF debugRuns THEN SIGNAL Foo; EXIT; }; IF total = Jukebox.bytesPerChirp THEN EXIT; ENDLOOP; }; StartServer: PUBLIC PROC [stream: IO.STREAM] = { VoiceStream.ioStream _ stream; }; Init: PROC = { p: PROCESS; p _ FORK Server[]; Process.Detach[p]; }; Init[]; END. 1983, Stewart, run-encoded chirps May 10, 1986 4:14:09 pm PDT, Stewart, Cedar 5, December  File: VoiceStreamServerImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. This file contains part of the implementation of voicestreams. The routines in this file implement the voicestream server, a background process that transfers information between disk and buffers in voicestreams. Ades, February 10, 1986 4:01:59 pm PST Swinehart, June 17, 1986 11:56:14 pm PDT Last Edited by: Ousterhout, March 8, 1983 11:39 am compile Last Edited by: Stewart, January 3, 1984 11:47 am This is a monitor procedure used by the server to wait for work to do. For the server to have work to do, there must not be any errors associated with the stream. Furthermore, either there must be a buffer waiting for the server, or there must be an idle buffer and there must be a valid piece waiting for the server. If there is a server buffer waiting, this routine transfers the frontmost server buffer to the front of the idle list, from which position the server will process it. Note: it doesn't make any difference how the buffers on the idle list are ordered. Notify anybody trying to close a stream that we've done all the work we can, and are waiting. This procedure just records error information in a voice stream and wakes up waiting clients. This is made a separate procedure because exclusive access is needed on the voice stream, which the server doesn't have. This procedure is called only by the server. It is made a separate procedure in order to guarantee exclusive access to the voice stream information. Because of the high overheads in opening and closing tunes, we check the next piece and if it's the same tune, then don't bother closing the current one. NIL is returned if either there are no pieces left, or if all the space of the frontmost piece is allocated to buffers but the buffers haven't been processed completely. This means we can't buffer ahead across pieces, but the alternative results in messy synchronization problems. Client notification of the start-of-transfer ($started) and end-of-transfer ($finished) for each piece is done here. Piece in progress is now finished. This procedure is responsible for buffering ahead and behind the client processes. Be VERY careful with this code. The procedure runs unmonitored, so that other processes can be getting and putting voice data while we do disk operations. However, you must be extremely careful to make sure that the processes don't get in each others' way. On EOF, just discard the rest of the current piece. IO.PutRope[ioStream, "Unknown error in voice stream demon.\n"]; If this buffer is to be written, and the buffer is valid, then write it out. make sure that no partial chirps are recorded! fill the rest of the chirp with silence last thing placed in chirp was non-silent IO.PutF[ioStream, "Wrote chirp %d to disk.\n", IO.int[buffer.chirp]]; Collect information for the next buffer's worth of voice. First of all, find a piece that isn't empty. ioStream.PutF["Number of bytes left: %d\n", IO.int[piece.intervalSpec.length]]; Set up the encryption key index (copy over from piece). Make sure that the tune is open. Figure out which chirp this is. Figure out which portion of the chirp we'll actually use. Trim the tail if needed disallow a recording that ends in the middle of a chirp Account for the part of the piece in this chirp. Read in the chirp, if necessary. Note: we don't look up the disk location until its time to actually transfer information. This way, the jukebox routines won't allocate the space until it's actually needed. Also, for playback we substitute a homemade all-silent chirp if there isn't a chirp on the disk. Construct a chirp with a single run of silence Read in the whole chirp, including its runData The value of skipLeading is the amount to chase down the runData skip the whole run i.e. on playback this is what is left to be played back since we are recording it is okay to rewrite this and the next runArray entries Not all of this is needed, since some might be silence, but we don't know how much and don't want to spend the time analyzing the runData. IO.PutF[ioStream, "Read chirp %d from disk.\n", IO.int[buffer.chirp]]; This procedure simply forks the stream buffer server and initializes shared state. Swinehart, May 10, 1986 4:13:59 pm PDT Convert to new communications package changes to: VoiceStreamServerImpl, EXPORTS, BEGIN, GetServerBuffer, GiveClientBuffer, SetError, GetPiece, Server, CheckRunData Swinehart, June 9, 1986 9:52:06 am PDT create option no longer supported. Tunes must be open by arrival at server, GetServerBuffer, GetPiece Κ ƒ˜šœ ™ Icodešœ Οmœ1™<—JšœC™CJšœD™DJšœ>™>šœ ™ K™&K™(—J˜Jšœ:™:Jšœ1™1J˜šΟk ˜ Jšžœžœ˜Jšžœžœžœ˜Jšœžœžœz˜¦Jšœžœ1˜>Jšœžœžœ˜Jšœ žœžœ˜ Jšžœžœ˜ J˜ J˜—Jšœžœžœ˜5Jšžœžœžœ"žœ ˜?šžœ˜J˜—Jšž˜Jšœžœžœ˜Jšœ žœžœ˜J˜:J˜šœžœžœžœ!˜DJ˜JšœΌ™ΌJ˜Jšžœžœžœ˜J˜Jšœžœ˜J˜šžœžœž˜ J˜šžœ žœž˜šžœžœžœ˜"šžœžœžœ˜*J˜"J˜'J˜%J˜ Jšžœ ˜Jšœ˜—šž˜šžœžœžœ˜Jšžœžœžœžœ ˜7šžœžœ˜Jšžœ'˜*Jšžœžœ˜#Jšžœžœ ˜—Jšœ ˜ ——Jšœ˜—J˜Jšžœ˜J˜—Jšœ;™;Jšœ!™!J˜Jšž œ˜%Jšžœ˜!Jšžœ˜—Jšœ˜J˜—J˜šœžœžœ!˜=Jšžœžœžœ˜J˜Jšœ žœ˜#J˜J˜ J˜%Jšœžœ˜Jšžœž˜!šžœ˜Jšœ"˜"Jšžœ˜J˜—šžœ˜J˜Jšžœ žœžœžœ˜,J˜Jšœ˜—Jšž œ˜Jšœ˜J˜—š œ žœžœžœ)žœ"˜jJ˜Jšœ8™8Jšœ=™=Jšœ9™9Jšœ&™&J˜Jšžœžœžœ˜J˜J˜Jšž œ˜Jšž œ˜$Jšœ˜J˜—J˜Jšœ žœžœžœ˜Q˜Jšœ,™,J˜Jšœ™J™J™tJ˜Jšžœžœžœ˜J˜Jšœ˜Jšœžœ˜J˜šžœžœž˜ J˜Jš žœ žœžœžœžœžœ˜FJšžœ žœ˜2šžœžœ˜ J˜$Jšžœžœžœ ˜5J™"J˜šžœžœ˜#Jš žœžœžœžœžœΟc˜I—šžœ žœžœŸ˜7Jšœ1˜1—š žœžœžœžœžœ5˜Tšžœ˜J˜(Jšœžœ˜ J˜——J˜"J˜—Jšœžœ˜Jš žœ žœžœž œžœ˜<šžœžœžœ˜JšœM˜M—J˜šžœ žœžœŸ@˜XJšœ7˜7—Jšžœ˜—Jšžœžœ˜ Jšœ˜J˜—J˜Jšœžœ˜˜JšœΦ™ΦJ˜J˜Jšœžœ˜Jšœ žœ˜ Jšœ žœ˜Jšœ˜Jšœ žœ˜J˜J˜$J˜šžœžœž˜ šžœ˜˜˜(šœžœE˜MJšžœ!žœ!˜F——Jšžœ˜ Jšœ˜J˜—Jšœ3™3J˜šœžœ˜J˜Jšžœ˜ Jšœ˜J˜—šœ žœ˜˜#J˜.—Jšžœ=™?Jšžœ˜Jšœ˜—Jšœ˜—J˜J˜ J˜JšœD™DJšœ™J˜šžœžœžœ˜+Jšœžœ˜˜PJšœ)žœ žœ˜B—J™.šžœ2žœ˜:J™'šžœAžœ˜IJ™)šœ#˜#JšœžœDžœžœ˜V—Jšœ8˜8Jšœ˜—šœ*˜*šœ˜šžœ*˜0Jšœ%˜%—Jšœ4˜4Jšœ˜——JšœC˜CJšœ˜—Jšœ˜Jšžœ`žœ/˜“JšœE™EJšœ˜J˜—JšœH™HJšœ™J˜J˜Jšžœ žœžœžœ˜JšœO™OJ™Jšœ7™7Jšœ.˜.J˜Jšœ ™ J˜šžœžœžœžœžœžœžœ˜OJ˜—Jšœ™J˜JšžœžœQ˜uJ˜Jšœ@˜@J˜Jšœ9™9J˜Jšœ'žœ˜AJšœ'˜'Jšœ.˜.J™J™šžœ%žœ˜-J™7Jšžœžœ$˜:Jšžœ%˜)Jšžœžœ<˜RJ˜—J˜J™0J˜@Jšœ>˜>J˜Jšœ³™³J˜Jšœ˜J˜J˜šžœ#žœžœ˜=Jšœ žœ˜˜IJ˜/Jšœž˜Jšœ%žœžœ˜6—šžœ žœ˜šžœžœ˜J™.J˜Jšœ"˜"Jšœ0˜0J˜—šžœŸ.˜5J˜Jšœ(˜(Jšœ4˜4Jšœ˜—Jšœ˜—šžœ˜J™.Jšžœ_žœ/˜’šžœžœ˜Jšœ žœ˜Jšœ@™@Jšœ˜šž˜Jš žœ0žœžœvžœXžœžœ˜₯šžœžœ˜ J™Jšœ$˜$Jšžœ@žœ=˜ƒJšœ%žœBžœžœ˜xJ˜—šžœŸ ˜Jšžœ0žœž˜?Jšœzžœžœžœd˜ϋJšœ7™7Jš žœŸ/œ žœžœ"žœ˜JšœO™OJšœ:žœ€žœžœ˜ςJ˜—Jšžœ˜—JšœB˜BJ˜—šžœ˜J˜J™ŠJšœ(˜(J˜—J˜—JšœF™FJšœ˜—šžœŸ+˜2J˜Jšœ(˜(Jšœ)˜)J˜—Jšœžœ˜&J˜WJ˜Jšžœ˜—Jšœ˜—J˜J˜šΟn œžœ žœ˜7Jšœžœ˜Jšœžœ1Ÿ%˜cšžœžœ žœžœŸ.œžœžœ7žœžœžœžœ˜οJš œ7žœ#žœžœ`žœLžœžœ˜Γšžœž˜'šžœ#žœž˜2Jšœ?˜?Jšœe˜ešœŸJ˜XJšœ9žœW˜–Jšœj˜jJšœ˜—Jšžœ˜Jšžœ žœžœ˜Jšžœ˜J˜——Jšžœžœžœ˜+Jšžœ˜—J˜—J˜Jš   œžœžœ žœžœ'˜RJ˜Jš œžœ˜˜Jšœ8™8Jšœ™J˜Jšœžœ˜ J˜Jšœžœ ˜J˜Jšœ˜—J˜Jšœ˜J˜Jšžœ˜J˜!Jšœ-Οr ˜7J˜™&K™%Kšœ ‘r™~—™&KšœK‘™f—K™K™—…—(’G/