DIRECTORY
BluejaySmarts USING [ConvDesc, GetConversation, DistributeKey, IntervalReq, IntervalReqBody, SetInterval],
BluejayUtils USING [DescribeTune, DescribeInterval],
GVNames USING [IsMemberUpArrow],
IV,
Jukebox USING [CloseTune, CreateTune, EOF, Error, IntervalSpec, IntervalSpecBody, IntervalSpecs, MissingChirp, Tune, TuneID],
RecordingServiceRegister USING [database, jukebox],
RefID USING [ID],
Rope USING [Cat, Equal, Find, ROPE],
RPC USING [GetCaller],
Thrush USING [ConversationID, Credentials, EncryptionKey, NB, none, nullKey, SHHH],
VoiceStream USING [AddPiece, FlushPieces],
VoiceUtils USING [Problem],
VoiceRopeDB USING [AddInterest, DropInterest, Error, Handle, Header, Interest, InterestInfo, InterestForRef, NextTuneOnList, ReadVoiceRope, SimpleTuneList, TuneID, TuneList, TuneListInterval, UnpackHeader, UnpackInterest, VoiceRopeInfo, WriteVoiceRope],
VoiceRopeServer;

VoiceRopeServerImpl: CEDAR PROGRAM  -- Should this be a monitor?
IMPORTS BluejaySmarts, BluejayUtils, GVNames, Jukebox, RecordingServiceRegister, Rope, RPC, VoiceStream, VoiceUtils, VoiceRopeDB
EXPORTS VoiceRopeServer
~ BEGIN
OPEN VoiceRopeServer;

ROPE: TYPE ~ Rope.ROPE;
beep: VoiceRope _ NIL;  -- beep to prompt recording
playBeep: BOOLEAN _ FALSE;

Record:  PUBLIC PROC[shhh: Thrush.SHHH _ Thrush.none, credentials: Thrush.Credentials, serviceID: RefID.ID, intID: CARD _ 0, queueIt: BOOL _ TRUE] RETURNS [nb: Thrush.NB _ $success, voiceRope: VoiceRope] = {
ENABLE {
VoiceRopeDB.Error => { 
VoiceUtils.Problem[explanation, $VoiceRope];
nb _ ec; GOTO Fail; };
Jukebox.Error, Jukebox.MissingChirp, Jukebox.EOF => {
VoiceUtils.Problem["Error detected in Bluejay", $Bluejay];
nb _ $voiceStreamFailure; GOTO Fail; };
};
key: Thrush.EncryptionKey;
tune: Jukebox.Tune;
tuneID: Jukebox.TuneID;
intervalSpec: Jukebox.IntervalSpec _ NEW[Jukebox.IntervalSpecBody];
cDesc: BluejaySmarts.ConvDesc;
ir: BluejaySmarts.IntervalReq;
vrinfo: VoiceRopeDB.VoiceRopeInfo;
[nb, cDesc] _ BluejaySmarts.GetConversation[smartsID: serviceID, conv: credentials.convID];
IF nb # $success THEN RETURN;
IF beep = NIL THEN {
[voiceRope: beep] _ GetByInterest[shhh: shhh, class: "SysNoises", refID: "BeepTune"];
IF beep = NIL THEN
VoiceUtils.Problem["Can't find beep tune to prompt recording", $VoiceRope];
};
TRUSTED {
tune _ Jukebox.CreateTune[RecordingServiceRegister.jukebox, -1];
tuneID _ intervalSpec.tuneID _ tune.tuneId;
Jukebox.CloseTune[RecordingServiceRegister.jukebox, tune];
};
IF playBeep AND beep#NIL THEN
[] _ Play[shhh: shhh, voiceRope: beep, credentials: credentials, serviceID: serviceID, queueIt: queueIt]
ELSE IF NOT queueIt THEN
TRUSTED {
VoiceStream.FlushPieces[cDesc.info.stream]; -- Will report, synchronously
};
ir _ NEW[BluejaySmarts.IntervalReqBody _ [
iSpec: intervalSpec^, direction: $record, cDesc: cDesc, requestingParty: credentials.partyID,
intID: intID, firstInterval: TRUE, lastInterval: TRUE]];
[nb] _ BluejaySmarts.SetInterval[ir];
IF nb # $success THEN RETURN;
intervalSpec.length _ ir.iSpec.length;
IF cDesc.keyTable # NIL THEN
key _ cDesc.keyTable[1];
TRUSTED {
VoiceStream.AddPiece[cDesc.info.stream, intervalSpec, $record, ir];
};
vrinfo.struct _ VoiceRopeDB.SimpleTuneList[tune: tuneID, start: 0, length: -1, key: key];
vrinfo.creator _ RPC.GetCaller[shhh];
vrinfo _ VoiceRopeDB.WriteVoiceRope[handle: RecordingServiceRegister.database, vr: vrinfo];
voiceRope _ NEW[VoiceRopeInterval _ [ropeID: vrinfo.vrID, start: 0, length: vrinfo.length]];
EXITS Fail => RETURN[nb, NIL];
};

