DIRECTORY
Atom USING [ DottedPair, DottedPairNode ],
IO,
Commander USING [ CommandProc, Register ],
Convert USING [ RopeFromInt ],
Lark USING [ bStar, bThorp, ConnectionSpec, ConnectionSpecRec, CommandEvent, CommandEvents, CommandEventSequence, Device, disabled, EchoParameters, EchoParameterRecord, enabled, endNum, Event, KeyTable, Milliseconds, o3i1, o2i2, o1i1, Passel, reset, SHHH, StatusEvent, Tone, ToneSpec, ToneSpecRec, VoiceBuffer ],
LarkPlay USING [ ToneList, ToneSpec, ToneSpecRec ],
LarkOpsRpcControl,
LarkSmarts,
List USING [ Nconc1 ],
Multicast USING [ HandleMulticast, StopHandlingMulticast ],
Nice,
Process USING [ Detach, EnableAborts, MsecToTicks, SetTimeout ],
Pup USING [ nullSocket ],
Rope USING [ Cat, Concat, Fetch, FromChar, Length, ROPE, Substr, Translate, TranslatorType ],
RPC USING [ CallFailed ],
ThNet USING [ pd ],
ThParty USING [ PartyInfo ],
ThPartyPrivate USING [ SmartsData ],
Thrush USING[ NetAddress, ProseSpec, ProseSpecs, ROPE, SHHH, SmartsID ],
ThSmartsPrivate
USING [
ConvDesc, flushMarker, proseFailure, indexMarkerEnd, LarkInfo, LarkProseQueue, LarkState, LSwitches, LState, maxClientMarker, maxControlMarker, ProgressTones, ProseCmd, SmartsInfo, SwitchState ],
TU USING [ RefAddr ],
VoiceUtils USING [ ProblemFR, Report ]
;
Declarations
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
LarkInfo: TYPE = ThSmartsPrivate.LarkInfo;
LarkState: TYPE = ThSmartsPrivate.LarkState;
SmartsData: TYPE = ThPartyPrivate.SmartsData;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
SmartsID: TYPE = Thrush.SmartsID;
ProseCmd: TYPE = ThSmartsPrivate.ProseCmd;
ROPE: TYPE = Thrush.ROPE;
firstTone: LarkState = FIRST[ThSmartsPrivate.ProgressTones];
bStar: Lark.Event = Lark.bStar;
bThorp: Lark.Event = Lark.bThorp;
enabled: Lark.Event = Lark.enabled;
endNum: Lark.Event = Lark.endNum;
disabled: Lark.Event = Lark.disabled;
reset: Lark.Event = Lark.reset;
larkRegistry: ROPE ← ".Lark";
PD:
TYPE =
RECORD [
waitForTelco: CARDINAL ← 500, -- default values for initial pause, on time, and off time
telcoMinOn: CARDINAL ← 60, -- when generating touch-tones.
telcoMinOff: CARDINAL ← 60,
flashWaitTime: CARDINAL ← 800,
idleWaitTime: CARDINAL ← 20000, -- time for supervisor to wait for more events.
blinkWaitTime: CARDINAL ← 500, -- if LED is blinking, don't wait so long.
backDoorOH: BOOL←FALSE,
callTimeoutOK: BOOL←FALSE, -- set to keep Thrush alive when debugging a Lark.
tonesInvalid: BOOL←TRUE,
tonesLast:
BOOL←
TRUE,
-- sets up alternate lark setup situation in supervisor loop
<<Presently not used.>>
noisyFeeps: BOOL←FALSE -- TRUE if caller is to hear automatically-generated touchtones.
];
pd: REF PD ← NEW[PD←[]];
dialTone: LarkPlay.ToneSpec ← NIL;
busyTone: LarkPlay.ToneSpec ← NIL;
errorTone: LarkPlay.ToneSpec ← NIL;
ringbackTone: LarkPlay.ToneSpec ← NIL;
quenchSpec: Lark.ToneSpec ← NEW[Lark.ToneSpecRec ←[volume: 0, totalTime: 0, tones: LIST[[0,0,0,0]]]];
definitions to interface to Prose text-to-speech synthesizer; more in ThSmartsPrivate
pReset: PUBLIC ProseCmd ← "\022";
stopAndFlush: PUBLIC ProseCmd ← "\033[S";
commenceSpeech: ProseCmd = "\033[C"; -- \033 is ESC
cmdLeader: ProseCmd = "\033["; -- for constructing arbitrary commands
indexMarkerLen:
INT = 6;
indexMarkerEnd: CHAR = 'i; -- in ThSmartsPrivate
maxTextPktLen:
INT ← 90;
Byte (char) count; RPCLupine.maxDataLength is word count
No bigger than RPCLupine.maxDataLength - 2*indexMarkerLen
maxPkts: INT = 250 / maxTextPktLen; -- Prose can hold `several hundred' chars
numPkts: INT ← 3;
minClientMarker:
INT = 1;
maxClientMarker: INT = 199; -- in ThSmartsPrivate
minControlMarker:
INT = 200;
-- reserved for packet control
maxControlMarker: INT = 250; -- in ThSmartsPrivate
speechDoneMarker: INT = 255;
speechDone: ProseCmd = "\033[C\033[255i"; -- commence speech & report back when done
Internal Procedures
FailE: ENTRY PROC[info: LarkInfo] = {Fail[info];};
Fail:
INTERNAL
PROC[info: LarkInfo] = {
IF info.larkState#recovering THEN info.larkStateiled; };
LarkSupervisor:
PROCEDURE[ info: LarkInfo ] = {
In a loop, keep the state of the Lark up to date. Awakens itself whenever any pending
tone is otherwise likely to time out. Awakened by EnterLarkState whenever the state
of tones, switches, and the like might have to change.
ENABLE UNWIND => NULL;
req: REF;
WaitForAction:
ENTRY
PROC[info: LarkInfo]
RETURNS [ref:
REF←
NIL] =
TRUSTED {
ENABLE UNWIND=>NULL;
DO
elt: LIST OF REF ← info.newActions;
IF info.larkState=failed OR info.larkState=recovering THEN RETURN[NIL];
IF elt #
NIL
THEN {
ref𡤎lt.first;
info.newActions ← elt.rest;
RETURN[ref];
};
Process.SetTimeout[@info.stateChange, Process.MsecToTicks[pd.idleWaitTime]];
WAIT info.stateChange;
IF info.newActions =
NIL
THEN {
IF info.nextToneList#NIL THEN Fail[info]; -- Notification is late.
RETURN[NIL];
};
ENDLOOP;
};
IF info.larkToneSpec=
NIL
THEN
info.larkToneSpec ← NEW[Lark.ToneSpecRec ← [volume: 0, totalTime: 0, tones: NIL]];
IF info.cSpec=
NIL
THEN
info.cSpec ←
NEW[Lark.ConnectionSpecRec ← [
protocol: interactive,
encoding: muLaw,
sampleRate: 8000,
packetSize: 160,
buffer: in1,
keyIndex: IF ThNet.pd.encryptVoice THEN 1 ELSE 0,
localSocket: [[0],[0], Pup.nullSocket],
remoteSocket: [[0],[0], Pup.nullSocket]
]];
TRUSTED { Process.EnableAborts[@info.stateChange]; };
When initializing the supervisor process, multicasting should already be off for this host. But be as careful as possible here: stop any that's detectably going on, and in any case, turn off multicast forwarding for this host.
StopMulticast[info];
Multicast.StopHandlingMulticast[shh: info.shh, realHost: info.netAddress.host];
WHILE (req←WaitForAction[info])#
NIL
DO
-- Deal with communications failure.
ENABLE {
RPC.CallFailed =>
IF pd.callTimeoutOK
THEN
RESUME
ELSE {
LarkProblem["%g: Call Failed", info]; GOTO Failed; };
ABORTED => {
LarkProblem["%g: LarkSupervisor aborted", info]; GOTO Failed; };
};
DoTones:
PROC[newTones:
BOOL] = {
-- Does one tone from current list of tones
IF info.nextToneList=NIL THEN RETURN;
info.larkToneSpec.volume ← info.toneSpec.volume;
info.expectedNotification ←
IF info.expectedNotification='z THEN 'a ELSE info.expectedNotification+1;
info.larkToneSpec.notification ← [tones, info.expectedNotification];
info.larkToneSpec.tones ← info.nextToneList.first;
[]←info.interface.SpecifyTones[shh: info.shh, queueIt: ~newTones, tones: info.larkToneSpec];
};
SpeakText:
PROC = {
textPkt: Rope.ROPE;
IF ~info.textToSpeech OR info.flushInProgress THEN RETURN;
WHILE info.pktsOutstanding < numPkts
AND info.textToSpeak.Length[] > 0
DO
proseText: Rope.ROPE ← info.textToSpeak;
IF proseText.Length[] <= maxTextPktLen
THEN {
textPkt ← Rope.Concat[proseText, speechDone];
speechDone (or some control marker) is needed to keep the Prose going if new text comes in after the last bit of the previous request was sent, but before the Prose finishes speaking it. Triggers a call from LarkInImpl.HandleProseOutput.
info.textToSpeak ← "";
info.ctrlMarkerQueue ← List.Nconc1[info.ctrlMarkerQueue, NEW[INT ← speechDoneMarker]];
}
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[proseText, index-1]] = other
DO
index ← index-1;
IF index=0
THEN {index ← maxTextPktLen;
EXIT};
Too long between break chars; just make progress somehow.
ENDLOOP;
IF Rope.Fetch[proseText, index-1] = IO.ESC THEN index ← index-1
ELSE
IF Rope.Fetch[proseText, index-2] =
IO.
ESC
THEN index ← index-2;
Make sure not to break in the middle of a client marker!!
info.controlMarker ← IF info.controlMarker>=ThSmartsPrivate.maxControlMarker THEN minControlMarker ELSE info.controlMarker+1;
textPkt ← Rope.Cat[cmdLeader, Convert.RopeFromInt[info.controlMarker], Rope.FromChar[ThSmartsPrivate.indexMarkerEnd], proseText.Substr[len: index]];
info.textToSpeak ← proseText.Substr[start: index];
info.ctrlMarkerQueue ← List.Nconc1[info.ctrlMarkerQueue, NEW[INT ← info.controlMarker]];
};
info.interface.CommandString[shh: info.shh, device: keyboard, commands: textPkt];
info.pktsOutstanding ← info.pktsOutstanding+1;
ENDLOOP;
};
WITH req
SELECT
FROM
d:
REF ADisconnectType =>
IF info.spec#
NIL
THEN {
buf: Lark.VoiceBuffer ← out1;
info.interface.Disconnect[ shh: info.shh, buffer: in1 ]; -- always stop in1 (tx1)
FOR i:
NAT
IN [1..info.spec.numParties)
DO
info.interface.Disconnect[ shh: info.shh, buffer: buf ]; -- out1 (tx1) through outn (txn)
buf ← SUCC[buf];
ENDLOOP;
IF info.spec.numParties>2 THEN StopMulticast[info];
info.spec ← NIL;
};
spec: ThParty.PartyInfo => {
cSpec: Lark.ConnectionSpec = info.cSpec;
conference: BOOL ← spec.numParties>=3;
IF spec.numParties>=2
THEN {
If conferencing, set up multicasting for this host, first.
IF conference THEN conference ← StartMulticast[info, spec.conferenceHost];
Transmit buffer
cSpec.buffer ← in1;
cSpec.remoteSocket ← spec[0].socket;
cSpec.remoteSocket.host ←
IF conference THEN spec.conferenceHost.host ELSE spec[1].socket.host;
cSpec.localSocket ← cSpec.remoteSocket; -- superstition
info.interface.Connect[shh: info.shh, specs: cSpec ];
Receive buffers
cSpec.buffer ← out1;
FOR i:
NAT
IN [1..spec.numParties)
DO
cSpec.localSocket ← spec[i].socket;
IF conference THEN cSpec.localSocket.host ← spec.conferenceHost.host;
cSpec.remoteSocket ← cSpec.localSocket; -- superstition
info.interface.Connect[shh: info.shh, specs: cSpec ];
cSpec.buffer ← SUCC[cSpec.buffer];
ENDLOOP;
};
};
keyTable: Lark.KeyTable => info.interface.SetKeyTable[shh: info.shh, table: keyTable];
ts: LarkPlay.ToneSpec => {
info.toneSpec←ts;
info.nextToneList ← info.toneSpec.tones;
DoTones[TRUE];
};
ps: Thrush.ProseSpecs => {
IF ~info.textToSpeech THEN LOOP;
FOR proseS: Thrush.ProseSpecs ← ps, proseS.rest
WHILE proseS#
NIL
DO
pSpec: Thrush.ProseSpec = proseS.first;
newText: Rope.ROPE;
IF ~pSpec.queueIt THEN ProseFlush[info, stopAndFlush];
newText ←
IF pSpec.direction=record
-- really want BOOLEAN pSpec.filter
THEN pSpec.prose -- allows client to send Prose reset
ELSE Rope.Translate[base: pSpec.prose, translator: FilterText];
info.textToSpeak ← Rope.Concat[info.textToSpeak, newText];
info.clientMarker ←
IF info.clientMarker = ThSmartsPrivate.maxClientMarker
THEN minClientMarker
ELSE info.clientMarker + 1;
EnterProseQueue[info, pSpec, info.clientMarker];
info.textToSpeak ← Rope.Cat[info.textToSpeak, cmdLeader,
Convert.RopeFromInt[info.clientMarker],
Rope.FromChar[ThSmartsPrivate.indexMarkerEnd]];
ENDLOOP;
Maybe just as satisfactory to treat each elt of list as separate request & terminate with CommenceSpeech to keep Prose going.
SpeakText[];
};
pd:
REF ASpeechDoneType => {
marker: REF INT ← NIL;
IF pd.indexMarker = ThSmartsPrivate.flushMarker
THEN {
info.flushInProgress ← FALSE;
info.ctrlMarkerQueue ← NIL;
info.pktsOutstanding ← 0;
SpeakText[];
}
ELSE {
IF info.ctrlMarkerQueue =
NIL
THEN {
LarkProblem["Text-to-speech service: empty control marker list", info];
GOTO Failed;
};
marker ← NARROW[info.ctrlMarkerQueue.first];
IF pd.indexMarker = marker^
THEN {
info.ctrlMarkerQueue ← info.ctrlMarkerQueue.rest;
info.pktsOutstanding ← info.pktsOutstanding-1;
SpeakText[];
}
ELSE {
LarkProblem["Text-to-speech service: wrong marker received", info];
GOTO Failed;
};
};
};
td:
REF ATonesDoneType =>
IF info.toneSpec#
NIL
THEN {
IF td.event#info.expectedNotification THEN GOTO Failed;
IF info.nextToneList#
NIL
AND info.nextToneList.rest#
NIL
THEN
info.nextToneList ← info.nextToneList.rest
ELSE IF NOT info.toneSpec.repeatIndefinitely THEN { info.toneSpec←NIL; info.nextToneList←NIL }
ELSE info.nextToneList ← info.toneSpec.tones;
IF info.nextToneList#NIL THEN DoTones[FALSE];
};
a:
REF ANoTonesType => {
info.toneSpec←NIL;
info.nextToneList←NIL;
quenchSpec.tones.first.on ← 0;
[]←info.interface.SpecifyTones[shh: info.shh, tones: quenchSpec, queueIt: FALSE];
};
led:
REF AToggleLEDType => {
info.lState.lSw[led] ← IF info.lState.lSw[led] = disabled THEN enabled ELSE disabled;
ledToggler[0] ← [led, info.lState.lSw[led]];
[]←info.interface.Commands[info.shh, ledToggler];
};
resetAction:
REF ResetActionType => {
[]←info.interface.Reset[shh: info.shh, rName: "Don't revert"];
<<NIL => revert. Remember to change 2d parameter next time Lark.mesa changes.>>
StopMulticast[info];
ProseFlush[info, pReset];
};
echoParameters: Lark.EchoParameters =>
[]←info.interface.EchoSupression[shh: info.shh, echo: echoParameters];
commands: Lark.CommandEvents =>
IF commands#NIL THEN info.interface.Commands[ info.shh, commands ];
feepSpecs:
REF AFeepType => {
IF feepSpecs=NIL THEN LOOP;
IF (quenchSpec.tones.first.on ← feepSpecs.initialDelay)#0
THEN
[]←info.interface.SpecifyTones[shh: info.shh, tones: quenchSpec, queueIt: FALSE];
[]←info.interface.Feep[
shh: info.shh, on: feepSpecs.on, off: feepSpecs.off, notify: [tones, 'F],
waveTable: ThNet.pd.feepVolume, queueIt: TRUE, events: feepSpecs.digits];
}
ENDCASE;
REPEAT Failed => { FailE[info]; };
ENDLOOP;
info.larkProcess ← NIL;
};
StartMulticast:
PROC[info: LarkInfo, hostAddress: Thrush.NetAddress]
RETURNS[ok: BOOL←TRUE] = {
IF info.spec=NIL OR info.spec.numParties <=2 THEN RETURN[FALSE];
ok ← Multicast.HandleMulticast[shh: info.shh, net: info.netAddress.net,
realHost: info.netAddress.host, listeningTo: hostAddress.host];
IF ~ok
THEN {
VoiceUtils.ProblemFR["Couldn't set multicasting [%g => %g]", $Lark, info,
int[info.netAddress.host], int[hostAddress.host]];
RETURN;
};
info.interface.SetHostNumber[shh: info.shh, host: [hostAddress.net, hostAddress.host]];
};
StopMulticast:
PROC[info: LarkInfo, force:
BOOL←
FALSE] = {
IF info.spec=NIL OR info.spec.numParties <=2 THEN RETURN;
info.interface.SetHostNumber[shh: info.shh, host: [info.netAddress.net, info.netAddress.host]];
Multicast.StopHandlingMulticast[shh: info.shh, realHost: info.netAddress.host];
};
FilterText: Rope.TranslatorType = {
-- PROC [old: CHAR] RETURNS [new: CHAR]
Remove chars that the Prose considers illegal.
SELECT old
FROM
IO.TAB, IO.LF, IO.CR, IO.ESC => new ← old; -- Don't allow user to send reset (ControlR)
< IO.SP => new ← IO.SP;
ENDCASE => new ← old;
};
ProseFlush:
PROC[info: LarkInfo, proseCmd: ProseCmd] = {
Called only from LarkSupervisor, whose sequentiality implies that no locks are needed.
info.textToSpeak ← "";
IF
~info.flushInProgress
THEN {
info.interface.CommandString[shh: info.shh, device: keyboard, commands: proseCmd];
info.flushInProgress ← TRUE;
};
};
EnterProseQueue:
ENTRY PROC[info: LarkInfo, pS: Thrush.ProseSpec, internalClientMarker:
INT] = {
This list is a queue - you expect the notifications to return in the order you sent them. LarkSupervisor "fails" if you don't get back the first thing in the queue.
elt: ThSmartsPrivate.LarkProseQueue ← LIST[[pS, internalClientMarker]];
IF info.proseQueue=NIL THEN info.proseQueue ← elt ELSE IF info.pTail=NIL THEN ERROR ELSE info.pTail.rest ← elt;
info.pTail ← elt;
};
Queue is a FIFO list of REFs, with a lastAction pointer to aid in rapid enqueuing.
QueueLarkAction:
INTERNAL
PROC[info: LarkInfo, ref:
REF] = {
elt: LIST OF REF = LIST[ref];
lst: LIST OF REF = info.lastAction;
IF info.newActions=NIL THEN info.newActions ← elt ELSE IF lst=NIL THEN ERROR ELSE lst.rest ← elt;
info.lastAction ← elt;
IF info.larkProcess=
NIL
THEN
TRUSTED {
Process.Detach[info.larkProcess ← FORK LarkSupervisor[ info ]]; };
NOTIFY info.stateChange;
};
LarkProblem:
PROC[remark:
ROPE, info: LarkInfo] = {
VoiceUtils.ProblemFR[remark, $Lark, info, TU.RefAddr[info.larkSmartsInfo]];
};
QueueCommandSequence:
INTERNAL
PROC[
info: LarkInfo, commands: ROPE,
lState: LONG POINTER TO LState, scratchEv: Lark.CommandEvents]
RETURNS[otherAction: REF←NIL]= TRUSTED {
eventIndex: INTEGER←-1;
c: CHAR;
i: NAT;
index: INTEGER;
event: Lark.Event;
events: Lark.CommandEvents ← scratchEv;
nextState: LState ← [];
len: NAT;
IF commands=NIL THEN RETURN; -- status quo
len ← commands.Length[];
See Hardware Switching Tables below for interpretations of these command sequences.
FOR i
IN [0..len)
DO
SELECT (c𡤌ommands.Fetch[i]) FROM 'J, 'j => IF ~pd.backDoorOH THEN LOOP; ENDCASE;
SELECT c
FROM
'Z => { lState^← []; otherAction ← resetAction; }; -- Zap; reset hardware to full idle
'X, 'x => {
-- Crossbar switch specification, possibly parameterized by next character
param: CHAR ← commands.Fetch[i+1];
outputs: PACKED ARRAY [0..8) OF BOOLEAN;
row: NAT;
wasParam, doIt: BOOL←TRUE;
SELECT param
FROM
IN ['0..'9] => wasParam ← FALSE; -- no parameter, just do it.
Parameters determine whether this crossbar specification should be obeyed depending on outside influences, like "line(A/B) mode" or global indications of whether automatically-generated touch-tones should be heard by the caller.
'A => IF info.audioSource#$linea THEN doIt←FALSE;
'B => IF info.audioSource#$lineb THEN doIt←FALSE;
'r => IF info.audioSource=$linea OR info.audioSource=$lineb THEN doIt←FALSE;
't => IF info.transmitOnly THEN doIt←FALSE; -- inhibit all receive switching.
'F => IF ~pd.noisyFeeps THEN doIt←FALSE; -- caller hears auto-feeping
ENDCASE => doIt←FALSE; -- unrecognized parameter, don't do it;
IF wasParam THEN { i←i+1; param ← commands.Fetch[i+1]; };
row ← Digit[param];
outputs ← LOOPHOLE[nextState.xbar[row]];
IF doIt THEN outputs[Digit[commands.Fetch[i+2]]] ← (c='X);
nextState.xbar[row] ← LOOPHOLE[outputs];
i←i+2;
LOOP;
};
'E, 'e => {
nextState.echoStyle ← commands.Fetch[i←i+1]-'0;
otherAction ← echosOn[nextState.echoStyle];
};
'M, 'm => {
nextState.voiceMode ←
SELECT commands.Fetch[i←i+1]
FROM
'0 => Lark.o3i1,
'1 => Lark.o2i2,
'2 => Lark.o1i1,
ENDCASE => ERROR;
};
ENDCASE=> {
IF c
IN ['a..'z]
THEN { event ← Lark.disabled; c𡤌-('a-'A); }
ELSE event ← Lark.enabled;
IF lStateForLetter[c]#none THEN nextState.lSw[lStateForLetter[c]] ← event;
};
ENDLOOP;
IF nextState=lState^ THEN RETURN;
IF nextState.echoStyle#lState.echoStyle
AND otherAction=
NIL
THEN
otherAction ← echosOff[lState.echoStyle];
FOR iteration:
NAT
IN [0..1]
DO
-- 0: compute size; 1: fill in result sequence.
index←-1;
IF nextState.voiceMode#lState.voiceMode
THEN
events[index←index+1] ← [voiceMode, nextState.voiceMode];
FOR i: LSwitches
DECREASING IN LSwitches
DO
IF nextState.lSw[i]#lState.lSw[i]
THEN
events[index←index+1] ← [lDevs[i], nextState.lSw[i]]; ENDLOOP;
FOR i:
NAT
IN [0..8)
DO
IF nextState.xbar[i]#lState.xbar[i]
THEN {
outputs: PACKED ARRAY[0..8) OF BOOLEAN = LOOPHOLE[lState.xbar[i]];
nxtOutputs: PACKED ARRAY[0..8) OF BOOLEAN = LOOPHOLE[nextState.xbar[i]];
FOR j:
NAT
IN [0..8)
DO
IF outputs[j]#nxtOutputs[j]
THEN
events[index←index+1] ← [
Lark.Device[LOOPHOLE[IF nxtOutputs[j] THEN 23 ELSE 22]],
LOOPHOLE[i*16+j] ];
ENDLOOP;
}; ENDLOOP;
IF index=-1 THEN { events←NIL; EXIT; };
IF iteration=0 THEN events ← NEW[Lark.CommandEventSequence[index+1]]; ENDLOOP;
QueueLarkAction[info, events];
lState^ ← nextState;
lState.lSw[xBarAll] ← Lark.disabled;
};
Digit: PROC[c: CHAR] RETURNS [digit: NAT] = INLINE { RETURN[c-'0]; };
RopeToDTMF:
PROC [r: Thrush.
ROPE]
RETURNS [fs:
REF AFeepType] = {
len: INT ← MIN[Lark.Passel.LAST, r.Length[]];
ce: Lark.CommandEvents;
ceIndex: INT𡤀
number: INTEGER;
fs ← NEW[AFeepType←[]];
FOR rIndex:
INT
IN [0..len)
DO
c: CHAR = r.Fetch[rIndex];
SELECT c
FROM
IN ['0..'9], IN ['a..'d], '#, '* => ceIndex ← ceIndex+1;
'P => IF ceIndex#0 THEN ceIndex ← ceIndex+1 ELSE fs.initialDelay ← number*100;
'A => fs.audible ← TRUE;
'O => fs.on ← number*10;
'F => fs.off ← number*10;
IN ['\001..'\037] => number ← c-'\000;
ENDCASE; -- unrecognized, so ignore it.
ENDLOOP;
ce ← NEW[Lark.CommandEventSequence[ceIndex]];
fs.digits ← ce;
ceIndex ← 0;
FOR rIndex:
INT
IN [0..len)
DO
c: CHAR = r.Fetch[rIndex];
val: CHAR ← '\000;
SELECT c
FROM
IN ['0..'9] => val ← LOOPHOLE[128 + c - '0];
IN ['a..'d] => val ← LOOPHOLE[138 + c - 'a];
'* => val ← Lark.bStar;
'# => val ← Lark.bThorp;
IN ['\001..'\037] => number ← c-'\000;
'P => IF ceIndex#0 THEN val ← LOOPHOLE[number*10];
ENDCASE;
IF val = '\000 THEN LOOP;
ce[ceIndex] ← [touchPad, val];
ceIndex ← ceIndex+1;
ENDLOOP;
};
SetTones:
INTERNAL PROC[] = {
IF ~pd.tonesInvalid THEN RETURN;
pd.tonesInvalid ← FALSE;
dialTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume, tones:
LIST[LIST[
[f1: 350, f2: 440, on: 5000, off: 0],
[f1: 350, f2: 440, on: 5000, off: 0]]]]];
busyTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume, tones:
LIST[LIST[
[f1: 480, f2: 620, on: 500, off: 500, repetitions: 5],
[f1: 480, f2: 620, on: 500, off: 500, repetitions: 5]]]]];
errorTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume, tones:
LIST[LIST[
[f1: 480, f2: 620, on: 250, off: 250, repetitions: 10],
[f1: 480, f2: 620, on: 250, off: 250, repetitions: 10]]]]];
ringbackTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume,
tones:
LIST[LIST[
[f1: 440, f2: 480, on: 2000, off: 4000],
[f1: 440, f2: 480, on: 2000, off: 4000]]]]];
};
Deb:
PROC[info: LarkInfo, c:
CHAR, p1, p2, p3, p4:
IO.Value←[null[]]] = {
s: IO.STREAM;
IF ~ThNet.pd.debug THEN RETURN;
s←IO.ROS[];
s.PutF["<%g", char[c]];
SELECT c
FROM
'M => s.PutF["-- %g", p1];
ENDCASE;
s.PutRope[">\r"];
VoiceUtils.Report[s.RopeFromROS[], $LarkDetailed, info];
};
QuickState:
ARRAY LarkState
OF
IO.Value = [
rope["none"], rope["idle"], rope["talking"], rope["trunkSignalling"],
rope["trunkTalking"], rope["trunkForwarding"], rope["trunkFlashing"], rope["failed"], rope["recovering"], rope["ringing"], rope["silence"],
rope["dialTone"], rope["ringBack"], rope["busyTone"], rope["errorTone"]];
Hardware Switching Tables
Interpretation of the command string characters:
En-- echo mode; n IN [0..3); selects echoStyleStd (default), ...FD, ...BD, or ...Fwd
F -- T/R lead reversion
G -- A/A1 hookswitch control reversion
H -- Assert Telewall hookswitch
I -- Assert A/A1 to Telewall
J -- Assert Telewall hookswitch, maybe (depends on boolean variable)
L -- Lights led
l -- Flashes led if trunk is forwarding a call.
R -- overrides volume control for ringing through speaker
S -- enables telset sidetone
T -- spMode; configures codecs for electronic trunk action
Xij -- connects crossbar input i to output j
Xmij -- connects crossbar input i to output j, conditionally
Conditions:
A -- only in lineA mode (input comes from line 1 in instead of telset or microphone)
B -- only in lineB mode (input comes from line 2 in instead of telset or microphone)
r -- only in non-radio (standard, non-lineA/B) mode
t -- inhibit in transmit-only mode (server stuff)
F -- only if feeping (touch-tone signalling) is to be audible.
Mn -- voiceMode; n IN [0..2); selects program O3I1 (default), O2I2, or O1I1
Z -- Resets hardware and crossbar
Radio mode: input comes from line 1 in instead of telset or microphone
Crossbar Connections:
Port Input Output
0 dec1 co1
1 Xmtr Rcvr
2 from Telewall to Telewall
3 mike speaker
4 silence DTMF receiver
5 dec2 co2
6 line 1 in line 1 out
7 line 2 in line 2 out
CommandsForState: TYPE = REF CommandsForStateArray;
CommandsForStateArray: TYPE = ARRAY LarkState OF ROPE;
larkCommands: ARRAY ThSmartsPrivate.SwitchState OF CommandsForState ← [
telsetCommands, -- good as any for "onhook" condition
telsetCommands, -- telset
speakerCommands, -- speaker
speakerCommands, -- sPEAKER
monitorCommands, -- monitor
monitorCommands -- mONITOR
];
telsetCommands: CommandsForState ←
NEW[CommandsForStateArray ←
Telset
[
NIL,
-- none
"Z", -- idle
"SXt01Xt06Xr10XA60XB70X14", -- talking
"HIX02X06X14XF01", -- trunkSignalling
"HISTX02Xr10XA60XB70Xt21Xt26X14", -- trunkTalking, codec-assisted electronic mode
"E3HIX02X20X24", -- trunkForwarding, trunk to remote Lark connection.
"STX02Xr10XA60XB70Xt21Xt26X14",
-- trunkFlashing, on-hook but otherwise trunkTalking.
"Z", -- trunkFlashing, on-hook for a second.
"Z", -- failed
"Z", -- recovering
"RX03X06X14", -- ringing
"SX14", -- silence
toneStdCommand, toneStdCommand, toneStdCommand, toneStdCommand -- tones
]];
toneStdCommand: ROPE = "SX01X14X06";
monitorCommands: CommandsForState ←
NEW[CommandsForStateArray ←
Monitoring Telset
[
NIL,
-- none
"Z", -- idle
"LSXt01Xt06Xt03Xr10XA60XB70X14", -- talking
"LHIX02X06X03X14XF01", -- trunkSignalling
"LHISTX02Xr10XA60XB70Xt21Xt26Xt23X14", -- trunkTalking, codec-assisted electronic mode
"LE3HIX02X20X24", -- trunkForwarding, trunk to remote Lark connection.
"LSTX02Xr10XA60XB70Xt21Xt26Xt23X14",
-- trunkFlashing, on-hook but otherwise trunkTalking.
"Z", -- trunkFlashing, on-hook for a second.
"Z", -- failed
"Z", -- recovering
"RX03X06X14", -- ringing
"LSX14", -- silence
toneMonCommand, toneMonCommand, toneMonCommand, toneMonCommand -- tones
]];
toneMonCommand: ROPE = "LSX01X03X14X06";
speakerCommands: CommandsForState ←
NEW[CommandsForStateArray ←
Speakerphone
[
NIL,
-- none
"Z", -- idle
"E1LXt03Xt06Xr30XA60XB70X14", -- talking
"LHIX02X06XF03", -- trunkSignalling
"E2M1THILXr30X02Xt25Xt26X24Xt53XA60XB70", -- trunkTalking, gain-controlled digital mode
"LE3HIX02X20X24", -- trunkForwarding, trunk to remote Lark connection. (need gain setting for echo?)
"Z",
-- trunkFlashing, on-hook for a second.
"STX02X10X21X26X14", -- trunkFlashing, on-hook but otherwise unchanged.
"Z", -- failed
"Z", -- recovering
"RX03X06", -- ringing
"LX14", -- silence
toneSpkrCommand, toneSpkrCommand, toneSpkrCommand, toneSpkrCommand -- tones
]];
toneSpkrCommand: ROPE = "LX03X14X06";
larkCommands:
ARRAY ThSmartsPrivate.SwitchState
OF CommandsForState ← [
telsetCommands, -- good as any for "onhook" condition
telsetCommands, -- telset
speakerCommands, -- speaker
speakerCommands, -- sPEAKER
monitorCommands, -- monitor
monitorCommands -- mONITOR
];
echoStyleFD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Front Door call using Speakerphone
buffer: out1,
buffer2Controlled: FALSE,
buffer1Controlled: TRUE,
decayTime: 5,
gain: [ 1024, 2048, 2048, 2048, 32767 ]
]];
echoStyleFwd: REF ← echoStyleFD;
echoStyleBD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Back Door call using Speakerphone
buffer: in2,
buffer2Controlled: FALSE,
buffer1Controlled: TRUE,
decayTime: 10,
gain: [ 2048, 4096, 8192, 16384, 32767 ]
]];
echoStyleNoFD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Standard FD or BD handset mode, no forwarding
buffer: out1, -- not interesting
buffer2Controlled: FALSE,
buffer1Controlled: TRUE, -- FALSE, compensates for a Lark bug; restore when fixed
decayTime: 5,
gain: [ 32767, 32767, 32767, 32767, 32767 ]
]];
echoStyleNoFwd: REF ← echoStyleNoFD;
echoStyleNoBD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Standard FD or BD handset mode, no forwarding
buffer: in2, -- not interesting
buffer2Controlled: FALSE,
buffer1Controlled: TRUE, -- FALSE, compensates for a Lark bug; restore when fixed
decayTime: 5,
gain: [ 32767, 32767, 32767, 32767, 32767 ]
]];
echosOn: ARRAY [0..3] OF REF←[ NIL, echoStyleFD, echoStyleBD, echoStyleFwd ];
echosOff: ARRAY [0..3] OF REF←[ NIL, echoStyleNoFD, echoStyleNoBD, echoStyleNoFwd ];
State tables
LSwitches: TYPE = ThSmartsPrivate.LSwitches;
LState: TYPE = ThSmartsPrivate.LState;
lDevs:
ARRAY LSwitches
OF Lark.Device = [
crossBar, offHookRelay, aRelay, sideTone, ringEnable, revertRelay, revertHookswitch, led, spMode, crossBar--random...not used-- ];
lStateForLetter:
ARRAY
CHAR['A..'Z]
OF LSwitches = [
none, none, none, none, none, -- A to E
revert, revertHook, hook, aSwitch, hook, none, led, -- F to L
none, none, none, none, none, -- M to Q
ringO, sideTone, spMode, none, none, none, none, none, -- R to Y
none -- Z --
];
LSTrans:
TYPE = {
nop, -- nothing to do
set, -- enter specified state (usu. step to recovery) without taking any other actions.
zap, zpu, zpn, -- reset Lark hardware (u means unconnect first, n means silence tones first)
trk, tkn, -- Set for electronic phone connection (n means silence tones first)
frd, frn, -- Trunk-to-network forwarding versions of trk, tkn (frn probably doesn't exist; wrong end <<Sep. FD/BD>>)
tlk, -- like supervision, but must also adjust switching.
sup, spn, -- supervision, OK to change connection, key table. (n means silence tones first)
ksp, -- key supervision, OK to change key table.
sgl, sgn, -- Do trunk signalling (n means silence tones first)
fls, -- Flash the phone line
rng, rgu, -- Set for ringing (u means unconnect first, r means repeating tone)
dia, diu, -- Set for dial tone (tones should be more generic and user-programmable than this!)
rbk, rbu, -- Set for ring back
bzy, bzu, -- Set for busy tone
err, eru, -- Set for error tone
sil, -- silence tones, ksp obtains
fai, -- enter failed state, by Smarts-level request. Don't complain, just do it. Make sure process goes away.
rec, -- move from failed state to recovering state, but complain to caller that Lark has failed, via signal.
X -- invalid transition; complain, then remain in present state (go idle?)
};
lsTrans:
ARRAY LarkState
OF
ARRAY LarkState
OF LSTrans = [[
non idl tlk sig trk fwd fls fai rec rng shh dia rbk bzy err ←new old \/
nop, zap, X, X, X, X, X, X, X, X, X, X, X, X, err ],[-- non (none)
X, nop, spn, sgl, trk, frd, X, fai, X, rng, nop, dia, rbk, bzy, err ],[-- idl (idle)
X, zpu, sup, X, X, X, X, fai, X, rgu, nop, diu, rbu, bzu, eru ],[-- tlk (talking)
X, zpn, X, ksp, trk, frd, X, fai, X, X, nop, X, X, X, X ],[-- sig (trkSignalling)
X, zap, X, sgl, ksp, X, fls, fai, X, X, nop, X, X, X, X ],[-- trk (trkTalking)
X, zpu, X, sgl, X, sup, X, fai, X, X, nop, diu, rbu, bzu, eru ],[-- fwd (trkForwarding)
X, zap, X, X, trk, X, sup, fai, X, X, nop, X, X, X, X ],[-- fls (trkFlash)
set, rec, rec, rec, rec, rec, rec, set, set, rec, rec, rec, rec, rec, rec ],[-- fai (failed)
set, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop ],[-- rec (recovering)
X, zpn, spn, sgn, tkn, frn, X, fai, X, ksp, sil, dia, rbk, bzy, err ],[-- rng (ringing)
X, zap, tlk, sgl, trk, frd, X, fai, X, rng, ksp, dia, rbk, bzy, err ],[-- shh (silence)
X, zpn, spn, sgn, tkn, frn, X, fai, X, rng, sil, ksp, rbk, bzy, err ],[-- dia (dialTone)
X, zpn, spn, sgn, tkn, frn, X, fai, X, rng, sil, dia, ksp, bzy, err ],[-- rbk (ringBack)
X, zpn, spn, sgn, tkn, frn, X, fai, X, rng, sil, dia, rbk, ksp, err ],[-- bzy (busyTone)
X, zpn, spn, sgn, tkn, frn, X, fai, X, rng, sil, dia, rbk, bzy, ksp ] -- err (errorTone)
];
Subtransition codes and tables
TDisconn: TYPE = { X, disconnect };
tDisconn: ARRAY LSTrans OF TDisconn = [ -- zpu, rgu, diu, rbu, bzu, eru
X, X, X, disconnect, X, X, X, X, X, X, X, X, X, X, X, X, X, disconnect, X, disconnect, X, disconnect, X, disconnect, X, disconnect, X, X, X, X ];
TDoTones: TYPE = { X, doTones, stopTones };
tDoTones: ARRAY LSTrans OF TDoTones = [ -- rng, rgu, dia, diu, rbk, rbu, bzy, bzu, err, eru; zpn, tkn, spn, sgn, sil
X, X, X, X, stopTones, X, stopTones, X, X, X, X, stopTones, X, X, stopTones, X, doTones, doTones, doTones, doTones, doTones, doTones, doTones, doTones, doTones, doTones, stopTones, X, X, X ];
THookState: TYPE = { X, reset, spkrTrans };
tHookState: ARRAY LSTrans OF THookState = [ -- zap, zpu, zpn; trk, tkn, tlk, spn, fls, dia, diu, rbk, rbu, bzy, bzu, err, eru, sil
X, X, reset, reset, reset, spkrTrans, spkrTrans, X, X, spkrTrans, X, spkrTrans, X, X, X, spkrTrans, X, X, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, X, X, X ];
TSwitch: TYPE = { X, switch, switchIfDiff };
tSwitch: ARRAY LSTrans OF TSwitch = [
-- zap, zpu, zpn, trk, tkn, frd, frn, tlk, spn, sgl, sgn, fls; rng, rgu, dia, diu, rbk, rbu, bzy, bzu, err, eru
X, X, switch, switch, switch, switch, switch, switch, switch, switch, X, switch, X, switch, switch, switch, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switch, X, X, X ];
TSetFwd: TYPE = { X, setFwd };
tSetFwd: ARRAY LSTrans OF TSetFwd = [
-- trk, tkn
X, X, X, X, X, setFwd, setFwd, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X ];