<> <> <> <<>> <> <<>> 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, convID: Thrush.ConversationID _ Thrush.nullConvID, -- conv hint (see below) serviceID: RefID.ID _ RefID.nullID, imported: BOOLEAN _ FALSE, 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 [] ~ { <> < play after all other record/playback requests are satisfied.>> < playing is optional; leave connection open if tune doesn't exist.>> < wait until things appear to be started properly, or have failed.>> 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; cDesc: FinchSmarts.ConvDesc; cDesc _ GetConversation[]; IF cDesc = NIL OR cDesc.situation.self.state#$active OR NOT RecordingServiceInterface[cDesc] THEN RETURN; nb _ VoiceRopeServer.Stop[info.shhh, 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 ~ { <<[ nb: NB, cDesc: ConvDesc, remark: ROPE_NIL ]>> IF cDesc = NIL OR cDesc.situation.self.convID # info.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 ~ { <<[ cDesc: ConvDesc, actionReport: Thrush.ActionReport, actionRequest: REF ] RETURNS [betterActionRequest: REF]>> request: PendingRequest; IF actionReport.self.convID # info.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, info.convID]; IF nb#$success THEN { VoiceUtils.Problem[Rope.Concat["Can't establish conversation - ",Atom.GetPName[nb]], $VoiceRope]; RETURN[NIL]; }; info.convID _ cDesc.situation.self.convID; -- 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. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <>