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;
Intervoice operations (record and play)
The following routines were adapted from RecordPlayImpl.mesa
Open:
PUBLIC
PROC[voiceRopeDBName: Rope.
ROPE ←
NIL, voiceRopeDBInstance: Rope.
ROPE ←
NIL, localName: Rope.
ROPE ←
NIL, 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:
BOOL←
TRUE]
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:
BOOL←
TRUE, failOK:
BOOL←
FALSE, wait:
BOOL←
FALSE] = {
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[];
};
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:
INT ←
LAST[
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:
INT ←
LAST[
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];
};