FinchRecordingImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, September 27, 1986 5:45:16 pm PDT
DIRECTORY
Atom USING [ GetPropFromList, PutPropOnList ],
FinchRecording,
FinchSmarts USING [ ConvDesc, DisconnectCall, LookupServiceInterface, RegisterForReports, ReportConversationState, ReportConversationStateProc, ReportRequestStateProc, VoiceConnect ],
GList USING [ Member, Nconc ],
PupSocket USING [ GetUniqueID ],
RefID USING [ ID ],
RPC USING [ ImportFailed ],
ThParty USING [ GetKeyTable, RegisterKey ],
Thrush USING [ ConversationID, EncryptionKey, InterfaceSpec, NB, nullConvID, nullID, nullKey, ROPE, SHHH ],
VoiceTemp USING [ IntervalSpec, IntervalSpecBody, IntervalSpecs, nullTuneID, TuneID, VoiceTime ],
VoiceTempRpcControl USING [ DescribeInterval, ImportNewInterface, InterfaceRecord, Play, Record, Stop ]
;
FinchRecordingImpl: CEDAR MONITOR
Confusion about locks here and in FinchSmartsImpl; careful!
IMPORTS GList, VoiceTempRpcControl, Atom, FinchSmarts, PupSocket, RPC, ThParty
EXPORTS FinchRecording = {
Declarations
ConvDesc: TYPE = FinchSmarts.ConvDesc;
ConversationID: TYPE = Thrush.ConversationID;
NB: TYPE = Thrush.NB;
nullID: RefID.ID = Thrush.nullID;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Thrush.SHHH;
NconcIntervals: PROC[l1, l2: VoiceTemp.IntervalSpecs] RETURNS [VoiceTemp.IntervalSpecs] = INLINE { RETURN[NARROW[GList.Nconc[l1, l2]]]; };
voiceTemp: VoiceTempRpcControl.InterfaceRecord ← NIL;
shhh: SHHH;
Supervision
ConvStateReport: ENTRY FinchSmarts.ReportConversationStateProc = {
[nb: NB, cDesc: ConvDesc, remark: ROPE]
rCDesc: FinchRecording.RConvDesc = GetRCDesc[cDesc];
IF rCDesc=NIL THEN ERROR;
BROADCAST rCDesc.stateChange; -- This will be enough to alert interested parties
};
ReportReport: ENTRY FinchSmarts.ReportRequestStateProc = {
[cDesc: ConvDesc, actionReport: Thrush.ActionReport, actionRequest: REF]
RETURNS[betterActionRequest: REF]
This one should get the reports first. Then the client that requested action reports can get them too.
rCDesc: FinchRecording.RConvDesc = GetRCDesc[cDesc];
IF rCDesc=NIL THEN ERROR;
BROADCAST rCDesc.stateChange;
SELECT actionReport.actionClass FROM
$keyDistribution => {
BROADCAST rCDesc.keysMightBeDistributed; rCDesc.keysDistributed ← TRUE; };
$recording, $playback => {
FOR rqs: VoiceTemp.IntervalSpecs ← rCDesc.pendingIntervals, rqs.rest WHILE rqs#NIL DO
IF actionReport.actionID#rqs.first.intID THEN LOOP;
betterActionRequest ← rqs.first;
SELECT actionReport.actionType FROM
Throw away pending intervals that are complete. This includes intervals preceding the one being reported on.
$finished, $flushed => rCDesc.pendingIntervals ← rqs.rest; ENDCASE;
EXIT;
ENDLOOP;
IF betterActionRequest=NIL THEN ERROR; -- Complain?["missing reports"]
};
ENDCASE=> cDesc ← cDesc; -- a place to stand during debugging
Endcase events are now expected, since there is no event-filtering in FinchSmarts; these will be events for other applications.
};
Client Functions
PlaybackTune: PUBLIC ENTRY PROC [
convID: Thrush.ConversationID,
intervalSpec: VoiceTemp.IntervalSpec,
key: Thrush.EncryptionKey,
queueIt: BOOLTRUE,
failOK: BOOLFALSE -- playing is optional; leave connection open if tune doesn't exist.
] RETURNS [ started: BOOLFALSE, newConvID: Thrush.ConversationID←Thrush.nullConvID ] = { -- FALSE if failed
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
rCDesc: FinchRecording.RConvDesc;
nb: NB;
keyIndex: [0..17B];
IF intervalSpec.tuneID<=VoiceTemp.nullTuneID THEN {
FinchSmarts.ReportConversationState[$convNotActive, NIL, "No such tune"]; RETURN; }; -- UGH!
[nb,cDesc,rCDesc]←VoiceConnect[convID];
IF nb#$success THEN RETURN; -- Complain?
newConvID ← cDesc.situation.self.convID;
[nb, keyIndex] ← ThParty.RegisterKey[
shh: shhh, credentials: cDesc.situation.self, key: key, reportNewKeys: TRUE];
{SELECT nb FROM
$success => NULL;
$newKeys => {
A quandary. I hate these things that wait until something else has happened. Would rather queue up later tasks to do and return. But haven't come up with a general method yet. The problem here is that we don't want to schedule new voice until the encryption keys for it have been distributed. The Register key function will tell everybody about the keys and then tell us that it's told everybody. We wait one time around a timeout for a special condition variable (ugh) and then give up. 
rCDesc.keysDistributed ← FALSE;
WAIT rCDesc.keysMightBeDistributed;
IF ~rCDesc.keysDistributed THEN GOTO Fail;
};
$stateMismatch => ???;
ENDCASE => GOTO Fail;
EXITS Fail => {
IF ~failOK THEN
FinchSmarts.DisconnectCall[newConvID, $error, "Could not encode encryption key", $failed];
RETURN;
}; };
intervalSpec ← NEW[VoiceTemp.IntervalSpecBody ← intervalSpec^];
intervalSpec.keyIndex ← keyIndex;
intervalSpec.intID ← NewID[];
-- sets interface & serviceID
nb ← voiceTemp.Play[shhh, cDesc.situation.self, rCDesc.voiceTempID, intervalSpec, queueIt];
started ← nb=$success;
IF ~started THEN RETURN;
rCDesc.pendingIntervals ← NconcIntervals[rCDesc.pendingIntervals, LIST[intervalSpec]];
};
RecordTune: PUBLIC ENTRY PROC [
convID: Thrush.ConversationID,
useIntervalSpec: VoiceTemp.IntervalSpec←NIL,
useKey: Thrush.EncryptionKey,
queueIt: BOOLFALSE
]
RETURNS[
nb: NB,
intervalSpec: VoiceTemp.IntervalSpec←NIL,
key: Thrush.EncryptionKey← Thrush.nullKey,
newConvID: Thrush.ConversationID←Thrush.nullConvID
] = {
cDesc: ConvDesc;
rCDesc: FinchRecording.RConvDesc;
tuneID: VoiceTemp.TuneID;
[nb,cDesc, rCDesc]←VoiceConnect[convID];
IF nb#$success THEN RETURN; -- Complain?
newConvID ← cDesc.situation.self.convID;
intervalSpec ← NEW[VoiceTemp.IntervalSpecBody ← []];
IF useIntervalSpec#NIL THEN intervalSpec^ ← useIntervalSpec^;
intervalSpec.keyIndex ← 1;
intervalSpec.intID ← NewID[];
[nb, tuneID] ← voiceTemp.Record[shhh, cDesc.situation.self, rCDesc.voiceTempID, intervalSpec, queueIt];
IF nb#$success THEN RETURN;
intervalSpec.tuneID ← tuneID;
rCDesc.pendingIntervals ← NconcIntervals[rCDesc.pendingIntervals, LIST[intervalSpec]];
WHILE cDesc.situation.self.state#idle AND
GList.Member[intervalSpec,rCDesc.pendingIntervals] DO
WAIT rCDesc.stateChange;
ENDLOOP;
IF rCDesc.keyTable=NIL THEN
[nb, rCDesc.keyTable] ← ThParty.GetKeyTable[shhh, cDesc.situation.self];
IF nb=$success THEN RETURN[nb, intervalSpec, rCDesc.keyTable[1], newConvID];
};
StopTune: PUBLIC ENTRY PROC [convID: Thrush.ConversationID] = {
ENABLE UNWIND=>NULL;
cDesc: ConvDesc;
rCDesc: FinchRecording.RConvDesc;
IF ([,cDesc, rCDesc]←VoiceConnect[convID]).nb#$success OR cDesc.situation.self.state#$active THEN RETURN;
[] ← voiceTemp.Stop[shhh, cDesc.situation.self, rCDesc.voiceTempID];
};
DescribeInterval: PUBLIC ENTRY PROC [intervalSpec: VoiceTemp.IntervalSpec, minSilence: VoiceTemp.VoiceTime𡤁]
RETURNS[nb: NB, length: INT, intervals: VoiceTemp.IntervalSpecs] = {
ENABLE UNWIND=>NULL;
IF (nb←VoiceTempInterface[NIL,NIL]) # $success THEN RETURN;
[nb, length, intervals] ← voiceTemp.DescribeInterval[shhh: shhh, targetInterval: intervalSpec, minSilence: minSilence, credentials: [], serviceID: nullID];
};
Utilities
VoiceConnect: INTERNAL PROC[convID: Thrush.ConversationID]
RETURNS [nb: NB, cDesc: ConvDesc, rCDesc: FinchRecording.RConvDesc] = {
[nb, cDesc] ← FinchSmarts.VoiceConnect["recording", convID];
IF nb#$success THEN RETURN;
rCDesc ← GetRCDesc[cDesc];
nb ← VoiceTempInterface[cDesc, rCDesc];
};
GetRCDesc: INTERNAL PROC[
cDesc: ConvDesc ] RETURNS [rCDesc: FinchRecording.RConvDesc] = {
IF cDesc=NIL THEN RETURN[NIL];
rCDesc ← NARROW[Atom.GetPropFromList[cDesc.props, $RConvDesc]];
IF rCDesc#NIL THEN RETURN;
rCDesc ← NEW[FinchRecording.RConvDescBody ← []];
cDesc.props ← Atom.PutPropOnList[cDesc.props, $RConvDesc, rCDesc];
};
VoiceTempInterface: PROC[cDesc: ConvDesc, rCDesc: FinchRecording.RConvDesc]
RETURNS [nb: NB←$success] = {
interfaceSpec: Thrush.InterfaceSpec;
IF rCDesc#NIL THEN { IF rCDesc.voiceTempID#nullID THEN RETURN }
ELSE IF voiceTemp#NIL THEN RETURN; -- want interface only
[nb, shhh, interfaceSpec] ← FinchSmarts.LookupServiceInterface["recording", "VoiceTemp", cDesc];
IF nb#$success THEN RETURN;
voiceTemp ← VoiceTempRpcControl.ImportNewInterface[
interfaceName: interfaceSpec.interfaceName,
hostHint: interfaceSpec.hostHint!RPC.ImportFailed => CONTINUE
];
IF voiceTemp=NIL THEN RETURN[$noVoiceTempInterface];
IF rCDesc#NIL THEN rCDesc.voiceTempID ← interfaceSpec.serviceID;
};
NewID: PROC RETURNS[LONG CARDINAL] ={RETURN[LOOPHOLE[PupSocket.GetUniqueID[]]]};
Initialization
InitializeFinchRecording: PUBLIC ENTRY PROC = {
FinchSmarts.RegisterForReports[s: NIL, c: ConvStateReport, r: ReportReport];
Ask Finch to inform us of changes in conversation state, and action reports.
};
}.
Swinehart, September 25, 1986 11:33:39 am PDT
Extracted from FinchSmartsImpl, to handle workstation-controlled voice recording and playback. To be replaced soon by VoiceRopes.