<> <> <> <> <> <<>> DIRECTORY Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Parse, Failed], Convert USING [RopeFromInt, IntFromRope], Imager USING [Context, Move, ShowChar, SetStrokeWidth, SetStrokeJoint, MaskStroke, SetXY, ShowRope, SetFont, black, SetColor], ImagerFont USING [Extents, Find, Font, Escapement], ImagerPath USING [PathProc], Jukebox USING [bytesPerChirp], MBQueue USING [CreateMenuEntry], Menus USING [MenuProc, MenuEntry, InsertMenuEntry, GetLine, SetLine, ChangeNumberOfLines], MessageWindow USING [Append, Blink], NodeStyle USING [Ref], NodeStyleOps USING [OfStyle], Real USING [Fix], Rope USING [ROPE, Length, Substr, Concat, Fetch, Cat, Find], RopeEdit USING [AlphaNumericChar], TEditFormat USING [CharacterArtwork, CharacterArtworkRep, CharacterArtworkClass, CharacterArtworkClassRep, RegisterCharacterArtwork, GetFont], TextEdit USING [CharSet, FetchChar, GetCharProp, PutCharProp], TextNode USING [Location, Ref], TiogaButtons USING [TextNodeRef], TiogaOps USING [GetRope, GetSelection, FirstChild, ViewerDoc, CallWithLocks, SaveSelA, RestoreSelA], TiogaOpsDefs USING [Location, Ref], TiogaVoicePrivate USING [ CancelPlayBack, ChangeMenu, GetCurrentPlayBackPos, GetVoiceLock, GetVoiceViewerInfoList, IntPair, LastSilenceInSoundList, MakeVoiceEdited, PlayBackInProgress, PlaySlabSection, RecordInPlaceOfSelection, RedrawViewer, ReplaceSelectionWithSavedInterval, Selection, SelectionRec, SetVoiceViewerEditStatus, SoundChars, SoundInterval, soundRopeCharLength, StopRecording, TextMarkEntry, TextMarkRec, thrushHandle, voiceButtonQueue, voiceCharDescent, voiceCharHeight, voiceCharSet, voiceCharWidth, VoiceViewerInfo, VoiceViewerInfoList, voiceViewerMenu ], Vector2 USING [VEC], ViewerClasses USING [Viewer], ViewerOps USING [FetchProp, PaintViewer], VoiceRope USING [Stop, VoiceRopeInterval] ; <<>> VoiceMarkersImpl: CEDAR PROGRAM IMPORTS Commander, CommandTool, Convert, Imager, ImagerFont, MBQueue, Menus, MessageWindow, Real, Rope, RopeEdit, TEditFormat, TextEdit, TiogaButtons, TiogaOps, TiogaVoicePrivate, ViewerOps, VoiceRope EXPORTS TiogaVoicePrivate = { <> <<>> <> <> <> <> <> AddCharMark: PUBLIC Menus.MenuProc = { SELECT mouseButton FROM red, blue => AddMarksAtSelection[]; yellow => AddMarkAtPlayBackLocation[]; ENDCASE; }; AddMarkAtPlayBackLocation: PROC = { <> viewer: ViewerClasses.Viewer _ NIL; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; display: BOOLEAN _ FALSE; currentPos: INT _ -1; IF TiogaVoicePrivate.PlayBackInProgress[] THEN { [display, viewer, currentPos] _ TiogaVoicePrivate.GetCurrentPlayBackPos[]; }; IF currentPos = -1 THEN { MessageWindow.Append["No current playback to mark (possibly abort in progress)", TRUE]; MessageWindow.Blink[]; RETURN; }; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF (NOT display) OR (viewerInfo = NIL) THEN { MessageWindow.Append["No voice viewer to mark for current playback", TRUE]; MessageWindow.Blink[]; RETURN; }; IF TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN { MarkChar[viewerInfo, currentPos]; { trueContents: Rope.ROPE _ TiogaVoicePrivate.SoundChars[viewerInfo].soundRope; [] _ TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; TiogaVoicePrivate.SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress _ FALSE } }; }; AddMarksAtSelection: PROC = { <> viewer: ViewerClasses.Viewer; start, end: TiogaOpsDefs.Location; pendingDelete, caretBefore: BOOLEAN; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; [viewer: viewer, start: start, end: end, pendingDelete: pendingDelete, caretBefore: caretBefore] _ TiogaOps.GetSelection[]; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF viewerInfo = NIL THEN { MessageWindow.Append["Make a selection in a voice viewer first", TRUE]; MessageWindow.Blink[]; RETURN }; IF TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN { IF NOT ((start.node = end.node) AND (start.node = TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]])) THEN ERROR; IF pendingDelete OR caretBefore THEN MarkChar[viewerInfo, start.where]; IF pendingDelete OR ~caretBefore THEN MarkChar[viewerInfo, end.where]; { trueContents: Rope.ROPE _ TiogaVoicePrivate.SoundChars[viewerInfo].soundRope; [] _ TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; TiogaVoicePrivate.SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress _ FALSE } } }; MarkChar: PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, position: INT] = { restOfList: LIST OF INT; IF viewerInfo.charMarkList = NIL THEN viewerInfo.charMarkList _ CONS [position, NIL] ELSE { IF viewerInfo.charMarkList.first >= position THEN { IF viewerInfo.charMarkList.first > position THEN viewerInfo.charMarkList _ CONS [position, viewerInfo.charMarkList] } ELSE { FOR restOfList _ viewerInfo.charMarkList, restOfList.rest WHILE restOfList.rest # NIL DO IF restOfList.rest.first >= position THEN { IF restOfList.rest.first > position THEN restOfList.rest _ CONS [position, restOfList.rest]; RETURN } ENDLOOP; restOfList.rest _ CONS [position, NIL] } } }; LockedAddCharMark: PUBLIC PROC [viewer: ViewerClasses.Viewer, position: INT] = { <> viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF viewerInfo = NIL THEN ERROR; MarkChar[viewerInfo, position]; { trueContents: Rope.ROPE _ TiogaVoicePrivate.SoundChars[viewerInfo].soundRope; [] _ TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; TiogaVoicePrivate.SetVoiceViewerEditStatus[viewer]; } }; <<>> DeleteCharMarks: PUBLIC Menus.MenuProc = { viewer: ViewerClasses.Viewer; start, end: TiogaOpsDefs.Location; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; RemoveMarkChars: PROC [from, to: INT] = { WHILE viewerInfo.charMarkList # NIL AND viewerInfo.charMarkList.first IN [from..to] DO viewerInfo.charMarkList _ viewerInfo.charMarkList.rest ENDLOOP; IF viewerInfo.charMarkList # NIL THEN FOR l: LIST OF INT _ viewerInfo.charMarkList, l.rest WHILE l # NIL AND l.rest # NIL DO IF l.rest.first IN [from..to] THEN l.rest _ l.rest.rest ENDLOOP -- the dual WHILE condition is because we can delete the next entry and it could be the last }; IF mouseButton = blue THEN -- right click means delete all marks in the viewer { viewer _ NARROW[parent, ViewerClasses.Viewer]; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN { viewerInfo.charMarkList _ NIL; { trueContents: Rope.ROPE _ TiogaVoicePrivate.SoundChars[viewerInfo].soundRope; [] _ TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; TiogaVoicePrivate.SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress _ FALSE } } } ELSE -- both other clicks mean delete all marks from selection { [viewer: viewer, start: start, end: end] _ TiogaOps.GetSelection[]; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF viewerInfo = NIL THEN { MessageWindow.Append["Make a selection in a voice viewer first", TRUE]; MessageWindow.Blink[]; RETURN }; IF TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN { IF ~ (start.node = end.node AND start.node = TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]) THEN ERROR; RemoveMarkChars[start.where, end.where]; { trueContents: Rope.ROPE _ TiogaVoicePrivate.SoundChars[viewerInfo].soundRope; [] _ TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered]; TiogaVoicePrivate.SetVoiceViewerEditStatus[viewer]; viewerInfo.editInProgress _ FALSE } } } }; DisplayCharMarks: PUBLIC PROC [ unMarkedRope: Rope.ROPE, charMarkList: LIST OF INT, skipChars: INT] RETURNS [markedRope: Rope.ROPE] = { <> endChar: INT _ unMarkedRope.Length - 1; lastMark: INT _ -1; nextMark: INT; markedRope _ NIL; WHILE charMarkList # NIL AND charMarkList.first < skipChars DO charMarkList _ charMarkList.rest ENDLOOP; DO nextMark _ IF charMarkList = NIL THEN endChar + 1 ELSE charMarkList.first - skipChars; markedRope _ markedRope.Concat[unMarkedRope.Substr[lastMark+1, nextMark-lastMark-1]]; IF nextMark = endChar + 1 THEN RETURN; markedRope _ markedRope.Concat["@"]; -- the marker character lastMark _ nextMark; charMarkList _ charMarkList.rest ENDLOOP }; <<>> ExtractCharMarks: PUBLIC PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, soundInterval: TiogaVoicePrivate.SoundInterval] = { <> currLastInList: LIST OF INT; soughtStart: INT _ soundInterval.ropeInterval.start/TiogaVoicePrivate.soundRopeCharLength; soughtEnd: INT _ (soundInterval.ropeInterval.start + soundInterval.ropeInterval.length)/TiogaVoicePrivate.soundRopeCharLength; soundInterval.charMarkList _ NIL; -- should be already so, but . . . FOR l: LIST OF INT _ viewerInfo.charMarkList, l.rest WHILE l # NIL AND l.first < soughtEnd DO IF l.first >= soughtStart THEN { IF soundInterval.charMarkList = NIL THEN { soundInterval.charMarkList _ CONS [l.first - soughtStart, NIL]; currLastInList _ soundInterval.charMarkList; } ELSE { currLastInList.rest _ CONS [l.first - soughtStart, NIL]; currLastInList _ currLastInList.rest } } ENDLOOP }; <<>> EditCharMarks: PUBLIC PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, unchangedHead, deleteChars, insertChars: INT, soundInterval: TiogaVoicePrivate.SoundInterval] = { <> <> atHead: BOOLEAN _ TRUE; l: LIST OF INT; WHILE atHead AND viewerInfo.charMarkList # NIL DO SELECT viewerInfo.charMarkList.first FROM >= unchangedHead+deleteChars => {viewerInfo.charMarkList.first _ viewerInfo.charMarkList.first + insertChars - deleteChars; atHead _ FALSE}; >= unchangedHead => viewerInfo.charMarkList _ viewerInfo.charMarkList.rest; ENDCASE => atHead _ FALSE; ENDLOOP; IF viewerInfo.charMarkList # NIL THEN { l _ viewerInfo.charMarkList; WHILE l # NIL AND l.rest # NIL DO --the dual WHILE condition is because we can delete the next entry and it could be the last SELECT l.rest.first FROM >= unchangedHead+deleteChars => {l.rest.first _ l.rest.first + insertChars - deleteChars; l _ l.rest}; >= unchangedHead => l.rest _ l.rest.rest; ENDCASE => l _ l.rest ENDLOOP }; IF soundInterval # NIL AND soundInterval.charMarkList # NIL THEN -- now put the character marks from the soundInterval into the list at the appropriate point { beforeInsert, afterInsert: LIST OF INT _ NIL; newSection, endNewSection: LIST OF INT; IF viewerInfo.charMarkList # NIL THEN { IF viewerInfo.charMarkList.first >= unchangedHead THEN afterInsert _ viewerInfo.charMarkList ELSE { l _ viewerInfo.charMarkList; WHILE l.rest # NIL AND l.rest.first < unchangedHead DO l _ l.rest ENDLOOP; beforeInsert _ l; afterInsert _ l.rest } }; { insertMarks: LIST OF INT _ soundInterval.charMarkList.rest; newSection _ CONS [soundInterval.charMarkList.first + unchangedHead, NIL]; endNewSection _ newSection; WHILE insertMarks # NIL DO endNewSection.rest _ CONS [soundInterval.charMarkList.first + unchangedHead, NIL]; endNewSection _ endNewSection.rest; insertMarks _ insertMarks.rest ENDLOOP }; endNewSection.rest _ afterInsert; IF beforeInsert = NIL THEN viewerInfo.charMarkList _ newSection ELSE beforeInsert.rest _ newSection } }; <<>> <> TextInput: PUBLIC PROC [viewer: ViewerClasses.Viewer, input: Rope.ROPE] = { node: TextNode.Ref; textEntry: TiogaVoicePrivate.TextMarkEntry; AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { TextEdit.PutCharProp[node, textEntry.position, $Artwork, NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, textEntry.text.Substr[0, textEntry.displayChars]]; }; selectionViewer: ViewerClasses.Viewer; start, end, char: TiogaOpsDefs.Location; insertPosition: INT; caretBefore: BOOLEAN; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; charsAlreadyDisplayed: INT _ 0; redraw: BOOLEAN _ FALSE; [viewer: selectionViewer, start: start, end: end, caretBefore: caretBefore] _ TiogaOps.GetSelection[]; IF selectionViewer # viewer THEN ERROR; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF ~TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN RETURN; char _ IF caretBefore THEN start ELSE end; node _ TiogaButtons.TextNodeRef[char.node]; insertPosition _ char.where; IF viewerInfo.textMarkList = NIL OR viewerInfo.textMarkList.first.position > insertPosition THEN { textEntry _ NEW [TiogaVoicePrivate.TextMarkRec _ [insertPosition, input, 0, 0]]; viewerInfo.textMarkList _ CONS [textEntry, viewerInfo.textMarkList]; LimitText[textEntry, IF viewerInfo.textMarkList.rest = NIL THEN LAST[INT] ELSE viewerInfo.textMarkList.rest.first.position - insertPosition]; TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE } ELSE { IF viewerInfo.textMarkList.first.position = insertPosition THEN { textEntry _ viewerInfo.textMarkList.first; charsAlreadyDisplayed _ textEntry.displayChars; textEntry.text _ textEntry.text.Concat[input]; LimitText[textEntry, IF viewerInfo.textMarkList.rest = NIL THEN LAST[INT] ELSE viewerInfo.textMarkList.rest.first.position - insertPosition]; IF textEntry.displayChars # charsAlreadyDisplayed THEN {TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE} } ELSE { l: LIST OF TiogaVoicePrivate.TextMarkEntry _ viewerInfo.textMarkList; WHILE l.rest # NIL AND l.rest.first.position < insertPosition DO l _ l.rest ENDLOOP; IF l.rest = NIL OR l.rest.first.position > insertPosition THEN -- insert a new entry: previous entry may need trimming { oldWidth: INT _ l.first.width; textEntry _ l.first; LimitText[textEntry, insertPosition - textEntry.position]; IF textEntry.width # oldWidth THEN {TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE}; textEntry _ NEW [TiogaVoicePrivate.TextMarkRec _ [insertPosition, input, 0, 0]]; l.rest _ CONS [textEntry, l.rest] } ELSE -- appending text to an entry already there { textEntry _ l.rest.first; textEntry.text _ textEntry.text.Concat[input]; charsAlreadyDisplayed _ textEntry.displayChars }; l _ l.rest; -- point at the new/modified entry LimitText[textEntry, IF l.rest = NIL THEN LAST[INT] ELSE l.rest.first.position - insertPosition]; IF textEntry.displayChars # charsAlreadyDisplayed THEN {TiogaOps.CallWithLocks[AddArtwork]; redraw _ TRUE} } }; <<**** a Plass-bug: shouldn't be necessary and he says he'll have a look at it>> <> TiogaVoicePrivate.MakeVoiceEdited[viewer]; viewerInfo.editInProgress _ FALSE }; LimitText: PROC [entry: TiogaVoicePrivate.TextMarkEntry, maxLength: INT] = { <> maxEscapement: REAL _ REAL[maxLength]*TiogaVoicePrivate.voiceCharWidth; textLength: INT _ entry.text.Length[]; currPos: INT _ 0; currEscapement: REAL _ 0.0; WHILE currPos < textLength DO newEscapement: REAL _ currEscapement + ImagerFont.Escapement[voiceMarkerFont, [TiogaVoicePrivate.voiceCharSet, entry.text.Fetch[currPos]-'\000]].x; IF newEscapement > maxEscapement THEN EXIT; currPos _ currPos + 1; currEscapement _ newEscapement ENDLOOP; <> <> <<>> entry.width _ Real.Fix[(currEscapement+TiogaVoicePrivate.voiceCharWidth-1.0)/ TiogaVoicePrivate.voiceCharWidth]; entry.displayChars _ currPos; }; <<>> Back: PROC [viewer: ViewerClasses.Viewer, findNewLen: BackProc] = { <> node: TextNode.Ref; textEntry: TiogaVoicePrivate.TextMarkEntry; AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { TextEdit.PutCharProp[node, textEntry.position, $Artwork, IF textEntry.text = NIL THEN NIL ELSE NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, IF textEntry.text = NIL THEN NIL ELSE textEntry.text.Substr[0, textEntry.displayChars]]; }; selectionViewer: ViewerClasses.Viewer; start, end, char: TiogaOpsDefs.Location; deletePosition, charsToNextPosition: INT; prevptr: LIST OF TiogaVoicePrivate.TextMarkEntry _ NIL; caretBefore: BOOLEAN; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; [viewer: selectionViewer, start: start, end: end, caretBefore: caretBefore] _ TiogaOps.GetSelection[]; IF selectionViewer # viewer THEN ERROR; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF ~TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN RETURN; char _ IF caretBefore THEN start ELSE end; node _ TiogaButtons.TextNodeRef[char.node]; deletePosition _ char.where; textEntry _ NIL; FOR l: LIST OF TiogaVoicePrivate.TextMarkEntry _ viewerInfo.textMarkList, l.rest WHILE l # NIL DO IF l.first.position >= deletePosition THEN { IF l.first.position = deletePosition THEN { textEntry _ l.first; charsToNextPosition _ IF l.rest = NIL THEN LAST[INT] ELSE l.rest.first.position - deletePosition }; EXIT }; prevptr _ l; ENDLOOP; IF textEntry = NIL THEN MessageWindow.Append["No textual annotation at point of selection", TRUE] ELSE { charsOnDisplay: INT _ textEntry.displayChars; len: INT _ findNewLen[textEntry]; textEntry.text _ IF len<=0 THEN NIL ELSE textEntry.text.Substr[0, len]; DO LimitText[textEntry, charsToNextPosition]; IF charsOnDisplay # textEntry.displayChars THEN { TiogaOps.CallWithLocks[AddArtwork]; <<**** a Plass-bug: shouldn't be necessary and he says he'll have a look at it>> <> }; IF textEntry.text=NIL THEN -- remove this marker from textMarkList IF prevptr=NIL THEN viewerInfo.textMarkList _ viewerInfo.textMarkList.rest ELSE { -- also need to repaint previous text marker prevptr.rest _ prevptr.rest.rest; IF charsToNextPosition # LAST[INT] THEN charsToNextPosition _ charsToNextPosition + textEntry.position - prevptr.first.position; textEntry _ prevptr.first; LOOP; }; EXIT; ENDLOOP; TiogaVoicePrivate.MakeVoiceEdited[selectionViewer]; }; viewerInfo.editInProgress _ FALSE }; BackProc: TYPE = PROC [textEntry: TiogaVoicePrivate.TextMarkEntry] RETURNS [len: INT]; BackSpaceProc: BackProc ~ { len _ textEntry.text.Length[] - 1; }; BackWordProc: BackProc ~ { len _ textEntry.text.Length[]; WHILE len>0 AND NOT RopeEdit.AlphaNumericChar[textEntry.text.Fetch[len-1]] DO len _ len-1; ENDLOOP; WHILE len>0 AND RopeEdit.AlphaNumericChar[textEntry.text.Fetch[len-1]] DO len _ len-1; ENDLOOP; }; BackSpace: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { <> Back[viewer, BackSpaceProc]; }; BackWord: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { <> Back[viewer, BackWordProc]; }; RemoveTextMarkers: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { <> node: TextNode.Ref; textEntry: TiogaVoicePrivate.TextMarkEntry; AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { TextEdit.PutCharProp[node, textEntry.position, $Artwork, IF textEntry.text = NIL THEN NIL ELSE NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, IF textEntry.text = NIL THEN NIL ELSE textEntry.text.Substr[0, textEntry.displayChars]]; }; selectionViewer: ViewerClasses.Viewer; start, end: TiogaOpsDefs.Location; textRemoved: BOOLEAN _ FALSE; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; [viewer: selectionViewer, start: start, end: end] _ TiogaOps.GetSelection[]; IF selectionViewer # viewer THEN ERROR; IF start.node # end.node THEN ERROR; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; node _ TiogaButtons.TextNodeRef[start.node]; IF ~TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN RETURN; IF viewerInfo.textMarkList = NIL THEN { viewerInfo.editInProgress _ FALSE; RETURN }; IF viewerInfo.textMarkList.first.position >= start.where THEN -- any text to remove is at the head of the list WHILE viewerInfo.textMarkList # NIL AND viewerInfo.textMarkList.first.position <= end.where DO textEntry _ viewerInfo.textMarkList.first; textEntry.text _ NIL; TiogaOps.CallWithLocks[AddArtwork]; textRemoved _ TRUE; viewerInfo.textMarkList _ viewerInfo.textMarkList.rest ENDLOOP ELSE { l: LIST OF TiogaVoicePrivate.TextMarkEntry _ viewerInfo.textMarkList; WHILE l.rest # NIL AND l.rest.first.position < start.where DO l _ l.rest ENDLOOP; WHILE l.rest # NIL AND l.rest.first.position <= end.where DO textEntry _ l.rest.first; textEntry.text _ NIL; TiogaOps.CallWithLocks[AddArtwork]; textRemoved _ TRUE; l.rest _ l.rest.rest ENDLOOP; IF textRemoved THEN { currChars: INT; textEntry _ l.first; <> currChars _ textEntry.displayChars; LimitText[textEntry, IF l.rest = NIL THEN LAST[INT] ELSE l.rest.first.position - textEntry.position]; IF currChars # textEntry.displayChars THEN TiogaOps.CallWithLocks[AddArtwork] } }; IF textRemoved THEN { TiogaVoicePrivate.MakeVoiceEdited[selectionViewer]; <<**** a Plass-bug: shouldn't be necessary and he says he'll have a look at it>> <> }; viewerInfo.editInProgress _ FALSE }; RedrawTextMarkers: PUBLIC PROC [ viewer: ViewerClasses.Viewer, voiceCharNode: TiogaOpsDefs.Ref] = { <> AddArtwork: PROC [root: TiogaOpsDefs.Ref] = { textEntry: TiogaVoicePrivate.TextMarkEntry _ l.first; TextEdit.PutCharProp[node, textEntry.position, $Artwork, NARROW["VoiceMarker", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created TextEdit.PutCharProp[node, textEntry.position, $VoiceMark, textEntry.text.Substr[0, textEntry.displayChars]]; }; node: TextNode.Ref _ TiogaButtons.TextNodeRef[voiceCharNode]; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; l: LIST OF TiogaVoicePrivate.TextMarkEntry; FOR l _ viewerInfo.textMarkList, l.rest WHILE l # NIL DO TiogaOps.CallWithLocks[AddArtwork, TiogaOps.ViewerDoc[viewer]] ENDLOOP }; ExtractTextMarks: PUBLIC PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, soundInterval: TiogaVoicePrivate.SoundInterval] = { <> currLastInList: LIST OF TiogaVoicePrivate.TextMarkEntry; soughtStart: INT _ soundInterval.ropeInterval.start/TiogaVoicePrivate.soundRopeCharLength; soughtEnd: INT _ (soundInterval.ropeInterval.start + soundInterval.ropeInterval.length)/TiogaVoicePrivate.soundRopeCharLength; soundInterval.textMarkList _ NIL; -- should be already so, but . . . FOR l: LIST OF TiogaVoicePrivate.TextMarkEntry _ viewerInfo.textMarkList, l.rest WHILE l # NIL AND l.first.position < soughtEnd DO IF l.first.position >= soughtStart THEN { newEntry: TiogaVoicePrivate.TextMarkEntry _ NEW [TiogaVoicePrivate.TextMarkRec _ l.first^]; newEntry.position _ newEntry.position - soughtStart; IF soundInterval.textMarkList = NIL THEN { soundInterval.textMarkList _ CONS [newEntry, NIL]; currLastInList _ soundInterval.textMarkList; } ELSE { currLastInList.rest _ CONS [newEntry, NIL]; currLastInList _ currLastInList.rest } } ENDLOOP }; EditTextMarks: PUBLIC PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, unchangedHead, deleteChars, insertChars: INT, soundInterval: TiogaVoicePrivate.SoundInterval] = { <> <> atHead: BOOLEAN _ TRUE; l: LIST OF TiogaVoicePrivate.TextMarkEntry; <> WHILE atHead AND viewerInfo.textMarkList # NIL DO SELECT viewerInfo.textMarkList.first.position FROM >= unchangedHead+deleteChars => {viewerInfo.textMarkList.first.position _ viewerInfo.textMarkList.first.position + insertChars - deleteChars; atHead _ FALSE}; >= unchangedHead => viewerInfo.textMarkList _ viewerInfo.textMarkList.rest; ENDCASE => atHead _ FALSE; ENDLOOP; IF viewerInfo.textMarkList # NIL THEN { l _ viewerInfo.textMarkList; WHILE l # NIL AND l.rest # NIL DO --the dual WHILE condition is because we can delete the next entry and it could be the last SELECT l.rest.first.position FROM >= unchangedHead+deleteChars => {l.rest.first.position _ l.rest.first.position + insertChars - deleteChars; l _ l.rest}; >= unchangedHead => l.rest _ l.rest.rest; ENDCASE => l _ l.rest ENDLOOP }; IF soundInterval # NIL AND soundInterval.textMarkList # NIL THEN -- now put the textual marks from the soundInterval into the list at the appropriate point { beforeInsert, afterInsert: LIST OF TiogaVoicePrivate.TextMarkEntry _ NIL; newSection, endNewSection: LIST OF TiogaVoicePrivate.TextMarkEntry; IF viewerInfo.textMarkList # NIL THEN { IF viewerInfo.textMarkList.first.position >= unchangedHead THEN afterInsert _ viewerInfo.textMarkList ELSE { l _ viewerInfo.textMarkList; WHILE l.rest # NIL AND l.rest.first.position < unchangedHead DO l _ l.rest ENDLOOP; beforeInsert _ l; afterInsert _ l.rest } }; { insertMarks: LIST OF TiogaVoicePrivate.TextMarkEntry _ soundInterval.textMarkList.rest; newSection _ CONS [NEW [TiogaVoicePrivate.TextMarkRec _ soundInterval.textMarkList.first^], NIL]; newSection.first.position _ newSection.first.position + unchangedHead; endNewSection _ newSection; WHILE insertMarks # NIL DO endNewSection.rest _ CONS [NEW [TiogaVoicePrivate.TextMarkRec _ soundInterval.textMarkList.first^], NIL]; endNewSection _ endNewSection.rest; insertMarks _ insertMarks.rest; endNewSection.first.position _ endNewSection.first.position + unchangedHead ENDLOOP }; endNewSection.rest _ afterInsert; LimitText[endNewSection.first, IF endNewSection.rest = NIL THEN LAST[INT] ELSE endNewSection.rest.first.position - endNewSection.first.position]; IF beforeInsert = NIL THEN viewerInfo.textMarkList _ newSection ELSE { beforeInsert.rest _ newSection; LimitText[beforeInsert.first, newSection.first.position - beforeInsert.first.position] } } ELSE -- no new entries to be inserted, but we must LimitText on the entry previous to the cut {IF viewerInfo.textMarkList # NIL AND viewerInfo.textMarkList.first.position < unchangedHead THEN { beforeCut: LIST OF TiogaVoicePrivate.TextMarkEntry _ viewerInfo.textMarkList; WHILE beforeCut.rest # NIL AND beforeCut.rest.first.position < unchangedHead DO beforeCut _ beforeCut.rest ENDLOOP; LimitText[beforeCut.first, IF beforeCut.rest = NIL THEN LAST[INT] ELSE beforeCut.rest.first.position - beforeCut.first.position] } } }; RopeFromTextList: PUBLIC PROC [ l: LIST OF TiogaVoicePrivate.TextMarkEntry] RETURNS [r: Rope.ROPE _ NIL] = { <> WHILE l # NIL DO textEntry: Rope.ROPE _ l.first.text; positionInSamples: INT _ l.first.position*TiogaVoicePrivate.soundRopeCharLength; r _ r.Cat[Convert.RopeFromInt[positionInSamples], ":", Convert.RopeFromInt[textEntry.Length], ":"]; r _ r.Concat[textEntry]; l _ l.rest ENDLOOP }; TextListFromRope: PUBLIC PROC [r: Rope.ROPE] RETURNS [l: LIST OF TiogaVoicePrivate.TextMarkEntry _ NIL] = { <> endOfList: LIST OF TiogaVoicePrivate.TextMarkEntry; WHILE r.Length > 0 DO nextColon: INT _ r.Find[":"]; newEntry: TiogaVoicePrivate.TextMarkEntry _ NEW [TiogaVoicePrivate.TextMarkRec _ [Convert.IntFromRope[r.Substr[0, nextColon]]/TiogaVoicePrivate.soundRopeCharLength, NIL, 0, 0]]; textLength: INT; r _ r.Substr[nextColon+1]; nextColon _ r.Find[":"]; textLength _ Convert.IntFromRope[r.Substr[0, nextColon]]; r _ r.Substr[nextColon+1]; newEntry.text _ r.Substr[0, textLength]; r _ r.Substr[textLength]; IF l = NIL THEN { l _ CONS [newEntry, NIL]; endOfList _ l } ELSE { endOfList.rest _ CONS [newEntry, NIL]; endOfList _ endOfList.rest } ENDLOOP; FOR list: LIST OF TiogaVoicePrivate.TextMarkEntry _ l, list.rest WHILE list # NIL DO LimitText[list.first, IF list.rest = NIL THEN LAST[INT] ELSE list.rest.first.position - list.first.position] ENDLOOP }; voiceMarkerFont: PUBLIC ImagerFont.Font _ ImagerFont.Find["xerox/tiogafonts/tioga10"]; <> <<>> <> <> <<>> VoiceMarkerDataRep: TYPE ~ RECORD [ letter: CHAR, label: Rope.ROPE, charHeight: REAL, charWidth: REAL ]; VoiceMarkerPaint: PROC [ self: TEditFormat.CharacterArtwork, context: Imager.Context] ~ { data: REF VoiceMarkerDataRep ~ NARROW[self.data]; DrawArrow: ImagerPath.PathProc = { moveTo[[0.0, 2.0*data.charHeight]]; lineTo[[0.0, data.charHeight]]; lineTo[[3.0, 4.0*data.charHeight/3.0]]; lineTo[[-2.0, 4.0*data.charHeight/3.0]]; lineTo[[0.0, data.charHeight]] }; Imager.Move[context]; Imager.ShowChar[context, data.letter]; Imager.SetColor[context, Imager.black]; Imager.SetStrokeWidth[context, 1.0]; Imager.SetStrokeJoint[context, round]; Imager.MaskStroke[context, DrawArrow, TRUE]; Imager.SetXY[context, [2.0, 3.0*data.charHeight/2.0]]; Imager.SetFont[context, voiceMarkerFont]; Imager.ShowRope[context, data.label]; }; VoiceMarkerFormat: PROC [ class: TEditFormat.CharacterArtworkClass, loc: TextNode.Location, style: NodeStyle.Ref, kind: NodeStyleOps.OfStyle] RETURNS [TEditFormat.CharacterArtwork] ~ { voiceCharSet: TextEdit.CharSet _ TextEdit.FetchChar[loc.node, loc.where].charSet; letter: CHAR _ TextEdit.FetchChar[loc.node, loc.where].char; label: Rope.ROPE _ NARROW[TextEdit.GetCharProp[loc.node, loc.where, $VoiceMark], Rope.ROPE]; escapement: Vector2.VEC _ ImagerFont.Escapement[TEditFormat.GetFont[style], [set: TiogaVoicePrivate.voiceCharSet, code: letter-'\000]]; charWidth: REAL _ escapement.x; data: REF VoiceMarkerDataRep ~ NEW[VoiceMarkerDataRep _ [ letter: letter, label: label, charHeight: TiogaVoicePrivate.voiceCharHeight, charWidth: charWidth ]]; extents: ImagerFont.Extents _ [leftExtent: 0.0, rightExtent: 20.0, ascent: 5.0*data.charHeight/2.0, descent: TiogaVoicePrivate.voiceCharDescent]; RETURN [NEW[TEditFormat.CharacterArtworkRep _ [paint: VoiceMarkerPaint, extents: extents, escapement: escapement, data: data]]] }; voiceMarkerClass: TEditFormat.CharacterArtworkClass ~ NEW[TEditFormat.CharacterArtworkClassRep _ [ name: $VoiceMarker, format: VoiceMarkerFormat, data: NIL ]]; <<>> <> <<>> <> <> <> <> <youngest>> <<>> oldest: PUBLIC INT _ 5; youngest: PUBLIC INT _ 1; <> ageColors: ARRAY [1..6] OF Rope.ROPE; ReColorViewer: PUBLIC PROC [ viewer: ViewerClasses.Viewer, voiceViewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NIL, repaint: BOOLEAN _ FALSE] = { <> node: TextNode.Ref _ TiogaButtons.TextNodeRef[TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]]; nodeLength: INT _ TiogaOps.GetRope[TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]].Length; IF voiceViewerInfo = NIL THEN voiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; voiceViewerInfo.color _ viewer.column = color; IF voiceViewerInfo.ageList = NIL OR ~voiceViewerInfo.color THEN RETURN; TextEdit.PutCharProp[node, 0, $Postfix, NIL, nodeLength]; -- may not be necessary FOR l: LIST OF TiogaVoicePrivate.IntPair _ voiceViewerInfo.ageList, l.rest WHILE l # NIL DO TextEdit.PutCharProp[node, l.first.position, $Postfix, ageColors[l.first.age], IF l.rest # NIL THEN MIN[l.rest.first.position, nodeLength]-l.first.position ELSE nodeLength-l.first.position] ENDLOOP; TiogaVoicePrivate.SetVoiceViewerEditStatus[viewer]; <<**** I still haven't got right when to repaint and when not>> ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE, whatChanged: NIL] }; AgeAllViewers: PUBLIC PROC [youngestViewer: ViewerClasses.Viewer] = { <> FOR vList: TiogaVoicePrivate.VoiceViewerInfoList _ TiogaVoicePrivate.GetVoiceViewerInfoList[], vList.rest WHILE vList # NIL DO currInfo: TiogaVoicePrivate.VoiceViewerInfo _ vList.first; l: LIST OF TiogaVoicePrivate.IntPair _ currInfo.ageList; IF l # NIL THEN IF ~(l.rest = NIL AND l.first.age = oldest+1) THEN { IF (l.rest = NIL AND l.first.age = oldest) THEN { ReColorViewer[currInfo.viewer, currInfo, currInfo.viewer # youngestViewer]; l.first.age _ oldest+1 -- special treatment because this list may be the result of having cut out some newer voice in the most recent edit, in which case the colors need redrawing } ELSE { --in all other cases the ages are only allowed to be incremented to oldest WHILE l # NIL DO l.first.age _ MIN[l.first.age+1, oldest]; l _ l.rest ENDLOOP; l _ currInfo.ageList; WHILE l # NIL AND l.rest # NIL DO --the dual WHILE condition is because we can delete the next entry and it could be the last IF l.first.age = l.rest.first.age THEN l.rest _ l.rest.rest ELSE l _ l.rest ENDLOOP; ReColorViewer[currInfo.viewer, currInfo] } } ENDLOOP }; EditAges: PUBLIC PROC [ viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, unchangedHead, deleteChars, insertChars: INT] = { <> ageAtEndOfCut: INT; atHead: BOOLEAN _ TRUE; l: LIST OF TiogaVoicePrivate.IntPair _ viewerInfo.ageList; IF l = NIL THEN ERROR; WHILE l.rest # NIL AND l.rest.first.position < unchangedHead + deleteChars DO l _ l.rest ENDLOOP; ageAtEndOfCut _ l.first.age; WHILE atHead AND viewerInfo.ageList # NIL DO SELECT viewerInfo.ageList.first.position FROM >= unchangedHead+deleteChars => {viewerInfo.ageList.first.position _ viewerInfo.ageList.first.position + insertChars - deleteChars; atHead _ FALSE}; >= unchangedHead => viewerInfo.ageList _ viewerInfo.ageList.rest; ENDCASE => atHead _ FALSE; ENDLOOP; IF viewerInfo.ageList # NIL THEN { l _ viewerInfo.ageList; WHILE l # NIL AND l.rest # NIL DO --the dual WHILE condition is because we can delete the next entry and it could be the last SELECT l.rest.first.position FROM >= unchangedHead+deleteChars => {l.rest.first.position _ l.rest.first.position + insertChars - deleteChars; l _ l.rest}; >= unchangedHead => l.rest _ l.rest.rest; ENDCASE => l _ l.rest ENDLOOP }; IF viewerInfo.ageList = NIL OR viewerInfo.ageList.first.position >= unchangedHead + insertChars THEN { IF viewerInfo.ageList = NIL OR viewerInfo.ageList.first.position # unchangedHead + insertChars THEN viewerInfo.ageList _ CONS [[unchangedHead + insertChars, ageAtEndOfCut], viewerInfo.ageList]; IF insertChars # 0 THEN viewerInfo.ageList _ CONS [[unchangedHead, youngest-1], viewerInfo.ageList] } ELSE { l _ viewerInfo.ageList; WHILE l.rest # NIL AND l.rest.first.position < unchangedHead + insertChars DO l _ l.rest ENDLOOP; IF l.rest = NIL OR l.rest.first.position # unchangedHead + insertChars THEN l.rest _ CONS [[unchangedHead + insertChars, ageAtEndOfCut], l.rest]; IF insertChars # 0 THEN l.rest _ CONS [[unchangedHead, youngest-1], l.rest] }; FOR l _ viewerInfo.ageList, l.rest WHILE l # NIL DO IF l.first.age > oldest THEN l.first.age _ oldest ENDLOOP; l _ viewerInfo.ageList; WHILE l # NIL AND l.rest # NIL DO --the dual WHILE condition is because we can delete the next entry and it could be the last IF l.first.age = l.rest.first.age THEN l.rest _ l.rest.rest ELSE l _ l.rest ENDLOOP }; <> <<>> <> <> <> <<>> DictationOps: PUBLIC Menus.MenuProc = { <> TiogaVoicePrivate.ChangeMenu[NARROW[parent], dictationMenu] }; <<>> ToggleDictationMenu: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { <> TiogaVoicePrivate.ChangeMenu[viewer, dictationMenu] }; FindEnd: PROC [viewerInfo: TiogaVoicePrivate.VoiceViewerInfo] RETURNS [sample: INT] = { <> IF viewerInfo.ropeInterval.start # 0 THEN ERROR; <> IF ~viewerInfo.color THEN RETURN [viewerInfo.ropeInterval.length] ELSE { minAge: INT _ oldest+2; endOfAge: INT _ LAST[INT]; FOR l: LIST OF TiogaVoicePrivate.IntPair _ viewerInfo.ageList, l.rest WHILE l # NIL DO IF l.first.age <= minAge THEN { endOfAge _ IF l.rest=NIL THEN LAST[INT] ELSE l.rest.first.position; minAge _ l.first.age } ENDLOOP; RETURN [IF endOfAge = LAST[INT] THEN viewerInfo.ropeInterval.length ELSE endOfAge*TiogaVoicePrivate.soundRopeCharLength] } }; StillInVoiceViewerList: PROC [viewerInfo: TiogaVoicePrivate.VoiceViewerInfo] RETURNS [BOOLEAN] = { <> FOR vList: TiogaVoicePrivate.VoiceViewerInfoList _ TiogaVoicePrivate.GetVoiceViewerInfoList[], vList.rest WHILE vList # NIL DO IF vList.first = viewerInfo THEN RETURN[TRUE]; ENDLOOP; MessageWindow.Append["Voice viewer has been deleted (contents NIL): dictation operation therefore invalid", TRUE]; MessageWindow.Blink[]; RETURN[FALSE]; }; PlayFromSelection: Menus.MenuProc = { viewer: ViewerClasses.Viewer; start: TiogaOpsDefs.Location; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; node: TiogaOpsDefs.Ref; from, to: INT; [viewer: viewer, start: start] _ TiogaOps.GetSelection[]; IF viewer = NIL THEN { MessageWindow.Append["Make a selection first", TRUE]; MessageWindow.Blink[]; RETURN }; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF viewerInfo = NIL THEN { MessageWindow.Append["Make a selection in a voice viewer first", TRUE]; MessageWindow.Blink[]; RETURN }; VoiceRope.Stop[TiogaVoicePrivate.thrushHandle]; TiogaVoicePrivate.CancelPlayBack[]; TiogaVoicePrivate.StopRecording[]; IF ~StillInVoiceViewerList[viewerInfo] THEN RETURN; <<**** need to take the voice lock before performing the following!>> node _ TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]; from _ start.where; to _ FindEnd[viewerInfo]/TiogaVoicePrivate.soundRopeCharLength; IF to >= from THEN TiogaVoicePrivate.PlaySlabSection[viewer, node, from, to] ELSE { MessageWindow.Append["Make a selection before the end of the most recent edit", TRUE]; MessageWindow.Blink[] }; }; ResumeFromSelection: Menus.MenuProc = { viewer: ViewerClasses.Viewer; start: TiogaOpsDefs.Location; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; node: TiogaOpsDefs.Ref; replaceRopeInterval: VoiceRope.VoiceRopeInterval; startDelete, endDelete: INT; [viewer: viewer, start: start] _ TiogaOps.GetSelection[]; IF viewer = NIL THEN { MessageWindow.Append["Make a selection first", TRUE]; MessageWindow.Blink[]; RETURN }; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF viewerInfo = NIL THEN { MessageWindow.Append["Make a selection in a voice viewer first", TRUE]; MessageWindow.Blink[]; RETURN }; VoiceRope.Stop[TiogaVoicePrivate.thrushHandle]; TiogaVoicePrivate.CancelPlayBack[]; TiogaVoicePrivate.StopRecording[]; IF ~StillInVoiceViewerList[viewerInfo] THEN RETURN; IF ~TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN RETURN; node _ TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]; startDelete _ start.where*TiogaVoicePrivate.soundRopeCharLength; endDelete _ FindEnd[viewerInfo]; IF startDelete>endDelete THEN { MessageWindow.Append["Make a selection before the end of the most recent edit", TRUE]; MessageWindow.Blink[]; viewerInfo.editInProgress _ FALSE; RETURN }; replaceRopeInterval _ [ropeID: viewerInfo.ropeInterval.ropeID, start: startDelete, length: endDelete - startDelete]; TiogaVoicePrivate.RecordInPlaceOfSelection[NEW[TiogaVoicePrivate.SelectionRec _ [viewer: viewer, voiceViewerInfo: viewerInfo, ropeInterval: replaceRopeInterval, displayNode: node]]] }; ResumeFromEnd: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent, ViewerClasses.Viewer]; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; node: TiogaOpsDefs.Ref; replaceRopeInterval: VoiceRope.VoiceRopeInterval; VoiceRope.Stop[TiogaVoicePrivate.thrushHandle]; TiogaVoicePrivate.CancelPlayBack[]; TiogaVoicePrivate.StopRecording[]; IF ~StillInVoiceViewerList[viewerInfo] THEN RETURN; IF ~TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN RETURN; node _ TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]; replaceRopeInterval _ [ropeID: viewerInfo.ropeInterval.ropeID, start: FindEnd[viewerInfo], length: 0]; TiogaVoicePrivate.RecordInPlaceOfSelection[NEW[TiogaVoicePrivate.SelectionRec _ [viewer: viewer, voiceViewerInfo: viewerInfo, ropeInterval: replaceRopeInterval, displayNode: node]]] }; <<>> <> <<>> criticalSilenceLength: INT _ Jukebox.bytesPerChirp/2; <<>> SetCriticalSilenceLength: Commander.CommandProc = { argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GOTO Quit}]; IF argv.argc # 2 THEN { msg _ "Usage: SetCriticalSilenceLength lengthInMilliseconds"; GOTO Quit }; criticalSilenceLength _ Convert.IntFromRope[argv[1]]*8; RETURN EXITS Quit => RETURN [$Failure, msg] }; AdjustSilences: Menus.MenuProc = { viewer: ViewerClasses.Viewer; viewerInfo: TiogaVoicePrivate.VoiceViewerInfo; selectionToRemove: TiogaVoicePrivate.Selection; startOfSilence, lengthOfSilence: INT; viewer _ NARROW [parent, ViewerClasses.Viewer]; viewerInfo _ NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo]; IF ~TiogaVoicePrivate.GetVoiceLock[viewerInfo] THEN { MessageWindow.Append["Unable to examine silences in viewer - editing operation already in progress", TRUE]; MessageWindow.Blink[]; RETURN }; TiogaOps.SaveSelA[]; DO [startsAt: startOfSilence, lasts: lengthOfSilence] _ TiogaVoicePrivate.LastSilenceInSoundList[soundList: viewerInfo.soundList, lengthGreaterThan: criticalSilenceLength]; IF lengthOfSilence = -1 THEN { viewerInfo.editInProgress _ FALSE; TiogaOps.RestoreSelA[]; RETURN }; selectionToRemove _ NEW[TiogaVoicePrivate.SelectionRec _ [viewer: viewer, voiceViewerInfo: viewerInfo, ropeInterval: [ropeID: viewerInfo.ropeInterval.ropeID, start: startOfSilence+criticalSilenceLength, length: lengthOfSilence-criticalSilenceLength], displayNode: TiogaOps.FirstChild[TiogaOps.ViewerDoc[viewer]]]]; [] _ TiogaVoicePrivate.ReplaceSelectionWithSavedInterval[selection: selectionToRemove, soundInterval: NIL, leaveSelected: FALSE]; <> <> TiogaVoicePrivate.MakeVoiceEdited[viewer]; ENDLOOP }; dictationMenu: Menus.MenuEntry; <> <<>> TEditFormat.RegisterCharacterArtwork[voiceMarkerClass]; ageColors[1] _ "0.13 0.97 0.96 textColor"; ageColors[2] _ "0.06 0.94 1.00 textColor"; ageColors[3] _ "0.99 1.00 1.00 textColor"; ageColors[4] _ "0.99 1.00 0.65 textColor"; ageColors[5] _ ageColors[6] _ "0.97 1.00 0.28 textColor"; -- last two must be the same <> <<>> Menus.InsertMenuEntry[TiogaVoicePrivate.voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "AdjustSilences", AdjustSilences], 1]; Menus.InsertMenuEntry[TiogaVoicePrivate.voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "ResumeFromEnd", ResumeFromEnd], 1]; Menus.InsertMenuEntry[TiogaVoicePrivate.voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "ResumeFromSelection", ResumeFromSelection], 1]; Menus.InsertMenuEntry[TiogaVoicePrivate.voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "PlayFromSelection", PlayFromSelection], 1]; dictationMenu _ Menus.GetLine[TiogaVoicePrivate.voiceViewerMenu, 1]; Menus.SetLine[TiogaVoicePrivate.voiceViewerMenu, 1, NIL]; Menus.ChangeNumberOfLines[TiogaVoicePrivate.voiceViewerMenu, 1]; Commander.Register["SetCriticalSilenceLength", SetCriticalSilenceLength, "SetCriticalSilenceLength lengthInMilliseconds - when the AdjustSilences button is bugged for any voice viewer, all silences of more than this length will be reduced to this length"]; }. <> < mark the current playback spot; otherwise mark the selection as before.>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>>