DIRECTORY Atom USING [GetPName], FinchSmarts USING [ConvDesc, LookupServiceInterface, ReportSystemStateProc, ReportConversationStateProc, ReportRequestStateProc, RegisterForReports, VoiceConnect], Process USING [SecondsToTicks, SetTimeout, Ticks], PupSocket USING [GetUniqueID], RefID USING [ID, nullID], Rope USING [ROPE, Concat], RPC USING [CallFailed, ImportFailed], Thrush USING [ActionType, ConversationID, InterfaceSpec, NB, none, nullConvID, SHHH], VoiceRopeServer, VoiceRopeServerRpcControl USING [ImportInterface], VoiceUtils USING [Problem], VoiceRope; VoiceRopeImpl: CEDAR MONITOR IMPORTS Atom, FinchSmarts, Process, PupSocket, Rope, RPC, VoiceRopeServer, VoiceRopeServerRpcControl, VoiceUtils EXPORTS VoiceRope ~ BEGIN ROPE: TYPE ~ Rope.ROPE; recordingService: ROPE = "recording"; recordingServiceInterface: ROPE = "VoiceRopeServer"; RequestID: TYPE = CARD; PendingRequest: TYPE = REF PendingRequestBody; PendingRequestBody: TYPE = RECORD [ id: RequestID, state: Thrush.ActionType _ $unknown ]; PendingRequests: TYPE = LIST OF PendingRequest; VoiceRopeInfo: TYPE = REF VoiceRopeInfoBody; VoiceRopeInfoBody: TYPE = RECORD [ shhh: Thrush.SHHH _ Thrush.none, serviceID: RefID.ID _ RefID.nullID, imported: BOOLEAN _ FALSE, cDesc: FinchSmarts.ConvDesc _ NIL, -- conv hint (see below) pendingRequests: PendingRequests _ NIL, reportArrived: CONDITION ]; info: VoiceRopeInfo _ NEW[VoiceRopeInfoBody]; Handle: TYPE = VoiceRope.Handle; -- handles are no longer used for anything Open: PUBLIC PROC [voiceRopeDBName: ROPE _ NIL, localName: ROPE _ NIL, Complain: PROC[complaint: ROPE] _ NIL] RETURNS [handle: Handle] ~ { RETURN[NIL]; -- this routine is no longer needed! }; Record: PUBLIC PROC [handle: Handle_NIL] RETURNS [voiceRope: VoiceRope.VoiceRope] ~ { nb: Thrush.NB; cDesc: FinchSmarts.ConvDesc; intID: RequestID; cDesc _ GetConversation[]; IF cDesc = NIL OR NOT RecordingServiceInterface[cDesc] THEN RETURN; intID _ NewID[]; AddRequest[intID]; [nb, voiceRope] _ VoiceRopeServer.Record[info.shhh, cDesc.situation.self, info.serviceID, intID, TRUE ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to record failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; WaitForRequestState[state: $finished, id: intID]; [nb, voiceRope.length] _ VoiceRopeServer.Length[info.shhh, voiceRope ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to get voicerope length failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; Play: PUBLIC PROC [handle: Handle_NIL, voiceRope: VoiceRope.VoiceRope, queueIt: BOOL_TRUE, failOK: BOOL_FALSE, wait: BOOL_FALSE] RETURNS [] ~ { nb: Thrush.NB; cDesc: FinchSmarts.ConvDesc; intID: RequestID; cDesc _ GetConversation[]; IF cDesc = NIL OR NOT RecordingServiceInterface[cDesc] THEN RETURN; intID _ NewID[]; AddRequest[intID]; nb _ VoiceRopeServer.Play[info.shhh, voiceRope, cDesc.situation.self, info.serviceID, intID, queueIt ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to play voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; IF wait THEN { WaitForRequestState[state: $started, id: intID]; }; }; Stop: PUBLIC PROC [handle: Handle _ NIL] RETURNS [] ~ { ENABLE UNWIND => NULL; nb: Thrush.NB; IF info.cDesc = NIL OR NOT RecordingServiceInterface[info.cDesc] THEN RETURN; nb _ VoiceRopeServer.Stop[info.shhh, info.cDesc.situation.self, info.serviceID ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to stop failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; Retain: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, class: VoiceRope.InterestClass, refID: ROPE, other: ROPE _ NIL] RETURNS [] ~ { nb: Thrush.NB; IF NOT RecordingServiceInterface[] THEN RETURN; nb _ VoiceRopeServer.Retain[info.shhh, vr, class, refID, other ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to retain voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; Forget: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, class: VoiceRope.InterestClass, refID: ROPE] RETURNS [] ~ { nb: Thrush.NB; IF NOT RecordingServiceInterface[] THEN RETURN; nb _ VoiceRopeServer.Forget[info.shhh, vr, class, refID ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to forget voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; GetByInterest: PUBLIC PROC [handle: Handle _ NIL, class: VoiceRope.InterestClass, refID: ROPE] RETURNS [voiceRope: VoiceRope.VoiceRope] ~ { nb: Thrush.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, voiceRope] _ VoiceRopeServer.GetByInterest[info.shhh, class, refID ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to lookup voicerope by interest failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; Cat: PUBLIC PROC [handle: Handle _ NIL, vr1, vr2, vr3, vr4, vr5: VoiceRope.VoiceRope _ NIL] RETURNS [new: VoiceRope.VoiceRope] ~ { nb: Thrush.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Cat[info.shhh, vr1, vr2, vr3, vr4, vr5 ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to cat voiceropes failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; Substr: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, start: INT _ 0, len: INT _ LAST[INT]] RETURNS [new: VoiceRope.VoiceRope] ~ { nb: Thrush.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Substr[info.shhh, vr, start, len ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to substring voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; Replace: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, start: INT _ 0, len: INT _ LAST[INT], with: VoiceRope.VoiceRope _ NIL] RETURNS [new: VoiceRope.VoiceRope] ~ { nb: Thrush.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, new] _ VoiceRopeServer.Replace[info.shhh, vr, start, len, with ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to replace part of a voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; Length: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope] RETURNS [len: INT] ~ { nb: Thrush.NB; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, len] _ VoiceRopeServer.Length[info.shhh, vr ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to get voicerope length failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; DescribeRope: PUBLIC PROC [handle: Handle _ NIL, vr: VoiceRope.VoiceRope, minSilence: INT _ -1] RETURNS [noise: VoiceRope.IntervalSpecs] ~ { nb: Thrush.NB; len: INT; IF NOT RecordingServiceInterface[] THEN RETURN; [nb, len, noise] _ VoiceRopeServer.DescribeRope[info.shhh, vr, minSilence ! RPC.CallFailed => {nb _ $callFailed; CONTINUE}]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Attempt to describe voicerope failed - ",Atom.GetPName[nb]], $VoiceRope]; RETURN; }; }; SystemReport: FinchSmarts.ReportSystemStateProc ~ { NULL; }; ConversationReport: FinchSmarts.ReportConversationStateProc ~ { IF cDesc = NIL OR info.cDesc = NIL OR cDesc.situation.self.convID # info.cDesc.situation.self.convID THEN RETURN; SELECT cDesc.situation.self.state FROM $active => NULL; $idle, $neverWas, $failed => { -- throw away pending requests RemoveRequests[NIL]; NotifyWaiters[]; }; ENDCASE; }; RequestReport: FinchSmarts.ReportRequestStateProc ~ { request: PendingRequest; IF info.cDesc = NIL OR actionReport.self.convID # info.cDesc.situation.self.convID THEN RETURN[NIL]; SELECT actionReport.actionClass FROM $recording, $playback => { request _ GetRequestByID[actionReport.actionID]; IF request = NIL THEN RETURN[NIL]; request.state _ actionReport.actionType; SELECT request.state FROM $scheduled, $started => NULL; $finished, $flushed => RemoveRequests[request]; ENDCASE; NotifyWaiters[]; }; ENDCASE=> request _ request; -- a place to stand during debugging RETURN[NIL]; -- leave actionRequest alone }; GetRequestByID: ENTRY PROC [id: RequestID] RETURNS [request: PendingRequest _ NIL] ~ { FOR q: PendingRequests _ info.pendingRequests, q.rest WHILE q # NIL DO IF q.first.id = id THEN RETURN[q.first]; ENDLOOP; }; AddRequest: ENTRY PROC [id: RequestID] RETURNS [] ~ { request: PendingRequest _ NEW[PendingRequestBody _ [id]]; info.pendingRequests _ CONS[request, info.pendingRequests]; }; RemoveRequests: ENTRY PROC [request: PendingRequest] RETURNS [] ~ { IF request = NIL OR info.pendingRequests = NIL OR info.pendingRequests.first = request THEN info.pendingRequests _ NIL ELSE FOR q: PendingRequests _ info.pendingRequests, q.rest WHILE q.rest # NIL DO IF q.rest.first = request THEN { q.rest _ NIL; -- truncate queue to remove this and all previous requests EXIT; }; ENDLOOP; }; NotifyWaiters: ENTRY PROC [] RETURNS [] ~ { BROADCAST info.reportArrived; }; Wait: ENTRY PROC [] RETURNS [] ~ { WAIT info.reportArrived; }; WaitForRequestState: PROC [state: Thrush.ActionType, id: RequestID] RETURNS [] ~ { request: PendingRequest; timeout: Process.Ticks _ IF state = $started THEN Process.SecondsToTicks[2] ELSE Process.SecondsToTicks[10]; TRUSTED { Process.SetTimeout[@info.reportArrived, timeout]; }; DO request _ GetRequestByID[id]; IF request = NIL THEN EXIT; -- completed or conv went idle SELECT state FROM $scheduled => SELECT request.state FROM $scheduled, $started, $finished, $flushed => EXIT; ENDCASE => NULL; -- unknown? $started => SELECT request.state FROM $started, $finished, $flushed => EXIT; ENDCASE => NULL; -- probably $scheduled $finished => SELECT request.state FROM $finished, $flushed => EXIT; ENDCASE => NULL; -- probably $scheduled or $started ENDCASE => EXIT; Wait[]; ENDLOOP; }; GetConversation: PROC [] RETURNS [cDesc: FinchSmarts.ConvDesc _ NIL] ~ { nb: Thrush.NB; [nb, cDesc] _ FinchSmarts.VoiceConnect[recordingService, IF info.cDesc#NIL THEN info.cDesc.situation.self.convID ELSE Thrush.nullConvID]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Can't establish conversation - ",Atom.GetPName[nb]], $VoiceRope]; RETURN[NIL]; }; info.cDesc _ cDesc; -- save for future use }; RecordingServiceInterface: PROC[cDesc: FinchSmarts.ConvDesc _ NIL] RETURNS [imported: BOOLEAN _ TRUE] = { nb: Thrush.NB; interfaceSpec: Thrush.InterfaceSpec; IF info.imported THEN RETURN; [nb, info.shhh, interfaceSpec] _ FinchSmarts.LookupServiceInterface[recordingService, recordingServiceInterface, cDesc]; IF nb#$success THEN RETURN[FALSE]; info.serviceID _ interfaceSpec.serviceID; info.imported _ TRUE; VoiceRopeServerRpcControl.ImportInterface[interfaceName: interfaceSpec.interfaceName, hostHint: interfaceSpec.hostHint ! RPC.ImportFailed => { info.imported _ FALSE; VoiceUtils.Problem["Can't import recording service.", $VoiceRope]; CONTINUE}]; RETURN[info.imported]; }; NewID: PROC RETURNS[LONG CARDINAL] ={RETURN[LOOPHOLE[PupSocket.GetUniqueID[]]]}; FinchSmarts.RegisterForReports[c: ConversationReport, r: RequestReport]; END. bVoiceRopeImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Doug Terry, October 14, 1986 3:59:55 pm PDT Client code for interacting with the Voice Rope Service. cDesc is a hint of a conversation that used to work. It may well be gone by the time we can use it for anything, but it won't hurt anything either. If the conversation is still active the next time we call record/play/stop, we'd better use it! Eventually we should provide for the possibility that several conversations could exist at once. VoiceRope client interface If voiceRopeDBName or localName is omitted, a default based on the user profile choice of Thrush Server is invented. If Complain is omitted, Log.Problem[...$Finch] is used. Records a voice rope, registers it , and returns its ID. A NIL return value indicates that something went wrong. wait for report that says it finished (or was abandoned) Play a specified voice rope. The boolean arguments are interpreted as follows: queueIt => play after all other record/playback requests are satisfied. failOK => playing is optional; leave connection open if tune doesn't exist. wait => wait until things appear to be started properly, or have failed. Stops any recording or playback in progress. Registers a new interest in the voice rope. The voice rope will be retained until either a corresponding Forget is done or the class' garbage collection process determines that the voice rope is no longer referenced, e.g. refID no longer exists. Taken together, the vr, class, and refID must be unique. Repeated calls of Retain with the same parameters (ignoring other) will only register a single interest. The specified refID of the specified class drops its interest in the voice rope. The voice rope is not necessarily deleted, however, since other interests in the same voice rope may exist. Returns any voice rope that is of interest to the given class and refID; returns NIL if no such voice rope exists. Concatenates together the non-NIL voice ropes to produce a new voice rope. Creates a new voice rope that is a substring of an existing voice rope. Creates a new voice rope in which the given interval of the voice rope "vr" is replaced by the voice rope "with". Returns the actual length of the voice rope. This operation ignores the start and length values specified in the voice rope. Thus, vr.start _ 0; vr.length _ Length[handle, vr] will restore a voice rope to its full contents. Report handling Action reports are generated by the voice rope server when a request for $recording/$playback is $scheduled, $started, $finished, or $flushed. Reports are also available from FinchSmarts concerning the state of a conversation or Finch in general. A list of pending requests is maintained as part of VoiceRopeInfo. This list is sorted so that the most recent request is at the head of the list. Upon recent of a $finished or $flushed report, the associated request (and all previous requests) are removed from the pending request queue. All pending requests are flushed if the established conversation goes idle (i.e. the user hangs up). The condition, reportArrived, is raised whenever a new report is received. Procedures that wish to wait for a given action report may wait on this condition; they should then check the state of the pending request queue and take appropriate action (such as waiting again). Don't care about changes in system state at the moment. [ nb: NB, cDesc: ConvDesc, remark: ROPE_NIL ] [ cDesc: ConvDesc, actionReport: Thrush.ActionReport, actionRequest: REF ] RETURNS [betterActionRequest: REF] Throw away pending requests that are complete. This includes requests preceding the one being reported on. The following routines manage the queue of pending requests. (They are the only monitor entry procedures in this module.) Establishing conversations and RPC connections Initializations Doug Terry, August 14, 1986 9:40:42 am PDT changes to: DIRECTORY, VoiceRopeClientImpl, EXPORTS, ~, Open, Record, Play, Stop, Retain, Forget, GetByInterest, Cat, Substr, Replace, Length, DescribeRope, Proc, Proc Doug Terry, August 14, 1986 9:58:55 am PDT changes to: DIRECTORY, VoiceRopeClientImpl, EXPORTS, ~, Open, Record, Play, Stop, Retain, Forget, GetByInterest, Cat, Substr, Replace, Length, DescribeRope, VoiceRopeInterface, Proc, Proc, END Doug Terry, August 27, 1986 11:43:29 am PDT changes to: DIRECTORY, VoiceRopeImpl, ~, Open, Play, Stop, VoiceRopeInterface, DescribeRope Doug Terry, August 29, 1986 4:13:15 pm PDT changes to: ~, Play, VoiceRopeImpl, Record, Stop, NewID, Proc, Retain, Forget, GetByInterest, Cat, Substr, Replace, Length, DescribeRope, RecordingServiceInterface Doug Terry, September 5, 1986 4:28:37 pm PDT changes to: DIRECTORY, IMPORTS, EXPORTS, ~, Handle, Open, Proc Doug Terry, September 25, 1986 2:59:31 pm PDT changes to: DIRECTORY, SystemReport, ConversationReport, RequestReport, FinchSmarts, Record Doug Terry, September 29, 1986 5:56:59 pm PDT changes to: SystemReport, Record, ConversationReport, RequestReport, ~ Doug Terry, October 3, 1986 3:51:47 pm PDT changes to: ~, GetRequestByID, GetRequestByRef, NewRequest, RemoveRequests, NewID, Record, Play, ConversationReport, RequestReport, WaitForRequestState, DIRECTORY, IMPORTS, Retain, Forget, GetByInterest, Cat, Substr, Replace, Length, DescribeRope, RecordingServiceInterface, Stop, WaitForRequestState, Handle, Open Doug Terry, October 3, 1986 4:45:36 pm PDT changes to: RequestReport Doug Terry, October 6, 1986 1:44:44 pm PDT changes to: DIRECTORY, ~, Record, Play, Stop, Retain, Forget, GetByInterest, Cat, Substr, Replace, Length, DescribeRope, RecordingServiceInterface, IMPORTS Doug Terry, October 9, 1986 8:00:18 pm PDT changes to: Record, Stop, Retain, Forget, GetByInterest, Cat, Substr, Replace, Length, DescribeRope Doug Terry, October 9, 1986 8:45:25 pm PDT changes to: Wait, WaitForRequestState Doug Terry, October 14, 1986 3:59:55 pm PDT changes to: ConversationReport สพ˜codešœ™Kšœ ฯmœ1™Kšœžœ˜Kšœ˜Jšœ˜—Jšžœ˜—K˜K˜—šœ5˜5KšœEžœžœžœ™mJšœ˜Jš žœžœžœ=žœžœžœ˜dšžœž˜$šœ˜Kšœ0˜0Kš žœ žœžœžœžœ˜"Kšœ(˜(šžœž˜Kšœžœ˜K™kšœ˜Kšœ˜—Kšžœ˜—Kšœ˜K˜—Kšžœ $˜A—Kšžœžœ ˜*Kšœ˜K˜—K™zK˜šฃœž œžœžœ˜Všžœ3žœžœž˜FKšžœžœžœ ˜(Kšžœ˜—K˜K˜—šฃ œž œžœ˜5Kšœžœ˜9Kšœžœ ˜;K˜K˜—šฃœž œžœ˜Cš žœ žœžœžœžœ&ž˜[Kšœž˜—šž˜šžœ3žœ žœž˜Kšžœžœ˜ Kšœ žœ :˜IKšžœ˜K˜—Kšžœ˜——K˜K˜—šฃ œž œžœ˜+Kšž œ˜K˜—K˜šฃœž œžœ˜"Kšžœ˜K˜—K˜šฃœžœ+žœ˜RJšœ˜Kšœžœžœžœ˜lJšžœ7˜>šž˜Kšœ˜Kš žœ žœžœžœ ˜;šžœž˜šœžœž˜'Kšœ-žœ˜2Kšžœžœ  ˜—šœ žœž˜%Kšœ!žœ˜&Kšžœžœ ˜(—šœ žœž˜&Kšœžœ˜Kšžœžœ "˜4—Kšžœžœ˜—Kšœ˜Kšžœ˜—K˜K˜——™.šฃœžœžœ žœ˜HJšœ žœ˜Jš œ9žœ žœžœ"žœ˜‰šžœ žœ˜Jšœa˜aJšžœžœ˜ J˜—Jšœ ˜+K˜—J˜š ฃœžœžœžœ žœžœ˜iJ˜J˜$Jšžœžœžœ˜Jšœx˜xJšžœ žœžœžœ˜"Jšœ)˜)Jšœžœ˜šœw˜wšœžœ˜Kšœžœ˜KšœB˜BKšžœ˜ ——Jšžœ˜J˜J˜—Jšฃœžœžœžœžœžœžœ˜P—™KšœH˜H—K˜Kšžœ˜™*Kšœ ฯr›™ง—™*Kšœ คด™ภ—™+Kšœ คO™[—™*Kšœ ค—™ฃ—™,Kšœ ค2™>—™-Kšœ คO™[—™-Kšœ ค:™F—™*Kšœ คฎ™บ—™*Kšœ ค ™—™*Kšœ ค™›—™*Kšœ คW™c—™*Kšœ ค™%—™+Kšœ ค™——…—,rS’