<> <> <> DIRECTORY IO, Atom USING [ GetPropFromList, PutPropOnList ], Convert USING [ IntFromRope, RopeFromInt ], GList USING [ Nconc ], Lark USING [ StatusEvent ], LarkOpsRpcControl, LarkSynthesizer USING [ LarkSynthReport ], Rope USING [ Cat, Concat, FromChar, Length, ROPE, Translate, TranslatorType ], Synthesizer USING [ BreakText, SynthSpec, SynthSpecs ], SynthesizerServer, ThSmartsPrivate USING [ Deb, EnterLarkSt, Fail, FailInt, LarkCallBody, LarkInfo ], ThNet USING [ pd ], Thrush USING [ NB ], VoiceUtils USING [ Problem, ProblemFR ] ; LarkSynthesizerImpl: CEDAR MONITOR LOCKS info USING info: LarkInfo IMPORTS IO, Atom, Convert, GList, LarkOpsRpcControl, LarkSynthesizer, Rope, Synthesizer, ThNet, ThSmartsPrivate, VoiceUtils EXPORTS LarkSynthesizer = { OPEN IO; <> <<>> LarkInfo: TYPE = ThSmartsPrivate.LarkInfo; SynthSpecs: TYPE = Synthesizer.SynthSpecs; SynthCmd: TYPE = Rope.ROPE; <> SynthInfo: TYPE = REF SynthInfoBody; SynthInfoBody: TYPE = RECORD [ <> synthResponse: Rope.ROPE_NIL, -- holds incomplete Synthesizer responses flushJustFinished: BOOL_FALSE, -- can expect to have to flush synthQueue next time thru LarkSynthesizerImpl.HandleSynthOutput synthQueue: Synthesizer.SynthSpecs_NIL, -- holds queue of client markers and synthSpecs textToSpeak: Rope.ROPE_NIL, clientMarker: INT_maxClientMarker, controlMarker: INT_maxControlMarker, ctrlMarkerQueue: LIST OF REF INT_NIL, -- holds queue of control markers pktsOutstanding: INT_0, flushInProgress: BOOL_FALSE -- consider combining w flushJustFinished? ]; <<>> <> pReset: PUBLIC SynthCmd _ "cP;z:cp0\\P;z:pp0\\P;z:ra180\\P;z:np\\"; << is ESC. Also explicitly resets voice, rate, etc, because machine reset doesn't.>> stopAndFlush: PUBLIC SynthCmd _ "P;10z\\"; <> commenceSpeech: SynthCmd = "P;z,\\"; -- phonemic comma indexMarkerStart: SynthCmd = "P;21;"; -- for constructing index commands indexMarkerLen: INT = 12; indexMarkerEnd: SynthCmd = "z\\P;z,\\"; <> indexMarkerEndChar: CHAR = 'z; pResetConfirmEnd: CHAR = 'n; stopAndFlushEnd: CHAR = 'n; -- for confirm, rename to flushConfirmEnd flushMarker: INT = 1000; synthFailure: INT = 1001; maxControlMarker: INT = 250; maxClientMarker: INT = 199; maxTextPktLen: INT _ 150; <> <> minClientMarker: INT = 1; minControlMarker: INT = 200; -- reserved for packet control maxPkts: INT = 250 / maxTextPktLen; -- Prose can hold `several hundred' chars numPkts: INT _ 2; speechDoneMarker: INT = 255; speechDone: SynthCmd = "P;21;255z\\P;z,\\"; <> plainSpeechDone: SynthCmd = " P;21;255z\\ "; <> NconcSpecs: PROC[l1, l2: SynthSpecs] RETURNS [SynthSpecs] = INLINE { RETURN[NARROW[GList.Nconc[l1, l2]]]; }; <> <<>> AddText: PUBLIC ENTRY PROC[ info: LarkInfo, synthSpec: Synthesizer.SynthSpec, filter: BOOL_TRUE, queueIt: BOOL_TRUE] = { <<Do some reasonable-state checking? At present, am assuming service did it at the conversation level, and that LarkOut will keep us out of trouble.>> <> ENABLE UNWIND => NULL; newText: Rope.ROPE; synthInfo: SynthInfo _ GetSynthInfo[info]; IF synthInfo=NIL THEN RETURN; -- error already reported IF ~queueIt THEN DoSynthFlush[info, stopAndFlush]; IF ThNet.pd.debug THEN ThSmartsPrivate.Deb[info, "Rq Prose", rope[synthSpec.textToSpeak], bool[queueIt], int[synthSpec.actionID]]; newText _ IF ~filter THEN synthSpec.textToSpeak ELSE Rope.Translate[base: synthSpec.textToSpeak, translator: FilterText]; synthSpec.synthMarker _ synthInfo.clientMarker _ IF synthInfo.clientMarker = maxClientMarker THEN minClientMarker ELSE synthInfo.clientMarker + 1; synthInfo.textToSpeak _ Rope.Cat[synthInfo.textToSpeak, newText, indexMarkerStart, Convert.RopeFromInt[synthInfo.clientMarker], indexMarkerEnd]; synthInfo.synthQueue _ NconcSpecs[synthInfo.synthQueue, LIST[synthSpec]]; SpeakText[info, synthInfo]; }; <> <<>> < pResetConfirmEnd otherwise Index marker response: Undetermined problem: BEL>> <<>> HandleAndReport: PROC [info: LarkInfo, sEvent: Lark.StatusEvent] ~ { <> nb: Thrush.NB; sS: Synthesizer.SynthSpec _ HandleSynthesizerOutput[info, sEvent]; IF sS=NIL THEN RETURN; IF (nb _ LarkSynthesizer.LarkSynthReport[sS, $finished]) = $success THEN RETURN; IF info#NIL AND ~info.failed THEN ThSmartsPrivate.Fail[info, IO.PutFR["Couldn't report text-synthesizer completion -- %g", atom[nb]], TRUE] ELSE VoiceUtils.ProblemFR["Couldn't report text-synthesizer completion, probably due to earlier failure -- %g", $System, NIL, atom[nb]]; }; HandleSynthesizerOutput: ENTRY PROC[info: LarkInfo, commandEvent: Lark.StatusEvent] RETURNS [sS: Synthesizer.SynthSpec _ NIL] = { ENABLE UNWIND => NULL; c: CHAR _ commandEvent.event; synthInfo: SynthInfo _ GetSynthInfo[info]; IF synthInfo = NIL THEN RETURN; -- error already reported SELECT c FROM IO.BEL => NULL; -- take some error action? IO.ESC, ';, '\\ => synthInfo.synthResponse _ ""; -- start of some response '[ => NULL; -- peel this character off stopAndFlushEnd => { SynthControlDone[info, synthInfo, flushMarker]; synthInfo.flushJustFinished _ TRUE; }; indexMarkerEndChar => { marker: INT; IF Rope.Length[synthInfo.synthResponse]=0 THEN RETURN; <> marker _ Convert.IntFromRope[synthInfo.synthResponse]; IF marker > maxClientMarker THEN SynthControlDone[info, synthInfo, marker] ELSE { sQ: Synthesizer.SynthSpecs _ synthInfo.synthQueue; IF sQ=NIL THEN ERROR; IF synthInfo.flushJustFinished THEN { <> FOR sSkip: Synthesizer.SynthSpecs _ sQ, sSkip.rest WHILE sSkip#NIL DO IF sSkip.first.synthMarker = marker THEN sQ _ sSkip; ENDLOOP; synthInfo.flushJustFinished _ FALSE; }; IF sQ.first.synthMarker = marker THEN { sS _ sQ.first; synthInfo.synthQueue _ sQ.rest; -- Flush any skipped items. } ELSE SynthControlDone[info, synthInfo, synthFailure]; -- fail!!! }; }; < { -- this case is now the same as stopAndFlushEnd>> <> <> <> <> <> <<};>> <<};>> ENDCASE => synthInfo.synthResponse _ Rope.Concat[synthInfo.synthResponse, Rope.FromChar[c]]; }; SynthControlDone: PUBLIC INTERNAL PROC[ info: LarkInfo, synthInfo: SynthInfo, marker: INT] = { problem: Rope.ROPE_NIL; { marker1: INT; IF ~info.textToSpeech OR info.failed THEN RETURN; IF marker= synthFailure THEN { problem _ "Text-to-speech service failed"; GOTO Failed; }; IF marker = flushMarker THEN { -- Flushing complete synthInfo.flushInProgress _ FALSE; synthInfo.ctrlMarkerQueue _ NIL; synthInfo.pktsOutstanding _ 0; SpeakText[info, synthInfo]; RETURN; }; IF synthInfo.ctrlMarkerQueue = NIL THEN { problem _ "Text-to-speech service: empty control marker list"; GOTO Failed; }; marker1 _ NARROW[synthInfo.ctrlMarkerQueue.first, REF INT]^; IF marker # marker1 THEN { problem _ "Text-to-speech service: wrong marker received"; GOTO Failed; }; synthInfo.ctrlMarkerQueue _ synthInfo.ctrlMarkerQueue.rest; synthInfo.pktsOutstanding _ synthInfo.pktsOutstanding-1; SpeakText[info, synthInfo]; EXITS Failed => ThSmartsPrivate.FailInt[info, problem, TRUE]; }; }; <> <<>> SpeakText: INTERNAL PROC[info: LarkInfo, synthInfo: SynthInfo] = { textPkt: Rope.ROPE; controlMarker: INT; elt: LIST OF REF INT; IF synthInfo.flushInProgress THEN RETURN; WHILE synthInfo.pktsOutstanding < numPkts AND synthInfo.textToSpeak.Length[] > 0 DO synthText: Rope.ROPE _ synthInfo.textToSpeak; IF synthText.Length[] <= maxTextPktLen THEN { textPkt _ Rope.Concat[synthText, speechDone]; <> synthInfo.textToSpeak _ ""; controlMarker _ speechDoneMarker; } ELSE { [packet: textPkt, remainder: synthText] _ Synthesizer.BreakText[synthText, maxTextPktLen]; <> controlMarker _ synthInfo.controlMarker _ IF synthInfo.controlMarker>= maxControlMarker THEN minControlMarker ELSE synthInfo.controlMarker+1; textPkt _ Rope.Cat[indexMarkerStart, Convert.RopeFromInt[synthInfo.controlMarker], indexMarkerEnd, textPkt]; synthInfo.textToSpeak _ synthText; -- info is monitored record }; elt _ LIST[NEW[INT _ controlMarker]]; synthInfo.ctrlMarkerQueue _ NARROW[GList.Nconc[synthInfo.ctrlMarkerQueue, elt]]; ScheduleCommandString[info, textPkt]; synthInfo.pktsOutstanding _ synthInfo.pktsOutstanding+1; ENDLOOP; }; <<>> <> <<>> FilterText: Rope.TranslatorType = { -- PROC [old: CHAR] RETURNS [new: CHAR] <> SELECT old FROM IO.TAB, IO.LF, IO.CR, IO.ESC => new _ old; <> < IO.SP => new _ IO.SP; ENDCASE => new _ old; }; SynthFlush: PUBLIC ENTRY PROC[info: LarkInfo] = { ENABLE UNWIND => NULL; DoSynthFlush[info, stopAndFlush]; }; SynthReset: ENTRY PROC[info: LarkInfo] = { <> ENABLE UNWIND => NULL; DoSynthFlush[info, pReset] }; DoSynthFlush: INTERNAL PROC[info: LarkInfo, synthCmd: SynthCmd] = { <> synthInfo: SynthInfo _ GetSynthInfo[info]; IF synthInfo=NIL THEN RETURN; -- failure already reported IF synthInfo.flushInProgress THEN RETURN; synthInfo.flushInProgress _ TRUE; synthInfo.textToSpeak _ ""; IF ThNet.pd.debug THEN ThSmartsPrivate.Deb[info, "Rq Prose flush", rope[synthCmd], bool[FALSE], int[0]]; ScheduleCommandString[info, synthCmd]; }; GetSynthInfo: INTERNAL PROC[info: LarkInfo] RETURNS [synthInfo: SynthInfo_NIL] = { <> IF info=NIL THEN { VoiceUtils.Problem["No larkInfo at GetSynthInfo", $System]; RETURN; }; info.keyboardEventHandler _ HandleAndReport; info.keyboardResetHandler _ SynthReset; synthInfo _ NARROW[Atom.GetPropFromList[info.props, $synthInfo]]; IF synthInfo#NIL THEN RETURN; synthInfo _ NEW[SynthInfoBody_[]]; info.props _ Atom.PutPropOnList[info.props, $synthInfo, synthInfo]; }; ScheduleCommandString: PROC[info: LarkInfo, command: SynthCmd] = { <> ThSmartsPrivate.EnterLarkSt[info, info.larkState, LIST[NEW[ThSmartsPrivate.LarkCallBody _ [IssueCommandString, command]]]]; }; IssueCommandString: PROC[info: LarkInfo, clientData: REF] = { textPkt: Rope.ROPE = NARROW[clientData]; IF ThNet.pd.debug THEN ThSmartsPrivate.Deb[info, "Issue prose", rope[textPkt]]; info.interface.CommandString[shh: info.shh, device: keyboard, commands: textPkt]; }; }. <> <> <<>> <<>>