VoiceRopeServerImpl.mesa
Copyright © 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.
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;
Creating and playing voice ropes
beep: VoiceRope ← NIL; -- beep to prompt recording
playBeep: BOOLEANFALSE;
Record: PUBLIC PROC[shhh: Thrush.SHHH ← Thrush.none, credentials: Thrush.Credentials, serviceID: RefID.ID, intID: CARD ← 0, queueIt: BOOLTRUE] RETURNS [nb: Thrush.NB ← $success, voiceRope: VoiceRope] = {
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.
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;
Check that conversation exists, etc.
[nb, cDesc] ← BluejaySmarts.GetConversation[smartsID: serviceID, conv: credentials.convID];
IF nb # $success THEN RETURN;
Lookup beep tune
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];
};
Get tune identifier
TRUSTED {
tune ← Jukebox.CreateTune[RecordingServiceRegister.jukebox, -1];
tuneID ← intervalSpec.tuneID ← tune.tuneId;
Jukebox.CloseTune[RecordingServiceRegister.jukebox, tune];
};
Have Bluejay record tune
note: playing the beep will flush if necessary
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];
};
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
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] = {
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.
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];
check access rights
IF info.playAccess # NIL THEN {
caller: ROPERPC.GetCaller[shhh];
IF NOT CheckPermission[caller, info.playAccess] THEN RETURN[nb: $noPermission];
};
distribute encryption keys for tune segments (and build play queue)
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;
schedule the various tune segments for playback
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] = {
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.
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];
};
Voice Interests
The VoiceRope interface simply allows clients to register (and unregister) interests in voice ropes. Garbage collection is actually done through VoiceCleanup.mesa.
Retain: PUBLIC PROC [shhh: Thrush.SHHH ← Thrush.none, vr: VoiceRope, class: InterestClass, refID: Rope.ROPE, other: Rope.ROPENIL] RETURNS [nb: Thrush.NB ← $success] ~ {
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.
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] ~ {
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.
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] ~ {
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".
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]];
};
Editing operations
Cat: PUBLIC PROC [shhh: Thrush.SHHH ← Thrush.none, vr1, vr2, vr3, vr4, vr5: VoiceRope ← NIL] RETURNS [nb: Thrush.NB ← $success, new: VoiceRope] ~ {
Concatenates together the non-NIL voice ropes to produce a new voice rope.
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: INTLAST[INT]] RETURNS [nb: Thrush.NB ← $success, new: VoiceRope] ~ {
Creates a new voice rope that is a substring of an existing voice rope.
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];
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted
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: INTLAST[INT], with: VoiceRope ← NIL] RETURNS [nb: Thrush.NB ← $success, new: VoiceRope] ~ {
Creates a new voice rope in which the given interval of the voice rope "vr" is replaced by the voice rope "with".
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 {
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted
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 {
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted
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] ~ {
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.
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;
};
Information about voice ropes
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] ~ {
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.
RETURN[$notYetImplemented, NIL];
};
FetchBlock: PUBLIC PROC [shhh: Thrush.SHHH ← Thrush.none, vr: VoiceRope, start: INT, len: INT ← 8000, decrypt: BOOLEANTRUE] RETURNS [nb: Thrush.NB, block: VoiceBlock] ~ {
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!).
RETURN[$notYetImplemented, NIL];
};
StoreBlock: PUBLIC PROC [shhh: Thrush.SHHH ← Thrush.none, block: VoiceBlock, key: Thrush.EncryptionKey ← Thrush.nullKey] RETURNS [nb: Thrush.NB, voiceRope: VoiceRope] ~ {
Creates a new voice rope whose contents are the given block.
RETURN[$notYetImplemented, NIL];
};
Access control
SetPermissions: PUBLIC PROC [shhh: Thrush.SHHH ← Thrush.none, vr: VoiceRope, playAccess: Users, editAccess: Users] RETURNS [nb: Thrush.NB] ~ {
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.
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] ~ {
Returns the access control lists for the given voice rope.
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];
};
Miscellaneous
ReadVoiceRope: PROC [vr: VoiceRope, unpackHeader: BOOLEANFALSE] 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};
read voice rope info recorded in database
[header, info.struct] ← VoiceRopeDB.ReadVoiceRope[RecordingServiceRegister.database, vr.ropeID];
IF header = NIL THEN RETURN;
this case will be checked by caller, who will report nb=$noSuchVoiceRope
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];
only freshly-recorded (simple) voice ropes should have length=-1
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;
};
note: must multiply the size in chirps by 8000 to get samples
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);
};
now get desired voice rope interval
IF wantSubinterval THEN {
subintervals must be on 8-byte boundaries since tunes are block-mode encrypted
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] ~ {
Concatenates the two lists together (destructive to the first).
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] ~ {
Copies one list to another.
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.
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