VoiceRopeImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Doug Terry, June 5, 1986 11:56:21 am PDT
Swinehart, June 26, 1986 4:10:42 pm PDT
Operations for manipulating recorded voice.
This should probably be separated into different modules: PlayRecordImpl, VoiceRopeImpl, VoiceInterestImpl. For the moment I just want to get something on which a voice editing tool can be built. ... Doug
DIRECTORY
FinchSmarts USING [CurrentFinchState, FinchState, GetProcs, Procs],
FS USING [ComponentPositions, ExpandName],
Rope USING [Cat, Replace, ROPE],
Thrush USING [EncryptionKey, NB],
UserProfile USING [Token],
VoiceTemp USING [ TuneID, IntervalSpec, IntervalSpecs, IntervalSpecBody ],
VoiceUtils USING [Problem, RnameToRspec],
VoiceRopeDB,
VoiceRope;
VoiceRopeImpl: CEDAR PROGRAM -- Should this be a monitor?
IMPORTS FinchSmarts, FS, 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, voiceRopeDBInstance: Rope.ROPENIL, localName: Rope.ROPENIL, Complain: PROC[complaint: Rope.ROPE]←NIL] RETURNS [handle: Handle] = {
If voiceRopeDBName, voiceRopeDBInstance, or localName is omitted, a default based on the user profile choice of Thrush Server is invented. If Complain is omitted, VoiceUtils.Problem[...$Finch] is used.
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;
remoteDBName: ROPE;
cp: FS.ComponentPositions;
default parameters
IF Complain = NIL THEN Complain ← FinchProblem;
IF voiceRopeDBName = NIL THEN
voiceRopeDBName ← Rope.Cat["/", server, "//", VoiceUtils.RnameToRspec[server].simpleName, "/VoiceRopeDB"];
put instance in db name
set up handle
vdbHandle ← VoiceRopeDB.Open[voiceRopeDBName];
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;
};
};
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.
intervalSpec: VoiceTemp.IntervalSpec;
key: Thrush.EncryptionKey;
nb: Thrush.NB;
handle ← ValidateHandle[handle];
IF StartFinch[handle]#running THEN RETURN;
I don't know how you decided to name and lookup SysNoises
Play[handle: handle, refID: "BeepTune", refIDType: "SysNoises", failOK: TRUE];
[nb, intervalSpec, key, handle.convID] ← handle.procs.recordTune[convID: handle.convID, queueIt: TRUE];
IF nb#$success THEN RETURN;
Can't clip the rope to remove surrounding silences, because of the matching screen stuff. See /voice/voice/dcs/top/thrush.df/recordplayimpl.mesa for an example of that.
IF intervalSpec.length = -1 THEN
intervalSpec.length ← handle.procs.describeInterval[intervalSpec].length;
voiceRope ← NEW[VoiceRopeInterval ← [ropeID: NIL, start: 0, length: -1]];
[voiceRope.ropeID, voiceRope.length] ← VoiceRopeDB.Write[handle: handle.vdbHandle, struct: VoiceRopeDB.SimpleTuneList[intervalSpec.tuneID, intervalSpec.start, intervalSpec.length, key]];
};
Currently, the Play routine can only play a voice rope given its ID; the refID and refIDType fields are missing. How to manage system noises, like Beeps and intolerably cute rollback tunes, in the context of voice ropes needs to be resolved.
Play: PUBLIC PROC[handle: Handle←NIL, voiceRope: VoiceRope, queueIt: BOOLTRUE, failOK: 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; };
tuneID: VoiceTemp.TuneID;
start, length: INT;
intervalSpec: VoiceTemp.IntervalSpec;
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
[tuneID, start, length, key, struct] ← VoiceRopeDB.NextTuneOnList[struct];
intervalSpec ← NEW[VoiceTemp.IntervalSpecBody ← [tuneID, start, length]];
handle.convID←handle.procs.playbackTune[intervalSpec: intervalSpec, key: key, queueIt: queueIt, failOK: failOK, convID: handle.convID].newConvID;
queueIt ← TRUE;
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[handle.convID];
};
Voice Interests
Voice interests and the garbage collection of voice ropes is not yet implemented.
Retain: PUBLIC PROC [
Existence of refID retains interest in voiceFileID until corresponding Forget.
Creator is assumed to be logged-in user.
handle: Handle ← NIL,
voiceRope: VoiceRope,
refID: Rope.ROPE,
refIDType: Rope.ROPE
] ~ {
handle ← ValidateHandle[handle];
};
Forget: PUBLIC PROC [
Remove refID from database, eliminating its interest in any voiceFileID's
Creator is assumed to be logged-in user.
handle: Handle ← NIL,
refID: Rope.ROPE,
refIDType: Rope.ROPE
] ~ {
handle ← ValidateHandle[handle];
};
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;
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]];
new ← NEW[VoiceRopeInterval ← [ropeID: NIL, start: 0, length: -1]];
[new.ropeID, new.length] ← VoiceRopeDB.Write[handle.vdbHandle, struct];
};
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;
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];
new ← NEW[VoiceRopeInterval ← [ropeID: NIL, start: 0, length: -1]];
[new.ropeID, new.length] ← VoiceRopeDB.Write[handle.vdbHandle, struct];
};
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;
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]]];
new ← NEW[VoiceRopeInterval ← [ropeID: NIL, start: 0, length: -1]];
[new.ropeID, new.length] ← VoiceRopeDB.Write[handle.vdbHandle, struct];
};
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; };
handle ← ValidateHandle[handle];
len ← VoiceRopeDB.Read[handle.vdbHandle, vr.ropeID].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: VoiceTemp.IntervalSpecs;
tuneID: VoiceTemp.TuneID;
start, length: INT;
noiseEnd: IntervalSpecs ← NIL; -- last item on current list of noise intervals
samples: INT ← 0;
handle ← ValidateHandle[handle];
IF StartFinch[handle]#running THEN RETURN[NIL]; -- Need some error reporting??
struct ← ReadVoiceRope[handle, vr];
UNTIL struct=NIL DO
intervalSpec: VoiceTemp.IntervalSpec;
[tuneID: tuneID, start: start, length: length, rest: struct] ← VoiceRopeDB.NextTuneOnList[struct];
intervalSpec ← NEW[VoiceTemp.IntervalSpecBody ← [tuneID, start, length]];
tuneSpec ← handle.procs.describeInterval[intervalSpec: intervalSpec, minSilence: minSilence].intervals;
UNTIL tuneSpec = NIL DO
IF noiseEnd = NIL THEN {
noise ← LIST[[start: samples + tuneSpec.first.start - intervalSpec.start, length: tuneSpec.first.length]];
noiseEnd ← noise;
}
ELSE {
noiseEnd.rest ← LIST[[start: samples + tuneSpec.first.start - intervalSpec.start, length: tuneSpec.first.length]];
noiseEnd ← noiseEnd.rest;
};
tuneSpec ← tuneSpec.rest;
ENDLOOP;
samples ← samples + intervalSpec.length;
ENDLOOP;
};
Fetch: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope, index: INT] RETURNS [VoiceSample];
Miscellaneous
ReadVoiceRope: PROC [handle: Handle, vr: VoiceRope] RETURNS [struct: VoiceRopeDB.TuneList] ~ {
length: INT;
[length: length, struct: struct] ← VoiceRopeDB.Read[handle.vdbHandle, vr.ropeID];
IF NOT (vr.start = 0 AND vr.length = length) THEN
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.
Swinehart, June 25, 1986 11:06:22 am PDT
New Thrush, Finch
changes to: DIRECTORY, Open, Record, Play, Stop, Substr, Replace, DescribeRope