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.
DIRECTORY
Args,
AudioLibrary,
Basics,
IO,
LoganBerryEntry,
NameDB,
PhSmarts,
PhSwitch,
Process,
RefTab,
Rope,
ThParty,
Thrush,
TuneParse,
TonePlay,
VoiceUtils;
PhSwitchImpl: CEDAR MONITOR
IMPORTS
Args, AudioLibrary, IO, LoganBerryEntry, NameDB, Process, RefTab, Rope, TonePlay, TuneParse, VoiceUtils
EXPORTS
PhSwitch
~ BEGIN
Generic definitions
ROPE: TYPE ~ Rope.ROPE;
defaultAmplitude: REAL ¬ 0.1; -- consider putting into slider parameters file somewhere.
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;
};
Connection utilities
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­];
};
Initializations
SetTones[];
END.
Use the marvelous TonePlay primitives. DCS August 12, 1992 9:21:04 am PDT
Add the AudioLibrary to permit calls to complete. DCS October 9, 1992 5:30:16 pm PDT
Add Ring Tunes. DCS October 15, 1992 3:34:10 pm PDT