Play: PUBLIC PROC[shhh: Thrush.SHHH _ Thrush.none, voiceRope: VoiceRope, credentials: Thrush.Credentials, serviceID: RefID.ID, intID: CARD _ 0, queueIt: BOOL _ TRUE] RETURNS [nb: Thrush.NB _ $success] = {
ENABLE {
VoiceRopeDB.Error => { 
VoiceUtils.Problem[explanation, $VoiceRope];
nb _ ec; GOTO Fail; };
Jukebox.Error, Jukebox.MissingChirp, Jukebox.EOF => {
VoiceUtils.Problem["Error detected in Bluejay", $Bluejay];
nb _ $voiceStreamFailure; GOTO Fail; };
};
key: Thrush.EncryptionKey;
cDesc: BluejaySmarts.ConvDesc;
ir: BluejaySmarts.IntervalReq;
info: VoiceRopeDB.VoiceRopeInfo;
intervalSpec: Jukebox.IntervalSpec;
playQueue, last: Jukebox.IntervalSpecs;
[nb, cDesc] _ BluejaySmarts.GetConversation[smartsID: serviceID, conv: credentials.convID];
IF nb # $success THEN RETURN;
IF NOT queueIt THEN 
TRUSTED {
VoiceStream.FlushPieces[cDesc.info.stream]; -- Will report, synchronously
};
info _ ReadVoiceRope[voiceRope];
IF info.struct=NIL THEN RETURN[nb: $noSuchVoiceRope];
IF info.playAccess # NIL THEN {
caller: ROPE _ RPC.GetCaller[shhh];
IF NOT CheckPermission[caller, info.playAccess] THEN RETURN[nb: $noPermission];
};
UNTIL info.struct=NIL DO
intervalSpec _ NEW[Jukebox.IntervalSpecBody];
[intervalSpec.tuneID, intervalSpec.start, intervalSpec.length, key, info.struct] _ VoiceRopeDB.NextTuneOnList[info.struct];
IF last = NIL THEN 
last _ playQueue _ LIST[intervalSpec]
ELSE {
last.rest _ LIST[intervalSpec];
last _ last.rest;
};
[nb, intervalSpec.keyIndex] _ BluejaySmarts.DistributeKey[cDesc: cDesc, key: key, wait: info.struct=NIL]; 
SELECT nb FROM $success, $newKeys => NULL; ENDCASE => GOTO Fail;
ENDLOOP;
FOR q: Jukebox.IntervalSpecs _ playQueue, q.rest WHILE q#NIL DO
ir _ NEW[BluejaySmarts.IntervalReqBody _ [
iSpec: q.first^, direction: $play, cDesc: cDesc, requestingParty: credentials.partyID,
intID: intID, firstInterval: q=playQueue, lastInterval: q.rest=NIL]];
[nb] _ BluejaySmarts.SetInterval[ir];
IF nb # $success THEN RETURN;
q.first.length _ ir.iSpec.length;
TRUSTED {
VoiceStream.AddPiece[cDesc.info.stream, q.first, $play, ir];
};
ENDLOOP;
EXITS 
Fail => RETURN[nb];
};

Stop: PUBLIC PROC[shhh: Thrush.SHHH _ Thrush.none, credentials: Thrush.Credentials, serviceID: RefID.ID] RETURNS [nb: Thrush.NB _ $success] = {
ENABLE {
Jukebox.Error, Jukebox.MissingChirp, Jukebox.EOF => {
VoiceUtils.Problem["Error detected in Bluejay", $Bluejay];
nb _ $voiceStreamFailure; GOTO Fail; };
};
cDesc: BluejaySmarts.ConvDesc;
[nb, cDesc] _ BluejaySmarts.GetConversation[smartsID: serviceID, conv: credentials.convID];
IF nb # $success THEN RETURN;
TRUSTED {
VoiceStream.FlushPieces[cDesc.info.stream]; -- Will report, synchronously
};
EXITS Fail => RETURN[nb];
};

Retain: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, class: InterestClass, refID: Rope.ROPE, other: Rope.ROPE _ NIL] RETURNS [nb: Thrush.NB _ $success] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
info: VoiceRopeDB.InterestInfo;
info.vrID _ vr.ropeID;
info.class _ class;
info. refID _ refID;
info.data _ other;
[] _ VoiceRopeDB.AddInterest[RecordingServiceRegister.database, info];
};

Forget: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, class: InterestClass, refID: Rope.ROPE] RETURNS [nb: Thrush.NB _ $success] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
info: VoiceRopeDB.InterestInfo;
info.vrID _ vr.ropeID;
info.class _ class;
info. refID _ refID;
VoiceRopeDB.DropInterest[RecordingServiceRegister.database, info];
};

GetByInterest: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, class: InterestClass, refID: Rope.ROPE] RETURNS [nb: Thrush.NB _ $success, voiceRope: VoiceRope] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
interest: VoiceRopeDB.Interest;
info: VoiceRopeDB.InterestInfo;
interest _ VoiceRopeDB.InterestForRef[RecordingServiceRegister.database, class, refID];
IF interest = NIL THEN RETURN[nb: $noSuchVoiceRope, voiceRope: NIL];
info _ VoiceRopeDB.UnpackInterest[interest];
voiceRope _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: -1]];
};
Cat: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr1, vr2, vr3, vr4, vr5: VoiceRope _ NIL] RETURNS [nb: Thrush.NB _ $success, new: VoiceRope] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
piece, info: VoiceRopeDB.VoiceRopeInfo;
caller: ROPE _ RPC.GetCaller[shhh];
info.struct _ NIL;
info.length _ 0;
IF vr1 # NIL THEN {
piece _ ReadVoiceRope[vr: vr1, unpackHeader: TRUE];
IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL];
IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
info.struct _ piece.struct;
info.length _ piece.length;
};
IF vr2 # NIL THEN {
piece _ ReadVoiceRope[vr: vr2, unpackHeader: TRUE];
IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL];
IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
info.struct _ CatEntries[info.struct, piece.struct];
info.length _ info.length + piece.length;
};
IF vr3 # NIL THEN {
piece _ ReadVoiceRope[vr: vr3, unpackHeader: TRUE];
IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL];
IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
info.struct _ CatEntries[info.struct, piece.struct];
info.length _ info.length + piece.length;
};
IF vr4 # NIL THEN {
piece _ ReadVoiceRope[vr: vr4, unpackHeader: TRUE];
IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL];
IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
info.struct _ CatEntries[info.struct, piece.struct];
info.length _ info.length + piece.length;
};
IF vr5 # NIL THEN {
piece _ ReadVoiceRope[vr: vr5, unpackHeader: TRUE];
IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL];
IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
info.struct _ CatEntries[info.struct, piece.struct];
info.length _ info.length + piece.length;
};
IF info.struct = NIL THEN RETURN[new: NIL]; -- nb=$success
info.creator _ caller;
info _ VoiceRopeDB.WriteVoiceRope[RecordingServiceRegister.database, info];
new _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: info.length]];
};
Substr: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, start: INT _ 0, len: INT _ LAST[INT]] RETURNS [nb: Thrush.NB _ $success, new: VoiceRope] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
info: VoiceRopeDB.VoiceRopeInfo;
caller: ROPE _ RPC.GetCaller[shhh];
info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE];
IF info.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL]; 
IF info.editAccess#NIL AND NOT CheckPermission[caller, info.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
start _ start - (start MOD 8);  -- round down to multiple of 8
len _ IF len<0 OR len=LAST[INT] THEN -1 ELSE len - (len MOD 8);
info.struct _ VoiceRopeDB.TuneListInterval[info.struct, start, len];
IF len # -1 AND start+len < info.length THEN
info.length _ len
ELSE
info.length _ info.length - start;
info.creator _ caller;
info _ VoiceRopeDB.WriteVoiceRope[RecordingServiceRegister.database, info];
new _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: info.length]];
};
Replace: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, start: INT _ 0, len: INT _ LAST[INT], with: VoiceRope _ NIL] RETURNS [nb: Thrush.NB _ $success, new: VoiceRope] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
baseInfo, withInfo, info: VoiceRopeDB.VoiceRopeInfo;
caller: ROPE _ RPC.GetCaller[shhh];
baseInfo _ ReadVoiceRope[vr: vr, unpackHeader: TRUE];
withInfo _ ReadVoiceRope[vr: with, unpackHeader: TRUE];
IF (baseInfo.struct=NIL) OR ((with#NIL) AND (withInfo.struct=NIL)) THEN
RETURN [nb: $noSuchVoiceRope, new: NIL];
IF baseInfo.editAccess#NIL AND NOT CheckPermission[caller, baseInfo.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
IF with#NIL AND withInfo.editAccess#NIL AND NOT CheckPermission[caller, withInfo.editAccess] THEN RETURN[nb: $noPermission, new: NIL];
info.struct _ NIL;
info.length _ 0;
IF start # 0 THEN {
start _ start - (start MOD 8);  -- round down to multiple of 8
info.struct _ VoiceRopeDB.TuneListInterval[CopyEntry[baseInfo.struct], 0, start];
info.length _ start;
};
info.struct _ CatEntries[info.struct, withInfo.struct];
info.length _ info.length + withInfo.length;
IF len # -1 AND start+len < baseInfo.length THEN {
len _ len - (len MOD 8);  -- round down to multiple of 8
info.struct _ CatEntries[info.struct, VoiceRopeDB.TuneListInterval[baseInfo.struct, start+len, LAST[INT]]];
info.length _ info.length + baseInfo.length - (start+len); 
};
info.creator _ caller;
info _ VoiceRopeDB.WriteVoiceRope[RecordingServiceRegister.database, info];
new _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: info.length]];
};
Length: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope] RETURNS [nb: Thrush.NB _ $success, len: INT] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
info: VoiceRopeDB.VoiceRopeInfo;
vr.start _ 0;
vr.length _ -1;
info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE];
IF info.struct = NIL THEN 
nb _ $noSuchVoiceRope; 
len _ info.length;
};
DescribeRope: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, minSilence: INT _ -1] RETURNS [nb: Thrush.NB _ $success, length: INT _ 0, noise: IntervalSpecs _ NIL] ~ {
ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; };
struct: VoiceRopeDB.TuneList;
tuneSpec: Jukebox.IntervalSpec _ NEW[Jukebox.IntervalSpecBody];
tuneNoises: Jukebox.IntervalSpecs;
tuneExists: BOOLEAN;
noiseEnd: IntervalSpecs _ NIL;  -- last item on current list of noise intervals
samples: INT _ 0;
struct _ ReadVoiceRope[vr].info.struct;
IF struct = NIL THEN 
nb _ $noSuchVoiceRope; 
UNTIL struct=NIL DO  -- find noises in each tune segment
[tune: tuneSpec.tuneID, start: tuneSpec.start, length: tuneSpec.length, rest: struct] _ VoiceRopeDB.NextTuneOnList[struct];
[tuneExists, tuneNoises] _ BluejayUtils.DescribeInterval[intervalSpec: tuneSpec, minSilence: minSilence];
IF NOT tuneExists THEN {
VoiceUtils.Problem[Rope.Cat["Nonexistent tune in voice rope: ", vr.ropeID], $VoiceRope];
RETURN[nb: $badVoiceRope, length: -1, noise: NIL];
};
UNTIL tuneNoises = NIL DO
IF noiseEnd = NIL THEN {
noise _ LIST[[start: samples + tuneNoises.first.start - tuneSpec.start, length: tuneNoises.first.length]];
noiseEnd _ noise;
}
ELSE {
noiseEnd.rest _ LIST[[start: samples + tuneNoises.first.start - tuneSpec.start, length: tuneNoises.first.length]];
noiseEnd _ noiseEnd.rest;
};
tuneNoises _ tuneNoises.rest;
ENDLOOP;
samples _ samples + tuneSpec.length;
ENDLOOP;
};
GetEnergies: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, samplesPerSegment: [1..8000] _ 160] RETURNS [nb: Thrush.NB, energies: EnergySequence] ~ {
RETURN[$notYetImplemented, NIL];
};
FetchBlock: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, start: INT, len: INT _ 8000, decrypt: BOOLEAN _ TRUE] RETURNS [nb: Thrush.NB, block: VoiceBlock] ~ {
RETURN[$notYetImplemented, NIL];
};

StoreBlock: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, block: VoiceBlock, key: Thrush.EncryptionKey _ Thrush.nullKey] RETURNS [nb: Thrush.NB, voiceRope: VoiceRope] ~ {
RETURN[$notYetImplemented, NIL];
};
SetPermissions: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, playAccess: Users, editAccess: Users] RETURNS [nb: Thrush.NB] ~ {
info: VoiceRopeDB.VoiceRopeInfo;
caller: ROPE _ RPC.GetCaller[shhh];
IF NOT Rope.Equal[caller, info.creator, FALSE] THEN 
RETURN[nb: $noPermission]; 
vr.start _ 0;
vr.length _ -1;
info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE];
IF info.struct = NIL THEN 
RETURN[nb: $noSuchVoiceRope]; 
info.playAccess _ playAccess;
info.editAccess _ editAccess;
info _ VoiceRopeDB.WriteVoiceRope[handle: RecordingServiceRegister.database, vr: info, replace: TRUE];
RETURN[nb: $success];
};

GetPermissions: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope] RETURNS [nb: Thrush.NB, playAccess: Users, editAccess: Users] ~ {
info: VoiceRopeDB.VoiceRopeInfo;
info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE];
IF info.struct = NIL THEN 
RETURN[nb: $noSuchVoiceRope, playAccess: NIL, editAccess: NIL]; 
RETURN[nb: $success, playAccess: info.playAccess, editAccess: info.editAccess]; 
};

