DIRECTORY
Atom USING [GetPName, MakeAtom],
Buttons USING [Button],
Commander USING [CommandProc, Register],
Containers USING [ChildXBound, ChildYBound, Container, Create],
Convert USING [RopeFromInt],
Icons USING [IconFlavor, NewIconFromFile],
IO USING [PutFR, int, rope],
MBQueue USING [QueueClientAction],
Menus USING [MenuProc, MouseButton],
MessageWindow USING [Append, Blink, Clear],
PopUpButtons USING [Class, Instantiate, MakeClass, nullChoice, PopUpButtonProc],
Rope USING [ROPE, Concat, FromChar, FromRefText, Substr],
Rules USING [Create, Rule],
TEditDocument USING [SelectionId],
TEditInput USING [CommandProc, editState, InterpInput, RecordRef, Register, UnRegister],
TEditInputBackdoor USING [editObject, pdelState, pDel, sel, selState, mouseColor, editMessage],
TEditInputOps USING [Delete, Copy, CopyFormat, CopyLooks, SetStyleName, Transpose, TransposeLooks, TransposeFormat],
TEditSelection USING [pSel, sSel, MakeSelection],
TextEdit USING [ChangeLooks, Size],
TextLooks USING [allLooks, RopeToLooks],
TextNode USING [Ref],
TiogaButtons USING [TextNodeRef],
TiogaOps USING [CallWithLocks, CancelSelection, CommandProc, FirstChild, GetRope, GetSelection, Location, RegisterCommand, Root, SelectPoint, ViewerDoc],
TiogaOpsDefs USING [Location, Ref, SelectionGrain, WhichSelection],
TiogaVoicePrivate USING [ AddCharMark, AddVoiceProc, AdjustSilences, AgeAllViewers, BackSpace, BackWord, CopyTextMarkerToTextViewer, CopyTextViewerToTextMarker, DebugRope, DeleteCharMarks, DeleteSourceMarkers, DisplayCharMarks, EditAges, EditCharMarks, EditTextMarks, ExtractCharMarks, ExtractSoundList, ExtractTextMarks, oldest, PlayBackMenuProc, PlayFromSelection, ReColorViewer, RedrawTextMarkers, RedrawViewer, RegisterViewer, RemoveViewerReferences, ReplaceSoundList, ResumeFromEnd, ResumeFromSelection, RopeFromTextList, SaveRopeAtSourceMarkers, Selection, SelectionRec, Sound, SoundChars, SoundInterval, SoundIntervalRec, SoundList, SoundListFromIntervalSpecs, soundRopeCharDivisions, soundRopeCharLength, soundRopeResolution, StoreVoiceAtSelection, TextInput, TextListFromRope, thrushHandle, TiogaInterpret, TrackDeletes, voiceButtonQueue, VoiceViewerInfo, VoiceViewerInfoList, VoiceViewerInfoRec, youngest ],
TIPUser USING [InstantiateNewTIPTable, TIPTable],
ViewerClasses USING [Viewer, NotifyProc, ViewerClass],
ViewerEvents USING [EventRegistration, RegisterEventProc, UnRegisterEventProc, EventProc],
ViewerLocks USING [CallUnderWriteLock],
ViewerOps USING [AddProp, CreateViewer, DestroyViewer, FetchProp, FetchViewerClass, OpenIcon, PaintViewer],
ViewerSpecs USING [menuBarHeight, menuHeight],
ViewerTools USING [TiogaContents, TiogaContentsRec, SetTiogaContents],
VoiceRope USING [ DescribeRope, IntervalSpecs, Length, Replace, VoiceRopeInterval ],
WindowManager USING [colorDisplayOn]
;
Voice Viewers
addClass, playClass, stopClass, dictationOpsClass, markClass, storeClass, saveClass, adjustSilencesClass: PopUpButtons.Class;
VoiceViewerInfo: TYPE = TiogaVoicePrivate.VoiceViewerInfo;
VoiceViewerInfoList: TYPE = TiogaVoicePrivate.VoiceViewerInfoList;
VoiceViewerInfoRec: TYPE = TiogaVoicePrivate.VoiceViewerInfoRec;
voiceViewerInfoList: VoiceViewerInfoList ← NIL;
nextVoiceViewerNumber:
INT ← 1;
voiceTIPTable: TIPUser.TIPTable ← NIL;
soundViewerIcon: Icons.IconFlavor ← unInit;
dirtySoundViewerIcon: Icons.IconFlavor ← unInit;
BuildVoiceViewer:
PUBLIC
PROC [
voiceID: Rope.ROPE, textInVoice: Rope.ROPE, youngVoice: BOOLEAN] RETURNS [viewer: ViewerClasses.Viewer, viewerInfo: VoiceViewerInfo, viewerNumber: INT] = {
Returns a ref to the $Text viewer that holds the voice capillary, not the outer container.
container: ViewerClasses.Viewer;
curIndent: INTEGER ← 0;
buttonSpacing: INTEGER = 9;
button: Buttons.Button;
rule: Rules.Rule;
viewerInfo ← InsertVoiceViewerInfo[];
container ← Containers.Create[
info: [name: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[viewerInfo.viewerNumber]],
column: IF WindowManager.colorDisplayOn THEN color ELSE left,
inhibitDestroy: FALSE,
scrollable: FALSE,
icon: soundViewerIcon,
paint: FALSE];
wy: -1 makes these menus match Tioga menus in placement
button ← addClass.Instantiate[viewerInfo: [ name: "Add", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
curIndent ← button.wx + button.ww + buttonSpacing;
button ← playClass.Instantiate[viewerInfo: [ name: "Play", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
curIndent ← button.wx + button.ww + buttonSpacing;
button ← stopClass.Instantiate[viewerInfo: [ name: "STOP", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
curIndent ← button.wx + button.ww + buttonSpacing;
button ← dictationOpsClass.Instantiate[viewerInfo: [ name: "DictationOps", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
curIndent ← button.wx + button.ww + buttonSpacing;
button ← markClass.Instantiate[viewerInfo: [ name: "Mark", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
curIndent ← button.wx + button.ww + buttonSpacing;
button ← storeClass.Instantiate[viewerInfo: [ name: "Store", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
curIndent ← button.wx + button.ww + buttonSpacing;
button ← saveClass.Instantiate[viewerInfo: [ name: "Save", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
curIndent ← button.wx + button.ww + buttonSpacing;
button ← adjustSilencesClass.Instantiate[viewerInfo: [ name: "AdjustSilences", wx: curIndent, wy: -1, border: FALSE, parent: container], paint: FALSE];
rule ← Rules.Create[
info: [
parent: container,
wx: 0,
wy: ViewerSpecs.menuHeight,
wh: ViewerSpecs.menuBarHeight
],
paint: FALSE];
Containers.ChildXBound[container, rule];
viewer ← ViewerOps.CreateViewer[
-- text viewer is first child of parent container
flavor: $Text,
info: [parent: container,
wx: 0,
wy: ViewerSpecs.menuHeight+ViewerSpecs.menuBarHeight,
border: FALSE,
paint: FALSE];
Containers.ChildXBound[container, viewer];
Containers.ChildYBound[container, viewer];
viewer.tipTable ← voiceTIPTable;
Do this after the call to CreateViewer because the init proc for $Text class viewers sets the tip table explicitly, so the tip table input param is effectively ignored. Viewer locking??
viewerNumber ← viewerInfo.viewerNumber;
ViewerOps.AddProp[viewer, $voiceViewerInfo, viewerInfo];
viewerInfo.viewer ← viewer;
viewerInfo.color ← viewer.column = color;
viewerInfo.destroyEvent ← ViewerEvents.RegisterEventProc[
proc: DestroyViewerEvent, event: destroy, filter: container, before: TRUE];
viewerInfo.changeColumnEvent ← ViewerEvents.RegisterEventProc[
proc: ChangeColumnEvent, event: changeColumn, filter: container, before: FALSE];
SetViewerContents[viewer, viewerInfo, voiceID, textInVoice, youngVoice];
MakeVoiceNotEdited[viewer];
ViewerOps.OpenIcon[icon: container];
PaintViewer failed to paint $Text subviewer, so create iconic & then open
CreateVoiceViewerPopUpButtons:
PROC ~ {
addClass ← PopUpButtons.MakeClass[[
proc: AddProc,
headMenu: FALSE,
choices:
LIST[
[$AddAtSelection, "Start recording at voice selection"],
[$DictationViewer, "Open new viewer & start recording"]
],
doc: "Add: recording operations"
]];
playClass ← PopUpButtons.MakeClass[[
proc: PlayProc,
headMenu: FALSE,
choices:
LIST[
[$PlayViewer, "Play back entire viewer"],
[$PlaySelection, "Play back contents of voice selection"]
],
doc: "Play: playback operations"
]];
stopClass ← PopUpButtons.MakeClass[[
proc: StopProc,
headMenu: FALSE,
choices:
LIST[
[$STOP, "Stop recording or playback"] -- stop all | stop one?
],
doc: "STOP: stop recording or playback"
]];
dictationOpsClass ← PopUpButtons.MakeClass[[
proc: DictationOpsProc,
headMenu: FALSE,
choices:
LIST[
[$PlayFromSelection, "Play back from voice selection to end of fresh voice"],
[$ReplaceFromSelection, "Delete from voice selection to end & start recording"],
[$AddAtEnd, "Start recording at end of fresh voice"]
],
doc: "DictationOps: specialized dictation machine operations"
]];
markClass ← PopUpButtons.MakeClass[[
proc: MarkProc,
headMenu: FALSE,
choices:
LIST[
[$MarkAtPlayback, "Mark current playback location"],
[$MarkAtSelection, "Mark beginning of voice selection"],
PopUpButtons.nullChoice,
PopUpButtons.nullChoice,
PopUpButtons.nullChoice,
PopUpButtons.nullChoice,
[$DeleteViewerMarks, "Delete all marks in viewer"],
[$DeleteSelectionMarks, "Delete all marks in voice selection"]
],
doc: "Mark: operations on temporary markers"
]];
storeClass ← PopUpButtons.MakeClass[[
proc: StoreProc,
guarded: TRUE,
headMenu: FALSE,
choices:
LIST[
[$Store, "Store viewer at Tioga selection"]
],
doc: "Store: store viewer at Tioga selection"
]];
saveClass ← PopUpButtons.MakeClass[[
proc: SaveProc,
headMenu: FALSE,
choices:
LIST[
[$Save, "Save viewer at all matching open voice balloons"]
],
doc: "Save: save viewer at all matching open voice balloons"
]];
adjustSilencesClass ← PopUpButtons.MakeClass[[
proc: AdjustSilencesProc,
headMenu: FALSE,
choices:
LIST[
[$AdjustSilences, "Reduce all silences in viewer to set length"]
],
doc: "AdjustSilences: reduce lengths of silences"
]];
};
QProcRef: TYPE ~ REF QProcBody;
QProcBody:
TYPE ~
RECORD [
proc: Menus.MenuProc,
parent: ViewerClasses.Viewer,
clientData: REF ANY ← NIL,
mouseButton: Menus.MouseButton ← $red,
shift, control: BOOL ← FALSE
];
QProc:
PROC [ref:
REF] ~ {
q: QProcRef ← NARROW [ref];
q.proc[q.parent, q.clientData, q.mouseButton, q.shift, q.control];
};
AddProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc,
NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret,
clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]],
parent: viewer.parent.child]]
];
};
PlayProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
menuData: QProcRef ← NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret,
clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]],
parent: viewer.parent.child]];
SELECT key
FROM
$PlayViewer => menuData.mouseButton ← $blue;
$PlaySelection => menuData.mouseButton ← $red;
ENDCASE;
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, menuData];
};
StopProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc,
NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVSTOP, parent: viewer.parent.child]]
];
};
SaveProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc,
NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVSaveVoice, parent: viewer.parent.child]]
];
};
StoreProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc,
NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVStoreVoice, parent: viewer.parent.child]]
];
};
MarkProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
menuData: QProcRef ← NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret,
clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]],
parent: viewer.parent.child]];
SELECT key
FROM
$MarkAtSelection => menuData.mouseButton ← $red;
$MarkAtPlayback => menuData.mouseButton ← $yellow;
$DeleteSelectionMarks => menuData.mouseButton ← $red;
$DeleteViewerMarks => menuData.mouseButton ← $blue;
ENDCASE;
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc, menuData];
};
DictationOpsProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc,
NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret,
clientData: Atom.MakeAtom[Rope.Concat["TV", Atom.GetPName[NARROW[key]]]],
parent: viewer.parent.child]]
];
};
AdjustSilencesProc: PopUpButtons.PopUpButtonProc ~ {
PROC [view: View, instanceData, classData, key: REF ANY]
viewer: ViewerClasses.Viewer ← NARROW[view];
MBQueue.QueueClientAction[TiogaVoicePrivate.voiceButtonQueue, QProc,
NEW [QProcBody ← [proc: TiogaVoicePrivate.TiogaInterpret, clientData: $TVAdjustSilences, parent: viewer.parent.child]]
];
};
RegisterButtonBehaviors:
PROC ~ {
TiogaVoice voice viewer menu commands. These are registered with Tioga so that other clients can use Tioga's extension mechanism to call their own procedures before or after these. Clients can find the button parameters in the viewer property $ButtonParams as type TiogaVoicePrivate.ButtonParams. We register $TVfoo (where $foo is used by the PopUpButtons mechanism) because PopUpButtons shows its atoms to the user, and we don't want collisions with Tioga atoms.
TiogaOps.RegisterCommand[name: $TVAddAtEnd, proc: TiogaVoicePrivate.ResumeFromEnd];
TiogaOps.RegisterCommand[name: $TVAddAtSelection, proc: TiogaVoicePrivate.AddVoiceProc];
TiogaOps.RegisterCommand[name: $TVAdjustSilences, proc: TiogaVoicePrivate.AdjustSilences];
TiogaOps.RegisterCommand[name: $TVDeleteSelectionMarks, proc: TiogaVoicePrivate.DeleteCharMarks];
TiogaOps.RegisterCommand[name: $TVDeleteViewerMarks, proc: TiogaVoicePrivate.DeleteCharMarks];
TiogaOps.RegisterCommand[name: $TVDictationViewer, proc: TiogaVoicePrivate.DictationMachine]; -- done already by VoiceInTextImpl
TiogaOps.RegisterCommand[name: $TVMarkAtPlayback, proc: TiogaVoicePrivate.AddCharMark];
TiogaOps.RegisterCommand[name: $TVMarkAtSelection, proc: TiogaVoicePrivate.AddCharMark];
TiogaOps.RegisterCommand[name: $TVPlayFromSelection, proc: TiogaVoicePrivate.PlayFromSelection];
TiogaOps.RegisterCommand[name: $TVPlaySelection, proc: TiogaVoicePrivate.PlayBackMenuProc];
TiogaOps.RegisterCommand[name: $TVPlayViewer, proc: TiogaVoicePrivate.PlayBackMenuProc];
TiogaOps.RegisterCommand[name: $TVReplaceFromSelection, proc: TiogaVoicePrivate.ResumeFromSelection];
TiogaOps.RegisterCommand[name: $TVSaveVoice, proc: SaveVoice];
TiogaOps.RegisterCommand[name: $TVSTOP, proc: TiogaVoicePrivate.CancelProc]; -- done already by VoiceInTextImpl
TiogaOps.RegisterCommand[name: $TVStoreVoice, proc: StoreVoice];
};
SetVoiceRopeInfo:
PROC [viewerInfo: VoiceViewerInfo, voiceID: Rope.
ROPE, textInVoice: Rope.
ROPE, youngVoice:
BOOLEAN]
RETURNS [soundRope: Rope.
ROPE] ~ {
intervals: VoiceRope.IntervalSpecs;
viewerInfo.soundList ← NIL;
IF voiceID #
NIL
THEN {
** ought to check here that the rope actually exists **
viewerInfo.ropeInterval ← [voiceID, 0, VoiceRope.Length[TiogaVoicePrivate.thrushHandle, NEW [VoiceRope.VoiceRopeInterval ← [voiceID, 0, 0]]]];
intervals ← VoiceRope.DescribeRope[TiogaVoicePrivate.thrushHandle, NEW [VoiceRope.VoiceRopeInterval ← viewerInfo.ropeInterval]];
viewerInfo.soundList ← TiogaVoicePrivate.SoundListFromIntervalSpecs[intervals, viewerInfo.ropeInterval.length];
[soundRope: soundRope, remnant: viewerInfo.remnant] ← TiogaVoicePrivate.SoundChars[viewerInfo];
viewerInfo.textMarkList ← TiogaVoicePrivate.TextListFromRope[textInVoice];
viewerInfo.ageList ← CONS [[0, IF youngVoice THEN TiogaVoicePrivate.youngest-1 ELSE TiogaVoicePrivate.oldest], NIL];
}
ELSE {
soundRope ← " ";
viewerInfo.ropeInterval ← [NIL, 0, 0];
};
};
SetViewerContents:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, viewerInfo: VoiceViewerInfo, voiceID: Rope.
ROPE, textInVoice: Rope.
ROPE, youngVoice:
BOOLEAN] = {
SetViewerDoIt:
PROC ~ {
soundRope: Rope.ROPE ← SetVoiceRopeInfo[viewerInfo, voiceID, textInVoice, youngVoice];
[] ← SetTiogaViewerParams[viewer, soundRope];
IF voiceID #
NIL
AND viewerInfo.ageList.first.age = TiogaVoicePrivate.youngest-1
THEN
TiogaVoicePrivate.AgeAllViewers[viewer]
ELSE TiogaVoicePrivate.ReColorViewer[viewer, viewerInfo];
**** the Plass-bug: shouldn't be necessary and he says he'll have a look at it
IF viewerInfo.textMarkList # NIL THEN ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: TRUE, whatChanged: NIL];
viewerInfo.editInProgress ← (voiceID = NIL) -- can do this because the viewer is created with editInProgress = TRUE, so nobody else can have grabbed the lock
};
ViewerLocks.CallUnderWriteLock[SetViewerDoIt, viewer];
SetTiogaViewerParams:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, newcontents: Rope.
ROPE]
RETURNS [voiceNode: TiogaOpsDefs.Ref] = {
Set the contents, style, and looks for a voice viewer.
SetTiogaViewerDoIt:
PROC ~ {
rootNode: TiogaOpsDefs.Ref;
viewercontents: ViewerTools.TiogaContents ←
NEW[ViewerTools.TiogaContentsRec ← [contents: newcontents, formatting: NIL]];
ViewerTools.SetTiogaContents[viewer: viewer, contents: viewercontents, paint: FALSE];
rootNode ← TiogaOps.ViewerDoc[viewer];
TEditInputOps.SetStyleName["voiceProfile", TiogaButtons.TextNodeRef[rootNode]];
voiceNode ← TiogaOps.FirstChild[rootNode];
TiogaVoicePrivate.RedrawTextMarkers[viewer, voiceNode];
};
ViewerLocks.CallUnderWriteLock[SetTiogaViewerDoIt, viewer];
};
SetVoiceLooks:
PUBLIC
PROC [node: TiogaOpsDefs.Ref, start, len:
INT, looks: Rope.
ROPE] ~ {
Used to set looks to provide voice playback feedback.
DoIt:
PROC [root: TiogaOpsDefs.Ref] ~ {
Note: all voice viewers contain only one node.
TextEdit.ChangeLooks[root: TiogaButtons.TextNodeRef[root], text: TiogaButtons.TextNodeRef[node], remove: TextLooks.allLooks, add: TextLooks.RopeToLooks[looks], start: start, len: len, event: NIL]
};
TiogaOps.CallWithLocks[DoIt, TiogaOps.Root[node]];
};
MakeVoiceEdited:
PUBLIC PROC [viewer: ViewerClasses.Viewer] = {
This procedure marks a voice viewer as edited: viewer, viewerInfo, and icon.
viewerInfo: VoiceViewerInfo;
container: ViewerClasses.Viewer;
DoIt:
PROC = {
viewerInfo.edited ← container.newVersion ← TRUE;
container.icon ← dirtySoundViewerIcon;
ViewerOps.PaintViewer[container, caption, FALSE];
};
IF viewer=NIL THEN RETURN;
viewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
IF viewerInfo=NIL THEN RETURN; -- not really a voice viewer
container ← viewer.parent;
IF NOT viewerInfo.edited THEN ViewerLocks.CallUnderWriteLock[DoIt, container];
MakeVoiceNotEdited:
PROC [viewer: ViewerClasses.Viewer] = {
This procedure will make a voice viewer think itself unedited, when we wish to assert that there is no voice rope held only in it - use carefully!!
viewerInfo: VoiceViewerInfo;
container: ViewerClasses.Viewer;
DoIt:
PROC = {
viewerInfo.edited ← container.newVersion ← container.newFile ← FALSE;
container.icon ← soundViewerIcon;
ViewerOps.PaintViewer[container, caption, FALSE];
};
IF viewer=NIL THEN RETURN;
viewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
IF viewerInfo=NIL THEN RETURN; -- not really a voice viewer
container ← viewer.parent;
ViewerLocks.CallUnderWriteLock[DoIt, container];
SetVoiceViewerEditStatus:
PUBLIC
PROC [viewer: ViewerClasses.Viewer] = {
This is similar to the above, except that it ensures that the viewer has the same idea about whether the voice is edited as the VoiceInfo record. For use by functions [such as moving a visual cue along a voice viewer during playback] that need to edit the viewer but not the voice and then want to reflect this fact in the viewer's status
viewerInfo: VoiceViewerInfo;
container: ViewerClasses.Viewer;
DoIt:
PROC = {
IF viewerInfo.edited # container.newVersion
THEN {
container.newVersion ← viewerInfo.edited;
IF ~viewerInfo.edited
THEN {
container.newFile ← FALSE;
container.icon ← soundViewerIcon;
}
ELSE container.icon ← dirtySoundViewerIcon;
ViewerOps.PaintViewer[container, caption, FALSE];
}
};
IF viewer=NIL THEN RETURN;
viewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
IF viewerInfo=NIL THEN RETURN; -- not really a voice viewer
container ← viewer.parent;
ViewerLocks.CallUnderWriteLock[DoIt, container]
ViewerContainsVoice:
PUBLIC
PROC [v: ViewerClasses.Viewer]
RETURNS [
BOOL] = {
IF v#
NIL
AND ViewerOps.FetchProp[v, $voiceViewerInfo]#
NIL
THEN {
voiceNode: TiogaOpsDefs.Ref← TiogaOps.FirstChild[TiogaOps.ViewerDoc[v]];
IF TextEdit.Size[TiogaButtons.TextNodeRef[voiceNode]] # 0 THEN RETURN[TRUE];
};
RETURN[FALSE];
SameViewerDoc:
PUBLIC
PROC [v1, v2: ViewerClasses.Viewer]
RETURNS [
BOOL] = {
Check for same or split text viewer (can't split voice viewers).
FOR v: ViewerClasses.Viewer ← v1, v1.link
DO
IF v=v2 THEN RETURN[TRUE];
IF v=v1 THEN EXIT;
ENDLOOP;
RETURN[FALSE];
SetParentViewer:
PUBLIC
PROC [viewerInfo: VoiceViewerInfo,
parentViewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location] = {
Set text parent viewer for voice viewer.
viewerInfo.parentViewer ← parentViewer;
viewerInfo.positionInParent ← positionInParent;
TiogaVoicePrivate.RegisterViewer[parentViewer];
FindAttachedVoiceViewers:
PUBLIC
PROC [viewer: ViewerClasses.Viewer]
RETURNS [viewerInfoList: VoiceViewerInfoList] = {
Returns a list of voiceViewerInfo describing all voice viewers whose parent is the text viewer.
FOR vList: VoiceViewerInfoList ← voiceViewerInfoList, vList.rest
WHILE vList #
NIL
DO
IF vList.first.parentViewer = viewer
THEN
viewerInfoList ← CONS[vList.first, viewerInfoList]
ENDLOOP;
RemoveParentPointersTo:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, findSplits:
BOOLEAN] = {
Removes the parentViewer pointers for all voice viewers pointing to this text viewer. If findSplits is TRUE, will replace with a pointer to a split of the text viewer if one exists.
FOR vList: VoiceViewerInfoList ← voiceViewerInfoList, vList.rest
WHILE vList #
NIL
DO
info: VoiceViewerInfo ← vList.first;
IF info.parentViewer = viewer
THEN {
splitLink: ViewerClasses.Viewer ← info.parentViewer.link;
IF findSplits
AND splitLink #
NIL
THEN {
The text viewer being destroyed has been split, so this isn't really the last one.
info.parentViewer ← splitLink;
TiogaVoicePrivate.RegisterViewer[splitLink];
}
ELSE {
info.parentViewer ← NIL;
MessageWindow.Append[IO.PutFR["Detaching parent link for voice viewer %d\n", IO.int[info.viewerNumber]], TRUE] -- was DebugRope; may be too verbose
};
}
ENDLOOP
};
RemoveParentViewer:
PUBLIC
PROC [viewerInfo: VoiceViewerInfo] = {
viewerInfo.parentViewer ← NIL;
GetVoiceViewerInfo:
PUBLIC
PROC [n:
INT]
RETURNS [viewerInfo: VoiceViewerInfo] = {
FOR vList: VoiceViewerInfoList ← voiceViewerInfoList, vList.rest
WHILE vList#
NIL
DO
IF vList.first.viewerNumber=n THEN RETURN[vList.first];
ENDLOOP;
GetVoiceViewerInfoList:
PUBLIC PROC
RETURNS [vList: VoiceViewerInfoList] = {
RETURN[voiceViewerInfoList];
};
InsertVoiceViewerInfo:
PROC
RETURNS [newViewerInfo: VoiceViewerInfo] = {
newViewerInfo ← NEW[VoiceViewerInfoRec];
newViewerInfo.viewerNumber ← nextVoiceViewerNumber;
nextVoiceViewerNumber ← nextVoiceViewerNumber + 1;
voiceViewerInfoList ← CONS[newViewerInfo, voiceViewerInfoList];
StoreVoice: TiogaOps.CommandProc = {
PROC [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOL ← TRUE, quit: BOOL ← FALSE]; viewer prop $ButtonParams = TiogaVoicePrivate.ButtonParams
**** locking against edits in this and the next one?
viewerInfo: VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
oldParentViewer: ViewerClasses.Viewer ← viewerInfo.parentViewer;
IF
NOT ViewerContainsVoice[viewer]
THEN {
MessageWindow.Append["Voice viewer is empty", TRUE];
MessageWindow.Blink[];
RETURN
};
IF TiogaVoicePrivate.StoreVoiceAtSelection[viewerInfo]
THEN {
Now need to remove all old links from doc to this viewer ($voiceWindow charprops). Don't want to remove the one we just put in, but we wanted to be sure it would succeed before removing the old ones. Do this by excepting the location we just added (could also check for the new rope value as the associated $voice property).
MakeVoiceNotEdited[viewer];
IF oldParentViewer #
NIL
THEN
TiogaVoicePrivate.DeleteSourceMarkers[oldParentViewer, viewerInfo.viewerNumber, viewerInfo.positionInParent];
};
SaveVoice: TiogaOps.CommandProc = {
PROC [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOL ← TRUE, quit: BOOL ← FALSE]; viewer prop $ButtonParams = TiogaVoicePrivate.ButtonParams
viewerInfo: VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
IF ~viewerInfo.edited
THEN {
MessageWindow.Append["Voice viewer is not edited", TRUE];
RETURN
};
IF
NOT ViewerContainsVoice[viewer]
THEN {
MessageWindow.Append["Voice viewer is empty", TRUE];
MessageWindow.Blink[];
RETURN
};
IF viewerInfo.parentViewer =
NIL
THEN {
MessageWindow.Append["Voice viewer has no parent text viewer", TRUE];
MessageWindow.Blink[];
RETURN
};
IF ~TiogaVoicePrivate.SaveRopeAtSourceMarkers[viewerInfo.parentViewer, viewerInfo.viewerNumber, viewerInfo.ropeInterval.ropeID, TiogaVoicePrivate.RopeFromTextList[viewerInfo.textMarkList]]
THEN {
MessageWindow.Append["Cannot find source marker for voice window", TRUE];
MessageWindow.Blink[];
RETURN
};
MakeVoiceNotEdited[viewer]
};
RemoveVoiceViewerInfo:
PROC [viewerInfo: VoiceViewerInfo] = {
IF voiceViewerInfoList #
NIL
THEN {
IF voiceViewerInfoList.first = viewerInfo
THEN
{voiceViewerInfoList ← voiceViewerInfoList.rest; RETURN};
FOR vList: VoiceViewerInfoList ← voiceViewerInfoList, vList.rest
WHILE vList#
NIL
DO
IF vList.rest.first = viewerInfo THEN {vList.rest ← vList.rest.rest; RETURN}
ENDLOOP;
};
ERROR; -- not in list!
DestroyViewerEvent: ViewerEvents.EventProc = {
RETURN [~DestroyVoiceInfo[viewer.child]] };
DestroyVoiceInfo:
PROC [
viewer: ViewerClasses.Viewer] RETURNS [didIt: BOOLEAN ← TRUE] = {
viewerInfo: VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
IF ~TiogaVoicePrivate.RemoveViewerReferences[viewer]
THEN {
MessageWindow.Append["Wait until voice cue disappears before destroying viewer", TRUE];
MessageWindow.Blink[];
RETURN [FALSE]
};
IF viewerInfo.editInProgress
THEN {
MessageWindow.Append["Finish edit in progress before destroying viewer", TRUE];
MessageWindow.Blink[];
RETURN [FALSE]
};
ViewerEvents.UnRegisterEventProc[viewerInfo.destroyEvent, destroy];
ViewerEvents.UnRegisterEventProc[viewerInfo.changeColumnEvent, changeColumn];
IF viewerInfo.parentViewer =
NIL
THEN
TiogaVoicePrivate.DebugRope[IO.PutFR["Voice viewer %d had no parent\n", IO.int[viewerInfo.viewerNumber]]]
ELSE
TiogaVoicePrivate.DeleteSourceMarkers[viewerInfo.parentViewer, viewerInfo.viewerNumber, [NIL, -1]];
RemoveVoiceViewerInfo[viewerInfo];
};
RedrawViewerMenuProc: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← parent;
viewerInfo: VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
IF GetVoiceLock[viewerInfo]
THEN
{ trueContents: Rope.
ROPE ← TiogaVoicePrivate.SoundChars[viewerInfo].soundRope;
[] ← TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered];
SetVoiceViewerEditStatus[viewer];
viewerInfo.editInProgress ← FALSE
}
ChangeColumnEvent: ViewerEvents.EventProc = {
viewerInfo: VoiceViewerInfo;
viewer ← viewer.child; -- want to get at the $Text viewer inside the outer container
viewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], VoiceViewerInfo];
IF (viewer.column = color) # viewerInfo.color THEN
{ viewerInfo.color ← viewer.column = color;
IF GetVoiceLock[viewerInfo]
THEN
{ trueContents: Rope.
ROPE ← TiogaVoicePrivate.SoundChars[viewerInfo].soundRope;
[] ← TiogaVoicePrivate.RedrawViewer[viewer, trueContents, 0, 0, 0, viewerInfo.remnant, FALSE, unAltered];
SetVoiceViewerEditStatus[viewer];
viewerInfo.editInProgress ← FALSE
}
ELSE
{ MessageWindow.Append["Cannot repaint viewer during edit: close & open viewer when changing between color and monochrome if the wrong state persists",
TRUE];
MessageWindow.Blink[]
}
}
GetVoiceLock:
PUBLIC
ENTRY
PROC [
info: VoiceViewerInfo] RETURNS [gotIt: BOOLEAN] = {
IF info = NIL THEN RETURN[FALSE];
gotIt ← NOT info.editInProgress;
IF info.editInProgress
THEN MessageWindow.Append["Try again when the viewer's contents are stable", TRUE]
ELSE info.editInProgress ← TRUE;
};
Voice Edit Intercept
Routines to pick up edit events affecting voice viewers and pass control from normal Tioga ops.
This code stinks for a number of reasons
i) it only picks up a subset of the events which could affect a voice viewer
ii) it redefines the 'notify proc' for the $Text class!! - it does exactly what the ousted notify proc would do in the case of non-voice viewers, by calling a TEditInput procedure. At the cost of another procedure call it could be more clean my saving the ousted procedure and calling that, but since I've had to import the Impl anyhow . . . .
The way things should be is that either an equivalent package to tioga is produced specially for voice editing windows or TEditInputOps is rewritten to call TiogaVoice code for viewers managed by TiogaVoice [detectable from the properties that those viewers have].
InterceptAllInput: ViewerClasses.NotifyProc = {
IF ViewerOps.FetchProp[self, $voiceViewerInfo] = NIL
THEN TEditInput.InterpInput[self, input] -- just what the real one does
ELSE
{ passOnList, endOfPassOnList:
LIST
OF
REF
ANY ←
NIL;
FOR currItem:
LIST
OF
REF
ANY ← input, currItem.rest
WHILE currItem #
NIL
DO
WITH currItem.first
SELECT
FROM
z: REF CHAR =>
{
IF passOnList #
NIL
THEN
{ TEditInput.InterpInput[self, passOnList];
passOnList ← NIL
};
TiogaVoicePrivate.TextInput[self, Rope.FromChar[z^]]
};
z: Rope.ROPE =>
{
IF passOnList #
NIL
THEN
{ TEditInput.InterpInput[self, passOnList];
passOnList ← NIL
};
TiogaVoicePrivate.TextInput[self, z]
};
z: REF TEXT =>
{
IF passOnList #
NIL
THEN
{ TEditInput.InterpInput[self, passOnList];
passOnList ← NIL
};
TiogaVoicePrivate.TextInput[self, Rope.FromRefText[z]]
};
ENDCASE =>
IF passOnList = NIL
THEN
{ passOnList ←
CONS[currItem.first,
NIL];
endOfPassOnList ← passOnList
}
ELSE
{ endOfPassOnList.rest ←
CONS[currItem.first,
NIL];
endOfPassOnList ← endOfPassOnList.rest
}
ENDLOOP;
IF passOnList # NIL THEN TEditInput.InterpInput[self, passOnList]
}
InterceptBackSpace: TEditInput.CommandProc = {
PROC [viewer: ViewerClasses.Viewer ← NIL]
RETURNS [recordAtom: BOOL ← TRUE, quit: BOOL ← FALSE];
IF TestPrimary[] = normal THEN RETURN;
TiogaVoicePrivate.BackSpace[viewer];
RETURN [quit: TRUE]
InterceptBackWord: TEditInput.CommandProc = {
PROC [viewer: ViewerClasses.Viewer ← NIL]
RETURNS [recordAtom: BOOL ← TRUE, quit: BOOL ← FALSE];
IF TestPrimary[] = normal THEN RETURN;
TiogaVoicePrivate.BackWord[viewer];
RETURN [quit: TRUE]
InterceptDelete: TEditInput.CommandProc = {
PROC [viewer: ViewerClasses.Viewer ← NIL]
RETURNS [recordAtom: BOOL ← TRUE, quit: BOOL ← FALSE];
IF TestPrimary[] = normal
THEN TiogaVoicePrivate.TrackDeletes[]
ELSE
{ Delete[];
RETURN [quit: TRUE]
};
InterceptEdit: TEditInput.CommandProc = {
this part of the code is rather sordid: it really should be in TEditInputImpl and for that reason it references TEditInputBackdoor variables 23 times: it gets called in front of the Tioga DoEdit procedure and returns quit=TRUE under all circumstances in order to prevent the real one ever being called
quit ← TRUE;
IF TEditSelection.pSel.viewer =
NIL
THEN {
IF TEditSelection.sSel.viewer # NIL THEN TEditSelection.MakeSelection[NIL,secondary]; -- get rid of secondary
recordAtom ← FALSE }
ELSE
IF TEditInput.editState = tolimbo
THEN {
recordAtom ← FALSE; TEditInput.RecordRef[$Delete]; CheckDelete[TRUE] }
ELSE IF TEditSelection.sSel.viewer = NIL THEN recordAtom ← FALSE
ELSE {
RecordEditObject:
PROC = {
TEditInput.RecordRef[
SELECT TEditInputBackdoor.editObject
FROM
text => $EditText,
looks => $EditLooks,
format => $EditFormat,
ENDCASE => ERROR] };
recordAtom ← TRUE;
SELECT TEditInput.editState
FROM
reset, abort => recordAtom ← FALSE;
toprimary => {
TEditInput.RecordRef[$GetSecondary];
TEditInput.RecordRef[$ToPrimary];
RecordEditObject[];
SELECT TEditInputBackdoor.editObject FROM
text => {
IF TEditSelection.pSel.pendingDelete THEN TEditInput.RecordRef[$MakePDel];
CheckCopy[primary] };
looks => CheckCopyLooks[primary];
format => CheckCopyFormat[primary];
ENDCASE => ERROR };
tosecondary => {
TEditInput.RecordRef[$GetSecondary];
TEditInput.RecordRef[$ToSecondary];
RecordEditObject[];
SELECT TEditInputBackdoor.editObject FROM
text => {
IF TEditSelection.pSel.pendingDelete THEN TEditInput.RecordRef[$MakePDel];
CheckCopy[secondary] };
looks => CheckCopyLooks[secondary];
format => CheckCopyFormat[secondary];
ENDCASE => ERROR };
toboth => {
TEditInput.RecordRef[$GetSecondary];
TEditInput.RecordRef[$ToBoth];
RecordEditObject[];
SELECT TEditInputBackdoor.editObject FROM
text => CheckTranspose[];
looks => CheckTransposeLooks[];
format => CheckTransposeFormat[];
ENDCASE => ERROR };
ENDCASE => ERROR };
TEditInput.editState ← reset; TEditInputBackdoor.editObject ← text;
TEditInputBackdoor.pdelState ← reset; TEditInputBackdoor.pDel ← FALSE; TEditInputBackdoor.selState ← reset; TEditInputBackdoor.sel ← primary;
TEditInputBackdoor.mouseColor ← red; -- put these back to normal
IF TEditInputBackdoor.editMessage # NIL THEN MessageWindow.Clear[];
TEditInputBackdoor.editMessage ← NIL };
selectionTypes:
TYPE = {bothNormal, primaryVoice, secondaryVoice, bothVoice};
TestSelections:
PROC
RETURNS [types: selectionTypes] = {
types ← IF ViewerOps.FetchProp[TiogaOps.GetSelection[primary].viewer, $voiceViewerInfo] = NIL THEN
( IF ViewerOps.FetchProp[TiogaOps.GetSelection[secondary].viewer, $voiceViewerInfo] = NIL THEN bothNormal ELSE secondaryVoice )
ELSE
( IF ViewerOps.FetchProp[TiogaOps.GetSelection[secondary].viewer, $voiceViewerInfo] = NIL THEN primaryVoice ELSE bothVoice )
primaryType:
TYPE = {normal, voice};
TestPrimary:
PROC
RETURNS [type: primaryType] = {
type ← IF ViewerOps.FetchProp[TiogaOps.GetSelection[primary].viewer, $voiceViewerInfo] = NIL THEN normal ELSE voice
CheckDelete:
PROC [saveForPaste:
BOOL ←
TRUE] = {
this and the following series of 'Check' procedures test if the proposed edit is applied to normal text: if so they first call the appropriate SourceMarkerTracking routine and then pass control to TEditInputOps [just like TEditInputImpl.DoEdit always would], otherwise they pass control to Voice Edit Ops
SELECT TestPrimary[]
FROM
normal =>
{ TiogaVoicePrivate.TrackDeletes[];
TEditInputOps.Delete[saveForPaste]
};
voice => Delete[]
ENDCASE
CheckCopy:
PROC [target: TEditDocument.SelectionId ← primary] = {
SELECT TestSelections[]
FROM
bothNormal => {
IF VoiceInSourceDocument[target] THEN TrackTextCopy[target];
TEditInputOps.Copy[target];
};
bothVoice => Copy[target];
primaryVoice =>
IF target=primary THEN TiogaVoicePrivate.CopyTextViewerToTextMarker[primary]
ELSE TiogaVoicePrivate.CopyTextMarkerToTextViewer[secondary];
secondaryVoice =>
IF target=secondary THEN TiogaVoicePrivate.CopyTextViewerToTextMarker[secondary]
ELSE TiogaVoicePrivate.CopyTextMarkerToTextViewer[primary];
ENDCASE;
VoiceInSourceDocument:
PROC [target: TEditDocument.SelectionId ← primary]
RETURNS [
BOOLEAN ← FALSE] = {
needs to be filled in -- PTZ, June 12, 1987 7:49:54 pm PDT
};
TrackTextCopy:
PROC [target: TEditDocument.SelectionId ← primary] = {
needs to be filled in -- PTZ, June 12, 1987 7:49:54 pm PDT
};
CheckCopyFormat:
PROC [target: TEditDocument.SelectionId ← primary] = {
SELECT TestSelections[]
FROM
bothNormal => TEditInputOps.CopyFormat[target];
bothVoice => Mumble["Copy format", "two voice"]
ENDCASE => Mumble["Copy format", "voice and text"]
CheckCopyLooks:
PROC [target: TEditDocument.SelectionId ← primary] = {
SELECT TestSelections[]
FROM
bothNormal => TEditInputOps.CopyLooks[target];
bothVoice => Mumble["Copy looks", "two voice"]
ENDCASE => Mumble["Copy looks", "voice and text"]
CheckTranspose:
PROC [target: TEditDocument.SelectionId ← primary] = {
SELECT TestSelections[]
FROM
bothNormal => TEditInputOps.Transpose[target];
bothVoice => Transpose[target];
ENDCASE => Mumble["Transpose", "voice and text"]
CheckTransposeFormat:
PROC [target: TEditDocument.SelectionId ← primary] = {
SELECT TestSelections[]
FROM
bothNormal => TEditInputOps.TransposeFormat[target];
bothVoice => Mumble["Transpose format", "two voice"]
ENDCASE => Mumble["Transpose format", "voice and text"]
CheckTransposeLooks:
PROC [target: TEditDocument.SelectionId ← primary] = {
SELECT TestSelections[]
FROM
bothNormal => TEditInputOps.TransposeLooks[target];
bothVoice => Mumble["Transpose looks", "two voice"]
ENDCASE => Mumble["Transpose looks", "voice and text"]
textClassDescriptor: ViewerClasses.ViewerClass ← ViewerOps.FetchViewerClass[$Text];
realNotifyProc: ViewerClasses.NotifyProc ← textClassDescriptor.notify;
HereWeGoAgain: Commander.CommandProc = {
This just whilste under development . . .
textClassDescriptor: ViewerClasses.ViewerClass ← ViewerOps.FetchViewerClass[$Text];
textClassDescriptor.notify ← realNotifyProc;
TEditInput.UnRegister[$BackSpace, InterceptBackSpace];
TEditInput.UnRegister[$BackWord, InterceptBackWord];
TEditInput.UnRegister[$Delete, InterceptDelete];
TEditInput.UnRegister[$DoEdit, InterceptEdit];
};
SoundList
Sound lists are descriptions of the silence/sound profiles of a ropeInterval and form part of the data structure behind a voice viewer.
SoundListFromIntervalSpecs:
PUBLIC
PROC [
intervalSpecs: VoiceRope.IntervalSpecs, lengthOfRopeInterval: INT] RETURNS [soundList: TiogaVoicePrivate.SoundList ← NIL] = {
lastSoundEnd: INT ← 0;
FOR l: VoiceRope.IntervalSpecs ← intervalSpecs, l.rest
WHILE l #
NIL
DO
soundList ← AppendSound[soundList, [l.first.start - lastSoundEnd, l.first.length]];
lastSoundEnd ← l.first.start + l.first.length
ENDLOOP;
IF lengthOfRopeInterval > lastSoundEnd THEN soundList ← AppendSound[soundList, [lengthOfRopeInterval-lastSoundEnd, 0]]
ExtractSoundList:
PUBLIC
PROC [
voiceViewerInfo: VoiceViewerInfo, soundInterval: TiogaVoicePrivate.SoundInterval] = {
voiceViewerInfo contains the sound list for a complete rope. Use the interval specification from soundInterval.ropeInterval to create a new sound list for that interval, placing it in soundInterval.soundList.
soughtStart: INT ← soundInterval.ropeInterval.start;
soughtEnd: INT ← soughtStart + soundInterval.ropeInterval.length;
currStart, currEnd: INT ← 0;
thisSilence, thisSound: INT;
FOR l: TiogaVoicePrivate.SoundList ← voiceViewerInfo.soundList, l.rest
WHILE l #
NIL
AND soughtEnd > currEnd
DO
currStart ← currEnd;
thisSilence ← l.first.silence;
thisSound ← l.first.sound;
currEnd ← currStart + thisSilence + thisSound;
IF soughtStart < currEnd THEN
{
IF soughtStart > currStart
THEN
{ thisSound ← thisSound -
MAX [soughtStart-currStart-thisSilence, 0];
thisSilence ← thisSilence - MIN [soughtStart-currStart, thisSilence]
};
IF soughtEnd < currEnd
THEN
{ thisSilence ← thisSilence -
MAX [currEnd-soughtEnd-thisSound, 0];
thisSound ← thisSound - MIN [ currEnd-soughtEnd, thisSound]
};
AppendSoundToSoundInterval[soundInterval, [thisSilence, thisSound]]
}
ENDLOOP
ReplaceSoundList:
PUBLIC
PROC [
voiceViewerInfo: VoiceViewerInfo, cutStart: INT, cutLength: INT, replacement: TiogaVoicePrivate.SoundList] = {
voiceViewerInfo contains the sound list for a complete rope. Cut out the specified portion and at the cutting point insert the replacement soundlist.
tail, workingPtr: TiogaVoicePrivate.SoundList;
endCurrSound, startCurrSound: INT ← 0;
IF cutStart = 0
THEN {
tail ← voiceViewerInfo.soundList;
voiceViewerInfo.soundList ← NIL
}
ELSE {
FOR workingPtr ← voiceViewerInfo.soundList, workingPtr.rest DO
No tests for shooting off the end in this routine. If we get an error then our caller has gone badly wrong and there is no obvious way to recover.
endCurrSound ← endCurrSound + workingPtr.first.silence + workingPtr.first.sound;
IF endCurrSound >= cutStart THEN EXIT
ENDLOOP;
tail ← workingPtr.rest;
workingPtr.rest ← NIL;
IF endCurrSound > cutStart THEN -- Have to 'move sound from workingPtr to tail'
{ silenceToMove:
INT ←
MAX [endCurrSound-cutStart-workingPtr.first.sound, 0];
soundToMove: INT ← MIN [endCurrSound-cutStart, workingPtr.first.sound];
tail ← CONS[[silenceToMove, soundToMove], tail];
workingPtr.first.silence ← workingPtr.first.silence - silenceToMove;
workingPtr.first.sound ← workingPtr.first.sound - soundToMove
}
With the head of the old rope in voiceViewerInfo.soundList, delete the unwanted sound from the tail.
endCurrSound ← 0;
DO
startCurrSound ← endCurrSound;
IF startCurrSound = cutLength THEN EXIT;
endCurrSound ← endCurrSound + tail.first.silence + tail.first.sound;
IF endCurrSound > cutLength THEN EXIT;
tail ← tail.rest;
ENDLOOP;
IF startCurrSound < cutLength THEN -- delete some of the sound in the head of the list
{ silenceToSave:
INT ←
MAX [endCurrSound-cutLength-tail.first.sound, 0];
soundToSave: INT ← MIN [endCurrSound-cutLength, tail.first.sound];
tail.first.silence ← silenceToSave;
tail.first.sound ← soundToSave
};
We can now join up our three lists.
IF replacement = NIL THEN replacement ← tail ELSE AppendSoundListToSoundList[replacement, tail];
IF voiceViewerInfo.soundList = NIL THEN voiceViewerInfo.soundList ← replacement ELSE AppendSoundListToSoundList[voiceViewerInfo.soundList, replacement]
SoundChars:
PUBLIC
PROC [
viewerInfo: VoiceViewerInfo, skipChars: INT ← 0] RETURNS [soundRope: Rope.ROPE ← NIL, remnant: INT] = {
Produce the graphical representation of the SoundList within a VoiceViewerInfo record. The caller already has the first skipChars of the representation in his hand, so omit these from the returned rope. If the sound list does not exactly fill a whole number of characters, return as remnant the number of samples left over. Any marker characters indicated in the VoiceViewerInfo are inserted.
soundList: TiogaVoicePrivate.SoundList ← viewerInfo.soundList;
partiallyFilled: BOOLEAN ← FALSE;
partsFilled: [0..TiogaVoicePrivate.soundRopeCharDivisions] ← 0;
soundMajority: ARRAY [0..TiogaVoicePrivate.soundRopeCharDivisions) OF BOOLEAN;
samplesAccounted, soundComponents: [0..TiogaVoicePrivate.soundRopeResolution] ← 0;
skipping: BOOLEAN ← skipChars>0;
skipSamples: INT ← skipChars*TiogaVoicePrivate.soundRopeCharLength;
AddChars:
PROC [length:
INT, sound:
BOOLEAN] = {
AddToPartChar:
PROC [availableSamples:
INT, sound:
BOOLEAN]
RETURNS [usedSamples: [0..TiogaVoicePrivate.soundRopeResolution]] = {
partiallyFilled ← TRUE;
usedSamples ← MIN[availableSamples, TiogaVoicePrivate.soundRopeResolution-samplesAccounted];
samplesAccounted ← samplesAccounted + usedSamples;
IF sound THEN soundComponents ← soundComponents + usedSamples;
IF samplesAccounted = TiogaVoicePrivate.soundRopeResolution
THEN
{ soundMajority[partsFilled] ← soundComponents >= TiogaVoicePrivate.soundRopeResolution/2;
samplesAccounted ← 0;
soundComponents ← 0;
partsFilled ← partsFilled + 1;
IF partsFilled = TiogaVoicePrivate.soundRopeCharDivisions
THEN
{ binaryOfChar: INT ← 0;
partsFilled ← 0;
partiallyFilled ← FALSE;
FOR i:
INT
IN [0..TiogaVoicePrivate.soundRopeCharDivisions)
DO
binaryOfChar ← binaryOfChar*2 + (IF soundMajority[i] THEN 1 ELSE 0)
ENDLOOP;
soundRope ← soundRope.Concat[Rope.FromChar[IF binaryOfChar # 0 THEN ('A + binaryOfChar) ELSE '!]] -- 'A is the base type of the sound font [i.e. all segments clear] but the 'all clear' character is actually ! so as to make 'word selections' work in normal tioga terms
}
}
};
WHILE partiallyFilled
AND length>0
DO
taken: INT ← AddToPartChar[length, sound];
length ← length - taken
ENDLOOP;
WHILE length >= TiogaVoicePrivate.soundRopeCharLength
DO
soundRope ← soundRope.Concat[Rope.FromChar[IF sound THEN ('A + 15) ELSE '!]];
15 represents 'all bits are sound' - i.e. 2**soundRopeCharDivisions-1
length ← length - TiogaVoicePrivate.soundRopeCharLength
ENDLOOP;
WHILE length>0
DO
taken: INT ← AddToPartChar[length, sound];
length ← length - taken
ENDLOOP
};
FOR l: TiogaVoicePrivate.SoundList ← soundList, l.rest
WHILE l #
NIL
DO
silence: INT ← l.first.silence;
sound: INT ← l.first.sound;
IF skipping
THEN
{
IF silence >= skipSamples
THEN
{ silence ← silence - skipSamples;
skipping ← FALSE;
AddChars[length: silence, sound: FALSE];
AddChars[length: sound, sound: TRUE]
}
ELSE
{ skipSamples ← skipSamples - silence;
IF sound >= skipSamples
THEN
{ sound ← sound - skipSamples;
skipping ← FALSE;
AddChars[length: sound, sound: TRUE]
}
ELSE skipSamples ← skipSamples - sound;
}
}
ELSE
{ AddChars[length: silence, sound:
FALSE];
AddChars[length: sound, sound: TRUE]
}
ENDLOOP;
remnant ← IF partiallyFilled THEN partsFilled*TiogaVoicePrivate.soundRopeResolution+samplesAccounted ELSE 0;
soundRope ← TiogaVoicePrivate.DisplayCharMarks[soundRope, viewerInfo.charMarkList, skipChars]
LastSilenceInSoundList:
PUBLIC
PROC [
soundList: TiogaVoicePrivate.SoundList, lengthGreaterThan: INT ← 0] RETURNS [startsAt: INT, lasts: INT ← -1] = {
All INTs are values in samples, as for the rest of this interface. lasts=-1 indicates that there are no silence intervals in the list of lengthGreaterThan that specified.
l: TiogaVoicePrivate.SoundList ← soundList;
currSilenceStart: INT ← 0;
WHILE l #
NIL
DO
IF l.first.silence > lengthGreaterThan THEN
{ startsAt ← currSilenceStart;
lasts ← l.first.silence
};
currSilenceStart ← currSilenceStart + l.first.silence + l.first.sound;
l ← l.rest
ENDLOOP
AppendSound:
PROC [
list: TiogaVoicePrivate.SoundList, entry: TiogaVoicePrivate.Sound] RETURNS [TiogaVoicePrivate.SoundList] = {
oneElementList: TiogaVoicePrivate.SoundList ← CONS[entry, NIL];
hangOffPoint: TiogaVoicePrivate.SoundList ← list;
IF list = NIL THEN RETURN [oneElementList];
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
hangOffPoint.rest ← oneElementList;
RETURN [list]
AppendSoundToSoundInterval:
PROC [
soundInterval: TiogaVoicePrivate.SoundInterval, entry: TiogaVoicePrivate.Sound] = {
oneElementList: TiogaVoicePrivate.SoundList ← CONS[entry, NIL];
hangOffPoint: TiogaVoicePrivate.SoundList ← soundInterval.soundList;
IF hangOffPoint = NIL THEN {soundInterval.soundList ← oneElementList; RETURN};
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
hangOffPoint.rest ← oneElementList
AppendSoundListToSoundList:
PROC [
head, tail: TiogaVoicePrivate.SoundList] = {
Head is assumed non-nil.
hangOffPoint: TiogaVoicePrivate.SoundList ← head;
IF tail = NIL THEN RETURN;
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
IF hangOffPoint.first.sound = 0 OR tail.first.silence = 0 THEN -- these two can be amalgamated
{ hangOffPoint.first.sound ← hangOffPoint.first.sound + tail.first.sound;
hangOffPoint.first.silence ← hangOffPoint.first.silence + tail.first.silence;
tail ← tail.rest
};
hangOffPoint.rest ← tail
}.