VoiceRopeImpl.mesa
Copyright Ó 1986, 1987 by Xerox Corporation. All rights reserved.
Doug Terry, July 7, 1986 11:36:52 am PDT
Swinehart, March 23, 1987 2:00:35 pm PST
Operations for manipulating recorded voice.
DIRECTORY
BluejayUtils USING [DescribeTune, DescribeInterval],
BluejayUtilsRpcControl USING [ImportInterface, UnimportInterface],
FinchSmarts USING [CurrentFinchState, FinchState, GetProcs, Procs, RecordReason],
LupineRuntime USING [BindingError],
Rope USING [Cat, ROPE],
Thrush USING [EncryptionKey, IntervalSpecs, Tune, VoiceInterval],
UserProfile USING [Token],
VoiceUtils USING [Problem, RnameToRspec],
VoiceRopeDB,
VoiceRope;
VoiceRopeImpl: CEDAR PROGRAM -- Should this be a monitor?
IMPORTS BluejayUtils, BluejayUtilsRpcControl, FinchSmarts, LupineRuntime, Rope, UserProfile, VoiceUtils, VoiceRopeDB
EXPORTS VoiceRope
~ BEGIN
OPEN VoiceRope;
ROPE: TYPE ~ Rope.ROPE;
defaultHandle: Handle←NIL;
Intervoice operations (record and play)
The following routines were adapted from RecordPlayImpl.mesa
Open: PUBLIC PROC[voiceRopeDBName: Rope.ROPENIL, localName: Rope.ROPENIL, Complain: PROC[complaint: Rope.ROPE]←NIL] RETURNS [handle: Handle] = {
If voiceRopeDBName is omitted, a default based on the user profile choice of Thrush Server is invented. If Complain is omitted, VoiceUtils.Problem[...$Finch] is used.
The localName parameter is currently ignored. Someday it might indicate a backup database in case the main database is inaccessible.
ENABLE VoiceRopeDB.Error => { -- Trouble opening; success of future calls in doubt
handle.Complain[explanation];
CONTINUE;
};
server: Rope.ROPE ← UserProfile.Token["ThrushClientServerInstance", "Strowger.Lark"];
vdbHandle: VoiceRopeDB.Handle;
default parameters
IF Complain = NIL THEN Complain ← FinchProblem;
IF voiceRopeDBName = NIL THEN
voiceRopeDBName ← Rope.Cat["/",server,"//",VoiceUtils.RnameToRspec[server].simpleName,"/VoiceRopeDB.df"];
set up handle
vdbHandle ← VoiceRopeDB.Open[voiceRopeDBName];
ImportBluejay[server];
handle ← NEW[HandleRec ← [vdbHandle: vdbHandle, Complain: Complain]];
};
FinchProblem: PROC[complaint: Rope.ROPE] = { VoiceUtils.Problem[complaint, $Finch]; };
StartFinch: PROC[handle: Handle, complain: BOOLTRUE]
RETURNS [state: FinchSmarts.FinchState] = {
Should arrange to load Finch if not loaded, start it if not started.
At present, does neither, tells caller what's what, and complains if asked to.
RW: PROC[c: Rope.ROPE] = {
IF ~complain THEN RETURN;
handle.Complain[complaint: c];
};
SELECT (state ← FinchSmarts.CurrentFinchState[]) FROM
unknown => RW["Sorry, Finch needs to be loaded and started.\n"];
stopped => RW["Sorry, Finch needs to be connected to telephone server.\nUse \"Finch\" command.\n"];
running => NULL;
ENDCASE => ERROR;
handle.procs ← IF state=unknown THEN NIL ELSE FinchSmarts.GetProcs[];
};
ValidateHandle: PROC[oldHandle: Handle] RETURNS [handle: Handle] = {
handle ← oldHandle;
IF handle=NIL THEN {
IF defaultHandle=NIL THEN defaultHandle ← Open[];
handle ← defaultHandle;
};
};
beep: VoiceRope ← NIL; -- beep to prompt recording
Record: PUBLIC PROC[handle: Handle ← NIL] RETURNS [voiceRope: VoiceRope] = {
Records a voice rope, registers it , and returns its ID. A NIL return value indicates that something went wrong.
ENABLE VoiceRopeDB.Error => { handle.Complain[explanation]; CONTINUE; };
Nothing much can be done about it; just tell the user. Should eventually log for system administrator's benefit.
interval: Thrush.VoiceInterval;
key: Thrush.EncryptionKey;
reason: FinchSmarts.RecordReason;
tune: Thrush.Tune;
info: VoiceRopeDB.VoiceRopeInfo;
handle ← ValidateHandle[handle];
IF StartFinch[handle]#running THEN RETURN;
-- no BeepTune until we store SysNoises in the new voice database
IF beep = NIL THEN
beep ← GetByInterest[handle: handle, class: "SysNoises", refID: "BeepTune"];
IF beep # NIL THEN
Play[handle: handle, voiceRope: beep, failOK: TRUE]
ELSE
handle.Complain["Can't play BEEP..."];
[reason, tune, interval, key] ← handle.procs.recordTune[queueIt: TRUE];
IF reason#ok THEN RETURN;
May need to call Bluejay to get actual length; note the size returned is in chirps so must multiply by 8000 to get samples
IF interval.length = -1 THEN
interval.length ← BluejayUtils.DescribeTune[tune].size * 8000;
info ← VoiceRopeDB.WriteVoiceRope[handle: handle.vdbHandle, struct: VoiceRopeDB.SimpleTuneList[tune, interval, key]];
voiceRope ← NEW[VoiceRopeInterval ← [ropeID: info.vrID, start: 0, length: info.length]];
};
Play: PUBLIC PROC[handle: Handle←NIL, voiceRope: VoiceRope, queueIt: BOOLTRUE, failOK: BOOLFALSE, wait: BOOLFALSE] = {
Play a specified voice rope. The boolean arguments are interpreted as follows:
queueIt => play after all other record/playback requests are satisfied.
failOK => playing is optional; leave connection open if tune doesn't exist.
wait => wait until things appear to be started properly, or have failed.
ENABLE VoiceRopeDB.Error => { handle.Complain[explanation]; CONTINUE; };
tune: Thrush.Tune;
interval: Thrush.VoiceInterval;
key: Thrush.EncryptionKey;
struct: VoiceRopeDB.TuneList;
handle ← ValidateHandle[handle];
IF StartFinch[handle]#running THEN RETURN;
struct ← ReadVoiceRope[handle, voiceRope];
IF struct=NIL THEN { handle.Complain["No such voice rope to play."]; RETURN;};
UNTIL struct=NIL DO
[tune, interval, key, struct] ← VoiceRopeDB.NextTuneOnList[struct];
[]←handle.procs.playbackTune[tune: tune, interval: interval, key: key, queueIt: queueIt, failOK: failOK, wait: wait];
ENDLOOP;
};
Stop: PUBLIC PROC[handle: Handle ← NIL] = {
Sender and Walnut Message viewer STOP buttons
ENABLE UNWIND => NULL;
handle ← ValidateHandle[handle];
IF StartFinch[handle, FALSE]#running THEN RETURN;
handle.procs.stopTune[];
};
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 [handle: Handle ← NIL, vr: VoiceRope, class: InterestClass, refID: ROPE, other: ROPENIL] ~ {
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 => { handle.Complain[explanation]; CONTINUE; };
info: VoiceRopeDB.InterestInfo;
info.vrID ← vr.ropeID;
info.class ← class;
info. refID ← refID;
info.data ← other;
handle ← ValidateHandle[handle];
VoiceRopeDB.AddInterest[handle.vdbHandle, info];
};
Forget: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope, class: InterestClass, refID: ROPE] ~ {
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 => { handle.Complain[explanation]; CONTINUE; };
info: VoiceRopeDB.InterestInfo;
info.vrID ← vr.ropeID;
info.class ← class;
info. refID ← refID;
handle ← ValidateHandle[handle];
VoiceRopeDB.DropInterest[handle.vdbHandle, info];
};
GetByInterest: PUBLIC PROC [handle: Handle ← NIL, class: InterestClass, refID: ROPE] RETURNS [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 => { handle.Complain[explanation]; CONTINUE; };
interest: VoiceRopeDB.Interest;
info: VoiceRopeDB.InterestInfo;
handle ← ValidateHandle[handle];
interest ← VoiceRopeDB.InterestForRef[handle.vdbHandle, class, refID];
IF interest = NIL THEN RETURN[NIL];
info ← VoiceRopeDB.UnpackInterest[interest];
voiceRope ← NEW[VoiceRopeInterval ← [ropeID: info.vrID, start: 0, length: LAST[INT]]];
};
Editing operations
Cat: PUBLIC PROC [handle: Handle ← NIL, vr1, vr2, vr3, vr4, vr5: VoiceRope ← NIL] RETURNS [new: VoiceRope] ~ {
Concatenates together the non-NIL voice ropes to produce a new voice rope.
ENABLE VoiceRopeDB.Error => { handle.Complain[explanation]; CONTINUE; };
struct: VoiceRopeDB.TuneList;
info: VoiceRopeDB.VoiceRopeInfo;
IF vr1 = NIL THEN RETURN[NIL];
handle ← ValidateHandle[handle];
struct ← ReadVoiceRope[handle, vr1];
IF vr2 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle, vr2]];
IF vr3 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle, vr3]];
IF vr4 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle, vr4]];
IF vr5 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle, vr5]];
info ← VoiceRopeDB.WriteVoiceRope[handle.vdbHandle, struct];
new ← NEW[VoiceRopeInterval ← [ropeID: info.vrID, start: 0, length: info.length]];
};
Substr: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope, start: INT ← 0, len: INTLAST[INT]] RETURNS [new: VoiceRope] ~ {
Creates a new voice rope that is a substring of an existing voice rope.
ENABLE VoiceRopeDB.Error => { handle.Complain[explanation]; CONTINUE; };
struct: VoiceRopeDB.TuneList;
info: VoiceRopeDB.VoiceRopeInfo;
handle ← ValidateHandle[handle];
struct ← ReadVoiceRope[handle, vr];
IF struct=NIL THEN { handle.Complain["No such voice rope."]; RETURN[NIL];};
struct ← VoiceRopeDB.TuneListInterval[struct, [start, len]];
info ← VoiceRopeDB.WriteVoiceRope[handle.vdbHandle, struct];
new ← NEW[VoiceRopeInterval ← [ropeID: info.vrID, start: 0, length: info.length]];
};
Replace: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope, start: INT ← 0, len: INTLAST[INT], with: VoiceRope ← NIL] RETURNS [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 => { handle.Complain[explanation]; CONTINUE; };
base, struct: VoiceRopeDB.TuneList;
info: VoiceRopeDB.VoiceRopeInfo;
handle ← ValidateHandle[handle];
base ← ReadVoiceRope[handle, vr];
IF base=NIL THEN { handle.Complain["No such voice rope."]; RETURN[NIL];};
struct ← VoiceRopeDB.TuneListInterval[CopyEntry[base], [0, start]];
struct ← CatEntries[struct, ReadVoiceRope[handle, with]];
struct ← CatEntries[struct, VoiceRopeDB.TuneListInterval[base, [start+len, LAST[INT]]]];
info ← VoiceRopeDB.WriteVoiceRope[handle.vdbHandle, struct];
new ← NEW[VoiceRopeInterval ← [ropeID: info.vrID, start: 0, length: info.length]];
};
Length: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope] RETURNS [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[handle, vr] will restore a voice rope to its full contents.
ENABLE VoiceRopeDB.Error => { handle.Complain[explanation]; CONTINUE; };
header: VoiceRopeDB.Header;
info: VoiceRopeDB.VoiceRopeInfo;
handle ← ValidateHandle[handle];
header ← VoiceRopeDB.ReadVoiceRope[handle.vdbHandle, vr.ropeID].header;
info ← VoiceRopeDB.UnpackHeader[header];
RETURN[info.length];
};
Information about voice ropes
DescribeRope: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope, minSilence: INT ← -1] RETURNS [noise: IntervalSpecs] ~ {
ENABLE VoiceRopeDB.Error => { handle.Complain[explanation]; CONTINUE; };
struct: VoiceRopeDB.TuneList;
tuneSpec: Thrush.IntervalSpecs;
tune: Thrush.Tune;
interval: Thrush.VoiceInterval;
noiseEnd: IntervalSpecs ← NIL; -- last item on current list of noise intervals
samples: INT ← 0;
handle ← ValidateHandle[handle];
struct ← ReadVoiceRope[handle, vr];
UNTIL struct=NIL DO
[tune: tune, interval: interval, rest: struct] ← VoiceRopeDB.NextTuneOnList[struct];
tuneSpec ← BluejayUtils.DescribeInterval[targetTune: tune, targetInterval: interval, minSilence: minSilence].intervals;
UNTIL tuneSpec = NIL DO
IF noiseEnd = NIL THEN {
noise ← LIST[[start: samples + tuneSpec.first.interval.start - interval.start, length: tuneSpec.first.interval.length]];
noiseEnd ← noise;
}
ELSE {
noiseEnd.rest ← LIST[[start: samples + tuneSpec.first.interval.start - interval.start, length: tuneSpec.first.interval.length]];
noiseEnd ← noiseEnd.rest;
};
tuneSpec ← tuneSpec.rest;
ENDLOOP;
samples ← samples + interval.length;
ENDLOOP;
};
Fetch: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope, index: INT] RETURNS [VoiceSample];
ImportBluejay: PROC[instance: ROPE] = {
ENABLE {
RPC.ImportFailed => NULL;
};
TRUSTED {
BluejayUtilsRpcControl.UnimportInterface[!LupineRuntime.BindingError => CONTINUE];
};
BluejayUtilsRpcControl.ImportInterface[["BluejayUtils.Lark", instance]];
};
Miscellaneous
ReadVoiceRope: PROC [handle: Handle, vr: VoiceRope] RETURNS [struct: VoiceRopeDB.TuneList] ~ {
IF vr = NIL THEN RETURN[NIL];
struct ← VoiceRopeDB.ReadVoiceRope[handle.vdbHandle, vr.ropeID].struct;
struct ← VoiceRopeDB.TuneListInterval[struct, [vr.start, 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.