CheckPermission: PROC [name: ROPE, acl: Users] RETURNS [OK: BOOLEAN] ~ {
FOR l: Users _ acl, l.rest WHILE l#NIL DO
IF Rope.Equal[l.first, name, FALSE] THEN RETURN[OK: TRUE];
ENDLOOP;
FOR l: Users _ acl, l.rest WHILE l#NIL DO
IF Rope.Find[l.first, "^"]#-1 AND GVNames.IsMemberUpArrow[l.first, name]=yes THEN RETURN[OK: TRUE];
ENDLOOP;
RETURN[OK: FALSE];
};
ReadVoiceRope: PROC [vr: VoiceRope, unpackHeader: BOOLEAN _ FALSE] RETURNS [info: VoiceRopeDB.VoiceRopeInfo] ~ {
header: VoiceRopeDB.Header;
tune: VoiceRopeDB.TuneID;
start, length: INT;
key: Thrush.EncryptionKey;
rest: VoiceRopeDB.TuneList;
tuneExists: BOOLEAN;
wantSubinterval: BOOLEAN;
IF vr = NIL THEN {info.length _ 0; RETURN};
[header, info.struct] _ VoiceRopeDB.ReadVoiceRope[RecordingServiceRegister.database, vr.ropeID];
IF header = NIL THEN RETURN;
wantSubinterval _ NOT (vr.start = 0 AND vr.length < 0);
IF unpackHeader OR wantSubinterval THEN {
info _ VoiceRopeDB.UnpackHeader[header];
IF info.length < 0 THEN {  -- must determine the actual tune length
[tune, start, length, key, rest] _ VoiceRopeDB.NextTuneOnList[info.struct];
IF NOT (rest=NIL AND length<0) THEN {
VoiceUtils.Problem[Rope.Cat["Negative length field for voice rope: ", vr.ropeID], $VoiceRope];
info.struct _ NIL;
RETURN;
};
[tuneExists, info.length] _ BluejayUtils.DescribeTune[tune];
IF NOT tuneExists THEN {
VoiceUtils.Problem[Rope.Cat["Nonexistent tune in voice rope: ", vr.ropeID], $VoiceRope];
info.struct _ NIL;
RETURN;
};
info.length _ info.length * 8000 - start;
info.struct _ VoiceRopeDB.SimpleTuneList[tune, start, info.length, key];
};
wantSubinterval _ wantSubinterval AND NOT (vr.start = 0 AND vr.length >= info.length);
};
IF wantSubinterval THEN {
vr.start _ vr.start - (vr.start MOD 8);  -- round down to multiple of 8
vr.length _ IF vr.length<0 THEN -1 ELSE vr.length - (vr.length MOD 8);
info.struct _ VoiceRopeDB.TuneListInterval[info.struct, vr.start, vr.length];
info.length _ info.length - vr.start;
IF vr.length > 0 THEN
info.length _ MIN[info.length, vr.length];
};
};

CatEntries: PROC [e1, e2: VoiceRopeDB.TuneList] RETURNS [VoiceRopeDB.TuneList] ~ {
ptr: VoiceRopeDB.TuneList _ e1;
IF ptr = NIL THEN RETURN[e2];
UNTIL ptr.rest = NIL DO
ptr _ ptr.rest;
ENDLOOP;
ptr.rest _ e2;
RETURN[e1];
};

CopyEntry: PROC [entry: VoiceRopeDB.TuneList] RETURNS [VoiceRopeDB.TuneList] ~ {
new, end: VoiceRopeDB.TuneList;
IF entry = NIL THEN RETURN[NIL];
new _ LIST[entry.first];
end _ new;
FOR e: VoiceRopeDB.TuneList _ entry.rest, e.rest WHILE e # NIL DO
end.rest _ LIST[e.first];
end _ end.rest;
ENDLOOP;
RETURN[new];
};

END.
��� ��VoiceRopeServerImpl.mesa
Copyright c 1986 by Xerox Corporation.  All rights reserved.
Doug Terry, July 29, 1987 2:26:21 pm PDT
Swinehart, October 19, 1986 5:32:04 pm PDT
Polle Zellweger (PTZ) May 27, 1987 3:40:31 pm PDT

Operations for manipulating recorded voice.

Creating and playing voice ropes
Records a voice rope, registers it, and returns its ID.  The following action reports are generated if intID#0:
{ $recording, $started, intID } when recording begins.
{ $recording, $finished, intID } when the recording of this interval has finished.
{ $recording, $abandoned, intID } when all recording and playback actions ending with this interval has been flushed.
Check that conversation exists, etc.
Lookup beep tune
Get tune identifier
Have Bluejay record tune
note: playing the beep will flush if necessary
Write voice rope entry in database
note: the length of the voice rope is unknown at this point since it has yet to be recorded
Play a specified voice rope.  If queueIt is FALSE, flush existing playback and recording requests, first.  The following action reports are generated if intID#0:
{ $recording/$playback, $started, intID } as the interval begins playing/recording.
{ $recording/$playback, $scheduled, intID } as the interval is accepted for recording/playback, if there is already a list of pieces in the queue ahead of this request.
{ $recording/$playback, $finished, intID } when the interval has finished.
{ $recording/$playback, $abandoned, intID } when all recording and playback actions ending with this interval has been flushed.
check access rights
distribute encryption keys for tune segments (and build play queue)
schedule the various tune segments for playback
Stop playing or recording some or all of the scheduled intervals.
Action reports are generated if intID#0 was specified in original request:
{ $recording, $abandoned, intID } as indicated above.
Voice Interests
The VoiceRope interface simply allows clients to register (and unregister) interests in voice ropes.  Garbage collection is actually done through VoiceCleanup.mesa.
Registers a new interest in the voice rope.  The voice rope will be retained until either a corresponding Forget is done or the class' garbage collection process determines that the voice rope is no longer referenced, e.g. refID no longer exists.  Taken together, the vr, class, and refID must be unique.  Repeated calls of Retain with the same parameters (ignoring other) will only register a single interest.
The specified refID of the specified class drops its interest in the voice rope.  The voice rope is not necessarily deleted, however, since other interests in the same voice rope may exist.
Returns any voice rope that is of interest to the given class and refID; returns NIL if no such voice rope exists. 
N.B.: System noises, like Beeps and intolerably cute rollback tunes are indexed in the interest database as refID's like "beep", "rollback", and so on, under the class "SysNoises".
Editing operations
Concatenates together the non-NIL voice ropes to produce a new voice rope.

