FinchSmartsImpl.mesa
Copyright © 1984, 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, May 25, 1986 8:11:26 pm PDT
Polle Zellweger (PTZ) December 13, 1985 3:05:18 pm PST
DIRECTORY
BasicTime USING [ Now, nullGMT, OutOfRange, Update ],
Commander USING [ CommandProc, Register ],
FinchSmarts,
IO,
Lark USING [ KeyTable, SHHH ],
Log USING [Problem, Report ],
LupineRuntime USING [ BindingError ],
Names USING [ CurrentPasskey, CurrentRName, InstanceFromNetAddress, OwnNetAddress ],
NamesGV USING [ GVGetAttribute ],
NamesGVImpExp USING [ GVImport, UnGVImport ],
NamesRPC USING [ StartConversation ],
Nice,
PrincOpsUtils USING [ IsBound ],
Process USING [ Detach, SecondsToTicks, SetTimeout, Ticks ],
RefQ USING [ Dequeue, Enqueue, Map, MapType, Queue ],
Rope,
RPC USING [ AuthenticateFailed, CallFailed, EncryptionKey, ImportFailed, VersionRange ],
RuntimeError USING [ UNCAUGHT ],
ThParty USING [ Advance, Alert, ConversationsForParty, CreateParty, Deregister, DescribeInterval, DescribeParty, GetNumbersForRName, GetParty, GetPartyFromNumber, OtherParty, Register, RegisterKey, SetIntervals, SetProse ],
ThPartyRpcControl,
Thrush USING[
CallUrgency, ConversationHandle, ConvEvent, Disposition, EncryptionKey, IntervalSpec, IntervalSpecs, IntervalSpecBody, IntID, IntSpecType, NB, none, nullConvHandle, nullHandle, nullKey, nullTune, PartyHandle, ProseSpec, ProseSpecs, ProseSpecBody, Reason, ROPE, SmartsHandle, StateID, StateInConv, ThHandle, Tune, unencrypted, VoiceDirection, VoiceInterval ],
ThSmarts,
ThSmartsRpcControl,
ThVersions USING [ GetThrushVR, FinchVersion, FinchVR ],
UserProfile USING [ Token]
;
FinchSmartsImpl: CEDAR MONITOR
IMPORTS BasicTime, Commander, IO, FinchSmarts, Log, LupineRuntime, Names, NamesGV, NamesGVImpExp, NamesRPC, Nice, PrincOpsUtils, Process, RefQ, RPC, Rope, RuntimeError, ThParty, ThPartyRpcControl, ThSmartsRpcControl, ThVersions, UserProfile
EXPORTS FinchSmarts, ThSmarts = {
OPEN IO;
Declarations
CallUrgency: TYPE = Thrush.CallUrgency;
ConvDesc: TYPE = FinchSmarts.ConvDesc;
ConversationHandle: TYPE = Thrush.ConversationHandle;
nullConvHandle: ConversationHandle = Thrush.nullConvHandle;
Disposition: TYPE = Thrush.Disposition;
FinchInfo: TYPE = FinchSmarts.FinchInfo;
IntervalSpec: TYPE = Thrush.IntervalSpec;
IntervalSpecs: TYPE = Thrush.IntervalSpecs;
ProseSpec: TYPE = Thrush.ProseSpec;
ProseSpecs: TYPE = Thrush.ProseSpecs;
NB: TYPE = Thrush.NB;
none: SHHH = Thrush.none;
nullHandle: Thrush.ThHandle = Thrush.nullHandle;
PartyHandle: TYPE = Thrush.PartyHandle;
Reason: TYPE = Thrush.Reason;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Lark.SHHH;
SmartsHandle: TYPE = Thrush.SmartsHandle;
StateInConv: TYPE = Thrush.StateInConv;
PD: TYPE = RECORD [
doReports: BOOLFALSE,
doNice: BOOLFALSE,
timeoutNoAction: INTEGER ← 30,
noActionTicks: Process.Ticks ← Process.SecondsToTicks[30],
timeoutJayConnect: INTEGER ← 1,
noJayTicks: Process.Ticks ← Process.SecondsToTicks[1],
encryptionRequested: BOOLEANTRUE,
interfacesAreImported: BOOLEANFALSE,
smartsIsExported: BOOLEANFALSE,
waitsForConnect: NAT𡤆,
queueIt: BOOLFALSE,
finchOn: BOOLEANFALSE,
maxProseLength: INT�,
intvReq: CARDINAL ← 177777B
];
info: FinchInfo;
pd: REF PDNEW[PD←[]];
stopIntervalSpec: Thrush.IntervalSpec = NEW[Thrush.IntervalSpecBody←[
interval: [length: 0], direction: play, queueIt: FALSE] ];
stopProseSpec: Thrush.ProseSpec = NEW[Thrush.ProseSpecBody←[
prose: "", direction: play, queueIt: FALSE] ];
resetProseSpec: Thrush.ProseSpec = NEW[Thrush.ProseSpecBody←[
prose: "\022", direction: record, queueIt: FALSE] ]; -- \022 is ControlR
Would prefer BOOLEAN filter field to control whether resets can be sent, this hack encodes it in direction=record. August 20, 1985
Report: PROC[what: ROPE] = {
IF NOT pd.doReports THEN RETURN;
Log.Report[what, $Finch];
};
BeNice: PROC[r: REF, d: INT] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Finch, NIL];
};
Supervision
Progress: PUBLIC ENTRY PROC[
shh: SHHH,
smartsID: Thrush.SmartsHandle,
event: Thrush.ConvEvent,
yourParty: BOOL,
latestEvent: BOOL,
informationOnly: BOOL
] RETURNS [ d: Thrush.Disposition ] = {
ENABLE UNWIND => NULL;
cDesc: ConvDesc;
IF info=NIL THEN RETURN[pass];
d←pass;
Update local state information
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["---- FnProg: %g(%d) %g %g yr=%g ",
PutFTime[event.credentials.convID], int[event.credentials.stateID],
refAny[NEW[StateInConv𡤎vent.state]], rope[info.myRName], bool[yourParty]],
IO.PutFR["lt=%g in=%g, pr=%g, ky=%g\n",
bool[latestEvent], bool[event.intervalSpecs#NIL], bool[event.proseSpecs#NIL], bool[event.keyTable#NIL]]]];
cDesc ← GetConv[event.credentials.convID, FALSE];
IF event.credentials.stateID <= cDesc.cState.credentials.stateID THEN RETURN[pass]; -- Old news!
Fields always extracted
cDesc.cState.credentials.smartsID ← smartsID;
cDesc.cState.credentials.stateID ← event.credentials.stateID;
<<Need to check for increasing stateID here, and reject old ones? If so, enable
test in alrt case below.>>
Extracted if present
IF event.keyTable#NIL THEN {
cDesc.cState.keyTable ← event.keyTable; cDesc.newKeys←TRUE; };
IF event.intervalSpecs#NIL THEN ReportIntervals[cDesc, event];
IF event.proseSpecs#NIL THEN ReportProses[cDesc, event];
IF event.address#NIL THEN { cDesc.cState.address𡤎vent.address; cDesc.newAddress←TRUE; };
Extracted if the event changes our state.
IF yourParty THEN {
Other: PROC={
oD: ROPE;
IF (~latestEvent) OR cDesc.otherPartyID#nullHandle THEN RETURN;
[,cDesc.otherPartyID, oD, cDesc.conference] ←
ThParty.OtherParty[shhh: info.shh, credentials: cDesc.cState.credentials];
IF oD#NIL THEN cDesc.otherPartyDesc ← oD;
};
cDesc.descValid ← TRUE;
cDesc.cState.credentials.partyID ← event.credentials.partyID;
cDesc.cState.state ← event.state;
cDesc.cState.comment ← event.comment;
cDesc.cState.reason ← event.reason;
IF event.spec#NIL THEN { cDesc.cState.spec ← event.spec; cDesc.newSpec←TRUE; };
Extracted if non-standard?
IF event.comment#NIL THEN cDesc.cState.comment ← event.comment;
IF event.urgency#normal THEN cDesc.cState.urgency ← event.urgency;
IF event.alertKind#standard THEN cDesc.cState.alertKind ← event.alertKind;
SELECT event.state FROM
reserved, parsing => cDesc.originator ← us;
initiating => { cDesc.originator ← us; Other[]; };
pending => { cDesc.originator ← them; Other[]; };
maybe, ringing, active, canActivate, inactive => Other[];
ENDCASE;
};
BeNice[event, 4];
BeNice[cDesc, 6];
cDesc.newEvent←TRUE;
IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process.
};
Supervise: PUBLIC ENTRY PROC[info: FinchInfo ] = {
problem: ROPENIL;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
IF info.apprise THEN DO
ENABLE {
UNWIND => NULL;
Specific fatal error condition, if any => GOTO Failing;
RuntimeError.UNCAUGHT => Problem["Unknown Finch Smarts Supervisor Failure"];
When you know what ones occur, catch them, and emulate...
ANY => { problem ← "Unknown Finch Smarts Supervisor Failure"; GOTO Failing; };
};
nb: NB←success;
trans: Transition;
info.apprise ← FALSE;
FOR conversations: RefQ.Queue ← info.conversations, conversations.rest WHILE conversations#NIL DO
cDesc: ConvDesc = NARROW[conversations.first];
stateNow: StateInConv = cDesc.cState.state;
newEvent: BOOL=cDesc.newEvent;
ours: BOOL ← (info.currentConvID = cDesc.cState.credentials.convID);
cDesc.newEvent←FALSE; -- For things that must be done once-only per new state.
IF newEvent OR stateNow#idle THEN {
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["**** FnSup: %g(%d) %g %g->%g",
PutFTime[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID],
rope[info.myRName], refAny[NEW[StateInConv←stateNow]], refAny[NEW[StateInConv�sc.desiredState]]],
IO.PutFR[" %g%g\n",
refAny[NEW[Transition←transForStates[stateNow][cDesc.desiredState]]],
rope[IF ours THEN " (ours)" ELSE ""]]]];
BeNice[cDesc, 6];
};
IF NOT cDesc.descValid THEN LOOP;
Record whether or not this is now "our" conversation
SELECT stateNow FROM
idle => IF ours THEN info.currentConvID ← nullConvHandle;
parsing, reserved, initiating, maybe, ringing, canActivate, active, inactive => {
ours←TRUE;
IF cDesc.ultimateState<active THEN cDesc.ultimateState ← stateNow;
info.currentConvID ← cDesc.cState.credentials.convID;
};
pending => cDesc.ultimateState ← pending;
any => NULL;
ENDCASE => NULL;
State and substate (below) and desired state indicate there's something to do. Do it:
SELECT (trans←transForStates[stateNow][cDesc.desiredState]) FROM
noop => NULL;
elim => {
cDesc.requestedIntervals�sc.pendingIntervals←NIL;
cDesc.bluejayConnection ← cDesc.proseConnection ← FALSE;
cDesc.requestedProses�sc.pendingProses←NIL;
};
alrt => { -- placing call, or reserving conversation
convID: ConversationHandle;
IF NOT ours THEN Problem["What to do in a race?"];
IF cDesc.desiredState#reserved THEN cDesc.weOriginated ← TRUE;
[nb, convID] ← ThParty.Alert [
credentials: cDesc.cState.credentials,
calledPartyID: cDesc.desiredPartyID,
state: IF cDesc.desiredState=reserved THEN reserved ELSE initiating,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
IF nb=success THEN {
cDesc.cState.credentials.convID ← info.currentConvID ← convID;
ours←TRUE;
};
};
idle, actv => -- Simple transitions
nb ← ThParty.Advance [
shhh: info.shh,
credentials: cDesc.cState.credentials,
state: cDesc.desiredState,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
spvs => {
IF cDesc.pendingIntervals#NIL THEN {
Issue any new requests.
nb ← ThParty.SetIntervals[
shhh: info.shh,
credentials: cDesc.cState.credentials,
intervalSpecs: cDesc.pendingIntervals
];
IF nb=success THEN cDesc.pendingIntervals←NIL;
};
IF cDesc.pendingProses#NIL THEN {
Issue any new requests. Send multiple proseSpecs as long as they aren't too long. When that happens, partition cDesc.pendingProses temporarily while calling SetProse. Can get short, long, short, long misbehavior here, but need semantics of separate requests?
nextPSL, prevPSL: ProseSpecs ← NIL;
stateID: Thrush.StateID=cDesc.cState.credentials.stateID+1;
reqID: CARDINAL ← 177777B;
textLen: INT ← 0;
FOR pSL: ProseSpecs ← cDesc.pendingProses, pSL.rest WHILE pSL#NIL DO
IF (textLen ← textLen+pSL.first.prose.Length[]) <= pd.maxProseLength
THEN prevPSL ← pSL
ELSE {
nextPSL ← pSL;
prevPSL.rest ← NIL;
EXIT;
};
ENDLOOP;
nb ← ThParty.SetProse[
shhh: info.shh,
credentials: cDesc.cState.credentials,
proseSpecs: cDesc.pendingProses
];
prevPSL.rest ← nextPSL; -- put the list back together so that cDesc.requestedProses is still connected
IF nb=success THEN {
cDesc.pendingProses ← nextPSL;
IF ~cDesc.proseConnection THEN cDesc.requestedProses ← NIL;
Using SetProse to request "feeps" over trunk connection; no feedback.
};
};
};
invl => {
Problem["FinchSmarts: Invalid state transition request."];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Invalid state transition";
};
ntiy => {
Problem["FinchSmarts: State transition not yet implemented."];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Unimplemented state transition";
};
ENDCASE => ERROR;
Analyze any results of trying to reach a different state.
IF nb#success AND pd.doReports THEN
Report[IO.PutFR[" ** Results: nb=%g\n", refAny[NEW[Thrush.NB←nb]]]];
SELECT nb FROM
success, stateMismatch => NULL;
noSuchParty2 => -- Have to get to error tone here. No such party.
Complain[info, cDesc, notFound, "Called party not found"];
narcissism => -- Have to get to error tone here. No such party.
Complain[info, cDesc, notFound, "Attempt to call self rejected on philosophical grounds"];
partyNotEnabled => {
info.ReportConversationState[noSuchSmarts, cDesc, "Your Etherphone is not connected to the telephone server"];
<< How to disarm the cDesc, which will continue to generate noise?>>
};
invalidTransition, convNotActive, convStillActive => {
Complain and transition to idle
comment: ROPE="Party-level detected invalid state transition request";
Problem[comment];
Complain[info, cDesc, error, comment];
};
notInConv, noSuchConv => { -- Complain, zap, go idle and repeat.
comment: ROPE="NotInConv or NoSuchConv";
Problem[comment];
IF ours THEN info.currentConvID ← nullConvHandle;
cDesc.cState.credentials.convID ← nullConvHandle;
cDesc.cState.credentials.stateID ← 0;
Complain[info, cDesc, error, comment];
};
noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning.
problem ← "NoSuchParty or NoSuchSmarts reported, must try to go away";
GOTO Failing;
};
ENDCASE => ERROR;
Set up switching and tones to match state.
IF newEvent THEN info.ReportConversationState[success, cDesc, NIL];
After reporting state of conversation (changeNoted set), there is no further use for finished record or playback intervals; delete the leading ones (should be all of the finished ones!)
FOR iSs: IntervalSpecs ← cDesc.requestedIntervals, iSs.rest WHILE iSs#NIL DO
IF iSs.first.type=finished AND iSs.first.changeNoted THEN
cDesc.requestedIntervals ← iSs.rest
ELSE EXIT;
ENDLOOP;
FOR pSs: ProseSpecs ← cDesc.requestedProses, pSs.rest WHILE pSs#NIL DO
IF pSs.first.type=finished AND pSs.first.changeNoted THEN
cDesc.requestedProses ← pSs.rest
ELSE EXIT;
ENDLOOP;
IF stateNow=idle THEN {
info.conversations ← RefQ.Dequeue[info.conversations, conversations];
cDesc.clientData ← NIL;
};
ENDLOOP;
IF info.conversations = NIL THEN EXIT;
IF NOT info.apprise THEN WAIT info.thAction;
IF info.conversations = NIL THEN EXIT;
REPEAT Failing => UninitFinchSmarts[problem]; -- now Failed
ENDLOOP;
info.thProcess ← NIL;
};
Client Functions
GetHistory: PUBLIC PROC[ convID: Thrush.ConversationHandle, toState: INT𡤀 -- i.e., all-- ]
RETURNS [ s: Thrush.EventSequence ] = {
s←ThParty.GetHistory[shhh: info.shh, convID: convID, firstState: 1, lastState: toState];
};
GetRname: PUBLIC PROC[partyID: Thrush.PartyHandle] RETURNS [rName: ROPE] = {
RETURN[IF info=NIL THEN NIL ELSE ThParty.DescribeParty[shh: info.shh, partyID: partyID]];
};
DisconnectCall: PUBLIC ENTRY PROC[
convID: Thrush.ConversationHandle, -- not used yet
reason: Thrush.Reason←terminating,
comment: ROPENIL] = {
ENABLE UNWIND => NULL;
cDesc: ConvDesc; state: StateInConv;
IF NOT(([,cDesc, state]𡤏inchOn[]).on) THEN RETURN;
cDesc.bluejayConnection ← cDesc.proseConnection ← FALSE;
SELECT state FROM
idle, any => {
info.ReportConversationState[convNotActive, NIL, "No conversation to disconnect"];
RETURN;
};
ringing, pending => IF reason=terminating THEN reason ← busy;
ENDCASE;
Request[info, cDesc, idle, reason, comment];
};
AnswerCall: PUBLIC ENTRY PROC[convID: Thrush.ConversationHandle -- not used yet -- ] = {
ENABLE UNWIND => NULL;
cDesc: ConvDesc; state: StateInConv;
IF NOT(([,cDesc, state]𡤏inchOn[]).on) THEN RETURN;
SELECT state FROM
ringing, pending => NULL;
IF convID#info.currentConvID THEN {
info.ReportConversationState[noSuchConv, NIL, "Can't answer that call"]; RETURN; };
ENDCASE=>RETURN;
Request[info, cDesc, active];
};
PlaceCall: PUBLIC ENTRY PROC [
convID: Thrush.ConversationHandle, -- not used yet --
rName: ROPE, -- rName or description
number: ROPE, -- telephone number, if present
urgency: Thrush.CallUrgency←normal,
useNumber: BOOLFALSE] = {
ENABLE { UNWIND=>NULL; RPC.CallFailed => { UninitFinchSmarts["Communication Failure"]; CONTINUE; }; };
Get fone for dest
calledPartyID: PartyHandle;
fullRName: ROPE;
IF NOT (FinchOn[].on) THEN RETURN;
calledPartyID ← SELECT TRUE FROM
~useNumber AND
(calledPartyID ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: rName]) #nullHandle =>
calledPartyID,
number#NIL =>
ThParty.GetPartyFromNumber[shh: info.shh, partyID: info.partyID, phoneNumber: number, description: rName, trunkOK: TRUE],
rName=NIL OR useNumber => nullHandle,
([fullRName, number,] ← ThParty.GetNumbersForRName[shh: info.shh, rName: rName]).number # NIL =>
ThParty.GetPartyFromNumber[
shh: info.shh, partyID: info.partyID, phoneNumber: number,
description: fullRName, trunkOK: TRUE],
ENDCASE => nullHandle;
IF calledPartyID = nullHandle THEN {
info.ReportConversationState[noSuchParty2, NIL,
IF rName=NIL THEN "You did not supply a call destination"
ELSE "No telephone number could be found for called party"];
};
[]𡤌onnect[calledPartyID, FALSE, FALSE, urgency];
};
RecordTune: PUBLIC ENTRY PROC [
useTune: Thrush.Tune,
useInterval: Thrush.VoiceInterval,
queueIt: BOOLFALSE
]
RETURNS[
reason: FinchSmarts.RecordReason←hopeless,
tune: Thrush.Tune←Thrush.nullTune,
interval: Thrush.VoiceInterval←[],
key: Thrush.EncryptionKey←Thrush.nullKey
] = {
ENABLE UNWIND=>NULL;
inst: ThMessageDB.MessageInstance = ThMessageDB.GetMessage["Beep.Lark", 1];
Out of luck, for a while . . . no way to talk about it here!!!!
IF inst#NIL THEN PlBaMe[tune, key: extract from equiv. of inst.];
<<If useTune#newTune, probably won't work -- no key. Nuthatch will solve.>>
<<<Won't work (key troubles) if new tunes are supplied. Vestigial>>>
ourSpec: Thrush.IntervalSpec;
cDesc: ConvDesc; state: StateInConv;
started: BOOLFALSE;
IF NOT (([,cDesc,state]←JayConnection[TRUE]).jayOpen) THEN RETURN;
That's all, folks . . . for now.
ourSpec ← NEW[Thrush.IntervalSpecBody ← [
type: request, direction: record, queueIt: queueIt,
tune: useTune, interval: useInterval, keyIndex: 1
]];
Wait for recording to (fail to) begin.
EnqueueIntervals[cDesc, LIST[ourSpec]];
started ← WaitForStartOrFail[cDesc, ourSpec];
state ← cDesc.cState.state;
IF ~started OR cDesc.cState.keyTable=NIL THEN {
Recording didn't start or can't be decrypted; return with error value.
IF state#idle THEN Complain[info, cDesc, error, "Voice server connection failed"];
RETURN[hopeless, Thrush.nullTune, , ];
};
Wait for recording or conversation to end.
WHILE cDesc.cState.state#idle AND ourSpec.type#finished DO
WAIT info.thAction;
ENDLOOP;
RETURN[ok, ourSpec.tune, ourSpec.interval, cDesc.cState.keyTable[ourSpec.keyIndex]];
If state is idle, possibly the interval doesn't know its actual duration.
};
WaitForStartOrFail: INTERNAL PROC[cDesc: ConvDesc, spec: Thrush.IntervalSpec]
RETURNS[started: BOOLFALSE] = {
ENABLE UNWIND => NULL;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; };
FOR i: NAT IN [0..pd.waitsForConnect) DO
WAIT info.thAction; -- <<The supervisor condition vbl; will this work?>>
IF cDesc.cState.state=idle OR (started←(spec.type#request)) THEN EXIT;
ENDLOOP;
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
};
StopTune: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
IF NOT (([,cDesc,]←JayConnection[FALSE]).jayOpen) THEN RETURN; -- Stop only if started
EnqueueIntervals[cDesc, LIST[NEW[Thrush.IntervalSpecBody←stopIntervalSpec^]]];
};
StopSpeech: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
IF NOT (([,cDesc,]←ProseConnection[FALSE]).proseOpen) THEN RETURN; -- Stop only if started
ClearPendingProses[cDesc];
EnqueueProses[cDesc, LIST[NEW[Thrush.ProseSpecBody←stopProseSpec^]]];
};
ResetProse: PUBLIC ENTRY PROC [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
IF NOT (([,cDesc,]←ProseConnection[FALSE]).proseOpen) THEN RETURN; -- Stop only if started
ClearPendingProses[cDesc];
EnqueueProses[cDesc, LIST[NEW[Thrush.ProseSpecBody←resetProseSpec^]]];
};
PlaybackTune: PUBLIC ENTRY PROC [
tune: Thrush.Tune,
interval: Thrush.VoiceInterval,
key: Thrush.EncryptionKey,
queueIt: BOOLFALSE,
failOK: BOOL, -- playing is optional; leave connection open if tune doesn't exist.
wait: BOOLFALSE
] RETURNS[ started: BOOLFALSE ] = { -- FALSE if failed or if didn't wait to find out
ENABLE UNWIND=>NULL;
cDesc: ConvDesc; state: StateInConv;
nb: NB; keyIndex: [0..17B];
exists: BOOL;
specs: Thrush.IntervalSpecs;
spec: Thrush.IntervalSpec;
IF tune<=Thrush.nullTune THEN {
info.ReportConversationState[convNotActive, NIL, "No such tune"]; RETURN; }; -- UGH!
IF NOT (([,cDesc,state]←JayConnection[TRUE]).jayOpen) THEN RETURN;
<< ??? No word ??? >>
[nb, keyIndex] ←
ThParty.RegisterKey[shh: info.shh, credentials: cDesc.cState.credentials, key: key];
IF nb#success AND nb#stateMismatch THEN {
IF ~failOK THEN Complain[info, cDesc, error, "Could not encode encryption key"];
RETURN;
};
spec ← NEW[Thrush.IntervalSpecBody ← [ tune: tune, interval: interval, direction: play ] ];
[, exists, specs] ←
ThParty.DescribeInterval[
shhh: info.shh, credentials: cDesc.cState.credentials,
targetInterval: spec, minSilence: LAST[INT]]; -- Just trim off surrounding silences.
IF ~exists THEN {
IF ~failOK THEN Complain[info, cDesc, error, "Could not play utterance"];
RETURN;
};
IF specs=NIL THEN specs ← LIST[spec];
spec ← specs.first;
FOR sL: Thrush.IntervalSpecs ← specs, sL.rest WHILE sL#NIL DO
Describe... may return a list in error; we only want the outer interval.
IF sL#specs THEN spec.interval.length ←
(sL.first.interval.start+sL.first.interval.length) - spec.interval.start;
ENDLOOP;
spec.keyIndex ← keyIndex; spec.type ← request; spec.direction ← play; spec.queueIt ← queueIt;
EnqueueIntervals[cDesc, specs ];
IF wait THEN started ← WaitForStartOrFail[cDesc, spec];
};
defaultTranslateProc: FinchSmarts.ProseTranslateProc←NIL;
RegisterTranslateProc: PUBLIC PROC [translate: FinchSmarts.ProseTranslateProc←NIL] ~ {
defaultTranslateProc ← translate;
};
TextToSpeech: PUBLIC ENTRY PROC [
text: Rope.ROPE, queueIt: BOOLTRUE,
proseTranslateProc: FinchSmarts.ProseTranslateProc←NIL]
RETURNS [reason: FinchSmarts.RecordReason←ok] = {
ENABLE UNWIND => NULL;
Modelled after RecordTune, but it breaks text up into manageable chunks so as to allow speech to start sooner and flush quicker...
ourSpec: Thrush.ProseSpec;
cDesc: ConvDesc; state: StateInConv;
started: BOOLFALSE;
IF proseTranslateProc#NIL THEN text ← proseTranslateProc[text]
ELSE IF defaultTranslateProc#NIL THEN text ← defaultTranslateProc[text];
IF NOT (([,cDesc,state]←ProseConnection[TRUE]).proseOpen) THEN
RETURN[hopeless];
IF ~queueIt THEN ClearPendingProses[cDesc]; -- need to generate reports??
WHILE text.Length[] > pd.maxProseLength DO
Scan backward from the end of the text 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. Except - need to handle $, Dr., ft. specially both here and in LarkOutImpl. Where to put a common procedure? They run on different machines!
index: INT ← pd.maxProseLength;
WHILE IO.TokenProc[Rope.Fetch[text, index-1]] = other DO
index ← index-1;
IF index=0 THEN {index ← pd.maxProseLength; EXIT};
Too long between break chars; just make progress somehow.
ENDLOOP;
ourSpec ← NEW[Thrush.ProseSpecBody ← [
type: request, direction: play, queueIt: queueIt, prose: text.Substr[len: index]
]];
text ← text.Substr[start: index];
EnqueueProses[cDesc, LIST[ourSpec]];
queueIt ← TRUE;
ENDLOOP;
ourSpec ← NEW[Thrush.ProseSpecBody ← [
type: request, direction: play, queueIt: queueIt, prose: text
]];
EnqueueProses[cDesc, LIST[ourSpec]];
For now, assume that everything will work okay from here.
};
Registration
InitFinchSmarts: PUBLIC PROC [
thrushInstance: Thrush.ROPENIL,
ReportSystemState: PROC[ on: BOOL ],
ReportConversationState: PROC[ nb: NB, cDesc: ConvDesc, remark: Rope.ROPE ]
] = {
problem: ROPENIL; {
ENABLE RPC.CallFailed => { problem ← "Communication Failure"; GOTO InitFailed; };
partyID: Thrush.PartyHandle;
smartsID: Thrush.SmartsHandle;
thVR: RPC.VersionRange = ThVersions.GetThrushVR;
problem: Thrush.ROPENIL;
namesGVInstance: Thrush.ROPENIL;
UninitFinchSmarts[NIL];
IF info=NIL THEN info ← NEW[FinchSmarts.FinchInfoBody];
info.conversations ← NIL;
info.thProcess ← NIL;
info.currentConvID ← nullConvHandle;
info.apprise ← FALSE;
info.ReportSystemState←ReportSystemState;
info.ReportConversationState←ReportConversationState;
IF thrushInstance = NIL THEN thrushInstance ← UserProfile.Token[
key: "ThrushClientServerInstance", default: "Strowger.Lark"];
info.myName ← [
type: "ThSmarts.Lark",
instance: Names.InstanceFromNetAddress[netAddress: Names.OwnNetAddress[], suffix: "0"],
version: ThVersions.FinchVR];
info.myRName ← Names.CurrentRName[];
info.myPassword ← Names.CurrentPasskey[];
info.serverInstanceName ← thrushInstance;
ThSmartsRpcControl.ExportInterface[
interfaceName: info.myName,
user: info.myRName,
password: info.myPassword];
pd.smartsIsExported←TRUE;
For the purpose of importing NamesGV, use prior binding of serverInstance if there is one and the server name is the same as before.
namesGVInstance ← UserProfile.Token[key: "NamesGVInstance", default: "Strowger.lark"];
IF info.namesGVInstance=NIL OR ~namesGVInstance.Equal[s2: info.namesGVInstanceName, case: FALSE] THEN info.namesGVInstance ← namesGVInstance;
info.namesGVInstanceName ← namesGVInstance;
IF PrincOpsUtils.IsBound[LOOPHOLE[NamesGVImpExp.GVImport]] AND
~NamesGVImpExp.GVImport[info.namesGVInstance] AND (Not any more!)
~NamesGVImpExp.GVImport[info.namesGVInstanceName] THEN {
problem ← "Couldn't import Grapevine Package"; GOTO InitFailed; };
info.shh ← IF NOT pd.encryptionRequested THEN Thrush.unencrypted
ELSE NamesRPC.StartConversation [
caller: info.myRName,
callee: thrushInstance,
key: info.myPassword,
level: --<<ECB>>--CBCCheck !
RPC.AuthenticateFailed=> { problem←"Could not authenticate"; GOTO InitFailed}];
Implement the GV lookup portion of importing the Thrush instance explicitly, in order to get the benefit of the local Grapevine cache.
IF (info.namesGVInstance ← NamesGV.GVGetAttribute[rName: info.namesGVInstanceName, attribute: $connect, default: NIL]) = NIL THEN { problem←"Telephone server not found"; GOTO InitFailed};
IF (info.serverInstance ← NamesGV.GVGetAttribute[rName: info.serverInstanceName, attribute: $connect, default: NIL]) = NIL THEN { problem←"Telephone server not found"; GOTO InitFailed};
ThPartyRpcControl.ImportInterface[
interfaceName: [type: "ThParty.Lark", instance: info.serverInstance, version: thVR] ! --
interfaceName: [type: "ThParty.Lark", instance: info.serverInstanceName, version: thVR] ! -- Don't do "explicit import" any more.
RPC.ImportFailed=> {
IF why=wrongVersion THEN
problem ← IO.PutFR["Finch version %d too old; import failed",
card[ThVersions.FinchVersion]];
GOTO InitFailed;
}];
pd.interfacesAreImported←TRUE;
partyID←ThParty.CreateParty[shh: info.shh, type: individual, rName: info.myRName];
IF partyID=Thrush.nullHandle THEN { problem←"Can't register with server"; GOTO InitFailed; };
smartsID←ThParty.Register[
shh: info.shh, partyID: partyID, interface: NEW[ThSmartsRpcControl.InterfaceName←info.myName],
properties: [x: manager[machine: Names.OwnNetAddress[]]]] ;
IF smartsID=Thrush.nullHandle THEN { problem←"Can't register with server"; GOTO InitFailed; }; -- <<try to delete the Party?>>
info.smartsID ← smartsID;
info.partyID ← partyID;
ThParty.ConversationsForParty[shh: info.shh, partyID: partyID];
Will shortly update our knowledge of what's already going on!!
pd.finchOn←TRUE;
info.ReportSystemState[pd.finchOn];
EXITS
InitFailed => UninitFinchSmarts[problem];
};};
UninitFinchSmarts: PUBLIC PROC[problem: ROPENIL] = {
<<Must eliminate participation in any conversation that only we know enough about to get rid of, and eliminate any evidence of our partipation in any others. For now, do only the latter thing>>
ENABLE RPC.CallFailed => GOTO Failed;
IF problem#NIL THEN Problem[problem];
IF info#NIL THEN {
IF pd.finchOn AND info.partyID#nullHandle AND info.smartsID#nullHandle THEN
ThParty.Deregister[info.shh, info.smartsID!RPC.CallFailed=>CONTINUE];
info.shh←none;
info.ReportSystemState[FALSE];
{
ClearConvs: RefQ.MapType = {
cDesc: ConvDesc=NARROW[subqueue.first];
cDesc.clientData ← NIL;
};
[]←RefQ.Map[info.conversations, ClearConvs];
info.conversations ← NIL;
};
};
pd.finchOn←FALSE;
IF PrincOpsUtils.IsBound[LOOPHOLE[NamesGVImpExp.UnGVImport]] THEN
NamesGVImpExp.UnGVImport[];
IF pd.interfacesAreImported THEN -- We don't do this any more.
ThPartyRpcControl.UnimportInterface[!LupineRuntime.BindingError=>CONTINUE];
pd.interfacesAreImported←FALSE;
IF pd.smartsIsExported THEN
ThSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
pd.smartsIsExported←FALSE;
EXITS
Failed => pd.interfacesAreImported ← pd.smartsIsExported ← FALSE;
};
FinchIsRunning: PUBLIC PROC RETURNS [finchIsRunning: BOOL] = { RETURN[pd.finchOn]; };
Utilities
GetConv: PUBLIC INTERNAL PROC[convID: ConversationHandle, validIfNew: BOOL
] RETURNS [ cDesc: ConvDesc←NIL ] = --INLINE-- {
IsConv: RefQ.MapType = {
cDesc ← NARROW[subqueue.first];
IF cDesc.cState.credentials.convID = convID THEN RETURN[TRUE];
};
IF convID#nullConvHandle AND RefQ.Map[info.conversations, IsConv] THEN RETURN;
New conversation
cDesc ← NEW[FinchSmarts.ConvDescBody←[]];
cDesc.otherPartyDesc ← "unknown party";
cDesc.descValid ← validIfNew;
cDesc.startTime ← BasicTime.Now[];
cDesc.desiredPartyID ← nullHandle;
cDesc.cState.credentials ← [
convID: convID, smartsID: info.smartsID, partyID: info.partyID, stateID: 0];
info.conversations ← RefQ.Enqueue[info.conversations, cDesc];
IF pd.doReports THEN
Report[IO.PutFR[" ** NewConv %g %g, vl=%g\n", PutFTime[convID], rope[info.myRName], bool[validIfNew]]];
};
PutFTime: PROC[convID: ConversationHandle] RETURNS [IO.Value] = {
ehsMemorialBool: BOOLFALSE; -- wouldn't need if could return from catch phrase.
IF convID=nullConvHandle OR convID=BasicTime.nullGMT
THEN RETURN[rope["(not assigned)"]];
[]�sicTime.Update[convID, 0!BasicTime.OutOfRange=> {
ehsMemorialBool←TRUE; CONTINUE; }];
IF ehsMemorialBool THEN RETURN[int[LOOPHOLE[convID, INT]]]
ELSE RETURN[time[convID]];
};
GetConvDesc: PUBLIC PROC[convID: ConversationHandle]RETURNS[cDesc: ConvDesc←NIL ] = {
Not at present protected by monitor -- FinchToolImpl back-pointer problem.
IsConv: RefQ.MapType = {
cDesc ← NARROW[subqueue.first];
IF cDesc.cState.credentials.convID = convID THEN RETURN[TRUE];
};
IF convID=nullConvHandle THEN RETURN[NIL];
RETURN[IF RefQ.Map[info.conversations, IsConv] AND cDesc.descValid
THEN cDesc ELSE NIL];
};
GetCurrentConvID: PUBLIC PROC RETURNS [convID: ConversationHandle] = {
Not at present protected by monitor -- FinchToolImpl back-pointer problem.
RETURN[info.currentConvID];
};
Apprise: PUBLIC INTERNAL PROC[info: FinchInfo] = --INLINE-- {
IF info.thProcess=NIL THEN
TRUSTED { Process.Detach[info.thProcess ← FORK Supervise[info]]; };
info.apprise ← TRUE;
BROADCAST info.thAction;
};
ReportIntervals: INTERNAL PROC[cDesc: ConvDesc, event: Thrush.ConvEvent] = {
n^2 match (worst-case) of reported interval information matched against the requests that we have made. This gives us needed progress information.
FOR eSs: IntervalSpecs ← event.intervalSpecs, eSs.rest WHILE eSs#NIL DO
eS: IntervalSpec = eSs.first;
eSID: CARDINAL = eS.intID.reqID;
FOR rSs: IntervalSpecs ← cDesc.requestedIntervals, rSs.rest WHILE rSs#NIL DO
rS: IntervalSpec = rSs.first;
IF eSID # rS.intID.reqID THEN LOOP;
rS^ ← eS^; -- Update status.
cDesc.requestedIntervals records all those intervals that have been requested but not denoted finished. They must finish in order. If an embedded entry in this list finishes, then the finish notifications for any preceding entries have been lost; here we simulate them. <<Consider various abnormal finish codes augmenting finish>>
IF rS.type=finished THEN -- Truncate requests
FOR fSs: IntervalSpecs ← cDesc.requestedIntervals, fSs.rest WHILE fSs#rSs DO
IF fSs.first.type # finished THEN {
fSs.first.type ← finished; fSs.first.changeNoted←FALSE; };
ENDLOOP;
EXIT;
ENDLOOP;
ENDLOOP;
};
ReportProses: INTERNAL PROC[cDesc: ConvDesc, event: Thrush.ConvEvent] = {
n^2 match (worst-case) of reported prose information matched against the requests that we have made. This gives us needed progress information.
FOR eSs: ProseSpecs ← event.proseSpecs, eSs.rest WHILE eSs#NIL DO
eS: ProseSpec = eSs.first;
eSID: CARDINAL = eS.intID.reqID;
FOR rSs: ProseSpecs ← cDesc.requestedProses, rSs.rest WHILE rSs#cDesc.pendingProses AND rSs#NIL DO
rS: ProseSpec = rSs.first;
IF eSID # rS.intID.reqID THEN LOOP;
IF rS.type # eS.type THEN rS^ ← eS^; -- Update status.
Avoids duplicate request reports during debugging (caused by a process race)
cDesc.requestedProses records all those intervals that have been requested but not denoted finished. They must finish in order. If an embedded entry in this list finishes, then the finish notifications for any preceding entries have been lost; here we simulate them. <<Consider various abnormal finish codes augmenting finish>>
NOTE: ALL ProseSpecs should be reported explicitly (except ones that were flushed)!!!
IF rS.type=finished THEN -- Truncate requests
FOR fSs: ProseSpecs ← cDesc.requestedProses, fSs.rest WHILE fSs#rSs DO
IF fSs.first.type # finished THEN {
rS.queueIt should be FALSE; all of these requests have actually been flushed.
fSs.first.type ← finished; fSs.first.changeNoted←FALSE; };
ENDLOOP;
EXIT;
ENDLOOP;
ENDLOOP;
};
EnqueueIntervals: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpecs] = {
Splice int to the end of cDesc.requestedIntervals, which is guaranteed to be a superset of pendingIntervals. When pendingIntervals is a trivial subset (NIL), it needs special attention.
FOR iSs: IntervalSpecs ← int, iSs.rest WHILE iSs#NIL DO
iSs.first.intID ← [0, pd.intvReq ← pd.intvReq+1];
reqID unique within 2^16, forget about stateID
ENDLOOP;
IF cDesc.pendingIntervals=NIL THEN cDesc.pendingIntervals←int;
IF cDesc.requestedIntervals=NIL THEN cDesc.requestedIntervals←int
ELSE FOR iSs: IntervalSpecs ← cDesc.requestedIntervals, iSs.rest WHILE iSs#NIL DO
IF iSs.rest=NIL THEN { iSs.rest ← int; EXIT; };
ENDLOOP;
Apprise[info];
};
EnqueueProses: INTERNAL PROC[cDesc: ConvDesc, prose: ProseSpecs] = {
Splice prose to the end of cDesc.requestedProses, which is guaranteed to be a superset of pendingProses. When pendingProses is a trivial subset (NIL), it needs special attention.
FOR pSs: ProseSpecs ← prose, pSs.rest WHILE pSs#NIL DO
pSs.first.intID ← [0, pd.intvReq ← pd.intvReq+1];
reqID unique within 2^16, forget about stateID
ENDLOOP;
IF cDesc.pendingProses=NIL THEN cDesc.pendingProses←prose;
IF cDesc.requestedProses=NIL THEN cDesc.requestedProses←prose
ELSE FOR pSs: ProseSpecs ← cDesc.requestedProses, pSs.rest WHILE pSs#NIL DO
cDesc.pendingProses is closer to the end of the list than cDesc.requestedProses is...
Yeah, but maybe it's a different list.
IF pSs.rest=NIL THEN { pSs.rest ← prose; EXIT; };
ENDLOOP;
Apprise[info];
};
ClearPendingProses: INTERNAL PROC [cDesc: ConvDesc] ~ {
FOR pSs: ProseSpecs ← cDesc.requestedProses, pSs.rest WHILE pSs#NIL DO
IF pSs.rest=cDesc.pendingProses THEN { pSs.rest ← NIL; EXIT; };
ENDLOOP;
cDesc.pendingProses←NIL;
};
Complain: INTERNAL PROC[info: FinchInfo, cDesc: ConvDesc, reason: Reason←wontSay, comment: ROPENIL] = {
cDesc.desiredState ← IF cDesc.cState.state#any THEN idle ELSE reserved;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
cDesc.desiredPartyID ← nullHandle;
cDesc.pendingIntervals ← NIL;
ClearPendingProses[cDesc];
Apprise[info]; -- BROADCAST is sometimes meaningless (running monitor.)
};
FinchOn: INTERNAL PROC RETURNS [
on: BOOLFALSE, cDesc: ConvDesc←NIL, state: StateInConv←idle] = {
IF ~pd.finchOn OR info=NIL THEN {
Log.Report["Your Finch is not running", $Finch]; RETURN; };
on←TRUE;
cDesc ← GetConvDesc[info.currentConvID];
state←IF cDesc=NIL THEN idle ELSE cDesc.cState.state;
};
Request: INTERNAL PROC[
info: FinchInfo,
cDesc: ConvDesc,
state: StateInConv,
reason: Reason←wontSay,
comment: ROPENIL
] = {
cDesc.desiredState ← state;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
Apprise[info];
};
Connect: INTERNAL PROC [
calledPartyID: PartyHandle, bluejayConnection: BOOL, proseConnection: BOOL, urgency: Thrush.CallUrgency, waitForActive: BOOLFALSE]
RETURNS [ cDesc: ConvDesc ] = {
state: StateInConv;
[,cDesc, state]𡤏inchOn[];
SELECT state FROM
idle => { cDesc ← GetConv[nullConvHandle, TRUE]; cDesc.cState.state𡤊ny; };
reserved => NULL;
ENDCASE => {
info.ReportConversationState[convStillActive, NIL, "Conversation already in progress"];
RETURN;
};
cDesc.bluejayConnection ← bluejayConnection;
cDesc.proseConnection ← proseConnection;
cDesc.desiredPartyID ← calledPartyID;
cDesc.pendingIntervals ← NIL;
ClearPendingProses[cDesc];
Request[info, cDesc, active];
IF ~waitForActive THEN RETURN;
FOR i: NAT IN [0..pd.waitsForConnect) DO
TRUSTED { Process.SetTimeout[@info.thAction, pd.noJayTicks]; };
WAIT info.thAction; -- <<The supervisor condition vbl; will this work?>>
TRUSTED { Process.SetTimeout[@info.thAction, pd.noActionTicks]; };
SELECT cDesc.cState.state FROM
active => RETURN;
idle => EXIT;
ENDCASE;
ENDLOOP;
Complain[info, cDesc, error, "Finch failed to connect to voice service."];
};
JayConnection: INTERNAL PROC[newConn: BOOLTRUE] RETURNS [
jayOpen: BOOL�LSE,
cDesc: ConvDesc←NIL,
state: StateInConv←idle
] = {
calledPartyID: PartyHandle;
IF NOT (([,cDesc, state]𡤏inchOn[]).on) THEN {
jayOpen←RepRet[FALSE, noSuchParty, "Your Finch is not running"]; RETURN; };
SELECT state FROM
idle, reserved => IF newConn THEN {
calledPartyID ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: "Recording", type: service];
IF calledPartyID # Thrush.nullHandle THEN
cDesc ← Connect[calledPartyID, TRUE, FALSE, normal, TRUE];
};
ENDCASE;
jayOpen←RepRet[IF cDesc=NIL THEN FALSE ELSE cDesc.bluejayConnection, noSuchConv, "Connection to voice server failed"];
};
ProseConnection: INTERNAL PROC[newConn: BOOLTRUE] RETURNS [
proseOpen: BOOL�LSE,
cDesc: ConvDesc←NIL,
state: StateInConv←idle
] = {
calledPartyID: PartyHandle;
IF NOT (([,cDesc, state]𡤏inchOn[]).on) THEN {
proseOpen←RepRet[FALSE, noSuchParty, "Your Finch is not running"]; RETURN; };
SELECT state FROM
idle, reserved => IF newConn THEN {
calledPartyID ← ThParty.GetParty[shh: info.shh, partyID: info.partyID, rName: "Text-to-Speech", type: service];
Would really like to be able to distinguish between service dead and service busy for client reports (return param from TextToSpeech). Also true for JayConnection above.
IF calledPartyID # Thrush.nullHandle THEN
cDesc ← Connect[calledPartyID, FALSE, TRUE, normal, TRUE];
};
ENDCASE;
proseOpen←RepRet[IF cDesc=NIL THEN FALSE ELSE cDesc.proseConnection, noSuchConv, "Connection to text-to-speech server failed"];
};
RepRet: PROC[bool: BOOL, nb: NB, remark: Rope.ROPE]
RETURNS[sameBool: BOOL] = {
IF bool=FALSE THEN
IF info#NIL THEN info.ReportConversationState[nb, NIL, remark]
ELSE Log.Report[remark, $Finch];
sameBool𡤋ool;
};
Problem: PROC[remark: ROPE←NIL] = { Log.Problem[remark, $Finch]; };
Feep: PUBLIC ENTRY PROC[feepString: ROPE] = {
cDesc: ConvDesc;
IF info=NIL OR info.conversations=NIL OR info.currentConvID=nullConvHandle
THEN RETURN;
cDesc ← GetConv[info.currentConvID, FALSE];
EnqueueProses[cDesc,
LIST[NEW[Thrush.ProseSpecBody←[prose: feepString, direction: play]]]];
};
State Transition Tables
Transition: TYPE = {
Just codes to dispatch on in Supervisor; explained there
noop, elim, idle, alrt, actv, spvs, invl, ntiy
};
transForStates: ARRAY StateInConv OF ARRAY StateInConv OF Transition ← [
idle resrv pars init pend mayb ring canAc activ inact any -- desired
[ elim, invl, invl, invl, invl, invl, invl, invl, elim, invl, elim ], -- idle  (current)
[ idle, noop, invl, invl, invl, invl, invl, invl, alrt, ntiy, noop ], -- reserved
[ idle, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, noop ], -- parsing
[ idle, invl, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- initiating
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- pending
[ idle, invl, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- maybe
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- ringing
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, ntiy ], -- canActivate
[ idle, invl, invl, invl, invl, invl, invl, invl, spvs, ntiy, spvs ], -- active
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, invl, ntiy ], -- inactive
[ elim, alrt, invl, invl, invl, invl, invl, invl, alrt, ntiy, invl ] -- any (nonex)
];
NB: examine relationship to validity table in PartyOpsImpl someday.
Debugging nonsense
ViewCmd: Commander.CommandProc = TRUSTED {
Nice.View[pd, "Finch PD"];
};
Commander.Register["VuFinch", ViewCmd, "Program Management variables Finch"];
Registration for use by arms-length systems
FinchSmarts.Register[NEW[FinchSmarts.ProcsRecord ← [
playbackTune: PlaybackTune,
recordTune: RecordTune,
stopTune: StopTune,
textToSpeech: TextToSpeech,
registerTranslateProc: RegisterTranslateProc,
stopSpeech: StopSpeech,
finchIsRunning: FinchIsRunning
]]];
}.
Swinehart, May 22, 1985 1:08:51 pm PDT
Changes to GetParty.
changes to: JayConnection
Polle Zellweger (PTZ) July 13, 1985 5:20:26 pm PDT
adding Text-to-Speech server
changes to: DIRECTORY, ProseSpec, ProseSpecs, stopIntervalSpec (was stopSpec), stopProseSpec, Progress, Supervise, DisconnectCall, PlaceCall, StopSpeech, TextToSpeech, InitFinchSmarts, GetConv, ReportProses, EnqueueProses, Complain, Connect, ProseConnection
Swinehart, August 6, 1985 5:43:08 pm PDT
Merge PTZ prose changes
changes to: DIRECTORY, NB, PD, stopIntervalSpec, stopProseSpec, Report, Progress, Supervise, DisconnectCall, PlaceCall, StopTune, NoiseSpec, PlaybackTune, TextToSpeech, InitFinchSmarts, GetConv, ReportProses, CompareIntID, EnqueueProses, Complain, Connect, JayConnection, ProseConnection, RepRet, FinchSmarts
Polle Zellweger (PTZ) August 19, 1985 4:16:40 pm PDT
Meter text in TextToSpeech so as to avoid sending large ropes all at once. Allows speech to begin sooner and flush faster. Also flushing changes.
changes to: FinchInfo, PD, Supervise, TextToSpeech, StopSpeech, ReportProses (comments only)
Polle Zellweger (PTZ) September 3, 1985 6:28:29 pm PDT
Allow registration of defaultTranslateProc.
changes to: Supervise, defaultTranslateProc, RegisterTranslateProc, TextToSpeech, FinchSmarts
Swinehart, September 16, 1985 10:00:59 am PDT
If Finch has never been initialized, info is NIL -- don't let that bother you.
changes to: GetRname, PlayNoise, FinchOn
Polle Zellweger (PTZ) October 22, 1985 5:10:44 pm PDT
changes to: Progress (add prose debugging reports), TextToSpeech (report connection failure), ReportProses (fix debugging reports), JayConnection (report connection failure), ProseConnection (report connection failure)
Polle Zellweger (PTZ) October 24, 1985 4:53:21 pm PDT
Move bluejayConnection and proseConnection from FinchInfo to ConvDesc.
changes to: Supervise, DisconnectCall, InitFinchSmarts, Connect, JayConnection, ProseConnection
Swinehart, October 28, 1985 12:31:20 pm PST
Merge Above changes with other other minor changes (RefQ and the like)
changes to: DIRECTORY, FinchSmartsImpl, Progress, Supervise, DisconnectCall, TextToSpeech, InitFinchSmarts, UninitFinchSmarts, IsConv, GetConv, IsConv (local of GetConvDesc), GetConvDesc, ReportProses, FinchOn, Connect, JayConnection, ProseConnection, PlaybackTune, ClearConvs (local of UninitFinchSmarts)
Polle Zellweger (PTZ) December 13, 1985 2:53:06 pm PST
New function for FinchToolImpl to use for managing selections in conversation log.
changes to: GetCurrentConvID
Swinehart, February 2, 1986 11:01:59 pm PST
intID.stateID couldn't be maintained properly. Change intID.reqID to be a semi-unique value (cycles every 2^16 requests), and look for that only in incoming reports. They'll have (correct) stateID values in them, too, but we don't use them for anything. Fixes a problem where some requests were being played multiple times, others not at all, if things were queued.
changes to: Supervise, ReportProses, EnqueueProses, DIRECTORY
Swinehart, May 25, 1986 8:10:59 pm PDT
Accommodate 6.1 server, which no longer does "explicit export", so can't "explicit import". 6.1 Finch does it using hostHint.
changes to: InitFinchSmarts