PhSwitchImpl.mesa
Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved.
Vin, September 17, 1990 1:39 pm PDT
Swinehar, November 3, 1992 9:21 am PST
Should possibly do something about gain settings. At present, the user is responsible for them.
IMPORTS
Args, AudioLibrary, IO, LoganBerryEntry, NameDB, Process, RefTab, Rope, TonePlay, TuneParse, VoiceUtils
Progress tone definitions
StateClass:
TYPE ~ { silent, noisy, conversing };
StateClasses:
ARRAY Thrush.StateInConv
OF StateClass = [
neverWas, idle, failed, reserved, parsing, initiating, pending, ringback, ringing,
silent, silent, noisy, noisy, silent, silent, silent, noisy, noisy,
canActivate, active, inActive
silent, conversing, silent
];
TransitionSpec:
TYPE ~
RECORD [
stop: StopSpec,
go: GoSpec,
supervise: SupSpec
];
StopSpec: TYPE ~ { stop, discon, noop };
GoSpec: TYPE ~ { play, conn, noop };
SupSpec: TYPE ~ { check, noop };
duetDelay: NAT ¬ 2400;
dialTone: TuneParse.ToneSpec;
busyTone: TuneParse.ToneSpec;
errorTone: TuneParse.ToneSpec;
ringbackTone: TuneParse.ToneSpec;
insideRingTone: TuneParse.ToneSpec;
outsideRingTone: TuneParse.ToneSpec;
subduedInsideRingTone: TuneParse.ToneSpec;
subduedOutsideRingTone: TuneParse.ToneSpec;
outsideTuneRope: ROPE ¬ "@300;G%>G<%G%>G<%G%>G<%G%>G<%G%>*C";
ringTuneDelay: NAT;
cachedRingTones: RefTab.Ref;
Voice connections
StartSession:
PUBLIC
ENTRY
PROC [cDesc: PhSmarts.ConvDesc]
RETURNS [vtClass: ROPE¬"Phoenix", vtAddress: ROPE] ~ {
ENABLE UNWIND => NULL;
[vtClass, vtAddress] ¬ StartSessionInt[cDesc];
};
StartSessionInt:
INTERNAL
PROC [cDesc: PhSmarts.ConvDesc]
RETURNS [vtClass: ROPE¬"Phoenix", vtAddress: ROPE] ~ {
addr: AudioLibrary.IPAddress;
port: AudioLibrary.IPPort;
fd: AudioLibrary.FD;
[addr, port] ¬ MakeGroupAddr[cDesc.situation.self.convID];
fd ¬ AudioLibrary.TransmitStreamCreate[groupAddr: addr, groupPort: port,
packetLen: 160, voxEnable: TRUE];
[localAddr: addr, localPort: port] ¬ AudioLibrary.StreamGetLocalAddr[fd];
cDesc.xmitStream ¬ [fd: fd, vtAddress: addr, vtPort: port, linkState: $unlinked];
vtAddress ¬ IO.PutFLR["%g.%g.%g.%g.%g.%g", LIST[
[integer[addr.a]], [integer[addr.b]], [integer[addr.c]], [integer[addr.d]], [integer[port.hi]], [integer[port.lo]]]];
};
Comply:
PUBLIC
ENTRY
PROC[cDesc: PhSmarts.ConvDesc]
RETURNS[nb: Thrush.
NB¬$success]~{
ENABLE UNWIND => NULL;
currentClass: StateClass ¬ StateClasses[cDesc.stateAye];
nextClass: StateClass ¬ StateClasses[cDesc.situation.self.state];
transitionSpec: TransitionSpec ¬ Transitions[nextClass][currentClass];
cDesc.stateAye ¬ cDesc.situation.self.state;
SELECT transitionSpec.stop
FROM
stop => StopSignal[cDesc];
discon => Disconnect[cDesc]; -- one-way NIY
noop => NULL;
ENDCASE;
SELECT transitionSpec.go
FROM
play => cDesc.toneBuf ¬ PlaySignal[cDesc]; -- will save its own process ID
conn => Connect[cDesc]; -- one-way NIY
noop => NULL;
ENDCASE;
If active then check for new members and add them.
If not active then zap any existing xmit and receive FD's, en masse.
SELECT transitionSpec.supervise
FROM
check => Supervise[cDesc];
noop => NULL;
ENDCASE;
};
Connect:
INTERNAL
PROC[cDesc: PhSmarts.ConvDesc] ~ {
Join the conversation.
IF cDesc.xmitStream.linkState = $neverWas
THEN
This doesn't really work, since the new port # won't be known to others? Placeholder
[-- bug!!! --] ¬ StartSessionInt[cDesc];
[]¬AudioLibrary.MixerLinkStream[fd: cDesc.xmitStream.fd];
cDesc.xmitStream.linkState ¬ $linked;
};
Disconnect:
INTERNAL
PROC[cDesc: PhSmarts.ConvDesc] ~ {
Drop out of the conversation.
[]¬AudioLibrary.MixerUnlinkStream[fd: cDesc.xmitStream.fd];
IF cDesc.stateAye <= $idle
THEN {
cDesc.xmitStream.linkState ¬ $neverWas;
[]¬AudioLibrary.StreamDestroy[cDesc.xmitStream.fd];
}
ELSE cDesc.xmitStream.linkState ¬ $unlinked;
};
Supervise:
INTERNAL PROC[cDesc: PhSmarts.ConvDesc] ~ {
React to changes in state of the other parties to the conversation.
partyInfo: ThParty.PartyInfo ¬ cDesc.partyInfo;
FOR ix:
NAT
IN [1..partyInfo.numParties]
DO
state: Thrush.StateInConv;
netStreamInfo: PhSmarts.NetStreamInfo;
IF ix=partyInfo.ixSelf THEN LOOP;
state ¬ cDesc.situation.self.state;
netStreamInfo ¬ GetNetStreamInfo[cDesc, ix, state=active];
IF netStreamInfo=NIL THEN LOOP; -- Nothing to do at this level.
SELECT state
FROM
active =>
IF partyInfo[ix].state=active
AND netStreamInfo.linkState#$linked
THEN {
groupAddr: AudioLibrary.IPAddress;
groupPort: AudioLibrary.IPPort;
[groupAddr, groupPort] ¬ MakeGroupAddr[cDesc.situation.self.convID];
netStreamInfo.fd ¬ AudioLibrary.ReceiveStreamCreate[
groupAddr: groupAddr, groupPort: groupPort,
remoteAddr: netStreamInfo.vtAddress, remotePort: netStreamInfo.vtPort, jitterDelay: 0];
[]¬AudioLibrary.MixerLinkStream[fd: netStreamInfo.fd];
netStreamInfo.linkState ¬ $linked;
};
ENDCASE =>
IF netStreamInfo.linkState=$linked
THEN {
[]¬AudioLibrary.MixerUnlinkStream[fd: netStreamInfo.fd];
[]¬AudioLibrary.StreamDestroy[netStreamInfo.fd];
netStreamInfo.linkState ¬ $unlinked;
};
IF state=$idle THEN netStreamInfo.linkState ¬ $neverWas;
ENDLOOP;
};
GetNetStreamInfo:
PROC [cDesc: PhSmarts.ConvDesc, ix:
NAT, createOK:
BOOL¬
FALSE]
RETURNS [netStreamInfo: PhSmarts.NetStreamInfo¬
NIL] ~
TRUSTED {
The attribute list is really a list of named ropes. We have to loophole. Mea Culpa.
attributes: ThParty.Attributes ¬ cDesc.partyInfo[ix].partyAttributes;
nsI: ROPE ¬ LoganBerryEntry.GetAttr[attributes, $PhNetStreamInfo];
netStreamInfo ¬ LOOPHOLE[nsI];
IF netStreamInfo#NIL AND netStreamInfo.linkState#$neverWas THEN RETURN;
IF ~createOK THEN RETURN;
IF netStreamInfo=
NIL
THEN {
vtAddress: ROPE;
remoteAddr: AudioLibrary.IPAddress;
remotePort: AudioLibrary.IPPort;
vtClass: ROPE ¬ LoganBerryEntry.GetAttr[attributes, $VTClass];
IF
NOT vtClass.Equal["Phoenix"]
THEN
RETURN[
NIL];
A connection is being attempted to a voice terminal other than a Phoenix voice terminal. Could complain, but silence is OK, too, and makes testing easier. Get what you deserve!
vtAddress ¬ LoganBerryEntry.GetAttr[attributes, $VTAddress];
IF vtAddress=NIL THEN ERROR; -- Shouldn't have got here with this problem.
[remoteAddr, remotePort] ¬ ParseIPAddrString[vtAddress];
netStreamInfo ¬
NEW[PhSmarts.NetStreamInfoBody ¬ [
fd: pseudostdin, vtAddress: remoteAddr, vtPort: remotePort]];
nsI ¬ LOOPHOLE[netStreamInfo];
cDesc.partyInfo[ix].partyAttributes ¬ CONS[[$PhNetStreamInfo, nsI], attributes];
};
netStreamInfo.linkState ¬ $unlinked;
};
MakeGroupAddr:
PROC [convID: Thrush.ConversationID]
RETURNS [ipAddress: AudioLibrary.IPAddress, port: AudioLibrary.IPPort] ~ TRUSTED {
addr: POINTER TO Basics.RawBytes ¬ LOOPHOLE[@convID];
ix: NAT ~ BYTES[Thrush.ConversationID];
IF ix<2 THEN ERROR;
ipAddress.a ¬ 224;
ipAddress.b ¬ 2; -- Standard multicast group address range
ipAddress.c ¬ addr[ix-2];
ipAddress.d ¬ addr[ix-1];
port ¬ [250,250]; -- A quasi-randomly chosen port number.
};
ParseIPAddrString:
PROC [iPS:
ROPE]
RETURNS [addr: AudioLibrary.IPAddress, port: AudioLibrary.IPPort] ~ {
DotToSpace: Rope.FetchType~{
r: ROPE¬NARROW[data];
c: CHAR ¬ r.Fetch[index];
RETURN[IF c='. THEN '\040 ELSE c];
};
a1, a2, a3, a4, a5, a6: Args.Arg;
[a1, a2, a3, a4, a5, a6] ¬ Args.ArgsGetFromRope[Rope.MakeRope[base: iPS, size: iPS.Length[], fetch: DotToSpace], "%i%i%i%i%i%i"];
addr ¬ [a: a1.int, b: a2.int, c: a3.int, d: a4.int];
port ¬ [hi: a5.int, lo: a6.int];
};
Progress sounds
PlaySignal:
INTERNAL
PROC[cDesc: PhSmarts.ConvDesc]
RETURNS [buf: TonePlay.AudioBuf¬
NIL] ~ {
tones: TuneParse.ToneSpec ¬ RingTone[cDesc];
IF tones=NIL THEN RETURN; -- silent ringing is the only current case.
buf ¬ TonePlay.OpenAudioDeviceForWrite[defaultAmplitude];
Process.Detach[FORK PlayProcess[buf, tones]];
};
StopSignal:
INTERNAL
PROC[cDesc: PhSmarts.ConvDesc] ~ {
IF cDesc.toneBuf=NIL THEN RETURN; -- silent ringing or something.
TonePlay.Stop[cDesc.toneBuf]; -- Will abort ringing process.
cDesc.toneBuf ¬ NIL;
};
Progress sound utilities
PlayProcess:
PROC[buf: TonePlay.AudioBuf, tones: TuneParse.ToneSpec] ~ {
finished: BOOL ¬ TRUE;
buf.self ¬ Process.GetCurrent[]; -- do this before first use; should be a proc in TonePlay.
DO
ENABLE ABORTED => { finished¬FALSE; EXIT; };
TonePlay.PlayRingTune[buf, tones];
IF ~tones.repeatIndefinitely THEN EXIT;
ENDLOOP;
TonePlay.CloseWriteAudioDevice[buf, ~finished];
};
Transitions:
ARRAY StateClass
OF
ARRAY StateClass
OF TransitionSpec ¬ [
silent noisy (old state) conversing
[[noop, noop, noop], [stop, noop, noop], [discon, noop, check]], -- silent
[[noop, play, noop], [ stop, play, noop], [ discon, play, check]],-- noisy
[[noop, conn, check], [ stop, conn, check], [ noop, noop, check]] -- conversing (new)
];
SetTones:
PROC[] = {
cachedRingTones ¬ RefTab.Create[59];
dialTone ←
NEW[TuneParse.ToneSpecRec ← [repeatIndefinitely:
TRUE,
tones: LIST[[f1: 350, f2: 440, on: 5000, off: 0]]]];
busyTone ←
NEW[TuneParse.ToneSpecRec ← [repeatIndefinitely:
TRUE,
tones: LIST[[f1: 480, f2: 620, on: 500, off: 500]]]];
errorTone ←
NEW[TuneParse.ToneSpecRec ← [repeatIndefinitely:
TRUE,
tones: LIST[[f1: 480, f2: 620, on: 250, off: 250]]]];
ringbackTone ←
NEW[TuneParse.ToneSpecRec ← [repeatIndefinitely:
TRUE,
tones: LIST[[f1: 440, f2: 480, on: 2000, off: 4000]]]];
insideRingTone ← ringbackTone;
outsideRingTone ←
NEW[TuneParse.ToneSpecRec ← [repeatIndefinitely:
TRUE,
tones: LIST[[f1: 440, f2: 480, on: 500, off: 500],[f1: 440, f2: 480, on: 500, off: 4500]]]];
subduedInsideRingTone ←
NEW[TuneParse.ToneSpecRec ← [repeatIndefinitely:
FALSE,
volume: 5, tones: LIST[[f1: 440, f2: 480, on: 500, off: 0]]]];
subduedOutsideRingTone ←
NEW[TuneParse.ToneSpecRec ←
[repeatIndefinitely: FALSE, volume: 5,
tones: LIST[[f1: 440, f2: 480, on: 500, off: 500], [f1: 440, f2: 480, on: 500, off: 0]]]];
outsideRingTone ¬ TuneParse.ParseTune[outsideTuneRope, 0];
ringTuneDelay ¬ 2400;
};
RingTone:
PROC[cDesc: PhSmarts.ConvDesc]
RETURNS [toneSpec: TuneParse.ToneSpec] = {
state: Thrush.StateInConv ¬ cDesc.situation.self.state;
partyID: Thrush.PartyID = cDesc.situation.self.partyID;
rName: ROPE;
originatingPartyID: Thrush.PartyID←Thrush.nullID;
otherRName: ROPE;
otherTone: TuneParse.ToneSpec←NIL;
otherType: Thrush.PartyType←$service; -- default value: not telephone, individual, trunk
defaultSpec: TuneParse.ToneSpec←NIL; -- outside or inside ringing
pInfo: ThParty.PartyInfo;
divisor: NAT ← 1;
rope: ROPE;
ringMode: ATOM;
ringDo: ATOM;
pInfo ← cDesc.partyInfo; IF pInfo.numParties<1 THEN RETURN[insideRingTone]; -- default
rName ← pInfo[pInfo.ixSelf].intendedName; -- self
SELECT state
FROM
$failed => RETURN[IF cDesc.situation.reason = $busy THEN busyTone ELSE errorTone];
$reserved => {
-- dialtone
rope ← NameDB.GetAttribute[rName, $dialtonetune];
IF rope=NIL OR rope.Equal["false", FALSE] THEN RETURN[dialTone];
ringDo ¬ $tune;
ringMode ¬ $r;
};
$ringing => originatingPartyID ← pInfo.conversationInfo.originator;
$ringback => NULL;
ENDCASE => RETURN[NIL];
IF state#$reserved
THEN
FOR i:
NAT
IN [1..pInfo.numParties]
DO
IF i=pInfo.ixSelf THEN LOOP;
SELECT state
FROM
ringback: choose original called party's ring tune for ringback tune. Or if more than one party is being called, use the first one that's still ringing or notified.
ringback =>
SELECT pInfo[i].state
FROM
ringing, notified => {
toneSpec ← GetCachedTone[pInfo[i].intendedName];
IF toneSpec=NIL THEN RETURN[ringbackTone];
toneSpec.volume ← toneSpec.volume+2;
toneSpec.repeatIndefinitely ← TRUE;
RETURN[toneSpec];
};
ENDCASE;
ringing: find the originator's party, in case we're going to play a duet.
ringing =>
IF pInfo[i].partyID = originatingPartyID
THEN {
otherType ← pInfo[i].type;
otherRName ← pInfo[i].intendedName;
EXIT;
};
ENDCASE=>ERROR;
ENDLOOP;
SELECT state
FROM
$ringback => RETURN[ringbackTone]; -- Didn't find another party above.
$reserved => defaultSpec ¬ dialTone;
$ringing => {
We are being called. Figure out how to respond. Obtain the ringing mode.
defaultSpec ← SELECT otherType FROM $trunk => outsideRingTone, ENDCASE => insideRingTone;
rope ← NameDB.GetAttribute[rName, $ringmode];
ringMode ← IF rope=NIL THEN $r ELSE VoiceUtils.MakeAtom[rName: rope, case: FALSE];
rope ← NameDB.GetAttribute[rName, $dotune];
ringDo ← IF rope=NIL THEN $standard ELSE VoiceUtils.MakeAtom[rName: rope, case: FALSE];
};
ENDCASE;
SELECT ringMode
FROM
-- $o, $s, $r
$o => RETURN[NIL];
$s =>
RETURN[
IF otherType=$trunk THEN subduedOutsideRingTone ELSE subduedInsideRingTone];
$r => NULL; -- largest case continues below
ENDCASE => RETURN[defaultSpec]; -- unknown case; act vanilla
We're ringing or dialtoning. Figure out how to do it.
SELECT ringDo
FROM
$false, $standard => RETURN[defaultSpec];
$both => {
SELECT otherType
FROM
$individual, $telephone => {
divisor ← 2;
rope ← NameDB.GetAttribute[otherRName, $dotune];
ringDo ← IF rope=NIL THEN $standard ELSE VoiceUtils.MakeAtom[rName: rope, case: FALSE];
IF ringDo=$both THEN otherTone ← GetCachedTone[otherRName];
This produces a second ring tune, compatible with the callee's; they will be played together.
};
$trunk => otherTone ← outsideRingTone;
ENDCASE;
};
$true, $tune => NULL; -- continue below
ENDCASE => RETURN[defaultSpec];
toneSpec ← GetCachedTone[rName];
IF toneSpec=NIL THEN RETURN[defaultSpec];
IF otherTone#NIL THEN toneSpec ← TuneParse.MergeToneSpecs[toneSpec, otherTone, divisor, ringTuneDelay];
};
GetCachedTone:
PROC[name:
ROPE]
RETURNS [ringTone: TuneParse.ToneSpec←
NIL] = {
Returns the parsed ring tune, if any, associated with the party. Copies the top level structure, since we're caching and the caller may change it.
nameAtom: ATOM;
ringTuneRope: ROPE;
IF name=NIL THEN RETURN;
ringTuneRope ← NameDB.GetAttribute[name, $ringtune];
IF ringTuneRope=NIL THEN RETURN;
nameAtom ← VoiceUtils.MakeAtom[name];
ringTone ← NARROW[cachedRingTones.Fetch[nameAtom].val];
IF ringTone=
NIL
OR
NOT ringTone.asRope.Equal[ringTuneRope,
FALSE]
THEN {
ringTone ← TuneParse.ParseTune[tune: ringTuneRope, volume: 0];
[]hedRingTones.Store[nameAtom, ringTone];
};
ringTone ¬ NEW[TuneParse.ToneSpecRec ¬ ringTone];
};