Creates a new voice rope that is a substring of an existing voice rope.
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted

Creates a new voice rope in which the given interval of the voice rope "vr" is replaced by the voice rope "with".
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted

Returns the actual length of the voice rope.  This operation ignores the start and length values specified in the voice rope.  Thus, vr.start _ 0; vr.length _ Length[vr] will restore a voice rope to its full contents.

Information about voice ropes

Returns a sequence of energy levels for the given voice rope.  The voice rope is divided into segments of size samplesPerSegment, and the average energy of each segment is computed.  The returned EnergySequence contains a value for each segment.  For best results, samplesPerSegment should be an integral divisor of 8000.

Returns a block of stored voice samples for the requested interval of the given voice rope.  Each sample is 8 bits of mu-law encoded voice.  If decrypt=TRUE, then the samples are decrypted before returned (the decryption can be quite SLOW!).
Creates a new voice rope whose contents are the given block.
Access control
Restricts access to the specified voice rope.  Only those users on the playAccess list are permitted to Play the voice rope; only those on the editAccess list can invoke the Cat, Substr, and Replace operations.  Only the creator of a voice rope may change its access control lists, i.e. invoke this operation.  A NIL access list implies that access is not restricted.  Access control lists may contain group names that are registered with Grapevine.
Note: by default, voice ropes are created with unrestricted access.
Returns the access control lists for the given voice rope.
Miscellaneous
read voice rope info recorded in database
this case will be checked by caller, who will report nb=$noSuchVoiceRope
only freshly-recorded (simple) voice ropes should have length=-1
note: must multiply the size in chirps by 8000 to get samples
now get desired voice rope interval
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted
Concatenates the two lists together (destructive to the first).
Copies one list to another.
Doug Terry, March 18, 1986 10:16:33 am PST
created.
Doug Terry, August 25, 1986 10:15:57 am PDT
Modifications to handle voice ropes in database with unknown length.
changes to: ReadVoiceRope, CatEntries
Doug Terry, August 25, 1986 2:23:25 pm PDT
changes to: ReadVoiceRope, CatEntries, Replace, Length, DescribeRope, Substr, Cat, Play, DIRECTORY, IMPORTS, Record, Stop, GetByInterest
Doug Terry, October 8, 1986 11:57:02 am PDT
changes to: Record, Open
Doug Terry, October 8, 1986 4:39:12 pm PDT
changes to: Open
Doug Terry, October 10, 1986 1:44:32 pm PDT
changes to: ReadVoiceRope, DescribeRope, GetByInterest
Doug Terry, October 10, 1986 5:01:30 pm PDT
changes to: beep, playBeep, Record
Doug Terry, October 28, 1986 4:24:55 pm PST
changes to: Play
Doug Terry, November 3, 1986 4:38:50 pm PST
Restricted editing to 8-sample boundaries.
changes to: Cat
Doug Terry, November 18, 1986 4:57:45 pm PST
changes to: Record, DIRECTORY, IMPORTS, ~, Play, Retain, Forget, GetByInterest, Cat, Substr, Replace, Length, DescribeRope, ReadVoiceRope
Polle Zellweger, May 27, 1987 2:43:07 pm PDT
Correctly handle Replace being called with with=NIL.
changes to: ReadVoiceRope, Replace
Polle Zellweger (PTZ) May 27, 1987 3:35:24 pm PDT
Fix RETURNs to avoid language flaw that uses default values rather than current values for omitted fields when some but not all fields are omitted.
changes to: Cat, Substr, Replace, Length, DescribeRope
Doug Terry, July 28, 1987 11:43:06 pm PDT
Added access control.
changes to: DIRECTORY, IMPORTS

