DIRECTORY
Commander USING [CommandProc, Register],
Convert USING [RopeFromInt],
Icons USING [IconFlavor, NewIconFromFile],
IO USING [PutFR, int, rope],
MBQueue USING [CreateMenuEntry],
Menus USING [Menu, AppendMenuEntry, CreateEntry, MenuProc, CreateMenu],
MessageWindow USING [Append, Blink, Clear],
Rope USING [ROPE, Concat, FromChar, FromRefText, Substr],
TEditDocument USING [SelectionId],
TEditInput USING [CommandProc, Register, UnRegister, editState, RecordRef],
TEditInputImpl USING [editObject, pdelState, pDel, sel, selState, mouseColor, editMessage, InterpInput],
TEditInputOps USING [Delete, Copy, CopyFormat, CopyLooks, SetStyleName, Transpose, TransposeLooks, TransposeFormat],
TEditSelection USING [pSel, sSel, MakeSelection],
TextEdit USING [ChangeLooks, Size],
TextLooks USING [allLooks, RopeToLooks ],
TiogaButtons USING [TextNodeRef],
TiogaOps USING [CallWithLocks, CancelSelection, FirstChild, GetRope, GetSelection, Location, SelectPoint, ViewerDoc],
TiogaOpsDefs USING [Location, Ref, SelectionGrain, WhichSelection],
TiogaVoicePrivate USING [ AddCharMark, AddVoiceProc, AgeAllViewers, BackSpace, BackWord, CancelProc, DebugRope, DeleteCharMarks, DeleteSourceMarker, DictationMachine, DictationOps, DisplayCharMarks, EditAges, EditCharMarks, EditTextMarks, ExtractCharMarks, ExtractSoundList, ExtractTextMarks, oldest, PlayBackMenuProc, ReColorViewer, RedrawTextMarkers, RedrawViewer, RegisterViewer, RemoveViewerReferences, ReplaceSoundList, RopeFromTextList, SaveRopeAtSourceMarker, Selection, SelectionRec, Sound, SoundChars, SoundList, SoundInterval, SoundIntervalRec, SoundListFromIntervalSpecs, soundRopeCharDivisions, soundRopeCharLength, soundRopeResolution, StoreVoiceAtSelection, TextInput, TextListFromRope, thrushHandle, TrackDeletes, voiceButtonQueue, VoiceViewerInfo, 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, PaintViewer, SetMenu],
ViewerTools USING [TiogaContents, TiogaContentsRec, SetTiogaContents],
VoiceRope USING [ DescribeRope, IntervalSpecs, Length, Replace, VoiceRopeInterval ],
WindowManager USING [colorDisplayOn]
;
Voice Viewers
voiceViewerMenu:
PUBLIC Menus.Menu;
CreateVoiceViewerMenu:
PROC = {
voiceViewerMenu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Add", TiogaVoicePrivate.AddVoiceProc]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Play", TiogaVoicePrivate.PlayBackMenuProc]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "STOP", TiogaVoicePrivate.CancelProc]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Save", SaveVoice]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Store", StoreVoice]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Redraw", RedrawViewerMenuProc]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "Mark", TiogaVoicePrivate.AddCharMark]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "DeleteMarks", TiogaVoicePrivate.DeleteCharMarks]];
Menus.AppendMenuEntry[voiceViewerMenu, MBQueue.CreateMenuEntry[TiogaVoicePrivate.voiceButtonQueue, "DictationMachine", TiogaVoicePrivate.DictationMachine]];
Menus.AppendMenuEntry[voiceViewerMenu, Menus.CreateEntry["DictationOps", TiogaVoicePrivate.DictationOps]];
voiceViewerInfoList:
PUBLIC TiogaVoicePrivate.VoiceViewerInfo ←
NIL;
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: TiogaVoicePrivate.VoiceViewerInfo, viewerNumber: INT] = {
viewerInfo ← InsertVoiceViewerInfo[];
viewer ← ViewerOps.CreateViewer[
flavor: $Text,
info: [name: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[viewerInfo.viewerNumber]],
column: IF WindowManager.colorDisplayOn THEN color ELSE left,
paint: TRUE];
viewer.inhibitDestroy ← FALSE;
viewer.icon ← soundViewerIcon;
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;
ViewerOps.SetMenu[viewer, voiceViewerMenu];
viewerInfo.destroyEvent ← ViewerEvents.RegisterEventProc[
proc: DestroyViewerEvent, event: destroy, filter: viewer, before: TRUE];
viewerInfo.changeColumnEvent ← ViewerEvents.RegisterEventProc[
proc: ChangeColumnEvent, event: changeColumn, filter: viewer, before: FALSE];
SetViewerContents[viewer, viewerInfo, voiceID, textInVoice, youngVoice];
MakeNotEdited[viewer];
SetVoiceRopeInfo:
PROC [viewerInfo: TiogaVoicePrivate.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: TiogaVoicePrivate.VoiceViewerInfo, voiceID: Rope.
ROPE, textInVoice: Rope.
ROPE, youngVoice:
BOOLEAN] = {
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
SetTiogaViewerParams:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, newcontents: Rope.
ROPE]
RETURNS [voiceNode: TiogaOpsDefs.Ref] = {
rootNode: TiogaOpsDefs.Ref;
viewercontents: ViewerTools.TiogaContents ←
NEW[ViewerTools.TiogaContentsRec ← [contents: newcontents, formatting: NIL]];
ViewerTools.SetTiogaContents[viewer, viewercontents];
rootNode ← TiogaOps.ViewerDoc[viewer];
TEditInputOps.SetStyleName["voiceProfile", TiogaButtons.TextNodeRef[rootNode]];
voiceNode ← TiogaOps.FirstChild[rootNode];
SetVoiceLooks[viewer, voiceNode, 0, LAST[INT], "v"];
TiogaVoicePrivate.RedrawTextMarkers[viewer, voiceNode];
SetVoiceLooks:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, node: TiogaOpsDefs.Ref, start, len:
INT, looks: Rope.
ROPE] ~ {
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.ViewerDoc[viewer]];
};
MakeNotEdited:
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!!
DoIt:
PROC = {
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo];
viewerInfo.edited ← viewer.newVersion ← viewer.newFile ← FALSE;
viewer.icon ← soundViewerIcon;
ViewerOps.PaintViewer[viewer, caption, FALSE];
};
ViewerLocks.CallUnderWriteLock[DoIt, viewer]
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] which need to edit the viewer but not the voice and then want to reflect this fact in the viewer's status
DoIt:
PROC = {
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo];
IF viewerInfo.edited # viewer.newVersion THEN
{ viewer.newVersion ← viewerInfo.edited;
IF ~viewerInfo.edited
THEN {
viewer.newFile ← FALSE;
viewer.icon ← soundViewerIcon;
}
ELSE viewer.icon ← dirtySoundViewerIcon;
ViewerOps.PaintViewer[viewer, caption, FALSE];
}
};
ViewerLocks.CallUnderWriteLock[DoIt, viewer]
SetParentViewer:
PUBLIC
PROC [
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo, parentViewer: ViewerClasses.Viewer, positionInParent: TiogaOpsDefs.Location] = {
viewerInfo.parentViewer ← parentViewer;
viewerInfo.positionInParent ← positionInParent;
TiogaVoicePrivate.RegisterViewer[parentViewer]
RemoveParentViewer:
PUBLIC
PROC [viewerNumber:
INT] = {
FOR info: TiogaVoicePrivate.VoiceViewerInfo ← voiceViewerInfoList, info.nextInfoRec
WHILE info #
NIL
DO
IF info.viewerNumber = viewerNumber THEN {info.parentViewer ← NIL; RETURN}
ENDLOOP
InsertVoiceViewerInfo:
PROC
RETURNS [
newViewerInfo: TiogaVoicePrivate.VoiceViewerInfo] = {
restOfList: TiogaVoicePrivate.VoiceViewerInfo ← voiceViewerInfoList;
newViewerInfo ← NEW[TiogaVoicePrivate.VoiceViewerInfoRec];
IF voiceViewerInfoList = NIL
THEN
{ newViewerInfo.viewerNumber ← 1;
voiceViewerInfoList ← newViewerInfo;
RETURN
};
IF voiceViewerInfoList.viewerNumber > 1 THEN
{ newViewerInfo.viewerNumber ← 1;
newViewerInfo.nextInfoRec ← voiceViewerInfoList;
voiceViewerInfoList ← newViewerInfo;
RETURN
};
DO
IF restOfList.nextInfoRec = NIL
OR restOfList.nextInfoRec.viewerNumber > restOfList.viewerNumber + 1
THEN
{ newViewerInfo.viewerNumber ← restOfList.viewerNumber + 1;
newViewerInfo.nextInfoRec ← restOfList.nextInfoRec;
restOfList.nextInfoRec ← newViewerInfo;
RETURN
};
restOfList ← restOfList.nextInfoRec
ENDLOOP
StoreVoice: Menus.MenuProc = {
**** locking against edits in this and the next one?
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[NARROW[parent], $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo];
oldParentViewer: ViewerClasses.Viewer ← viewerInfo.parentViewer;
oldPosition: TiogaOpsDefs.Location ← viewerInfo.positionInParent;
IF TiogaVoicePrivate.StoreVoiceAtSelection[viewerInfo]
THEN
{ MakeNotEdited[
NARROW[parent]];
IF oldParentViewer # NIL THEN TiogaVoicePrivate.DeleteSourceMarker[oldParentViewer, oldPosition, viewerInfo.viewerNumber]
}
SaveVoice: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo];
IF ~viewerInfo.edited THEN
{ MessageWindow.Append["voice viewer is not edited",
TRUE];
RETURN
};
IF viewerInfo.parentViewer = NIL THEN
{ MessageWindow.Append["voice viewer has no parent text viewer",
TRUE];
MessageWindow.Blink[];
RETURN
};
IF ~TiogaVoicePrivate.SaveRopeAtSourceMarker[viewerInfo.parentViewer, viewerInfo.positionInParent, viewerInfo.viewerNumber, viewerInfo.ropeInterval.ropeID, TiogaVoicePrivate.RopeFromTextList[viewerInfo.textMarkList]] THEN
{
MessageWindow.Append["unable to find source marker for voice window: voice not saved", TRUE];
MessageWindow.Blink[];
RETURN
};
MakeNotEdited[viewer]
};
RemoveVoiceViewerInfo:
PROC [
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo] = {
IF voiceViewerInfoList = NIL THEN ERROR;
IF voiceViewerInfoList = viewerInfo
THEN voiceViewerInfoList ← viewerInfo.nextInfoRec
ELSE
{ ptrToCurrInfo: TiogaVoicePrivate.VoiceViewerInfo ← voiceViewerInfoList;
currInfo: TiogaVoicePrivate.VoiceViewerInfo ← voiceViewerInfoList.nextInfoRec;
DO
IF currInfo = NIL THEN ERROR;
IF currInfo = viewerInfo
THEN
{ ptrToCurrInfo.nextInfoRec ← viewerInfo.nextInfoRec;
RETURN
};
ptrToCurrInfo ← currInfo;
currInfo ← currInfo.nextInfoRec
ENDLOOP
}
DestroyViewerEvent: ViewerEvents.EventProc = {
RETURN [~DestroyVoiceInfo[viewer]] };
DestroyVoiceInfo:
PROC [
viewer: ViewerClasses.Viewer] RETURNS [didIt: BOOLEAN ← TRUE] = {
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo];
IF ~TiogaVoicePrivate.RemoveViewerReferences[viewer] THEN
{ MessageWindow.Append["Wait until voice cue disappears before destroying viewer",
TRUE];
MessageWindow.Blink[];
RETURN [FALSE]
};
should check also whether viewer is undergoing editing
IF viewerInfo.parentViewer = NIL
THEN TiogaVoicePrivate.DebugRope[IO.PutFR["Voice viewer %d had no parent\n", IO.int[viewerInfo.viewerNumber]]]
ELSE TiogaVoicePrivate.DeleteSourceMarker[viewerInfo.parentViewer, viewerInfo.positionInParent, viewerInfo.viewerNumber];
RemoveVoiceViewerInfo[viewerInfo];
ViewerEvents.UnRegisterEventProc[viewerInfo.destroyEvent, destroy];
ViewerEvents.UnRegisterEventProc[viewerInfo.changeColumnEvent, changeColumn];
};
RedrawViewerMenuProc: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
viewerInfo: TiogaVoicePrivate.VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.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: TiogaVoicePrivate.VoiceViewerInfo ← NARROW[ViewerOps.FetchProp[viewer, $voiceViewerInfo], TiogaVoicePrivate.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: click REDRAW to change window between color and monchrome if the wrong state persists",
TRUE];
MessageWindow.Blink[]
}
}
GetVoiceLock:
PUBLIC
ENTRY
PROC [
info: TiogaVoicePrivate.VoiceViewerInfo] RETURNS [gotIt: BOOLEAN] = {
gotIt ← ~ 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 SHARES an Impl !!!!!!! - TEditInputImpl to be precise. This occurs only in InterceptEdit, for reasons explained above that procedure
iii) 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 TEditInputImpl 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 TEditInputImpl.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
{ TEditInputImpl.InterpInput[self, passOnList];
passOnList ← NIL
};
TiogaVoicePrivate.TextInput[self, Rope.FromChar[z^]]
};
z: Rope.ROPE =>
{
IF passOnList #
NIL
THEN
{ TEditInputImpl.InterpInput[self, passOnList];
passOnList ← NIL
};
TiogaVoicePrivate.TextInput[self, z]
};
z: REF TEXT =>
{
IF passOnList #
NIL
THEN
{ TEditInputImpl.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 TEditInputImpl.InterpInput[self, passOnList]
}
InterceptBackSpace: TEditInput.CommandProc = {
IF TestPrimary[] = normal THEN RETURN;
TiogaVoicePrivate.BackSpace[viewer];
RETURN [quit: TRUE]
InterceptBackWord: TEditInput.CommandProc = {
IF TestPrimary[] = normal THEN RETURN;
TiogaVoicePrivate.BackWord[viewer];
RETURN [quit: TRUE]
InterceptDelete: TEditInput.CommandProc = {
IF TestPrimary[] = normal
THEN TiogaVoicePrivate.TrackDeletes[]
ELSE
{ Delete[];
RETURN [quit: TRUE]
};
InterceptEdit: TEditInput.CommandProc = {
this part of the code is extremely sordid: it really needs to be in TEditInputImpl and for that reason it references private variables from TEditInputImpl 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 TEditInputImpl.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 TEditInputImpl.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 TEditInputImpl.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 TEditInputImpl.editObject FROM
text => CheckTranspose[];
looks => CheckTransposeLooks[];
format => CheckTransposeFormat[];
ENDCASE => ERROR };
ENDCASE => ERROR };
TEditInput.editState ← reset; TEditInputImpl.editObject ← text;
TEditInputImpl.pdelState ← reset; TEditInputImpl.pDel ← FALSE; TEditInputImpl.selState ← reset; TEditInputImpl.sel ← primary;
TEditInputImpl.mouseColor ← red; -- put these back to normal
IF TEditInputImpl.editMessage # NIL THEN MessageWindow.Clear[];
TEditInputImpl.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];
ENDCASE => Mumble["Copy", "voice and text"]
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];
};
Voice Edit Ops
Routines to edit voice viewers, plus some routines to intercept operations deemed inapplicable for voice or voice/text viewer combinations.
BadVoiceViewerDisplay:
ERROR [viewer: ViewerClasses.Viewer];
Mumble:
PUBLIC
PROC [opAttempted: Rope.
ROPE, viewerTypes: Rope.
ROPE] = {
MessageWindow.Append[IO.PutFR["%g is not a valid operation between %g viewers", IO.rope[opAttempted], IO.rope[viewerTypes]], TRUE];
MessageWindow.Blink[]
DescribeSelection:
PUBLIC
PROC [
which: TiogaOpsDefs.WhichSelection, forceDelete: BOOLEAN, returnSoundInterval: BOOLEAN, viewerAlreadyLocked: ViewerClasses.Viewer ← NIL] RETURNS [failed: BOOLEAN ← FALSE, selection: TiogaVoicePrivate.Selection, soundInterval: TiogaVoicePrivate.SoundInterval ← NIL] = {
this routine will characterise the voice corresponding to a selection: if the selection is pendingDelete [or if forceDelete] then the selection.ropeInterval will have non-zero length; if returnSoundInterval then the sound interval corresponding to the whole selection will be characterised - to be saved and inserted somewhere else
selections can only be described if the voiceLock can be taken out on them: this routine returns failed if it cannot get the lock; otherwise it returns with the lock still held. However the caller may pass a viewer in viewerAlreadyLocked to say that he already has that one locked and if this
selection is in the same viewer then we may proceed
selStart, selEnd: TiogaOps.Location;
caretBefore, pendingDelete: BOOL;
level: TiogaOpsDefs.SelectionGrain;
selection ← NEW[TiogaVoicePrivate.SelectionRec];
[viewer: selection.viewer, start: selStart, end: selEnd, level: level, caretBefore: caretBefore, pendingDelete: pendingDelete] ← TiogaOps.GetSelection[which];
selection.voiceViewerInfo ← NARROW[ViewerOps.FetchProp[selection.viewer, $voiceViewerInfo], TiogaVoicePrivate.VoiceViewerInfo];
IF ~( viewerAlreadyLocked = selection.viewer OR GetVoiceLock[selection.voiceViewerInfo] ) THEN {failed ← TRUE; RETURN};
IF selStart.node # selEnd.node THEN ERROR BadVoiceViewerDisplay[selection.viewer];
selection.displayNode ← selStart.node;
work out the VoiceRopeInterval corresponding to the whole selection
it is here that policy about trimming the sound selection e.g. to silence/sound boundaries could be implemented
selection.ropeInterval ← [selection.voiceViewerInfo.ropeInterval.ropeID, selStart.where*TiogaVoicePrivate.soundRopeCharLength, (selEnd.where+(IF level=point THEN 0 ELSE 1))*TiogaVoicePrivate.soundRopeCharLength -selStart.where*TiogaVoicePrivate.soundRopeCharLength];
IF selection.ropeInterval.start + selection.ropeInterval.length > selection.voiceViewerInfo.ropeInterval.length THEN ERROR BadVoiceViewerDisplay[selection.viewer];
IF returnSoundInterval THEN
{ soundInterval ←
NEW [TiogaVoicePrivate.SoundIntervalRec ← [ropeInterval: selection.ropeInterval]];
TiogaVoicePrivate.ExtractSoundList[selection.voiceViewerInfo, soundInterval];
TiogaVoicePrivate.ExtractCharMarks[selection.voiceViewerInfo, soundInterval];
TiogaVoicePrivate.ExtractTextMarks[selection.voiceViewerInfo, soundInterval] -- the width/displayChars fields in this will not necessarily be valid at the end of the list
};
IF ~(pendingDelete OR forceDelete)
THEN
{
IF ~caretBefore THEN selection.ropeInterval.start ← selection.ropeInterval.start + selection.ropeInterval.length;
selection.ropeInterval.length ← 0
}
ReplaceSelectionWithSavedInterval:
PUBLIC
PROC [
selection: TiogaVoicePrivate.Selection, soundInterval: TiogaVoicePrivate.SoundInterval, leaveSelected: BOOLEAN] RETURNS [viewerDeleted: BOOLEAN, insertChars, positionAfterInsert: INT] = {
this routine actually does all the editing work for voice: it is passed a selection specification and a sound specification: if the selection is of non-zero length it is deleted; then if the sound is non-nil it is inserted
this routine (i) works out what the updated voice viewer sound list and contents will be (ii) calls TiogaVoicePrivate to redraw the viewer [done there since the viewer may be somewhere in the queue of sounds to be played back] (iii) interacts with the voice rope data base to update the rope specification corresponding to the viewer
it may turn out that there is nothing left in the viewer at the end of this action, in which case the viewer will simply be deleted. However this is not done until after TiogaVoicePrivate. RedrawViewer has been called since a viewer cannot be destroyed whilst the playback caret is in it: after a call to RedrawViewer the playback caret cannot be in it!!
there is no way to stop this destruction in this implementation: in a Copy/Transpose both selections could be in the same viewer but if so the result of the first call to this procedure cannot be an empty viewer
newViewerContents: Rope.ROPE;
displayCutPoint, deleteChars: INT;
IF selection.ropeInterval.length = 0 AND soundInterval = NIL THEN RETURN;
TiogaVoicePrivate.ReplaceSoundList[voiceViewerInfo: selection.voiceViewerInfo, cutStart: selection.ropeInterval.start, cutLength: selection.ropeInterval.length, replacement: IF soundInterval = NIL THEN NIL ELSE soundInterval.soundList];
displayCutPoint ← selection.ropeInterval.start/TiogaVoicePrivate.soundRopeCharLength;
deleteChars ← selection.ropeInterval.length/TiogaVoicePrivate.soundRopeCharLength;
insertChars ← IF soundInterval = NIL THEN 0 ELSE soundInterval.ropeInterval.length/TiogaVoicePrivate.soundRopeCharLength;
those three need only be [and are only] approximate
positionAfterInsert ← displayCutPoint + insertChars + 1;
TiogaVoicePrivate.EditCharMarks[selection.voiceViewerInfo, displayCutPoint, deleteChars, insertChars, soundInterval];
TiogaVoicePrivate.EditTextMarks[selection.voiceViewerInfo, displayCutPoint, deleteChars, insertChars, soundInterval];
TiogaVoicePrivate.EditAges[selection.voiceViewerInfo, displayCutPoint, deleteChars, insertChars];
[newViewerContents, selection.voiceViewerInfo.remnant] ← TiogaVoicePrivate.SoundChars[selection.voiceViewerInfo, displayCutPoint];
newViewerContents ← Rope.Concat[(TiogaOps.GetRope[selection.displayNode]).Substr[0, displayCutPoint], newViewerContents];
done for efficiency - no need to recalculate the first characters again
selection.displayNode ← TiogaVoicePrivate.RedrawViewer[selection.viewer, newViewerContents, displayCutPoint, deleteChars, insertChars, selection.voiceViewerInfo.remnant, TRUE, IF leaveSelected THEN primaryOnInsert ELSE deSelected];
redrawing isdone in TiogaVoicePrivate for sync with any playback cues
the last but one parameter governs whether this edit will cause all previous edits to age [only if insertChars # 0]. It is simply set TRUE here: if there were any use for such a facility a parameter could be passed by caller of ReplaceSelectionWithSavedInterval to pass on here
viewerDeleted ← TextEdit.Size[TiogaButtons.TextNodeRef[selection.displayNode]] = 0;
IF viewerDeleted
THEN
{ MessageWindow.Append["voice viewer now completely empty - destroying",
TRUE];
ViewerOps.DestroyViewer[selection.viewer]
this will cause the event proc which clears down the voiceViewerInfo etc. to be called
}
ELSE
{
selection.voiceViewerInfo.ropeInterval ← VoiceRope.Replace[handle: TiogaVoicePrivate.thrushHandle, vr: NEW [VoiceRope.VoiceRopeInterval ← selection.voiceViewerInfo.ropeInterval], start: selection.ropeInterval.start, len: selection.ropeInterval.length, with: IF soundInterval = NIL THEN NIL ELSE NEW [VoiceRope.VoiceRopeInterval ← soundInterval.ropeInterval]]^
}
Delete:
PUBLIC
PROC = {
delete the primary selection
charAfterDelete: INT;
viewerDeleted: BOOLEAN;
selectionLocked: BOOLEAN;
selection: TiogaVoicePrivate.Selection;
[selection: selection, failed: selectionLocked] ← DescribeSelection[which: primary, forceDelete: TRUE, returnSoundInterval: FALSE];
IF selectionLocked THEN RETURN;
selection.voiceViewerInfo.edited ← TRUE;
[positionAfterInsert: charAfterDelete, viewerDeleted: viewerDeleted] ← ReplaceSelectionWithSavedInterval[selection: selection, soundInterval: NIL, leaveSelected: TRUE];
IF ~viewerDeleted THEN
{
TiogaOps.SelectPoint[viewer: selection.viewer, caret: [selection.displayNode, MIN [charAfterDelete, TextEdit.Size[TiogaButtons.TextNodeRef[selection.displayNode]]-1]], which: primary];
selection.voiceViewerInfo.editInProgress ← FALSE
}
Copy:
PUBLIC
PROC [target: TEditDocument.SelectionId ← primary] = {
sourceSelection, destSelection: TiogaVoicePrivate.Selection;
sourceInterval: TiogaVoicePrivate.SoundInterval;
selectionLocked: BOOLEAN;
destMovesForward: INT ← 0;
if source selection is pending delete and ahead of the destination selection in the same viewer, then after the first action of deleting source the destination will be nearer the start of the sound
[selection: sourceSelection, soundInterval: sourceInterval, failed: selectionLocked] ← DescribeSelection[which: (IF target=primary THEN secondary ELSE primary), forceDelete: FALSE, returnSoundInterval: TRUE];
IF ~selectionLocked THEN
{
[selection: destSelection, failed: selectionLocked] ← DescribeSelection[which: (IF target=primary THEN primary ELSE secondary), forceDelete: FALSE, returnSoundInterval: FALSE, viewerAlreadyLocked: sourceSelection.viewer];
IF selectionLocked THEN sourceSelection.voiceViewerInfo.editInProgress ← FALSE
};
TiogaOps.CancelSelection[primary];
TiogaOps.CancelSelection[secondary];
we drop the selections here because after a ReplaceSelectionWithSavedInterval call a viewer may not exist! If a selection above failed we should also drop the selections here as well - if selections are left around after an aborted tioga editing operation unpleasant thing tend to happen
IF selectionLocked THEN RETURN;
destSelection.voiceViewerInfo.edited ← TRUE;
IF sourceSelection.ropeInterval.length # 0
if true then source is pending delete: this means two edit operations
THEN
{ sourceSelection.voiceViewerInfo.edited ←
TRUE;
IF sourceSelection.viewer = destSelection.viewer -- special case of same viewer
THEN
{
IF (sourceSelection.ropeInterval.start< destSelection.ropeInterval.start+destSelection.ropeInterval.length) = (destSelection.ropeInterval.start< sourceSelection.ropeInterval.start+sourceSelection.ropeInterval.length)
that is the test for overlapping selections: if source is pending delete the copy operation then reduces to "delete the region(s) lying in the destination but not in the source selection" - done here as a special case
done by substituting first and second regions for source and destination selections
and setting soundInterval to NIL: note that either of these regions may now have zero length
THEN
{ firstStart:
INT ← destSelection.ropeInterval.start;
firstLength: INT ← MAX [sourceSelection.ropeInterval.start -destSelection.ropeInterval.start, 0];
secondStart: INT ← sourceSelection.ropeInterval.start + sourceSelection.ropeInterval.length;
secondLength: INT ← MAX [(destSelection.ropeInterval.start+destSelection.ropeInterval.length) - (sourceSelection.ropeInterval.start+sourceSelection.ropeInterval.length), 0];
sourceSelection.ropeInterval.start ← firstStart;
sourceSelection.ropeInterval.length ← firstLength;
destSelection.ropeInterval.start ← secondStart;
destSelection.ropeInterval.length ← secondLength;
sourceInterval ← NIL
};
IF sourceSelection.ropeInterval.start<destSelection.ropeInterval.start THEN destMovesForward ← sourceSelection.ropeInterval.length
};
IF ~ReplaceSelectionWithSavedInterval[sourceSelection, NIL, FALSE].viewerDeleted THEN
{ IF sourceSelection.viewer # destSelection.viewer THEN sourceSelection.voiceViewerInfo.editInProgress ← FALSE }
}
ELSE
{ IF sourceSelection.viewer # destSelection.viewer THEN sourceSelection.voiceViewerInfo.editInProgress ← FALSE
};
destSelection.ropeInterval.start ← destSelection.ropeInterval.start - destMovesForward;
[] ← ReplaceSelectionWithSavedInterval[destSelection, sourceInterval, TRUE];
destSelection.voiceViewerInfo.editInProgress ← FALSE
Transpose:
PUBLIC
PROC [target: TEditDocument.SelectionId ← primary] = {
IF target = feedback THEN ERROR;
TiogaVoicePrivate.DebugRope["Voice transpose:\n"];
SelectionStates[];
TiogaVoicePrivate.DebugRope["not yet implemented.\n"];
SelectionStates:
PROC = {
TiogaVoicePrivate.DebugRope[" Primary selection is"];
IF ~TiogaOps.GetSelection[primary].pendingDelete THEN TiogaVoicePrivate.DebugRope[" not"];
TiogaVoicePrivate.DebugRope[" pending delete,\n secondary selection is"];
IF ~TiogaOps.GetSelection[secondary].pendingDelete THEN TiogaVoicePrivate.DebugRope[" not"];
TiogaVoicePrivate.DebugRope[" pending delete\n"]
};
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: TiogaVoicePrivate.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: TiogaVoicePrivate.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: TiogaVoicePrivate.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
}.