DIRECTORY BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses, PulsesToMicroseconds ], MessageWindow USING [Append, Blink ], Process USING [Detach, MsecToTicks, Priority, priorityRealTime, SetPriority, SetTimeout, Ticks ], Rope USING [Length, ROPE ], TiogaOps USING [CancelSelection, CommandProc, FirstChild, GetRope, GetSelection, SelectionGrain, SelectPoint, SetSelection, ViewerDoc ], TiogaOpsDefs USING [Location, Ref, SelectionGrain ], TiogaVoicePrivate USING [ AgeAllViewers, BadRope, ButtonParams, PlayBackRequestState, PlaySelection, ReColorViewer, RecordingInProgress, SelectionsAfterRedraw, SetTiogaViewerParams, SetVoiceLooks, SetVoiceViewerEditStatus, SoundList, soundRopeCharLength, soundRopeCharsPerSecond, thrushHandle, VoiceViewerInfo ], ViewerClasses USING [MouseButton, Viewer ], ViewerOps USING [FetchProp ], VoiceRope USING [Length, NB, NewRequestID, nullRequestID, PauseNB, PlayNB, RequestID, ResumeNB, VoiceRope, VoiceRopeInterval ] ; VoicePlaybackImpl: CEDAR MONITOR IMPORTS BasicTime, MessageWindow, Process, Rope, TiogaOps, TiogaVoicePrivate, ViewerOps, VoiceRope EXPORTS TiogaVoicePrivate = { PlayBackRequestList: TYPE = LIST OF PlayBackRequest; playBackState: RECORD [ running: BOOLEAN ¬ FALSE, -- is there currently a forked instance of PlayBackProcess ? queue: PlayBackRequestList ¬ NIL, nextTime: BasicTime.Pulses, -- time to wake up playBackProcess next abort: BOOLEAN ¬ FALSE -- set when a 'stop voice playback' button is hit - playBackProcess will destroy itself next time it wakes up ]; abortCleared: CONDITION; -- signalled by PlayBackProcess when it has acted on an abort PlayBackRequest: TYPE = REF PlayBackRequestRec; PlayBackRequestRec: TYPE = RECORD[ requestID: VoiceRope.RequestID ¬ VoiceRope.nullRequestID, state: TiogaVoicePrivate.PlayBackRequestState ¬ waiting, expectFinishedReport: BOOLEAN ¬ TRUE, -- if FALSE, then this entry was edited after it was queued, so go on to the next entry automatically. See RedrawViewer. display: BOOLEAN ¬ TRUE, -- if not set then there is no displaying to be done viewer: ViewerClasses.Viewer ¬ NIL, node: TiogaOpsDefs.Ref, -- the text node is assumed to contain of rope of characters in the start: INT ¬ 0, -- range [start..end]: this is not checked by PlayBackProcess pauseStart: BasicTime.Pulses ¬ 0, end: INT ¬ 0, -- except that if display=FALSE then viewer, node are unused and these currentPos: INT ¬ -1, -- integers are used only for counting startTime: BasicTime.Pulses ¬ 0, -- when playback was started timeRemnant: INT ¬ 0 -- (end-start+1) represents the duration of the rope section in 'whole characters' rounded down: this is the rounding error in voice samples (OBSOLETE) ]; cuePriority: Process.Priority = Process.priorityRealTime; aVeryLongTime: BasicTime.Pulses = BasicTime.MicrosecondsToPulses[300000000]; oneSoundCharTime: BasicTime.Pulses = BasicTime.MicrosecondsToPulses[1000000/TiogaVoicePrivate.soundRopeCharsPerSecond]; waitForReportTime: BasicTime.Pulses = BasicTime.MicrosecondsToPulses[100000000]; -- 100 sec PlayBackProcess: PROC = { moreToDo: BOOLEAN; nextWakeUp: BasicTime.Pulses; Process.SetPriority[cuePriority]; [moreToDo, nextWakeUp] ¬ PlayBackNext[]; WHILE moreToDo DO now: BasicTime.Pulses ¬ BasicTime.GetClockPulses[]; IF nextWakeUp - now < aVeryLongTime THEN { -- so as to cope properly with wrap-around timeout: Process.Ticks ¬ Process.MsecToTicks[BasicTime.PulsesToMicroseconds[ nextWakeUp-now]/1000]; TRUSTED { Process.SetTimeout[@playBackCue, timeout]; }; Wait[]; }; [moreToDo, nextWakeUp] ¬ PlayBackNext[]; ENDLOOP; }; playBackCue: CONDITION; Wait: ENTRY PROC [] RETURNS [] ~ { WAIT playBackCue; }; PlayBackNext: ENTRY PROC RETURNS [moreToDo: BOOLEAN ¬ TRUE, nextWakeUp: BasicTime.Pulses] = { currRequest: PlayBackRequest ¬ playBackState.queue.first; IF playBackState.abort THEN { RemoveCue[currRequest]; playBackState.queue ¬ NIL; playBackState.running ¬ FALSE; playBackState.abort ¬ FALSE; TiogaVoicePrivate.SetVoiceViewerEditStatus[currRequest.viewer]; BROADCAST abortCleared; RETURN [moreToDo: FALSE, nextWakeUp: 0] -- the latter only to satisfy the compiler }; WHILE currRequest.state = done OR (NOT currRequest.expectFinishedReport AND currRequest.currentPos>currRequest.end) DO prevRequest: PlayBackRequest; RemoveCue[currRequest]; TiogaVoicePrivate.SetVoiceViewerEditStatus[currRequest.viewer]; playBackState.queue ¬ playBackState.queue.rest; IF playBackState.queue = NIL THEN { playBackState.running ¬ FALSE; RETURN[moreToDo: FALSE, nextWakeUp: 0]; }; prevRequest ¬ currRequest; currRequest ¬ playBackState.queue.first; IF NOT prevRequest.expectFinishedReport THEN IF currRequest.start > currRequest.end THEN currRequest.state ¬ done ELSE {currRequest.startTime ¬ BasicTime.GetClockPulses[]; currRequest.state ¬ busy}; ENDLOOP; IF currRequest.state = busy AND (currRequest.display OR NOT currRequest.expectFinishedReport) THEN { currRequest.currentPos ¬ currRequest.currentPos + 1; IF currRequest.display THEN { IF currRequest.currentPos-2 >= currRequest.start THEN TiogaVoicePrivate.SetVoiceLooks[currRequest.node, currRequest.currentPos-2, 1, ""]; IF currRequest.currentPos <= currRequest.end THEN TiogaVoicePrivate.SetVoiceLooks[currRequest.node, currRequest.currentPos, 1, "w"]; }; playBackState.nextTime ¬ currRequest.startTime + (currRequest.currentPos-currRequest.start+1)*oneSoundCharTime; } ELSE playBackState.nextTime ¬ BasicTime.GetClockPulses[] + waitForReportTime; RETURN[moreToDo: TRUE, nextWakeUp: playBackState.nextTime] }; RemoveCue: INTERNAL PROC [request: PlayBackRequest] ~ { IF NOT request.display THEN RETURN; IF request.start <= request.currentPos AND request.currentPos-1 <= request.end THEN TiogaVoicePrivate.SetVoiceLooks[request.node, request.currentPos-1, 2, ""]; }; QueuePlayBackCue: ENTRY PROC [request: PlayBackRequest] = { WHILE playBackState.running AND playBackState.abort DO WAIT abortCleared ENDLOOP; IF playBackState.running THEN AppendRequest[request] ELSE { playBackState.queue ¬ CONS[request, NIL]; playBackState.nextTime ¬ BasicTime.GetClockPulses[]; playBackState.running ¬ TRUE; TRUSTED {Process.Detach[FORK PlayBackProcess[]]}; }; }; AppendRequest: INTERNAL PROC [request: PlayBackRequest] = { hangOffPoint: LIST OF PlayBackRequest ¬ playBackState.queue; oneElementList: LIST OF PlayBackRequest ¬ CONS[request, NIL]; WHILE hangOffPoint.rest # NIL DO hangOffPoint ¬ hangOffPoint.rest ENDLOOP; hangOffPoint.rest ¬ oneElementList }; AbortCues: INTERNAL PROC = { IF playBackState.running THEN {playBackState.abort ¬ TRUE; BROADCAST playBackCue} }; PlayBackMenuProc: PUBLIC TiogaOps.CommandProc = { buttonParams: TiogaVoicePrivate.ButtonParams ¬ NARROW[ViewerOps.FetchProp[viewer, $ButtonParams]]; mouseButton: ViewerClasses.MouseButton ¬ IF buttonParams#NIL THEN buttonParams.mouseButton ELSE $red; IF TiogaVoicePrivate.RecordingInProgress[] THEN { MessageWindow.Append["Stop recording before trying to play back", TRUE]; MessageWindow.Blink[]; RETURN }; IF mouseButton = blue THEN -- i.e. right PlayWholeSlab[NARROW[viewer, ViewerClasses.Viewer]] ELSE { v: ViewerClasses.Viewer; start, end: TiogaOpsDefs.Location; [viewer: v, start: start, end: end] ¬ TiogaOps.GetSelection[]; IF v = NIL THEN -- no selection PlayWholeSlab[NARROW[viewer, ViewerClasses.Viewer]] ELSE IF ViewerOps.FetchProp[v, $voiceViewerInfo] # NIL THEN { IF start.node # end.node THEN ERROR; -- voice viewers only have one display node !!!! TiogaOps.SelectPoint[v, start]; -- leave caret at left of playback selection PlaySlabSection[v, start.node, start.where, end.where]; } ELSE TiogaVoicePrivate.PlaySelection[]; }; }; PlaySlabSection: PUBLIC PROC [viewer: ViewerClasses.Viewer, node: TiogaOpsDefs.Ref, from, to: INT] = { nb: VoiceRope.NB; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; newRequest: PlayBackRequest; IF viewerInfo = NIL THEN RETURN; -- somebody's buggering around with the selection IF node # TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]] THEN ERROR; newRequest ¬ NEW[PlayBackRequestRec ¬ [requestID: VoiceRope.NewRequestID[], viewer: viewer, node: node, start: from, end: to, currentPos: from-1]]; QueuePlayBackCue[newRequest]; [nb: nb] ¬ VoiceRope.PlayNB[handle: TiogaVoicePrivate.thrushHandle, voiceRope: NEW [VoiceRope.VoiceRopeInterval ¬ [viewerInfo.ropeInterval.ropeID, from*TiogaVoicePrivate.soundRopeCharLength, (to-from)*TiogaVoicePrivate.soundRopeCharLength]], requestID: newRequest.requestID, clientData: $TiogaVoice]; IF nb # $success THEN SetPlayBackState[newRequest.requestID, done]; }; PlayWholeSlab: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ¬ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF viewerInfo = NIL THEN MessageWindow.Append["Not a voice window or no selection", TRUE] ELSE { nb: VoiceRope.NB; node: TiogaOpsDefs.Ref ¬ TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]; nodeLength: INT ¬ TiogaOps.GetRope[node].Length; newRequest: PlayBackRequest ¬ NEW[PlayBackRequestRec ¬ [requestID: VoiceRope.NewRequestID[], viewer: viewer, node: node, end: nodeLength-1, timeRemnant: viewerInfo.remnant]]; QueuePlayBackCue[newRequest]; [nb: nb] ¬ VoiceRope.PlayNB[handle: TiogaVoicePrivate.thrushHandle, voiceRope: NEW [VoiceRope.VoiceRopeInterval ¬ viewerInfo.ropeInterval], requestID: newRequest.requestID, clientData: $TiogaVoice]; IF nb # $success THEN SetPlayBackState[newRequest.requestID, done]; } }; PlayRopeWithoutCue: PUBLIC PROC [voiceID: Rope.ROPE] RETURNS [badRope: TiogaVoicePrivate.BadRope]= { nb: VoiceRope.NB; fullRope: VoiceRope.VoiceRope ¬ NEW [VoiceRope.VoiceRopeInterval ¬ [voiceID, 0, 0]]; newRequest: PlayBackRequest; fullRope.length ¬ VoiceRope.Length[handle: TiogaVoicePrivate.thrushHandle, vr: fullRope]; badRope ¬ SELECT fullRope.length FROM = -1 => doesNotExist, = 0 => zeroLength, ENDCASE => okay; IF badRope < okay THEN RETURN; newRequest ¬ NEW [PlayBackRequestRec ¬ [requestID: VoiceRope.NewRequestID[], display: FALSE, end: fullRope.length/TiogaVoicePrivate.soundRopeCharLength, timeRemnant: fullRope.length MOD TiogaVoicePrivate.soundRopeCharLength]]; QueuePlayBackCue[newRequest]; [nb: nb] ¬ VoiceRope.PlayNB[handle: TiogaVoicePrivate.thrushHandle, voiceRope: fullRope, requestID: newRequest.requestID, clientData: $TiogaVoice]; IF nb # $success THEN SetPlayBackState[newRequest.requestID, done]; }; PlayBackInProgress: PUBLIC PROC RETURNS [BOOLEAN] = { RETURN [playBackState.running AND ~playBackState.abort] }; -- only a hint: a new playback request may be queued up and waiting for the abort to clear before becoming the new playBackState.queue PauseProc: PUBLIC TiogaOps.CommandProc = { buttonParams: TiogaVoicePrivate.ButtonParams ¬ NARROW[ViewerOps.FetchProp[viewer, $ButtonParams]]; mouseButton: ViewerClasses.MouseButton ¬ IF buttonParams#NIL THEN buttonParams.mouseButton ELSE $red; SELECT mouseButton FROM $red => [] ¬ VoiceRope.PauseNB[TiogaVoicePrivate.thrushHandle]; $yellow => [] ¬ VoiceRope.ResumeNB[TiogaVoicePrivate.thrushHandle]; ENDCASE; }; CancelPlayBack: PUBLIC ENTRY PROC = { AbortCues[] }; GetCurrentPlayBackPos: PUBLIC PROC RETURNS [display: BOOLEAN¬FALSE, viewer: ViewerClasses.Viewer¬NIL, currentPos: INT¬-1] = { IF playBackState.queue#NIL THEN { currRequest: PlayBackRequest ¬ playBackState.queue.first; IF currRequest.state=busy THEN RETURN[currRequest.display, currRequest.viewer, currRequest.currentPos]; }; }; SetPlayBackState: PUBLIC ENTRY PROC [reqID: VoiceRope.RequestID, newState: TiogaVoicePrivate.PlayBackRequestState] = { FOR playQ: PlayBackRequestList ¬ playBackState.queue, playQ.rest WHILE playQ#NIL DO IF playQ.first.requestID = reqID THEN { playQ.first.state ¬ newState; -- worry about busyi after donei ? IF newState=busy THEN playQ.first.startTime ¬ BasicTime.GetClockPulses[]; IF newState = paused THEN playQ.first.pauseStart ¬ BasicTime.GetClockPulses[]; IF newState = resumed THEN { playQ.first.startTime ¬ playQ.first.startTime + BasicTime.GetClockPulses[] - playQ.first.pauseStart; playQ.first.state ¬ busy; newState ¬ busy; }; IF playQ#playBackState.queue THEN FOR pQ: PlayBackRequestList ¬ playBackState.queue, pQ.rest WHILE pQ#playQ DO pQ.first.state ¬ done; -- must have missed the report ENDLOOP; BROADCAST playBackCue; RETURN; }; ENDLOOP; }; RemoveViewerReferences: PUBLIC ENTRY PROC [viewer: ViewerClasses.Viewer] RETURNS [okay: BOOLEAN ¬ TRUE] = { IF playBackState.queue = NIL THEN RETURN; IF playBackState.queue.first.display AND playBackState.queue.first.viewer = viewer THEN RETURN [FALSE]; FOR l: LIST OF PlayBackRequest ¬ playBackState.queue.rest, l.rest WHILE l # NIL DO IF l.first.viewer = viewer THEN l.first.display ¬ FALSE ENDLOOP }; RedrawViewer: PUBLIC ENTRY PROC [viewer: ViewerClasses.Viewer, newContents: Rope.ROPE, unchangedHead, deleteChars, insertChars: INT, timeRemnant: INT, age: BOOLEAN, selectionsAfterRedraw: TiogaVoicePrivate.SelectionsAfterRedraw] RETURNS [newNode: TiogaOpsDefs.Ref] = { pViewer, sViewer: ViewerClasses.Viewer; pStart, pEnd, sStart, sEnd: TiogaOpsDefs.Location; pLevel, sLevel: TiogaOpsDefs.SelectionGrain; pCaretBefore, sCaretBefore, pPendingDelete, sPendingDelete: BOOLEAN; [viewer: pViewer, start: pStart, end: pEnd, level: pLevel, caretBefore: pCaretBefore, pendingDelete: pPendingDelete] ¬ TiogaOps.GetSelection[primary]; [viewer: sViewer, start: sStart, end: sEnd, level: sLevel, caretBefore: sCaretBefore, pendingDelete: sPendingDelete] ¬ TiogaOps.GetSelection[secondary]; newNode ¬ TiogaVoicePrivate.SetTiogaViewerParams[viewer, newContents]; SELECT selectionsAfterRedraw FROM unAltered => -- if the selected character have been edited then we'll try to keep the selection on the same bits of voice { lastCharInNode: INT ¬ (TiogaOps.GetRope[newNode]).Length - 1; IF pViewer = viewer THEN { IF pStart.node # pEnd.node THEN TiogaOps.CancelSelection[primary] ELSE { pStart.node ¬ pEnd.node ¬ newNode; IF pStart.where >= unchangedHead THEN pStart.where ¬ pStart.where - deleteChars + insertChars; IF pEnd.where >= unchangedHead THEN pEnd.where ¬ pEnd.where - deleteChars + insertChars; IF pStart.where > lastCharInNode THEN pStart.where ¬ lastCharInNode; IF pEnd.where > lastCharInNode THEN pEnd.where ¬ lastCharInNode; TiogaOps.SetSelection[viewer: pViewer, start: pStart, end: pEnd, level: pLevel, caretBefore: pCaretBefore, pendingDelete: pPendingDelete, which: primary] } }; IF sViewer = viewer THEN { IF sStart.node # sEnd.node THEN TiogaOps.CancelSelection[secondary] ELSE { sStart.node ¬ sEnd.node ¬ newNode; IF sStart.where >= unchangedHead THEN sStart.where ¬ sStart.where - deleteChars + insertChars; IF sEnd.where >= unchangedHead THEN sEnd.where ¬ sEnd.where - deleteChars + insertChars; IF sStart.where > lastCharInNode THEN sStart.where ¬ lastCharInNode; IF sEnd.where > lastCharInNode THEN sEnd.where ¬ lastCharInNode; TiogaOps.SetSelection[viewer: sViewer, start: sStart, end: sEnd, level: sLevel, caretBefore: sCaretBefore, pendingDelete: sPendingDelete, which: secondary] } } }; deSelected => { IF pViewer = viewer THEN TiogaOps.CancelSelection[primary]; IF sViewer = viewer THEN TiogaOps.CancelSelection[secondary] }; primaryOnInsert => { nodeLength: INT ¬ (TiogaOps.GetRope[newNode]).Length; IF sViewer = viewer THEN TiogaOps.CancelSelection[secondary]; IF nodeLength = 0 THEN TiogaOps.CancelSelection[primary] -- in case the viewer is empty ELSE { pViewer ¬ viewer; pStart.node ¬ pEnd.node ¬ newNode; IF insertChars = 0 THEN -- leave a point selection { pStart.where ¬ pEnd.where ¬ (IF unchangedHead > nodeLength THEN nodeLength ELSE unchangedHead); pLevel ¬ point } ELSE { pStart.where ¬ unchangedHead; pEnd.where ¬ unchangedHead + insertChars -1; pLevel ¬ char }; pCaretBefore ¬ pPendingDelete ¬ FALSE; TiogaOps.SetSelection[viewer: pViewer, start: pStart, end: pEnd, level: pLevel, caretBefore: pCaretBefore, pendingDelete: pPendingDelete, which: primary] } }; ENDCASE => ERROR; IF age AND insertChars # 0 THEN TiogaVoicePrivate.AgeAllViewers[viewer] ELSE TiogaVoicePrivate.ReColorViewer[viewer]; IF playBackState.queue # NIL THEN FOR p: LIST OF PlayBackRequest ¬ playBackState.queue, p.rest WHILE p.rest # NIL DO currRequest: PlayBackRequest ¬ p.rest.first; IF currRequest.display AND currRequest.viewer = viewer THEN { currRequest.node ¬ newNode; -- I've just changed it under your feet!! [okay - monitored] IF currRequest.end >= unchangedHead -- if not, unaffected by the edit THEN { newRequests: LIST OF PlayBackRequest ¬ p.rest.rest; IF currRequest.end >= unchangedHead + deleteChars THEN -- non-empty third part { start: INT ¬ MAX[ unchangedHead + deleteChars, currRequest.start ] + (insertChars - deleteChars); end: INT ¬ currRequest.end + (insertChars - deleteChars); newRequests ¬ CONS[ NEW[ PlayBackRequestRec ¬ [requestID: currRequest.requestID, state: waiting, expectFinishedReport: TRUE, display: TRUE, viewer: viewer, node: newNode, start: start, end: end, currentPos: start-1, timeRemnant: timeRemnant]], newRequests] }; IF currRequest.start < unchangedHead + deleteChars THEN -- non-empty second part [since request cannot lie entirely in first part - see two IFs back] { start: INT ¬ MAX[unchangedHead, currRequest.start]; end: INT ¬ MIN [unchangedHead + deleteChars - 1, currRequest.end]; newRequests ¬ CONS[ NEW[ PlayBackRequestRec ¬ [requestID: currRequest.requestID, state: waiting, expectFinishedReport: FALSE, display: FALSE, start: start, end: end, currentPos: start-1, timeRemnant: 0]], newRequests] }; IF currRequest.start < unchangedHead THEN -- non-empty first part { start: INT ¬ currRequest.start; end: INT ¬ MIN [unchangedHead - 1, currRequest.end]; newRequests ¬ CONS[ NEW[ PlayBackRequestRec ¬ [requestID: currRequest.requestID, state: waiting, expectFinishedReport: FALSE, display: TRUE, viewer: viewer, node: newNode, start: start, end: end, currentPos: start-1, timeRemnant: 0]], newRequests] }; p.rest ¬ newRequests } } ENDLOOP; IF playBackState.queue # NIL THEN { currRequest: PlayBackRequest ¬ playBackState.queue.first; currentPosFound: BOOLEAN ¬ FALSE; IF currRequest.display AND currRequest.viewer = viewer THEN { newRequests: LIST OF PlayBackRequest ¬ playBackState.queue.rest; currRequest.node ¬ newNode; IF currRequest.end >= unchangedHead THEN { IF currRequest.end >= unchangedHead + deleteChars THEN { start: INT ¬ MAX[ unchangedHead + deleteChars, currRequest.start ] + (insertChars - deleteChars); end: INT ¬ currRequest.end + (insertChars - deleteChars); currentPos: INT ¬ MAX [ start-1, currRequest.currentPos + (insertChars - deleteChars) ]; newRequests ¬ CONS[ NEW[ PlayBackRequestRec ¬ [requestID: currRequest.requestID, state: waiting, expectFinishedReport: TRUE, display: TRUE, viewer: viewer, node: newNode, start: start, end: end, currentPos: currentPos, timeRemnant: timeRemnant]], newRequests]; IF currentPos # start-1 THEN currentPosFound ¬ TRUE; }; IF (~currentPosFound) AND currRequest.start < unchangedHead + deleteChars THEN { start: INT ¬ MAX[unchangedHead, currRequest.start]; end: INT ¬ MIN [unchangedHead + deleteChars - 1, currRequest.end]; currentPos: INT ¬ MAX [ start-1, currRequest.currentPos]; -- no need to put in a MIN clause since otherwise currentPosFound=TRUE and we'd never have got here newRequests ¬ CONS[ NEW[ PlayBackRequestRec ¬ [requestID: currRequest.requestID, state: waiting, expectFinishedReport: FALSE, display: FALSE, viewer: viewer, node: newNode, start: start, end: end, currentPos: currentPos, timeRemnant: 0]], newRequests]; IF currentPos # start-1 THEN currentPosFound ¬ TRUE; }; IF (~currentPosFound) AND currRequest.start < unchangedHead THEN { start: INT ¬ currRequest.start; end: INT ¬ MIN [unchangedHead - 1, currRequest.end]; currentPos: INT ¬ currRequest.currentPos; newRequests ¬ CONS[ NEW[ PlayBackRequestRec ¬ [requestID: currRequest.requestID, state: currRequest.state, expectFinishedReport: FALSE, display: TRUE, viewer: viewer, node: newNode, start: start, end: end, currentPos: currentPos, timeRemnant: 0]], newRequests] }; newRequests.first.state ¬ currRequest.state; newRequests.first.startTime ¬ currRequest.startTime; playBackState.queue ¬ newRequests; currRequest ¬ playBackState.queue.first; }; IF currRequest.display AND currRequest.currentPos>=currRequest.start THEN { start: INT ¬ MAX[currRequest.start, currRequest.currentPos-1]; len: INT ¬ MIN[currRequest.end, currRequest.currentPos] - start; TiogaVoicePrivate.SetVoiceLooks[node: currRequest.node, start: start, len: len, looks: "w"]; } } } }; }. : VoicePlaybackImpl.mesa Copyright Ó 1987, 1992 by Xerox Corporation. All rights reserved. Ades, September 24, 1986 5:11:21 pm PDT Swinehart, April 10, 1987 9:38:59 am PDT Polle Zellweger (PTZ) September 13, 1988 1:21:46 pm PDT Routines replay sounds, plus all the guff to move a cue along a slab display see TiogaVoicePrivate.mesa for the raison d'etre of this module when placed in the playback queue, a request should have currentPos=start-1 requests with end˜WK˜—Kšœžœžœ˜/šœžœžœ˜"K˜9K˜8Kšœžœžœ y˜ŸKšœ žœžœ 4˜MKšœžœ˜#Kšœ C˜[Kšœžœ =˜MK˜!Kšœžœ F˜TKšœ žœ &˜—˜šžœžœžœ ˜ Kšœžœ˜3—šžœžœ,ž œ˜=Kšžœžœžœ 0˜UKšœ  ,˜LKšœ7˜7Kšœ˜—Kšžœ#˜'K˜—K˜K˜—š¡œžœžœBžœ˜fKšœžœ˜Kšœ0žœS˜‰Kšœ˜Kš žœžœžœžœ 1˜RKšžœ8žœžœ˜EK™Kšœ žœƒ˜“K˜Kšœ˜˜CKšœ žœŸ˜­Kšœ:˜:—Kšžœžœ.˜CK˜K˜—š¡ œžœžœ#˜=Kšœ0žœS˜‰šžœžœž˜Kšœ;žœ˜@—šžœ˜Kšœžœ˜K˜IKšœ žœ!˜0KšœžœŽ˜¯K˜Kšœ˜˜CKšœ žœ9˜GKšœ:˜:—Kšžœžœ.˜CK˜—K˜K˜—š ¡œžœžœžœžœ(˜dKšœžœ˜Kšœ žœ2˜UKšœ˜K™K˜Yšœ žœž˜%Kšœ˜Kšœ˜Kšžœ ˜—Kšžœžœžœ˜K˜Kšœ žœFžœ[žœ)˜âKšœ˜˜XKšœ:˜:—Kšžœžœ.˜CK˜K˜—š¡œžœžœžœžœžœžœ ‡˜øK˜—š¡ œžœ˜*Kšžœžœžœžœžœžœžœ=™Kšœ/žœ-˜bKš œ)žœžœžœžœ˜ešžœ ž˜K˜?K˜CKšžœ˜—K˜K˜—š¡œžœž œ˜4K˜—š¡œžœžœžœ žœžœžœžœ˜}šžœžœžœ˜!K˜9šžœžœ˜KšžœB˜H—K˜—K˜K˜—š¡œžœžœžœS˜vK™7šžœ>žœž˜Sšžœžœžœ˜'Kšœ Ðcd  £ ˜AKšžœžœ4˜IK˜NK˜®K˜K˜šžœžœ˜"šžœ8žœž˜LKšœ ˜5Kšžœ˜——Kšž œ ˜Kšžœ˜K˜—Kšžœ˜—K˜K˜—š¡œžœžœžœ žœžœžœ˜kKšžœžœžœžœ˜)Kš žœ#žœ+žœžœžœ˜gš žœžœžœ4žœžœž˜RKšžœžœž˜7—Kšž˜Kšœ˜K˜—š¡ œžœžœžœ2žœ+žœžœžœBžœ ˜ŒKšœ‘™‘K™uK™ªK˜K™@Kšœ'˜'K˜2Kšœ,˜,Kšœ<žœ˜DK˜K˜–K˜˜K˜KšœÏtœ*˜FK˜K™#Kšžœž˜!šœ l˜zšœžœ*˜@Kšžœž˜šœžœžœ"˜DKšž˜˜%Kšžœžœ9˜^Kšžœžœ5˜XKšžœžœ˜DKšžœžœ˜@Kšœ™˜™—K˜—K˜Kšžœž˜šœžœžœ$˜FKšž˜˜%Kšžœžœ9˜^Kšžœžœ5˜XKšžœžœ˜DKšžœžœ˜@Kšœ›˜›—K˜—K˜—K˜—˜ šœžœžœ#˜>Kšžœžœ$˜<—K˜—˜šœžœ&˜8Kšžœžœ%˜=Kšžœžœ# ˜XKšž˜˜K˜"Kšžœ˜Kšžœ ˜šœ žœžœ žœ˜bK˜—Kšœ˜Kšž˜˜ K˜,K˜ —K˜Kšœ žœ˜&Kšœ™˜™—K˜—Kšœ˜—Kšžœžœ˜K™Kšžœžœžœ)žœ)˜uK™K™Ušžœžœžœžœžœžœ/žœ žœž˜tK˜,Kšžœžœž˜;šœ <˜[Kšžœ" !˜EKšžœ˜šœžœžœ˜6KšœÒ™ÒK™K™”K˜Kšžœ0žœ ˜Nšœ žœžœR˜eKšœžœ1˜9Kš œžœžœ`žœ žœv˜€—K˜K˜Kšžœ1žœ ]˜•šœ žœžœ$˜7Kšœžœžœ4˜BKš œžœžœ`žœ žœM˜Ù—K˜K˜Kšžœ#žœ ˜Ašœ žœ˜"Kšœžœžœ&˜4Kš œžœžœ`žœ žœl˜÷—K˜K˜K™=K˜—K˜—K˜—Kšžœ˜K˜K™ªK˜šžœžœžœ˜#K˜9Kšœžœžœ˜!šžœžœžœ˜=Kšœ žœžœ,˜@K˜šžœ"žœ˜*KšœT™TKšœy™yšžœ0žœ˜8KšœžœžœR˜bKšœžœ1˜9Kšœ žœžœD˜YKš œžœžœ`žœ žœz˜„šžœž˜Kšœžœ˜—K˜—K˜šžœžœ1žœ˜PKšœžœžœ$˜4Kšœžœžœ4˜BKšœ žœžœ% c˜Kš œžœžœ`žœ žœp˜üšžœž˜Kšœžœ˜—K˜—K˜šžœžœ#žœ˜BKšœžœ˜Kšœžœžœ&˜4Kšœ žœ˜)Kš œžœžœjžœ žœo˜„K˜—K˜,K˜4K™>K˜"K˜(K˜—K˜K™Yšžœžœ+žœ˜KKšœžœžœ.˜>Kšœžœžœ2˜@Kšœ\˜\K˜—K˜—K˜—K˜K˜—K˜K˜™(K™wKšœ Ïr9™E—™1K™-Kšœ ¥jœ¥œ™º—™1K™íKšœ ¥Nœ¥#œ¥™Ï—™1K™i—™2Kšœ ¥œ<™_—™1K™BKšœ ¥™!—™1K™dKšœ ¥™&—™3K™"Kšœ ¥™&—™2K™ÊKšœ ¥˜™¤—™2Kšœ ¥2™>—™5K™Kšœ ¥™)—™7K™›Kšœ ¥™#—™7K™]Kšœ ¥&™2—K™—…—PFÐ