ÊŸ��˜�codešœ™Kšœ
Ïmœ1™<Kšœ(™(K™*K™1—K™�K™+K™�šÏk	˜	KšœžœW˜jKšœ
žœ"˜4Kšœžœ˜ Kšœ˜KšœžœžœT˜}Kšœžœ˜3Kšœžœ˜Kšœžœžœ˜$Kšœžœ
˜Jšœžœ.žœžœ˜SKšœžœ˜*Kšœžœ˜Kšœžœì˜ýKšœ˜—K˜�KšÐblœžœžœÏc˜@Jšžœy˜€Kšžœ˜šœž˜Kšžœ˜K˜�Kšžœžœžœ˜—head™ Jšœžœ ˜3Jšœ
žœžœ˜J˜�šÏnœžœžœžœBžœ	žœžœžœžœ
žœ&˜Ï™oJ™6J™RJšœJÏbœ%™u—šžœ˜šœ˜Jšœ,˜,Jšœ	žœ	˜—šœ-žœ˜5Jšœ:˜:Jšœžœ	˜'—J˜—J˜J˜Jšœ˜Jšœ%žœ˜CJšœ˜Jšœ˜Jšœ"˜"J™$Jšœ[˜[Jšžœžœžœ˜Jšœ™šžœžœž˜JšœU˜Ušžœžœž˜JšœK˜K—J˜—Jšœ™šžœ˜	Jšœ@˜@Jšœ+˜+Jšœ:˜:J˜—Jšœ™Jšœ.™.šžœ
žœžœž˜JšœUžœ˜h—šžœžœžœ	ž˜šžœ˜	Jšœ, ˜IJ˜——šœžœ"˜*J˜]Jšœžœžœ˜8—Jšœ%˜%Jšžœžœžœ˜J˜&šžœž˜Jšœ˜—šžœ˜	JšœC˜CJ˜—J™"J™[JšœY˜YJšœ%˜%Jšœ[˜[KšœžœM˜\Jšžœ	žœžœ˜Jšœ˜J˜�—š¡œžœžœžœXžœ	žœžœžœžœ
žœ˜Ìšœ¡™¡J™SJ™¨J™JJšœT¢œ%™—šžœ˜šœ˜Jšœ,˜,Jšœ	žœ	˜—šœ-žœ˜5Jšœ:˜:Jšœžœ	˜'—J˜—J˜Jšœ˜Jšœ˜Kšœ ˜ Jšœ#˜#Jšœ'˜'Jšœ[˜[Jšžœžœžœ˜šžœžœ	žœ˜šžœ˜	Jšœ, ˜IJ˜——Jšœ ˜ Jšžœ
žœžœžœ˜5J™šžœžœžœ˜Jšœžœžœ˜#Jšžœžœ*žœžœ˜OJ˜—J™Cšžœ
žœž˜Jšœžœ˜-Kšœ{˜{šžœžœžœ˜Kšœžœ˜%—šžœ˜Kšœžœ˜K˜K˜—Kšœdžœ˜jJš
žœžœžœžœžœ˜@Kšžœ˜—J™/šžœ.žœžœž˜?šœžœ"˜*JšœV˜VJšœ?žœ˜E—Jšœ%˜%Jšžœžœžœ˜Jšœ!˜!šžœ˜	Jšœ<˜<J˜—Kšžœ˜—šžœ˜Jšœžœ˜—J˜J˜�—š¡œžœžœžœžœ2žœžœ
žœ˜J™A™JJšœ5™5—šžœ˜šœ-žœ˜5Jšœ:˜:Jšœžœ	˜'—J˜—Jšœ˜Jšœ[˜[Jšžœžœžœ˜šžœ˜	Jšœ, ˜IJ˜—Jšžœ	žœ˜Jšœ˜——™K™¤K˜�š¡œžœžœžœAžœžœžœžœ
žœ˜«Kšœš™šJšžœNžœ˜`Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜JšœF˜FJšœ˜J˜�—š¡œžœžœžœAžœžœ
žœ˜“Kšœ½™½JšžœNžœ˜`Jšœ˜Jšœ˜Jšœ˜Jšœ˜JšœB˜BJšœ˜—K˜�š¡
œžœžœžœ2žœžœ
žœ&˜¡Jšœs™sJšœ´™´JšžœNžœ˜`Jšœ˜Jšœ˜JšœW˜WJš
žœžœžœžœ"žœ˜DJšœ,˜,Kšœžœ@˜OJšœ˜——™š¡œžœžœžœ5žœžœ
žœ ˜“K™JJšžœNžœ˜`Jšœ'˜'Jšœžœ˜#Jšœžœ˜Jšœ˜šžœžœžœ˜Kšœ-žœ˜3Jš
žœžœžœžœžœ˜@Jšžœžœžœžœ+žœžœžœ˜sKšœ˜Jšœ˜K˜—šžœžœžœ˜Kšœ-žœ˜3Jš
žœžœžœžœžœ˜@Jšžœžœžœžœ+žœžœžœ˜sKšœ4˜4Jšœ)˜)K˜—šžœžœžœ˜Kšœ-žœ˜3Jš
žœžœžœžœžœ˜@Jšžœžœžœžœ+žœžœžœ˜sKšœ4˜4Jšœ)˜)K˜—šžœžœžœ˜Kšœ-žœ˜3Jš
žœžœžœžœžœ˜@Jšžœžœžœžœ+žœžœžœ˜sKšœ4˜4Jšœ)˜)K˜—šžœžœžœ˜Kšœ-žœ˜3Jš
žœžœžœžœžœ˜@Jšžœžœžœžœ+žœžœžœ˜sKšœ4˜4Jšœ)˜)K˜—Jšžœžœžœžœžœ ˜:Jšœ˜JšœK˜KKšœžœI˜RK˜K™�—š¡œžœžœžœ&žœžœžœžœžœ
žœ ˜¡K™GJšžœNžœ˜`Jšœ ˜ Jšœžœ˜#Kšœ+žœ˜1Jš
žœ
žœžœžœžœ˜@Jšžœžœžœžœ*žœžœžœ˜qKšœN™NKšœžœ ˜>Kšœžœžœžœžœžœžœžœ˜?KšœD˜Dšžœ
žœž˜,Kšœ˜—šž˜Kšœ"˜"—Jšœ˜JšœK˜KKšœžœI˜RK˜K™�—š¡œžœžœžœ&žœžœžœžœžœžœ
žœ ˜¹K™qJšžœNžœ˜`Jšœ4˜4Jšœžœ˜#Kšœ/žœ˜5Kšœ1žœ˜7š
žœžœžœžœžœžœž˜GJšžœžœ˜(—Jšžœžœžœžœ.žœžœžœ˜yJšžœžœžœžœžœžœ.žœžœžœ˜†Jšœž˜J˜šžœžœ˜KšœN™NKšœžœ ˜>JšœQ˜QJ˜J˜—Kšœ7˜7Kšœ,˜,šžœ
žœžœ˜2KšœN™NKšœžœ ˜8Kšœ_žœžœ˜kKšœ;˜;K˜—Jšœ˜JšœK˜KKšœžœI˜RK˜K™�—š¡œžœžœžœžœ
žœžœ˜uK™ÙJšžœNžœ˜`Jšœ ˜ J˜
Jšœ˜Kšœ+žœ˜1šžœžœžœ˜Jšœ˜—Kšœ˜K˜K™�——™š¡œžœžœžœ+žœžœ
žœžœžœ˜´JšžœNžœ˜`Jšœ˜Jšœ!žœ˜?Jšœ"˜"Kšœžœ˜Jšœžœ /˜OJšœ	žœ˜Jšœ'˜'šžœ
žœžœ˜Jšœ˜—šžœžœžœ #˜8Kšœ{˜{Jšœi˜išžœžœžœ˜KšœX˜XKšžœ'žœ˜2K˜—šžœžœž˜šžœžœžœ˜Jšœžœ^˜jJšœ˜J˜—šžœ˜Jšœžœ^˜rJšœ˜J˜—Jšœ˜Jšžœ˜—Kšœ$˜$Kšžœ˜—K˜K™�—š
¡œžœžœCžœ
žœ˜£JšœÁ™ÁJšžœžœ˜ J˜J™�—š¡
œžœžœ&žœžœžœžœžœ
žœ˜­Kšœñ™ñJšžœžœ˜ J˜J˜�—š
¡
œžœžœOžœ
žœ˜ªK™<Jšžœžœ˜ J˜——™š
¡œžœžœEžœ
žœ˜ŽKšœÁ™ÁK™CJšœ ˜ Jšœžœ˜#•StartOfExpansion-[s1: ROPE, s2: ROPE, case: BOOL _ TRUE]šžœžœ"žœžœ˜4Jšžœ˜—J˜
Jšœ˜Kšœ+žœ˜1šžœžœžœ˜Jšžœ˜—Jšœ˜Jšœ˜Jšœ`žœ˜fJšžœ˜K˜K˜�—š¡œžœžœžœžœ
žœ+˜ŽK™:Jšœ ˜ Kšœ+žœ˜1šžœžœžœ˜Jšžœ#žœžœ˜@—JšžœJ˜PK˜K˜�—š¡œžœžœžœžœžœ˜Hšžœžœžœž˜)Kšžœžœžœžœžœžœ˜:Kšžœ˜—šžœžœžœž˜)K–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]šžœžœ,žœžœžœžœ˜cKšžœ˜—Kšžœžœžœ˜K˜——™
š
¡
œžœžœžœžœ&˜pKšœ˜Kšœ˜Kšœžœ˜Kšœ˜Kšœ˜Kšœžœ˜Kšœžœ˜Kšžœžœžœžœ˜+K™)Kšœ`˜`šžœ
žœžœž˜KšœH™H—Kšœžœžœ˜7šžœžœžœ˜)Kšœ(˜(šžœžœ (˜CJšœK˜KK™@š
žœžœžœžœžœ˜%Kšœ^˜^Kšœžœ˜Kšžœ˜K˜—Kšœ<˜<šžœžœžœ˜KšœX˜XKšœžœ˜Kšžœ˜K˜—Kšœ=™=Jšœ)˜)JšœH˜HJ˜—Kšœ"žœžœžœ˜VK˜—K™#šžœžœ˜KšœN™NKšœ žœ ˜GKš	œžœ
žœžœžœ˜FKšœM˜MKšœ%˜%šžœž˜Kšœžœ˜*—K˜—K˜—š¡
œžœ žœ˜RK™?Kšœ˜Kšžœžœžœžœ˜šžœžœž˜Kšœ˜Kšžœ˜—Kšœ˜Kšžœ˜K˜K˜�—š¡	œžœžœ˜PK™Kšœ˜Kš
žœ	žœžœžœžœ˜ Kšœžœ˜K˜
šžœ.žœžœž˜AKšœžœ
˜K˜Kšžœ˜—Kšžœ˜K˜K˜�——Kšžœ˜™*KšÏr™—™+K™DKšœ£™%—™*Kšœ£|™ˆ—™+Kšœ£™—™*Kšœ£™—™+Kšœ£*™6—™+Kšœ£™"—™+Kšœ£™—™+K™*Kšœ£™—™,Kšœ£}™‰—™,K™4Kšœ£™"—™1K™“Kšœ£*™6—™)K™Kšœ£™—K™�—�…—����I<��yû��