<<>> <> <> <> <> <<>> <> 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 <<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] ~ { <> []¬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. <> <> <>