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 ROPE: TYPE ~ Rope.ROPE; defaultAmplitude: REAL ¬ 0.1; -- consider putting into slider parameters file somewhere. StateClass: TYPE ~ { silent, noisy, conversing }; StateClasses: ARRAY Thrush.StateInConv OF StateClass = [ silent, silent, noisy, noisy, silent, silent, silent, noisy, noisy, 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; 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; SELECT transitionSpec.supervise FROM check => Supervise[cDesc]; noop => NULL; ENDCASE; }; Connect: INTERNAL PROC[cDesc: PhSmarts.ConvDesc] ~ { IF cDesc.xmitStream.linkState = $neverWas THEN [-- bug!!! --] ¬ StartSessionInt[cDesc]; []¬AudioLibrary.MixerLinkStream[fd: cDesc.xmitStream.fd]; cDesc.xmitStream.linkState ¬ $linked; }; Disconnect: INTERNAL PROC[cDesc: PhSmarts.ConvDesc] ~ { []¬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] ~ { 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 { 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]; 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]; }; 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; }; 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 ¬ [ [[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 => 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 => 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 => { 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 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]; }; $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] = { 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]; []_cachedRingTones.Store[nameAtom, ringTone]; }; ringTone ¬ NEW[TuneParse.ToneSpecRec ¬ ringTone­]; }; SetTones[]; END. ΰ 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. Generic definitions Progress tone definitions neverWas, idle, failed, reserved, parsing, initiating, pending, ringback, ringing, canActivate, active, inActive Voice connections If active then check for new members and add them. If not active then zap any existing xmit and receive FD's, en masse. Join the conversation. This doesn't really work, since the new port # won't be known to others? Placeholder Drop out of the conversation. React to changes in state of the other parties to the conversation. Connection utilities The attribute list is really a list of named ropes. We have to loophole. Mea Culpa. 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! Progress sounds Progress sound utilities silent noisy (old state) conversing 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. ringing: find the originator's party, in case we're going to play a duet. We are being called. Figure out how to respond. Obtain the ringing mode. We're ringing or dialtoning. Figure out how to do it. This produces a second ring tune, compatible with the callee's; they will be played together. 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. Initializations 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 Κ •NewlineDelimiter –"cedarcode" style™™Jšœ Οeœ7™BJ™#J™&J™J™`—Icode˜šΟk ˜ K˜K˜ K˜Kšžœ˜K˜K˜K˜ K˜ K˜K˜K˜K˜K˜K˜ K˜ K˜ K˜K˜—KšΠln œž ˜šžœ˜KšœžœQ˜g—šž˜K˜—šœž˜K˜—™K˜Kšžœžœžœ˜KšœžœΟc:˜XK˜—™K™šœ žœ!˜1K˜—šΟn œžœžœ˜8J™XJ˜\J™J™#J˜"K˜K˜—šœžœžœ˜K˜K˜ K˜K˜K˜—Kšœ žœ˜(Kšœžœ˜$Kšœ žœ˜ K˜Jšœ žœ˜K˜K˜K˜K˜!K˜#K˜$K˜*K˜+Kšœžœ(˜=Kšœžœ˜K˜K˜K˜—™K™š‘ œžœžœžœ˜;Kšžœ žœžœ˜6Kšžœžœžœ˜K˜.K˜K˜—š‘œžœžœ˜:Kšžœ žœžœ˜6K˜K˜Kšœžœ˜K˜:˜IKšœžœ˜!—K˜IK˜Q˜0K˜u—K˜K˜—š ‘œžœžœžœžœ žœ ˜TKšžœžœžœ˜K˜8K˜AK˜FK˜,šžœž˜K˜Kšœ ˜+Kšœžœ˜ Kšžœ˜—šžœž˜Kšœ+ ˜JKšœ ˜&Kšœžœ˜ Kšžœ˜—K™2K™Dšžœž˜$K˜Kšœžœ˜ Kšžœ˜—K˜—K˜š‘œžœžœ˜4K™šžœ(ž˜.K™WK˜*—K˜9K˜%K˜K˜—š‘ œžœžœ˜7K™K˜;šžœž˜!K˜'K˜3K˜—Kšžœ(˜,K˜K˜—š‘ œž œ˜6Kšœ!Οbœ™CK˜/šžœžœžœž˜+K˜K˜&Kšžœžœžœ˜!K˜#K˜:Kš žœžœžœžœ ˜?šžœž˜šœ žœžœ!žœ˜RK˜"K˜K˜D˜4K˜+K˜W—K˜6K˜"K˜—šžœ˜ šžœ!žœ˜)K˜8K˜0K˜$K˜—Kšžœ žœ%˜8——Kšžœ˜—K˜K˜—™K˜—š‘œžœ žœ žœžœžœ(žœžœ˜KšœK›@" œ™UK˜EKšœžœ9˜BKš£’£˜Kš žœžœžœ#žœžœ˜GKšžœ žœžœ˜šžœžœžœ˜Kšœ žœ˜K˜#K˜ Kšœ žœ1˜>š žœžœžœžœžœ˜1K™²—K˜˜E˜Kšœžœžœ˜Kšœžœ˜Kšžœžœžœžœ˜"K˜—K˜!K˜K˜4K˜ K˜K˜——™K™š ‘ œžœžœžœžœ˜\K˜,Kš žœžœžœžœ +˜EJ˜9Kšœžœ˜-K˜K˜—š‘ œžœžœ˜7Kš žœžœžœžœ ˜AKšœ ˜Jšžœžœžœ˜šžœž˜J™₯šœ žœž˜&šœ˜J˜0Jšžœ žœžœžœ˜*J˜$Jšœžœ˜#Jšžœ ˜J˜—Jšžœ˜—J™Išœ žœ'žœ˜:J˜Jšœ#˜#Jšžœ˜J˜—Jšžœžœ˜—Jšžœ˜J˜—šžœž˜Jšœ žœžœ #˜FJ˜$˜ J™JJšœžœ žœžœ˜YJšœ-˜-Jš œ žœžœžœžœ(žœ˜RJšœ+˜+Jš œ žœžœžœ žœ(žœ˜WJ˜—Jšžœ˜—J™šžœ žœ  ˜"Jšœžœžœ˜šœžœ˜ Jšžœžœžœ˜L—Jšœžœ ˜+Jšžœžœ ˜<—J™J™6šžœž˜Jšœžœ˜)˜ šžœ ž˜˜Jšœ ˜ Jšœ0˜0Jš œ žœžœžœ žœ(žœ˜WJšžœžœ'˜;J™]J˜—J˜&Jšžœ˜—J˜—Jšœžœ ˜'Jšžœžœ˜—J˜ Jšžœ žœžœžœ˜)Jšžœ žœžœR˜gJšœ˜J˜—š ‘ œžœžœžœžœ˜NJ™“Jšœ žœ˜Jšœžœ˜Jšžœžœžœžœ˜Jšœ4˜4Jšžœžœžœžœ˜ Jšœ%˜%Jšœ žœ&˜7š žœ žœžœžœ%žœžœ˜HJ˜>J˜-J˜—Jšœ žœ$˜2J˜J˜——™J™K˜ K˜—Kšžœ˜K˜K™JK™UK™4—…—1@F>