WalnutVoiceImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Swinehart, June 21, 1986 7:32:29 pm PDT
DIRECTORY
Commander USING [ CommandProc, Register ],
CommandTool USING [ NextArgument ],
Intervoice USING [ Forget, Handle, Open, Play, Record, Retain, Stop ],
IO,
MBQueue USING [ Create ],
Menus USING [MenuProc],
Process USING [ Detach ],
RefText USING [ ObtainScratch, TrustTextAsRope],
Rope USING [Cat, Equal, Fetch, Find, Length, MakeRope, ROPE, Substr],
TiogaOps USING [ Location, GetRope, GetSelection, ViewerDoc ],
ViewerClasses USING [Viewer],
VoiceUtils USING [ CurrentRName ],
WalnutDefs USING [ Error ],
WalnutDocumentRope USING [ Create ],
WalnutOps USING [ GetMsgHeaders, MsgHeaders,ParseHeaders, ParseProc ],
WalnutRegistry USING [ MoveProc, MsgGroupProc, MsgGroupSize, Register ],
WalnutSendInternal USING [SenderReport],
WalnutSendOps USING [AddToSendMenu, AppendHeaderLine],
WalnutWindow USING [ AddToMsgMenu, GetMsgName ]
;
WalnutVoiceImpl: CEDAR MONITOR
IMPORTS
Commander, CommandTool, Intervoice, IO, MBQueue, Process, RefText, Rope, TiogaOps, VoiceUtils, WalnutDefs, WalnutDocumentRope, WalnutSendInternal, WalnutSendOps, WalnutOps, WalnutRegistry, WalnutWindow = {
OPEN IO;
Definitions
voiceHandle: Intervoice.Handle;
msgText: REF TEXT←RefText.ObtainScratch[5000];
Menu Buttons
Buttons should use existing walnut MBQueues! Are there any?
Walnut Sender
RecordProc: ENTRY Menus.MenuProc = {
Respond to record button in Sender by recording a message and inserting a VoiceFileID entry in the Walnut message header
ENABLE UNWIND => NULL;
TRUSTED { Process.Detach[FORK DoRecordProc[NARROW[parent]] ]; };
};
DoRecordProc: PROC [viewer: ViewerClasses.Viewer] = {
voiceFileID: Rope.ROPE = Intervoice.Record[voiceHandle].voiceFileID;
IF voiceFileID = NIL THEN
WalnutSendInternal.SenderReport[msg: "Attempt to record failed\n"]
ELSE WalnutSendOps.AppendHeaderLine[
viewer, IO.PutFR["VoiceFileID: %g", IO.rope[voiceFileID]]];
};
StopProc: ENTRY Menus.MenuProc = {
Sender and Walnut Message viewer STOP buttons
ENABLE UNWIND => NULL;
Intervoice.Stop[voiceHandle];
};
AttachProc: ENTRY Menus.MenuProc = {
Add a line to the Sender in which a VoiceFileID can be inserted. --
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer← NARROW[parent];
WalnutSendOps.AppendHeaderLine[viewer, "VoiceFileID: \001VoiceFileID\002"];
};
SenderPlayProc: ENTRY Menus.MenuProc = TRUSTED {
Sender Play button
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer← NARROW[parent];
If primary selection is in this viewer and selects a voiceFileID, should play it only; otherwise, play all of them.
IF ~PlayFromSelection[NARROW[parent]] THEN
PlayFromMessageHeaders[VoiceHdrsFromMsg[
WalnutDocumentRope.Create[LOOPHOLE [TiogaOps.ViewerDoc[viewer]]]]];
};
Walnut Message Viewers
MessagePlayProc: ENTRY Menus.MenuProc = {
Message viewer Play button
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer = NARROW[parent];
If primary selection is in this viewer and selects a voiceFileID, should play that instead; otherwise play all of them.
IF ~PlayFromSelection[NARROW[parent]] THEN
PlayFromMessageHeaders[VoiceHdrsFromGVID[WalnutWindow.GetMsgName[viewer]]];
};
Procedures registered with WalnutRegistry
VoiceMoveTo: ENTRY WalnutRegistry.MoveProc = {
[msgName: ROPE, fromMsgSet: ROPE, toMsgSet: ROPE, clientData: REF ANY]
ENABLE { ABORTED => CONTINUE; UNWIND => NULL; };
headers: WalnutOps.MsgHeaders;
IF voiceHandle=NIL OR toMsgSet=NIL THEN RETURN;
IF (Rope.Equal[toMsgSet, "deleted",FALSE] OR Rope.Equal[toMsgSet, "active", FALSE]) THEN RETURN;
headers ← VoiceHdrsFromGVID[msgName];
FOR hdrs: WalnutOps.MsgHeaders ← headers, hdrs.rest WHILE hdrs#NIL DO
[]←Intervoice.Retain[
handle: voiceHandle, voiceFileID: hdrs.first.value,
refID: Rid[msgName], refIDType: "GVID"];
ENDLOOP;
};
ChangeInterestEntry: ENTRY WalnutRegistry.MsgGroupProc = {
[msgGroup: REF WalnutRegistry.MsgGroup, event: WalnutRegistry.MsgGroupEvent, clientData: REF ANY]
ENABLE { ABORTED => CONTINUE; UNWIND => NULL; };
IF voiceHandle = NIL THEN RETURN;
FOR i: CARDINAL IN [0..WalnutRegistry.MsgGroupSize) DO
IF msgGroup[i]=NIL THEN LOOP;
SELECT event FROM
added => NULL; -- Present design calls for no action at message read time.
destroyed => Intervoice.Forget[voiceHandle, Rid[msgGroup[i]], "GVID"];
ENDCASE;
ENDLOOP;
};
Utilities, Available from elsewhere
PlayFromSelection: INTERNAL PROC[parentViewer: ViewerClasses.Viewer]
RETURNS [played: BOOLFALSE] = {
parentViewer is viewer containing Play menu entry. If selection is a VoiceFileID node (wherever located), play that and return TRUE. Otherwise ...
selV: ViewerClasses.Viewer;
selStart, selEnd: TiogaOps.Location;
nodeContents: Rope.ROPE;
vfIndex: INT←-1;
[selV, selStart, selEnd] ← TiogaOps.GetSelection[primary];
IF selV#parentViewer OR selStart.node#selEnd.node THEN RETURN;
nodeContents ← TiogaOps.GetRope[selStart.node];
IF (vfIndex←nodeContents.Find["VoiceFileID: "])<0 THEN RETURN;
Intervoice.Play[handle: voiceHandle, voiceFileID: nodeContents.Substr[start: 14]];
RETURN[TRUE];
};
PlayFromMessageHeaders: INTERNAL PROC[headers: WalnutOps.MsgHeaders] = {
Play in reverse order (observe clever recursion), to reverse the effect of ParseHeaders.
IF headers=NIL THEN RETURN;
PlayFromMessageHeaders[headers.rest];
Intervoice.Play[handle: voiceHandle, voiceFileID: headers.first.value];
};
VoiceHdrsFromGVID:
INTERNAL PROC[msgID: Rope.ROPE] RETURNS [WalnutOps.MsgHeaders] = {
msgText ← WalnutOps.GetMsgHeaders[msgID, msgText!
WalnutDefs.Error => IF who=$log AND code=$MsgHeadersTooLong THEN msgText.length𡤀];
RETURN[VoiceHdrsFromMsg[RefText.TrustTextAsRope[msgText]]];
};
VoiceHdrsFromMsg: INTERNAL PROC[msgHeaders: Rope.ROPE] RETURNS [WalnutOps.MsgHeaders] = {
RETURN[IF msgHeaders.Length[]=0 THEN NIL ELSE
WalnutOps.ParseHeaders[msgHeaders, FindVoiceID]];
};
FindVoiceID: WalnutOps.ParseProc = {
[fieldName: ROPE] RETURNS [wantThisOne: BOOL, continue: BOOL]
RETURN[fieldName.Equal["voicefileid", FALSE], TRUE];
};
LowerCaseRope: PROC[r: ROPE] RETURNS [ROPE] = {
RETURN[Rope.MakeRope[base: r, size: r.Length[], fetch: LCFetch]]};
LCFetch: SAFE PROC[data: REF, index: INT] RETURNS [c: CHAR] = TRUSTED {
SELECT (c←NARROW[data,ROPE].Fetch[index]) FROM IN ['A..'Z]=>c𡤌+('a-'A); ENDCASE};
Rid: PROC[msgHeader: Rope.ROPE] RETURNS [dbRidValue: Rope.ROPE] = {
RETURN[Rope.Cat[LowerCaseRope[VoiceUtils.CurrentRName[]], "/", msgHeader]];
};
WalnutComplain: PROC[complaint: Rope.ROPE] = {
WalnutSendInternal.SenderReport[complaint]; };
Initialization (Walnut Voice and Finch)
InitializeWalnutVoice: Commander.CommandProc = {
tunesDBName: Rope.ROPE ← CommandTool.NextArgument[cmd];
instance: Rope.ROPE ← CommandTool.NextArgument[cmd];
voiceHandle ← Intervoice.Open[
tunesDBName: tunesDBName, tunesDBInstance: instance, Complain: WalnutComplain];
Set up the button procs needed by Walnut.
WalnutSendOps.AddToSendMenu[label: "Record", proc: RecordProc];
WalnutSendOps.AddToSendMenu[label: "STOP!", proc: StopProc];
WalnutSendOps.AddToSendMenu[label: "Play", proc: SenderPlayProc];
WalnutSendOps.AddToSendMenu[label: "Attach", proc: AttachProc];
WalnutWindow.AddToMsgMenu[label: "Play", proc: MessagePlayProc];
WalnutWindow.AddToMsgMenu[label: "STOP!", proc: StopProc];
[]←WalnutRegistry.Register[
[
msgGroupProc: ChangeInterestEntry,
msgGroupData: NIL, -- used to be handle
moveProc: VoiceMoveTo,
moveProcData: NIL -- used to be handle
],
MBQueue.Create[]
];
};
Commander.Register["WalnutVoice", InitializeWalnutVoice,
"WalnutVoice [<tunes db root>] [Instance]\nRegister WalnutVoice with walnut, extend Walnut menus."];
}.
Swinehart, January 30, 1986 3:47:33 pm PST
Now uses Intervoice to manage recording and playback.
changes to: voiceHandle, RecordProc, DoRecordProc, StopProc, PlayFromSelection, PlayFromMessageHeaders, FindVoiceID, WalnutComplain, InitializeWalnutVoice
Swinehart, May 19, 1986 9:17:52 am PDT
Cedar 6.1
changes to: DIRECTORY, WalnutVoiceImpl, Rid