<> <> <> DIRECTORY Convert USING [IntFromRope, RopeFromInt], IO, Lark, LarkPlay USING [PlayString, MergeToneSpecs, ToneList, ToneSpec, ToneSpecRec], LarkRpcControl, LarkSmarts, LarkTestMonitorImpl, Process USING [ Detach, SecondsToTicks, SetTimeout, Yield ], Rope USING [Cat, Concat, Fetch, FromChar, Length, ROPE, Substr], RPC USING [CallFailed, CallFailure], RPCLupine USING [maxShortStringLength], -- find some way to allow maxDataLength? LarkTest; TonesAndSpeechImpl: CEDAR MONITOR LOCKS root IMPORTS Convert, IO, LarkPlay, LarkRpcControl, LarkTest, root: LarkTestMonitorImpl, Process, Rope, RPC EXPORTS LarkTest SHARES LarkTestMonitorImpl = { larkNotification: CONDITION; -- better not try to notify both tones and speech at once tonesToPlay: LarkPlay.ToneSpec _ NEW[LarkPlay.ToneSpecRec _ []]; lastTonesNotification: Lark.Event _ '\000; larkToneSpec: Lark.ToneSpec _ NEW[Lark.ToneSpecRec _ [volume: 0, totalTime: 0, tones: NIL]]; SpeechSpec: TYPE = Rope.ROPE; -- consider making this a STREAM textToSpeak: SpeechSpec _ ""; -- make LIST OF Rope? lastTextNotification: INT _ 0; proseResponse: ProseCmd _ ""; -- build up Prose response character by character MyToneProc: PUBLIC ENTRY LarkTest.ToneProc = { ENABLE { RPC.CallFailed => TRUSTED { self.msg.PutF["CallFailed: %g\r", IO.refAny[NEW[RPC.CallFailure _ why]]]; LarkTest.CleanupProc[self]; CONTINUE; }; UNWIND => NULL; }; waveTableNat: NAT _ SELECT waveTable FROM dB0 => 0, dB3 => 1, dB6 => 2, dB9 => 3 ENDCASE => 4; IF self.registered THEN [] _ self.lark.SpecifyTones[shh: self.shhh, queueIt: queueIt, tones: NEW[Lark.ToneSpecRec _ [ notification: IF notify THEN [tones, 'T] ELSE [nothing, 0C], volume: waveTableNat, totalTime: 0, tones: LIST[[f1: f1, f2: f2, on: on, off: off, repetitions: repetitions ]] ] ] ]; }; MyTuneProc: PUBLIC LarkTest.TuneProc = TRUSTED { ENABLE { RPC.CallFailed => TRUSTED { self.msg.PutF["CallFailed: %g\r", IO.refAny[NEW[RPC.CallFailure _ why]]]; LarkTest.CleanupProc[self]; CONTINUE; }; }; waveTableNat: NAT _ 0; IF self.registered THEN { tune: LarkPlay.ToneSpec _ LarkPlay.PlayString[music: music1, file: file, volume: waveTableNat]; tune2: LarkPlay.ToneSpec _ IF music2=NIL THEN NIL ELSE LarkPlay.PlayString[music: music2, file: file, volume: waveTableNat]; IF tune2#NIL THEN tune _ LarkPlay.MergeToneSpecs[tune, tune2, tune2Divisor, tune2Delay, volumeIncrement]; ScheduleToPlay[self, tune]; }; }; ScheduleToPlay: ENTRY PROC[self: LarkTest.LarkHandle, tunes: LarkPlay.ToneSpec] = { IF tonesToPlay.tones=NIL THEN TRUSTED { <> tonesToPlay^_tunes^; Process.Detach[FORK PlayTunes[self]]; } ELSE FOR tL: LIST OF LarkPlay.ToneList _ tonesToPlay.tones, tL.rest WHILE tL#NIL DO IF tL.rest=NIL THEN { tL.rest _ tunes.tones; RETURN; }; ENDLOOP; }; PlayTunes: ENTRY PROC[self: LarkTest.LarkHandle] = { ENABLE { RPC.CallFailed => TRUSTED { self.msg.PutF["CallFailed: %g\r", IO.refAny[NEW[RPC.CallFailure _ why]]]; LarkTest.CleanupProc[self]; CONTINUE; -- Give UP! }; UNWIND => NULL; }; thisNotification: Lark.Event _ 'B; TRUSTED { Process.SetTimeout[@larkNotification, Process.SecondsToTicks[20]]; }; WHILE tonesToPlay.tones#NIL DO larkToneSpec.tones _ tonesToPlay.tones.first; tonesToPlay.tones _ tonesToPlay.tones.rest; <> IF larkToneSpec.tones=NIL THEN LOOP; thisNotification _ IF thisNotification = 'B THEN 'A ELSE 'B; larkToneSpec.notification _ [ tones, thisNotification ]; larkToneSpec.volume _ tonesToPlay.volume; <> [] _ self.lark.SpecifyTones[ shh: self.shhh, queueIt: TRUE, tones: larkToneSpec]; WAIT larkNotification; IF lastTonesNotification # thisNotification THEN -- Timed out. Give up. <> tonesToPlay.tones _ NIL; ENDLOOP; }; <<>> <> ProseCmd: TYPE = Rope.ROPE; reset: ProseCmd = "\022"; -- ControlR resetLen: INT = 1; resetConfirmOK: ProseCmd = "\033[0R"; -- \033 is ESC resetConfirmEnd: CHAR = 'R; commenceSpeech: ProseCmd = "\033[C"; cmdLeader: ProseCmd = "\033["; -- for constructing arbitrary commands indexMarkerEnd: CHAR = 'i; indexMarker1: ProseCmd = "\033[1i"; indexMarker2: ProseCmd = "\033[2i"; speechDone: ProseCmd = "\033[C\033[9i"; -- commence speech & report back when done speechDoneNotification: INT _ 9; indexMarkerLen: INT = 4; maxTextPktLen: INT = RPCLupine.maxShortStringLength - 2*indexMarkerLen; maxPkts: INT = 200 / maxTextPktLen; -- Prose can hold `several hundred' chars numPkts: INT _ 4; MySpeechProc: PUBLIC LarkTest.SpeechProc = TRUSTED { ENABLE { RPC.CallFailed => TRUSTED { self.msg.PutF["CallFailed: %g\r", IO.refAny[NEW[RPC.CallFailure _ why]]]; LarkTest.CleanupProc[self]; CONTINUE; }; }; IF self.registered THEN { ScheduleToSpeak[self, text, queueIt]; }; }; ScheduleToSpeak: ENTRY PROC [self: LarkTest.LarkHandle, text: Rope.ROPE, queueIt: BOOL _ TRUE] = { IF textToSpeak.Length[] = 0 THEN TRUSTED { -- need to FORK a new speaker process textToSpeak _ text; Process.Detach[FORK SpeakText[self]]; } ELSE { IF queueIt THEN textToSpeak _ Rope.Concat[textToSpeak, text] ELSE textToSpeak _ text; -- only do explicit RESETs; retain speed, etc. params }; }; SpeakText: ENTRY PROC[self: LarkTest.LarkHandle] = { ENABLE { RPC.CallFailed => TRUSTED { self.msg.PutF["CallFailed: %g\r", IO.refAny[NEW[RPC.CallFailure _ why]]]; LarkTest.CleanupProc[self]; CONTINUE; -- Give UP! }; UNWIND => NULL; }; textPkt: Rope.ROPE; thisNotification: INT _ 1; TRUSTED { Process.SetTimeout[@larkNotification, Process.SecondsToTicks[20]]; }; WHILE textToSpeak.Length[] > 0 DO <> FOR i: INT IN [1..numPkts] DO IF textToSpeak.Length[] <= maxTextPktLen THEN { textPkt _ Rope.Concat[textToSpeak, speechDone]; textToSpeak _ ""; } ELSE { <> index: INT _ maxTextPktLen; WHILE IO.TokenProc[Rope.Fetch[textToSpeak, index-1]] = other DO index _ index-1; IF index=0 THEN {index _ maxTextPktLen; EXIT}; <> ENDLOOP; textPkt _ IF i = 1 THEN Rope.Cat[cmdLeader, Convert.RopeFromInt[thisNotification], Rope.FromChar[indexMarkerEnd], textToSpeak.Substr[len: index]] ELSE textToSpeak.Substr[len: index]; textToSpeak _ textToSpeak.Substr[start: index]; }; <> self.lark.CommandString[self.shhh, keyboard, textPkt]; self.msg.PutF["SpeakText: '%g'\n", IO.rope[textPkt]]; IF textToSpeak.Length[] = 0 THEN EXIT; Process.Yield[]; -- let someone else in, esp. to start some new text: SpeakText w/ queueIt=FALSE ENDLOOP; WAIT larkNotification; IF lastTextNotification = speechDoneNotification THEN IF textToSpeak.Length[] = 0 THEN self.msg.PutRope["Speech Done\n"] -- do more for client notification ELSE lastTextNotification _ thisNotification; -- keep going IF lastTextNotification # thisNotification THEN -- Timed out or all done. Give up. <> textToSpeak _ ""; thisNotification _ IF thisNotification = 1 THEN 2 ELSE 1; ENDLOOP; }; MyPlayStringProc: PUBLIC ENTRY LarkTest.PlayStringProc = { ENABLE { RPC.CallFailed => TRUSTED { self.msg.PutF["CallFailed: %g\r", IO.refAny[NEW[RPC.CallFailure _ why]]]; LarkTest.CleanupProc[self]; CONTINUE; }; UNWIND => NULL; }; self.msg.PutF["PlayStringCommand: '%g'\n", IO.rope[string]]; IF self.registered THEN self.lark.CommandString[self.shhh, keyboard, string]; }; < resetConfirmEnd otherwise Index marker response: indexMarker1, indexMarker2, or speechDone Undetermined problem: BEL>> HandleProseOutput: PUBLIC ENTRY PROC [c: CHAR] = { SELECT c FROM IO.BEL => NULL; -- take some error action? IO.ESC => proseResponse _ ""; -- start of some response '[ => NULL; -- peel this character off indexMarkerEnd => { lastTextNotification _ Convert.IntFromRope[proseResponse]; NOTIFY larkNotification; }; resetConfirmEnd => IF Convert.IntFromRope[proseResponse] # 0 THEN NULL; -- take some error action? ENDCASE => proseResponse _ Rope.Concat[proseResponse, Rope.FromChar[c]]; }; Ping: PUBLIC ENTRY PROC[e: Lark.Event] = { lastTonesNotification _ e; NOTIFY larkNotification; }; CleanupForNewRegistration: PUBLIC ENTRY LarkTest.BProc = { LarkTest.CleanupProc[self]; WHILE self.registered DO WAIT larkNotification; ENDLOOP; }; }.