TonesAndSpeechImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: PolleZ, May 9, 1985 5:46:58 pm PDT
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: NATSELECT 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 {
Volume won't change until tunes exhausted!
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 somebody does something to generate a different notification while this is going on, they get what they deserve!
IF larkToneSpec.tones=NIL THEN LOOP;
thisNotification ← IF thisNotification = 'B THEN 'A ELSE 'B;
larkToneSpec.notification ← [ tones, thisNotification ];
larkToneSpec.volume ← tonesToPlay.volume;
Find a way to report asynchronous failure.
[] ← self.lark.SpecifyTones[ shh: self.shhh, queueIt: TRUE, tones: larkToneSpec];
WAIT larkNotification;
IF lastTonesNotification # thisNotification THEN -- Timed out. Give up.
Find a way to report asynchronous failure.
tonesToPlay.tones ← NIL;
ENDLOOP;
};
definitions to interface to Prose text-to-speech synthesizer
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: BOOLTRUE] = {
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
Send numPkts packets at a time to keep the Prose speaking without needing to send explicit Commence Speech commands.
FOR i: INT IN [1..numPkts] DO
IF textToSpeak.Length[] <= maxTextPktLen THEN {
textPkt ← Rope.Concat[textToSpeak, speechDone];
textToSpeak ← "";
}
ELSE {
Scan backward from the end of the packet for a place to break between words. The Prose treats an index marker as a word terminator. Keep punctuation with its preceding word for correct prosodics.
index: INT ← maxTextPktLen;
WHILE IO.TokenProc[Rope.Fetch[textToSpeak, index-1]] = other DO
index ← index-1;
IF index=0 THEN {index ← maxTextPktLen; EXIT};
Too long between break chars; just make progress somehow.
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];
};
Find a way to report asynchronous failure.
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.
Find a way to report asynchronous failure if timed out.
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];
};
Only pass on index marker notifications. Swallow or handle all others.
Assume Prose XON / XOFF disabled.
Assume only get the following responses:
 On reset: resetConfirmOK       if everything's okay
    cmdLeader <0-16> 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;
};
}.