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: 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] = {
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: ROPE ← RPC.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.
ROPE ←
NIL]
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:
INT ←
LAST[
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:
INT ←
LAST[
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:
BOOLEAN ←
TRUE]
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:
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};
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