VoiceRopeImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Doug Terry, June 3, 1986 5:07:08 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
BasicTime USING [earliestGMT, Now, Period],
BluejayUtils USING [DescribeTune, DescribeInterval],
BluejayUtilsRpcControl USING [ImportInterface, UnimportInterface],
Convert USING [IntFromRope, RopeFromInt],
FinchSmarts USING [CurrentFinchState, FinchState, GetProcs, Procs, RecordReason],
FS USING [ComponentPositions, ExpandName],
IO USING [card, GetCard, GetInt, int, PutFR, RIS, rope, STREAM],
LoganBerryStub USING [AttributeType, AttributeValue, Entry, Error, ErrorCode, Open, OpenDB, ReadEntry, WriteEntry],
LupineRuntime USING [BindingError],
Rope USING [Cat, Concat, ROPE, Substr],
RPC USING [CallFailed, CallFailure, ImportFailed],
Thrush USING [EncryptionKey, IntervalSpecs, Tune, VoiceInterval],
UserProfile USING [Token],
UserCredentials USING [CredentialsChangeProc, Get, RegisterForChange],
VoiceUtils USING [Problem, RnameToRspec],
VoiceRope;
VoiceRopeImpl: CEDAR PROGRAM -- Should this be a monitor?
IMPORTS BasicTime, BluejayUtils, BluejayUtilsRpcControl, Convert, FinchSmarts, FS, IO, LupineRuntime, Rope, UserCredentials, UserProfile, VoiceUtils, LoganBerry: LoganBerryStub
EXPORTS VoiceRope
~ BEGIN
OPEN VoiceRope;
ROPE: TYPE ~ Rope.ROPE;
STREAM: TYPE ~ IO.STREAM;
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.
server: Rope.ROPE ← UserProfile.Token["ThrushClientServerInstance", "Strowger.Lark"];
vdbHandle: VoiceDBHandle;
ec: LoganBerry.ErrorCode;
expl: ROPE;
IF Complain = NIL THEN Complain ← FinchProblem;
IF voiceRopeDBInstance = NIL THEN voiceRopeDBInstance ← server;
IF voiceRopeDBName = NIL THEN
voiceRopeDBName ← Rope.Cat["///", VoiceUtils.RnameToRspec[server].simpleName, "/VoiceRopeDB"];
[vdbHandle, ec, expl] ← VoiceRopeDB.Open[voiceRopeDBName];
IF ec#NIL THEN Complain[expl]; -- Trouble opening; success of future calls in doubt
ImportBluejay[instance];
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 LoganBerry.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;
handle ← ValidateHandle[handle];
IF StartFinch[handle]#running THEN RETURN;
-- no BeepTune until we store SysNoises in the new voice database
Play[handle: handle, refID: "BeepTune", refIDType: "SysNoises", failOK: TRUE];
[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;
voiceRope ← NEW[VoiceRopeInterval];
[voiceRope.ropeID, voiceRope.length] ← VoiceRopeDB.Write[handle: handle.vdbHandle, struct: SimpleTuneList[tune, interval, 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, 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 LoganBerry.Error => { handle.Complain[explanation]; CONTINUE; };
tune: Thrush.Tune;
interval: Thrush.VoiceInterval;
key: Thrush.EncryptionKey;
struct: TuneList;
handle ← ValidateHandle[handle];
IF StartFinch[handle]#running THEN RETURN;
struct ← ReadVoiceRope[handle.vdbHandle, voiceRope].struct;
IF struct=NIL THEN { handle.Complain["No such voice rope to play."]; RETURN;};
UNTIL struct=NIL DO
[tune, interval, key, struct] ← 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
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.
struct: TuneList;
IF vr1 = NIL THEN RETURN[NIL];
handle ← ValidateHandle[handle];
struct ← ReadVoiceRope[handle.vdbHandle, vr1].struct;
IF vr2 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle.vdbHandle, vr2].struct];
IF vr3 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle.vdbHandle, vr3].struct];
IF vr4 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle.vdbHandle, vr4].struct];
IF vr5 # NIL THEN
struct ← CatEntries[struct, ReadVoiceRope[handle.vdbHandle, vr5].struct];
new ← WriteVoiceRope[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.
struct: TuneList;
handle ← ValidateHandle[handle];
struct ← ReadVoiceRope[handle.vdbHandle, vr].struct;
IF struct=NIL THEN { handle.Complain["No such voice rope."]; RETURN[NIL];};
struct ← TuneListInterval[struct, start, len];
new ← WriteVoiceRope[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".
base, struct: TuneList;
handle ← ValidateHandle[handle];
base ← ReadVoiceRope[handle.vdbHandle, vr].struct;
IF base=NIL THEN { handle.Complain["No such voice rope."]; RETURN[NIL];};
struct ← TuneListInterval[CopyEntry[base], 0, start];
struct ← CatEntries[struct, ReadVoiceRope[handle.vdbHandle, with].struct];
struct ← CatEntries[struct, TuneListInterval[base, start+len]];
new ← WriteVoiceRope[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.
header: LoganBerry.Entry;
value: LoganBerry.AttributeValue;
handle ← ValidateHandle[handle];
header ← ReadVoiceRope[handle.vdbHandle, vr].header;
IF header = NIL THEN { handle.Complain["No such voice rope."]; RETURN[0];};
value ← GetAttributeValue[entry: header, type: $Length];
len ← Convert.IntFromRope[value];
};
Information about voice ropes
DescribeRope: PUBLIC PROC [handle: Handle ← NIL, vr: VoiceRope, minSilence: INT ← -1] RETURNS [noise: IntervalSpecs] ~ {
struct: 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.vdbHandle, vr].struct;
UNTIL struct=NIL DO
[tune: tune, interval: interval, rest: struct] ← 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
CatEntries: PROC [e1, e2: LoganBerry.Entry] RETURNS [LoganBerry.Entry] ~ {
Concatenates the two lists together (destructive to the first).
ptr: LoganBerry.Entry ← e1;
IF ptr = NIL THEN RETURN[e2];
UNTIL ptr.rest = NIL DO
ptr ← ptr.rest;
ENDLOOP;
ptr.rest ← e2;
RETURN[e1];
};
CopyEntry: PROC [entry: LoganBerry.Entry] RETURNS [LoganBerry.Entry] ~ {
Copies one list to another.
new, end: LoganBerry.Entry;
IF entry = NIL THEN RETURN[NIL];
new ← LIST[entry.first];
end ← new;
FOR e: LoganBerry.Entry ← entry.rest, e.rest WHILE e # NIL DO
end.rest ← LIST[e.first];
end ← end.rest;
ENDLOOP;
RETURN[new];
};
GetAttributeValue: PROC [entry: LoganBerry.Entry, type: LoganBerry.AttributeType] RETURNS [LoganBerry.AttributeValue] ~ {
FOR e: LoganBerry.Entry ← entry, e.rest WHILE e # NIL DO
IF e.first.type = type THEN
RETURN[e.first.value];
ENDLOOP;
RETURN[NIL];
};
END.
Doug Terry, March 18, 1986 10:16:33 am PST
created.