<> <> << Ades, September 24, 1986 5:52:47 pm PDT>> <<>> DIRECTORY Convert USING [RopeFromInt, IntFromRope], Menus USING [MenuProc, MenuEntry, FindEntry, CreateEntry, AppendMenuEntry, ReplaceMenuEntry, Menu, MenuLine, GetNumberOfLines, GetLine, SetLine, InsertMenuEntry], MBQueue USING [Queue, Create, CreateMenuEntry], Rope USING [ROPE, Concat, Equal, Substr, Find], ViewerBLT USING [ChangeNumberOfLines], ViewerOps USING [FetchProp, PaintViewer], MessageWindow USING [Append, Blink], ViewerClasses USING [Viewer], SourceMarkerTracking USING [RemoveParentPointersTo, RegisterViewer], TiogaAccess USING [Reader, TiogaChar, FromSelection, EndOf, Get, FromViewer], TiogaOps USING [CallWithLocks, Ref, CommandProc, RegisterCommand, GetSelection, NoSelection, ViewerDoc, StepForward, SelectionGrain, SetSelection, Root, SaveSelA, SelectDocument, RestoreSelA], TiogaOpsDefs USING [Location], TiogaButtons USING [TextNodeRef], TextNode USING [Ref], TextEdit USING [PutCharProp, GetCharProp, PutProp, Size], IO USING [STREAM, PutF, PutFR, int], Atom USING [PropList], VoiceMarkers USING [RopeFromTextList], VoiceRecord USING [AddVoiceProc, StopRecording, DictationMachine], VoiceRope USING [Open, Handle, Stop, VoiceRope, VoiceRopeInterval, Length], Commander USING [CommandProc, Register], TiogaMenuOps USING [tiogaMenu], VoiceViewers USING [BuildVoiceViewer, VoiceViewerInfo, SetParentViewer, RemoveParentViewer], VoicePlayBack USING [CancelPlayBack, PlayRopeWithoutCue, PlayBackMenuProc], VoiceInText; VoiceInTextImpl: CEDAR PROGRAM IMPORTS Convert, Menus, MBQueue, Rope, ViewerBLT, ViewerOps, MessageWindow, TiogaAccess, SourceMarkerTracking, TiogaOps, TiogaButtons, TextEdit, IO, Commander, TiogaMenuOps, VoiceViewers, VoicePlayBack, VoiceMarkers, VoiceRecord, VoiceRope EXPORTS VoiceInText SHARES Menus = BEGIN VoiceMenu: Menus.MenuProc = { ChangeMenu[NARROW[parent], voiceMenu] }; <> ChangeMenu: PUBLIC PROC [viewer: ViewerClasses.Viewer, subMenu: Menus.MenuEntry] = { <> menu: Menus.Menu _ viewer.menu; found: BOOL _ FALSE; numLines: Menus.MenuLine = Menus.GetNumberOfLines[menu]; newLines: Menus.MenuLine _ numLines; FOR i: Menus.MenuLine IN [1..numLines) DO <> IF Rope.Equal[Menus.GetLine[menu,i].name, subMenu.name] THEN { -- yes, so remove it FOR j: Menus.MenuLine IN (i..numLines) DO Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]]; ENDLOOP; newLines _ newLines-1; found _ TRUE; EXIT }; ENDLOOP; IF ~found THEN { <> GoesBefore: PROC [m1, m2: Menus.MenuEntry] RETURNS [BOOL] = { Priority: PROC [m: Menus.MenuEntry] RETURNS [INTEGER] = { <> <> RETURN [SELECT TRUE FROM Rope.Equal[m.name, "Find"] => 1, Rope.Equal[m.name, "FirstLevelOnly"] => 0, ENDCASE => -1 -- unknown menu goes at bottom -- ] }; RETURN [Priority[m1] > Priority[m2]] }; newLast: Menus.MenuLine = MIN[numLines, LAST[Menus.MenuLine]]; newLines _ newLines+1; FOR i: Menus.MenuLine IN [1..numLines) DO IF GoesBefore[subMenu, Menus.GetLine[menu, i]] THEN { <> FOR j: Menus.MenuLine DECREASING IN (i..newLast] DO Menus.SetLine[menu, j, Menus.GetLine[menu, j-1]]; ENDLOOP; Menus.SetLine[menu, i, subMenu]; found _ TRUE; EXIT }; ENDLOOP; IF ~found THEN Menus.SetLine[menu, newLast, subMenu]; }; ViewerBLT.ChangeNumberOfLines[viewer, newLines]; }; ApplyToCharsInPrimarySelection: PUBLIC PROC [ActionProc: PROC [TiogaOpsDefs.Location]] = { <> ScanLocked: PROC [root: TiogaOps.Ref] = { current, end: TiogaOpsDefs.Location; [start: current, end: end] _ TiogaOps.GetSelection[]; IF current.node = end.node THEN FOR i: INT IN [current.where..end.where] DO ActionProc[[current.node, i]] ENDLOOP ELSE { FOR i: INT IN [current.where..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP; DO current.node _ TiogaOps.StepForward[current.node]; IF current.node = end.node THEN { FOR i: INT IN [0..end.where) DO ActionProc[[current.node, i]] ENDLOOP; RETURN } ELSE FOR i: INT IN [0..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP ENDLOOP } }; TiogaOps.CallWithLocks[ScanLocked] }; ApplyToLockedChars: PUBLIC PROC [ActionProc: PROC [TiogaOpsDefs.Location]] = { <> current, end: TiogaOpsDefs.Location; [start: current, end: end] _ TiogaOps.GetSelection[]; IF current.node = end.node THEN FOR i: INT IN [current.where..end.where] DO ActionProc[[current.node, i]] ENDLOOP ELSE { FOR i: INT IN [current.where..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP; DO current.node _ TiogaOps.StepForward[current.node]; IF current.node = end.node THEN { FOR i: INT IN [0..end.where) DO ActionProc[[current.node, i]] ENDLOOP; RETURN } ELSE FOR i: INT IN [0..TextEdit.Size[TiogaButtons.TextNodeRef[current.node]]) DO ActionProc[[current.node, i]] ENDLOOP ENDLOOP } }; <<>> StoreVoiceAtSelection: PUBLIC PROC [voiceViewerInfo: VoiceViewers.VoiceViewerInfo] RETURNS [succeeded: BOOLEAN _ FALSE] = { <> selectedViewer: ViewerClasses.Viewer; suitableViewer: BOOLEAN; alreadyVoiceThere: BOOLEAN; SuitableViewer: PROC RETURNS [BOOLEAN] = { RETURN [selectedViewer.class.flavor = $Text AND ViewerOps.FetchProp[selectedViewer, $voiceViewerInfo] = NIL] }; AddVoiceMarkerAtPrimarySelection: PROC [root: TiogaOps.Ref] = { startChar, endChar, targetChar: TiogaOpsDefs.Location; node: TextNode.Ref; caretBefore: BOOLEAN; pendingDelete: BOOLEAN; level: TiogaOps.SelectionGrain; [viewer: selectedViewer, start: startChar, end: endChar, caretBefore: caretBefore, pendingDelete: pendingDelete, level: level] _ TiogaOps.GetSelection[]; suitableViewer _ SuitableViewer[]; IF pendingDelete AND suitableViewer THEN { ApplyToLockedChars[DeleteVoiceFromChar]; TiogaOps.SetSelection[viewer: selectedViewer, start: startChar, end: endChar, level: level, caretBefore: caretBefore, pendingDelete: FALSE, which: primary] -- simply makes not pending delete }; IF suitableViewer THEN { targetChar _ IF caretBefore THEN startChar ELSE endChar; node _ TiogaButtons.TextNodeRef[targetChar.node]; -- just a type convertor alreadyVoiceThere _ TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL; IF alreadyVoiceThere THEN RETURN; TextEdit.PutCharProp[node, targetChar.where, $voice, voiceViewerInfo.ropeInterval.ropeID]; TextEdit.PutCharProp[node, targetChar.where, $textInVoice, VoiceMarkers.RopeFromTextList[voiceViewerInfo.textMarkList]]; <> TextEdit.PutCharProp[node, targetChar.where, $Artwork, NARROW["TalksBubble", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, targetChar.where, $voiceWindow, NEW[VoiceInText.VoiceWindowRec _ [label: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerInfo.viewerNumber]]]]]; voiceViewerInfo.parentViewer _ selectedViewer; voiceViewerInfo.positionInParent _ targetChar; SourceMarkerTracking.RegisterViewer[selectedViewer] } }; << >> IF voiceViewerInfo.ropeInterval.ropeID = NIL THEN RETURN; TiogaOps.CallWithLocks[AddVoiceMarkerAtPrimarySelection ! TiogaOps.NoSelection => {suitableViewer _ FALSE; CONTINUE}]; <> IF NOT suitableViewer THEN { MessageWindow.Append["Make a selection in a tioga viewer first", TRUE]; MessageWindow.Blink[] } ELSE { IF alreadyVoiceThere THEN { MessageWindow.Append["Cannot add sound on top of another sound", TRUE]; MessageWindow.Blink[] } ELSE succeeded _ TRUE } }; DeleteVoiceProc: Menus.MenuProc = { ApplyToCharsInPrimarySelection[DeleteVoiceFromChar] }; DeleteVoiceFromChar: PUBLIC PROC [position: TiogaOpsDefs.Location] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[position.node]; offset: INT _ position.where; voiceWindowRef: VoiceInText.VoiceWindowRef; voiceWindowRope: Rope.ROPE; TextEdit.PutCharProp[node, offset, $Artwork, NIL]; TextEdit.PutCharProp[node, offset, $voice, NIL]; TextEdit.PutCharProp[node, offset, $textInVoice, NIL]; voiceWindowRef _ NARROW[TextEdit.GetCharProp[node, offset, $voiceWindow], VoiceInText.VoiceWindowRef]; voiceWindowRope _ IF voiceWindowRef = NIL THEN NIL ELSE voiceWindowRef.label; IF voiceWindowRope # NIL THEN { TextEdit.PutCharProp[node, offset, $voiceWindow, NIL]; VoiceViewers.RemoveParentViewer[Convert.IntFromRope[ voiceWindowRope.Substr[voiceWindowRope.Find["#"]+1]]] } }; PlaySelection: PUBLIC PROC = { selectStream: TiogaAccess.Reader _ TiogaAccess.FromSelection[]; charsInSelection: INT _ 0; soundsInSelection: INT _ 0; heavyChar: TiogaAccess.TiogaChar; props: Atom.PropList; IF NOT TiogaAccess.EndOf[selectStream] THEN DO heavyChar _ TiogaAccess.Get[selectStream]; IF TiogaAccess.EndOf[selectStream] THEN EXIT; charsInSelection _ charsInSelection + 1; FOR props _ heavyChar.propList, props.rest WHILE props # NIL DO IF props.first.key = $voice THEN { VoicePlayBack.PlayRopeWithoutCue[NARROW[props.first.val, Rope.ROPE]]; soundsInSelection _ soundsInSelection + 1 }; ENDLOOP ENDLOOP; IF soundsInSelection = 0 THEN MessageWindow.Append["No sounds in selection", TRUE]; DebugRope[IO.PutFR["%d characters in selection\n", IO.int[charsInSelection]]] }; CancelProc: PUBLIC Menus.MenuProc = { VoiceRope.Stop[thrushHandle]; VoicePlayBack.CancelPlayBack[]; VoiceRecord.StopRecording[] }; EditVoiceProc: Menus.MenuProc = { <> voiceList: LIST OF Rope.ROPE _ NIL; textInVoiceList: LIST OF Rope.ROPE _ NIL; voiceViewerInfoList: LIST OF VoiceViewers.VoiceViewerInfo _ NIL; voiceViewerNumberList: LIST OF INT _ NIL; <<>> <> AppendRope: PROC [list: LIST OF Rope.ROPE, entry: Rope.ROPE] RETURNS [LIST OF Rope.ROPE] = { oneElementList: LIST OF Rope.ROPE _ CONS[entry, NIL]; hangOffPoint: LIST OF Rope.ROPE _ list; IF list = NIL THEN RETURN [oneElementList]; WHILE hangOffPoint.rest # NIL DO hangOffPoint _ hangOffPoint.rest ENDLOOP; hangOffPoint.rest _ oneElementList; RETURN [list] }; AppendViewerInfo: PROC [list: LIST OF VoiceViewers.VoiceViewerInfo, entry: VoiceViewers.VoiceViewerInfo] RETURNS [LIST OF VoiceViewers.VoiceViewerInfo] = { oneElementList: LIST OF VoiceViewers.VoiceViewerInfo _ CONS[entry, NIL]; hangOffPoint: LIST OF VoiceViewers.VoiceViewerInfo _ list; IF list = NIL THEN RETURN [oneElementList]; WHILE hangOffPoint.rest # NIL DO hangOffPoint _ hangOffPoint.rest ENDLOOP; hangOffPoint.rest _ oneElementList; RETURN [list] }; AppendInt: PROC [list: LIST OF INT, entry: INT] RETURNS [LIST OF INT] = { oneElementList: LIST OF INT _ CONS[entry, NIL]; hangOffPoint: LIST OF INT _ list; IF list = NIL THEN RETURN [oneElementList]; WHILE hangOffPoint.rest # NIL DO hangOffPoint _ hangOffPoint.rest ENDLOOP; hangOffPoint.rest _ oneElementList; RETURN [list] }; SearchForVoice: PROC [targetChar: TiogaOpsDefs.Location] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[targetChar.node]; voiceRopeID: Rope.ROPE _ NARROW[TextEdit.GetCharProp[node, targetChar.where, $voice], Rope.ROPE]; IF voiceRopeID # NIL THEN { fullRope: VoiceRope.VoiceRope _ NEW [VoiceRope.VoiceRopeInterval _ [voiceRopeID, 0, 0]]; fullRope.length _ VoiceRope.Length[handle: thrushHandle, vr: fullRope]; IF fullRope.length <= 0 THEN MessageWindow.Append["non-existant or zero length voice utterance(s) found in selection", TRUE] ELSE { IF TextEdit.GetCharProp[node, targetChar.where, $voiceWindow] = NIL THEN { voiceList _ AppendRope[voiceList, voiceRopeID]; textInVoiceList _ AppendRope[textInVoiceList, NARROW[TextEdit.GetCharProp[node, targetChar.where, $textInVoice], Rope.ROPE]] } ELSE DebugRope["Voice already has an associated viewer\n"] } } }; AddVoiceWindowProps: PROC [targetChar: TiogaOpsDefs.Location] = { <> <> node: TextNode.Ref _ TiogaButtons.TextNodeRef[targetChar.node]; voiceRopeID: Rope.ROPE _ NARROW[TextEdit.GetCharProp[node, targetChar.where, $voice], Rope.ROPE]; IF voiceList = NIL THEN RETURN; IF voiceRopeID = NIL THEN RETURN; IF TextEdit.GetCharProp[node, targetChar.where, $voiceWindow] # NIL THEN RETURN; IF voiceRopeID.Equal[voiceList.first] THEN { parentViewer: ViewerClasses.Viewer _ TiogaOps.GetSelection[].viewer; alreadyEdited: BOOLEAN _ parentViewer.newVersion; TextEdit.PutCharProp[node, targetChar.where, $voiceWindow, NEW[VoiceInText.VoiceWindowRec _ [label: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumberList.first]]]]]; VoiceViewers.SetParentViewer[voiceViewerInfoList.first, parentViewer, targetChar]; IF ~alreadyEdited THEN { parentViewer.newVersion _ FALSE; ViewerOps.PaintViewer[parentViewer, caption, FALSE] }; voiceList _ voiceList.rest; voiceViewerInfoList _ voiceViewerInfoList.rest; voiceViewerNumberList _ voiceViewerNumberList.rest <> }; }; ApplyToCharsInPrimarySelection[SearchForVoice ! TiogaOps.NoSelection => GOTO Quit]; IF voiceList = NIL THEN { MessageWindow.Append["No undisplayed sounds in selection", TRUE]; RETURN }; FOR l: LIST OF Rope.ROPE _ voiceList, l.rest WHILE l # NIL DO newInfo: VoiceViewers.VoiceViewerInfo; newNumber: INT; [viewerInfo: newInfo, viewerNumber: newNumber] _ VoiceViewers.BuildVoiceViewer[voiceID: l.first, textInVoice: textInVoiceList.first, youngVoice: FALSE]; voiceViewerInfoList _ AppendViewerInfo[voiceViewerInfoList, newInfo]; voiceViewerNumberList _ AppendInt[voiceViewerNumberList, newNumber]; textInVoiceList _ textInVoiceList.rest ENDLOOP; ApplyToCharsInPrimarySelection[AddVoiceWindowProps ! TiogaOps.NoSelection => CONTINUE] EXITS Quit => NULL }; DeleteSourceMarker: PUBLIC PROC [viewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location, voiceViewerNumber: INT] = { DoIt: PROC [root: TiogaOps.Ref] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[positionInParent.node]; voiceWindowRope: Rope.ROPE _ Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumber]]; IF TiogaOps.ViewerDoc[viewer] # TiogaOps.Root[positionInParent.node] THEN { DebugRope["source marker's viewer and node do not match!!\n"]; RETURN }; IF voiceWindowRope.Equal[NARROW[TextEdit.GetCharProp[node, positionInParent.where, $voiceWindow], VoiceInText.VoiceWindowRef].label] THEN { -- see comments about 'edited' status at head of procedure AddVoiceWindowProps alreadyEdited: BOOLEAN _ viewer.newVersion; TextEdit.PutCharProp[node, positionInParent.where, $voiceWindow, NIL]; <> IF TextEdit.GetCharProp[node, positionInParent.where, $voice] = NIL THEN TextEdit.PutCharProp[node, positionInParent.where, $Artwork, NIL]; IF ~alreadyEdited THEN { viewer.newVersion _ FALSE; ViewerOps.PaintViewer[viewer, caption, FALSE] } } ELSE DebugRope["source marker not found at expected position!!\n"] }; TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]]; }; SaveRopeAtSourceMarker: PUBLIC PROC [viewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location, voiceViewerNumber: INT, voiceRopeID: Rope.ROPE, textInVoice: Rope.ROPE] RETURNS [succeeded: BOOLEAN _ FALSE] = { DoIt: PROC [root: TiogaOps.Ref] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[positionInParent.node]; voiceWindowRope: Rope.ROPE _ Rope.Concat["Sound Viewer #", Convert.RopeFromInt[voiceViewerNumber]]; IF TiogaOps.ViewerDoc[viewer] # TiogaOps.Root[positionInParent.node] THEN { DebugRope["source marker's viewer and node do not match!!\n"]; RETURN }; IF voiceWindowRope.Equal[NARROW[TextEdit.GetCharProp[node, positionInParent.where, $voiceWindow], VoiceInText.VoiceWindowRef].label] THEN { TextEdit.PutCharProp[node, positionInParent.where, $voice, voiceRopeID]; TextEdit.PutCharProp[node, positionInParent.where, $textInVoice, textInVoice]; succeeded _ TRUE } ELSE DebugRope["source marker not found at expected position!!\n"] }; TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]]; }; ScanForVoice: TiogaOps.CommandProc = { IF viewer.newFile OR viewer.newVersion THEN RecordVoiceInstancesAtRoot[viewer] ELSE MessageWindow.Append["File is not altered", TRUE] }; DeleteLinks: Menus.MenuProc = { <> viewer: ViewerClasses.Viewer _ NARROW[parent]; RemoveAnySourceMarker: PROC [position: TiogaOpsDefs.Location] = { node: TextNode.Ref _ TiogaButtons.TextNodeRef[position.node]; IF TextEdit.GetCharProp[node, position.where, $voiceWindow] # NIL THEN { -- see comments about 'edited' status at head of procedure AddVoiceWindowProps alreadyEdited: BOOLEAN _ viewer.newVersion; TextEdit.PutCharProp[node, position.where, $voiceWindow, NIL]; IF ~alreadyEdited THEN { viewer.newVersion _ FALSE; ViewerOps.PaintViewer[viewer, caption, FALSE] } } }; TiogaOps.SaveSelA[]; TiogaOps.SelectDocument[viewer: viewer, level: branch]; ApplyToCharsInPrimarySelection[RemoveAnySourceMarker]; TiogaOps.RestoreSelA[]; SourceMarkerTracking.RemoveParentPointersTo[viewer]; }; << >> RecordVoiceInstancesAtRoot: PROC [viewer: ViewerClasses.Viewer] = { wholeFile: TiogaAccess.Reader _ TiogaAccess.FromViewer[viewer]; heavyChar: TiogaAccess.TiogaChar; props: Atom.PropList; soundsInDocument: INT _ 0; rootNode: TextNode.Ref; voiceList: Rope.ROPE _ NIL; DebugRope["Voice messages in document:"]; IF NOT TiogaAccess.EndOf[wholeFile] THEN DO heavyChar _ TiogaAccess.Get[wholeFile]; IF TiogaAccess.EndOf[wholeFile] THEN EXIT; FOR props _ heavyChar.propList, props.rest WHILE props # NIL DO IF props.first.key = $voice THEN { DebugRope["\n"]; DebugRope[NARROW[props.first.val, Rope.ROPE]]; soundsInDocument _ soundsInDocument + 1; voiceList _ voiceList.Concat["&"]; -- just used here as a separator: a character not found in the IDs voiceList _ voiceList.Concat[NARROW[props.first.val, Rope.ROPE]] } ENDLOOP ENDLOOP; rootNode _ TiogaButtons.TextNodeRef[TiogaOps.ViewerDoc[viewer]]; -- TextNodeRef is just a type convertor - replace with a more cautious implementation?? [several references throughout TiogaVoice] TextEdit.PutProp[rootNode, $voicelist, voiceList]; IF soundsInDocument = 0 THEN DebugRope[" none\n"] ELSE DebugRope["\n"] }; InstallMenuButton: PROC [name: Rope.ROPE, proc: Menus.MenuProc] = { old: Menus.MenuEntry = Menus.FindEntry[menu: TiogaMenuOps.tiogaMenu, entryName: name]; new: Menus.MenuEntry = Menus.CreateEntry[name: name, proc: proc]; IF old = NIL THEN Menus.AppendMenuEntry[TiogaMenuOps.tiogaMenu, new] ELSE Menus.ReplaceMenuEntry[TiogaMenuOps.tiogaMenu, old, new]; }; thrushHandle: PUBLIC VoiceRope.Handle _ VoiceRope.Open[]; voiceMenu: Menus.MenuEntry; voiceButtonQueue: PUBLIC MBQueue.Queue _ MBQueue.Create[]; <> debugStream: IO.STREAM _ NIL; DebugRope: PUBLIC PROC [rope: Rope.ROPE] = { IF debugStream # NIL THEN debugStream.PutF[rope] }; DebugStreamInit: Commander.CommandProc = { debugStream _ cmd.out }; Commander.Register[key: "VoiceInfo", proc: DebugStreamInit, doc: "VoiceInfo: registers an output stream for debugging messages"]; <<**** voiceButtonQueue should be deleted, or used for ALL button activities>> Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DeleteLinks", DeleteLinks], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DictationMachine", VoiceRecord.DictationMachine], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "DeleteVoice", DeleteVoiceProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "EditVoice", EditVoiceProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "STOP", CancelProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "PlayVoice", VoicePlayBack.PlayBackMenuProc], 1]; Menus.InsertMenuEntry[TiogaMenuOps.tiogaMenu, MBQueue.CreateMenuEntry[voiceButtonQueue, "AddVoice", VoiceRecord.AddVoiceProc], 1]; voiceMenu _ Menus.GetLine[TiogaMenuOps.tiogaMenu, 1]; InstallMenuButton["Voice", VoiceMenu]; Menus.SetLine[TiogaMenuOps.tiogaMenu, 1, NIL]; TiogaOps.RegisterCommand[name: $RedSave, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $YellowSave, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $BlueSave, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $RedStore, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $YellowStore, proc: ScanForVoice]; TiogaOps.RegisterCommand[name: $BlueStore, proc: ScanForVoice]; END.