DIRECTORY
BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses, PulsesToMicroseconds ],
Convert USING [RopeFromInt],
FinchSmarts USING [RegisterForReports, ReportConversationStateProc, ReportRequestStateProc],
Menus USING [MenuProc ],
MessageWindow USING [Append, Blink ],
Process USING [Detach, MsecToTicks, Pause, Priority, priorityRealTime, SetPriority ],
Rope USING [Concat, ROPE ],
TextEdit USING [GetCharProp, InsertChar, PutCharProp, Size ],
TextLooks USING [RopeToLooks ],
TextNode USING [Ref],
TiogaButtons USING [TextNodeRef],
TiogaExtraOps USING [GetTextKey, PutTextKey, RemoveTextKey, TextKeyNotFound ],
TiogaOps USING [CallWithLocks, FirstChild, GetSelection, NoSelection, Root, SelectionGrain, SetSelection, ViewerDoc ],
TiogaOpsDefs USING [Location, Ref ],
TiogaVoicePrivate USING [ ApplyToLockedChars, BuildVoiceViewer, CancelPlayBack, DeleteVoiceFromChar, DescribeSelection, LockedAddCharMark, MakeVoiceEdited, PlayBackInProgress, RedrawViewer, ReplaceSelectionWithSavedInterval, Selection, SelectionRec, SelectionsAfterRedraw, SetParentViewer, SetPlayBackState, SetViewerContents, SetVoiceViewerEditStatus, SoundChars, SoundInterval, SoundIntervalRec, SoundList, SoundListFromIntervalSpecs, soundRopeCharLength, soundRopeCharsPerSecond, thrushHandle, ToggleDictationMenu, VoiceViewerInfo, VoiceWindowRec ],
ViewerClasses USING [Viewer ],
ViewerOps USING [DestroyViewer, FetchProp, PaintViewer ],
VoiceRope USING [DescribeRope, GetClientData, NB, NewRequestID, RecordNB, RequestID, StopNB, VoiceRope, VoiceRopeInterval ]
;
VoiceRecordImpl: CEDAR MONITOR
IMPORTS BasicTime, Convert, FinchSmarts, MessageWindow, Process, Rope, TextEdit, TextLooks, TiogaButtons, TiogaExtraOps, TiogaOps, TiogaVoicePrivate, ViewerOps, VoiceRope EXPORTS TiogaVoicePrivate = {

recordingState: RECORD [
recordingInProgress: BOOLEAN _ FALSE,
addingIntoTextViewer: BOOLEAN,
node: TiogaOpsDefs.Ref,
textViewer: ViewerClasses.Viewer,
soundSelection: TiogaVoicePrivate.Selection,
cueInsertPosition: INT, -- where the little arrows will go 
timeInSoundChars: INT,
requestID: VoiceRope.RequestID,
nb: VoiceRope.NB,
nextWakeUp: BasicTime.Pulses, -- clock for timing recording
timerState: {off, quit, on},
newRope: VoiceRope.VoiceRope
];

timerOff, recordingDone: CONDITION;

RecordingInProgress: PUBLIC PROC RETURNS [BOOLEAN] = { 
RETURN [recordingState.recordingInProgress] };
CancelProc: PUBLIC Menus.MenuProc = { 
IF VoiceRope.StopNB[TiogaVoicePrivate.thrushHandle] # $success THEN {
StopRecording[];
TiogaVoicePrivate.CancelPlayBack[];
};
};

AddVoiceProc: PUBLIC ENTRY Menus.MenuProc = {
IF recordingState.recordingInProgress THEN 
{  MessageWindow.Append["Already recording", TRUE];
MessageWindow.Blink[];
RETURN
};

IF TiogaVoicePrivate.PlayBackInProgress[] THEN
{  MessageWindow.Append["Cancel playback before trying to record", TRUE];
MessageWindow.Blink[];
RETURN
}; -- PlayBackInProgress is only a hint: to be sure we'll do a cancel to avoid race conditions
TiogaVoicePrivate.CancelPlayBack[];

IF ~PrepareSelection[] THEN RETURN;

StartRecording[];
};
PrepareSelection: INTERNAL PROC RETURNS [succeeded: BOOLEAN _ FALSE] = {
targetViewer: ViewerClasses.Viewer;
targetStart, targetEnd: TiogaOpsDefs.Location;
targetCaretBefore, targetPendingDelete: BOOLEAN;

alreadyBeingEdited: BOOLEAN _ FALSE;
alreadyVoiceThere: BOOLEAN _ FALSE;
suitableViewer: BOOLEAN;

DoIt: INTERNAL PROC [root: TiogaOpsDefs.Ref] = {
level: TiogaOps.SelectionGrain;

[viewer: targetViewer, start: targetStart, end: targetEnd, caretBefore: targetCaretBefore, pendingDelete: targetPendingDelete, level: level] _ TiogaOps.GetSelection[];
suitableViewer _ targetViewer.class.flavor = $Text;

IF suitableViewer THEN 
{  recordingState.addingIntoTextViewer _ ViewerOps.FetchProp[targetViewer, $voiceViewerInfo] = NIL;

IF recordingState.addingIntoTextViewer THEN
{  IF targetPendingDelete THEN 
{  TiogaVoicePrivate.ApplyToLockedChars[TiogaVoicePrivate.DeleteVoiceFromChar];
TiogaOps.SetSelection[viewer: targetViewer, start: targetStart, end: targetEnd,
level: level, caretBefore: targetCaretBefore,
pendingDelete: FALSE, which: primary] -- simply makes not pending delete
};

{  
targetChar: TiogaOpsDefs.Location _ IF targetCaretBefore THEN targetStart ELSE targetEnd;
node: TextNode.Ref _ TiogaButtons.TextNodeRef[targetChar.node]; -- just a type convertor
alreadyVoiceThere _ TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL;
recordingState.node _ targetChar.node;
recordingState.textViewer _ targetViewer;
IF ~alreadyVoiceThere THEN TiogaExtraOps.PutTextKey[targetChar.node, targetChar.where, $recordingMark]
}
}
ELSE
{  
[selection: recordingState.soundSelection, failed: alreadyBeingEdited]  _ TiogaVoicePrivate.DescribeSelection[which: primary, forceDelete: FALSE, returnSoundInterval: FALSE];
IF alreadyBeingEdited THEN RETURN;
IF recordingState.soundSelection.ropeInterval.length # 0 -- pending delete
AND TiogaVoicePrivate.ReplaceSelectionWithSavedInterval[
recordingState.soundSelection, NIL, FALSE].viewerDeleted 
    THEN NewDictationWindow[]; -- this means "if applicable, do the pending delete. If that delete should reduce the window contents to zero it will disappear, so create a new one"
recordingState.cueInsertPosition _ recordingState.soundSelection.ropeInterval.start/ TiogaVoicePrivate.soundRopeCharLength
}
}
};

TiogaOps.CallWithLocks[DoIt ! TiogaOps.NoSelection => {suitableViewer _ FALSE; CONTINUE}];
IF NOT suitableViewer THEN
{  MessageWindow.Append["Make a selection in a tioga or text viewer first", TRUE];
MessageWindow.Blink[]
}
ELSE 
 {  IF alreadyBeingEdited THEN
{ MessageWindow.Append["Previous voice editing operation has yet to complete", TRUE];
MessageWindow.Blink[]
}
ELSE 
 {  IF alreadyVoiceThere THEN
{ MessageWindow.Append["Cannot add sound on top of another sound", TRUE];
MessageWindow.Blink[]
}
ELSE succeeded _ TRUE
}
}
};

StartRecording: INTERNAL PROC = {
recordingState.recordingInProgress _ TRUE;
recordingState.timerState _ off;
recordingState.newRope _ NIL;
recordingState.timeInSoundChars _ 0;
recordingState.requestID _ VoiceRope.NewRequestID[];
recordingState.nb _ $success;
TRUSTED {Process.Detach[FORK RecordNewRope[]]};
};

RecordNewRope: PROC = { 
BroadcastRecordingDone: ENTRY PROC = { BROADCAST recordingDone }; -- keeps the compiler happy!
[nb: recordingState.nb, voiceRope: recordingState.newRope] _ VoiceRope.RecordNB[handle: TiogaVoicePrivate.thrushHandle, requestID: recordingState.requestID, clientData: $TiogaVoice];
SELECT recordingState.nb FROM
$success, $noLength => BroadcastRecordingDone[];
ENDCASE => StopRecording[];
};

RecordingTimer: PROC = {	
aVeryLongTime: BasicTime.Pulses = BasicTime.MicrosecondsToPulses[300000000];
cuePriority: Process.Priority = Process.priorityRealTime;
Process.SetPriority[cuePriority]; 
recordingState.nextWakeUp _ BasicTime.GetClockPulses[];
recordingState.timerState _ on;

DO
now: BasicTime.Pulses _ BasicTime.GetClockPulses[];
IF recordingState.nextWakeUp - now < aVeryLongTime -- so as to cope properly with wrap-around

THEN Process.Pause[Process.MsecToTicks[BasicTime.PulsesToMicroseconds[ recordingState.nextWakeUp-now]/1000]];
IF ~ IncrementRecordTimer[] THEN RETURN;
ENDLOOP;
};

IncrementRecordTimer: ENTRY PROC RETURNS [stillRunning: BOOLEAN _ TRUE] = {
IF recordingState.timerState # on THEN 
{  recordingState.timerState _ off;
BROADCAST timerOff;
RETURN [FALSE]
};

recordingState.nextWakeUp _ recordingState.nextWakeUp + BasicTime.MicrosecondsToPulses[ 1000000/TiogaVoicePrivate.soundRopeCharsPerSecond];
recordingState.timeInSoundChars _ recordingState.timeInSoundChars + 1;

IF ~recordingState.addingIntoTextViewer THEN
NewRecordingMarker[recordingState.soundSelection.viewer, recordingState.cueInsertPosition + recordingState.timeInSoundChars - 1];
};

NewRecordingMarker: INTERNAL PROC [viewer: ViewerClasses.Viewer, loc: INT] ~ {
DoIt: PROC [root: TiogaOpsDefs.Ref] ~ {
IF NOT viewer.newVersion THEN TiogaVoicePrivate.MakeVoiceEdited[viewer];
[] _ TextEdit.InsertChar[root: TiogaButtons.TextNodeRef[root], dest: TiogaButtons.TextNodeRef[TiogaOps.FirstChild[root]], char: '>, destLoc: loc, inherit: FALSE, looks: TextLooks.RopeToLooks["v"]];
};
TiogaOps.CallWithLocks[DoIt, TiogaOps.ViewerDoc[viewer]];
};

StopRecording: PUBLIC ENTRY PROC = {
IF ~recordingState.recordingInProgress THEN RETURN;
IF recordingState.timerState = on THEN recordingState.timerState _ quit;
WHILE recordingState.newRope = NIL AND recordingState.nb = $success DO WAIT recordingDone ENDLOOP;
WHILE recordingState.timerState # off DO WAIT timerOff ENDLOOP;

recordingState.recordingInProgress _ FALSE;
IF recordingState.addingIntoTextViewer THEN {
badTarget: BOOLEAN _ FALSE;
IF recordingState.newRope = NIL OR recordingState.newRope.length = 0 THEN {
MessageWindow.Append["Zero length voice annotation - discarding", TRUE];
MessageWindow.Blink[];
}
ELSE {
AddVoice: PROC [root: TiogaOpsDefs.Ref] = {
TextEdit.PutCharProp[node, targetChar.where, $voice, recordingState.newRope.ropeID];
TextEdit.PutCharProp[node, targetChar.where, $Artwork, NARROW["TalksBubble", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created
};

targetChar: TiogaOpsDefs.Location;
node: TextNode.Ref;
targetChar _ TiogaExtraOps.GetTextKey[recordingState.node, $recordingMark ! TiogaExtraOps.TextKeyNotFound => {badTarget _ TRUE; CONTINUE}];
node _ TiogaButtons.TextNodeRef[targetChar.node]; -- just a type convertor
IF TextEdit.Size[node] <= targetChar.where OR badTarget 
OR TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL THEN { 
voiceViewer: ViewerClasses.Viewer;
[viewer: voiceViewer] _ TiogaVoicePrivate.BuildVoiceViewer[recordingState.newRope.ropeID, NIL, TRUE];
TiogaVoicePrivate.MakeVoiceEdited[voiceViewer];
MessageWindow.Append["No character to add voice to; new voice viewer contains voice just recorded", TRUE];
MessageWindow.Blink[];
}
ELSE {
TiogaExtraOps.RemoveTextKey[recordingState.node, $recordingMark];
TiogaOps.CallWithLocks[AddVoice, TiogaOps.Root[targetChar.node]];
}
}
}
ELSE IF recordingState.soundSelection.voiceViewerInfo.ropeInterval.ropeID = NIL THEN {
IF recordingState.newRope = NIL OR recordingState.newRope.length = 0 THEN {
MessageWindow.Append["Zero length voice dictation - destroying window", TRUE];
MessageWindow.Blink[];
ViewerOps.DestroyViewer[recordingState.soundSelection.viewer]
}
ELSE {
TiogaVoicePrivate.MakeVoiceEdited[recordingState.soundSelection.viewer];
TiogaVoicePrivate.SetViewerContents[recordingState.soundSelection.viewer, recordingState.soundSelection.voiceViewerInfo, recordingState.newRope.ropeID, NIL, TRUE]
} 
}
ELSE IF recordingState.newRope = NIL OR recordingState.newRope.length = 0 THEN {
trueContents: Rope.ROPE _ TiogaVoicePrivate.SoundChars[recordingState.soundSelection.voiceViewerInfo].soundRope;
MessageWindow.Append["Zero length voice addition - viewer contents unchanged", TRUE];
[] _ TiogaVoicePrivate.RedrawViewer[recordingState.soundSelection.viewer, trueContents, 0, 0, 0, recordingState.soundSelection.voiceViewerInfo.remnant, FALSE, deSelected];
TiogaVoicePrivate.SetVoiceViewerEditStatus[recordingState.soundSelection.viewer];
recordingState.soundSelection.voiceViewerInfo.editInProgress _ FALSE
}
ELSE {
newSoundList: TiogaVoicePrivate.SoundList _ TiogaVoicePrivate.SoundListFromIntervalSpecs [VoiceRope.DescribeRope[TiogaVoicePrivate.thrushHandle, recordingState.newRope], recordingState.newRope.length];
newSound: TiogaVoicePrivate.SoundInterval _ NEW [TiogaVoicePrivate.SoundIntervalRec _ [ropeInterval: recordingState.newRope^, soundList: newSoundList]]; 
recordingState.soundSelection.ropeInterval.length _ 0;
TiogaVoicePrivate.MakeVoiceEdited[recordingState.soundSelection.viewer];
[] _ TiogaVoicePrivate.ReplaceSelectionWithSavedInterval[recordingState.soundSelection, newSound, TRUE];
recordingState.soundSelection.voiceViewerInfo.editInProgress _ FALSE
} 
};
NewDictationWindow: INTERNAL PROC = { 
recordingState.addingIntoTextViewer _ FALSE;
recordingState.soundSelection _ NEW [TiogaVoicePrivate.SelectionRec];
[viewerInfo: recordingState.soundSelection.voiceViewerInfo, viewer: recordingState.soundSelection.viewer] _ TiogaVoicePrivate.BuildVoiceViewer[NIL, NIL, TRUE];
recordingState.cueInsertPosition _ 1
};


DictationMachine: PUBLIC ENTRY Menus.MenuProc = {

IF recordingState.recordingInProgress THEN 
{  ChangeVoiceInputFocus[]; -- the non-trivial case - recording in progress
RETURN
};

IF TiogaVoicePrivate.PlayBackInProgress[] THEN {
MessageWindow.Append["Cancel playback before trying to record", TRUE];
MessageWindow.Blink[];
RETURN
}; -- PlayBackInProgress is only a hint: to be sure we'll do a cancel to avoid race conditions
TiogaVoicePrivate.CancelPlayBack[];

NewDictationWindow[];
TiogaVoicePrivate.ToggleDictationMenu[recordingState.soundSelection.viewer];
StartRecording[];
};

ChangeVoiceInputFocus: INTERNAL PROC = {
IF ~recordingState.addingIntoTextViewer THEN
{  IF recordingState.soundSelection.voiceViewerInfo.ropeInterval.ropeID = NIL THEN
{  MessageWindow.Append["you're already using the dictation machine!", TRUE];
MessageWindow.Blink[];
RETURN
};

TiogaVoicePrivate.LockedAddCharMark[recordingState.soundSelection.viewer, MAX [recordingState.soundSelection.ropeInterval.start/TiogaVoicePrivate.soundRopeCharLength-1, 0]];
MessageWindow.Append["marker set where you were inserting voice", TRUE];
recordingState.soundSelection.voiceViewerInfo.editInProgress _ FALSE
};

{  currentContents: Rope.ROPE _ " ";
wasIntoText: BOOLEAN _ recordingState.addingIntoTextViewer; -- gets altered by NewDictationWindow 
NewDictationWindow[]; -- that's an empty one: set its contents to reflect the sound already recorded
	
IF wasIntoText THEN
{
AddVoiceWindowMarker: PROC [root: TiogaOpsDefs.Ref] = {
alreadyEdited: BOOLEAN _ recordingState.textViewer.newVersion;
TextEdit.PutCharProp[node, targetChar.where, $Artwork, NARROW["TalksBubble", Rope.ROPE]]; -- the NARROW prevents a REF TEXT being created
TextEdit.PutCharProp[node, targetChar.where, $voiceWindow, NEW[TiogaVoicePrivate.VoiceWindowRec _ [label: Rope.Concat["Sound Viewer #", Convert.RopeFromInt[recordingState.soundSelection.voiceViewerInfo.viewerNumber]]]]];	
TiogaVoicePrivate.SetParentViewer[recordingState.soundSelection.voiceViewerInfo, recordingState.textViewer, targetChar];

IF ~alreadyEdited THEN
{  recordingState.textViewer.newVersion _ FALSE;
ViewerOps.PaintViewer[recordingState.textViewer, caption, FALSE]
}
};

targetChar: TiogaOpsDefs.Location _ TiogaExtraOps.GetTextKey[recordingState.node, $recordingMark];
node: TextNode.Ref _ TiogaButtons.TextNodeRef[targetChar.node]; -- just a type convertor
IF recordingState.node # targetChar.node THEN ERROR; -- **** because you've caught all those !!!
TiogaExtraOps.RemoveTextKey[recordingState.node, $recordingMark];
IF TextEdit.GetCharProp[node, targetChar.where, $voice] # NIL THEN RETURN; -- can happen - a store might have been performed in the intervening time
IF TextEdit.GetCharProp[node, targetChar.where, $voiceWindow] # NIL THEN RETURN; 
TiogaOps.CallWithLocks[AddVoiceWindowMarker, TiogaOps.Root[targetChar.node]] -- called locked because otherwise tioga doesn't immediately repaint the artwork, also so we can muck with the 'edited' status of the viewer
};


TiogaVoicePrivate.ToggleDictationMenu[recordingState.soundSelection.viewer];
FOR i: INT IN [1..recordingState.timeInSoundChars] DO currentContents _ currentContents.Concat[">"] ENDLOOP;
currentContents _ currentContents.Concat[" "];
[] _ TiogaVoicePrivate.RedrawViewer[recordingState.soundSelection.viewer, currentContents, 0, 0, 0, 0, FALSE, deSelected]
}
};

RecordInPlaceOfSelection: PUBLIC ENTRY PROC [selection: TiogaVoicePrivate.Selection] = {
recordingState.soundSelection _ selection;
recordingState.addingIntoTextViewer _ FALSE;

IF recordingState.soundSelection.ropeInterval.length # 0 -- i.e. pending delete
THEN IF TiogaVoicePrivate.ReplaceSelectionWithSavedInterval[recordingState.soundSelection, NIL, FALSE].viewerDeleted 
    THEN NewDictationWindow[]; -- this means "if applicable, do the pending delete. If that delete should reduce the window contents to zero it will disappear, so create a new one"

recordingState.cueInsertPosition _ recordingState.soundSelection.ropeInterval.start/TiogaVoicePrivate.soundRopeCharLength;
StartRecording[];
};

ForkStopRecording: PROC = {TRUSTED {Process.Detach[FORK StopRecording[]]} };
ConversationReport: FinchSmarts.ReportConversationStateProc ~ {
IF cDesc = NIL THEN RETURN;
IF NARROW[VoiceRope.GetClientData[handle: TiogaVoicePrivate.thrushHandle, cDesc: cDesc], ATOM] = $TiogaVoice THEN
SELECT cDesc.situation.self.state FROM
$active => NULL;
$idle, $neverWas, $failed => {
TiogaVoicePrivate.CancelPlayBack[];
ForkStopRecording[];
};
ENDCASE;
};

RequestReport: FinchSmarts.ReportRequestStateProc ~ {
IF cDesc = NIL THEN RETURN;
IF NARROW[VoiceRope.GetClientData[handle: TiogaVoicePrivate.thrushHandle, cDesc: cDesc], ATOM] = $TiogaVoice THEN
SELECT actionReport.actionClass FROM
$recording => { 
IF actionReport.actionID = recordingState.requestID THEN 
SELECT actionReport.actionType FROM
$scheduled => NULL;
$started => TRUSTED {Process.Detach[FORK RecordingTimer[]]};
$finished, $flushed => ForkStopRecording[];
ENDCASE;
};
$playback => { 
SELECT actionReport.actionType FROM
$scheduled => RETURN[NIL];
$started => TiogaVoicePrivate.SetPlayBackState[actionReport.actionID, busy];
$finished, $flushed => TiogaVoicePrivate.SetPlayBackState[actionReport.actionID, done]; 
ENDCASE;
};
ENDCASE=> betterActionRequest _ NIL; -- a place to stand during debugging
RETURN[NIL];  -- leave actionRequest alone
};

FinchSmarts.RegisterForReports[c: ConversationReport, r: RequestReport, before: FALSE];

}.

��!�VoiceRecordImpl.mesa
Copyright Ó 1987 by Xerox Corporation.  All rights reserved.
Ades, September 24, 1986 5:11:21 pm PDT
Swinehart, April 10, 1987 9:38:59 am PDT
Polle Zellweger (PTZ) July 27, 1987 5:25:56 pm PDT

basic code to add voice to windows of both text and voice, also the DictationMachine button which transfers input from a current window to a newly created dictation window 

the selection where the voice is to be added, as a TiogaOps node: a text key gives the position within the node but that is implicitly represented by the atom $recordingMark
the selection where the voice is to be added, as a TiogaOps node: a text key gives the position within the node but that is implicitly represented by the atom $recordingMark
the viewer where the voice is to be added, if a text viewer
the selection where the voice is to be added, as a TiogaVoicePrivate selection
how long has the recording been going on, in terms of sound characters generated
the rope that is created as the result of this recording process

Otherwise these shouldn't be needed - $finished reports should clear recording/playback queues automatically.
the procedure behind all AddVoice buttons

this procedure locks the selection and fills in the TiogaOps target info above. If the selection is a voice selection then recordingState.soundSelection is set up. If the selection is text then a text key is placed in the text. If the selection is pending delete then the delete is done. If the selection is a voice selection then the viewer is locked [against other voice edits]
recordingState.cueInsertPosition _ MIN [targetSoundSelection.ropeInterval.start/TiogaVoicePrivate.soundRopeCharLength, TextEdit.Size[TiogaButtons.TextNodeRef[TiogaOps.FirstChild[TiogaOps.ViewerDoc[targetSoundSelection.viewer]]]]-1] -- because you can't position the cursor as a point after the last character
test for failure conditions and report them to the user after releasing the viewer lock

Fork process to call VoiceRope.RecordNB. It will return when a VoiceRope.StopNB occurs.  It will then place the ID of the Rope in the appropriate state variable, where the routine called when the $finished report is received can get it.
Process to time the recording will be forked when the $started report is received, and will be terminated when the $finished report is received or the conversation fails.

something failed in conversation or recording setup; guaranteed not to WAIT for recordingDone This control mechanism is pretty kludgy
see comments in VoicePlayBackImpl on this constant
try to keep this process up to time: however there is compensation for delay in each wake-up by looking at a real time clock; see below

this code adds another marker into a voice viewer to indicate how much has been recorded
Note: all voice viewers contain only one node.
'> is used as an 'inserting voice' indicator
this gets called every time a STOP button is clicked, after VoiceRope.StopNB has been called
next line places a 'talks bubble' on the selected character - see TalksBubbleImpl
A store might have been performed in the intervening time, or there might be just a point selection at the end of some node.
Called locked because otherwise Tioga doesn't immediately repaint the artwork
This is a new dictation window, so just set its contents to the new rope
this will cause the event proc which clears down the voiceViewerInfo etc. to be called
Shouldn't be needed - should have already been done while recording the new rope.
If the new rope to be added is of zero length then just redraw the viewer
This will always say edited now, because we make the voice viewer edited in NewRecordingMarker when we put the first '> character in
This may already be the case, but if not we already did the delete before starting to record, so set it zero. If we did the delete then recordingState.soundSelection.ropeInterval will not bear the correct ropeID but recordingState.soundSelection.voiceViewerInfo will and that is the ropeID which is used
Shouldn't be needed - should have already been done while recording the new rope.

Creates a new dictation window and sets recordingState.soundSelection up correctly to refer to it, also sets recordingState.addingIntoTextViewer false
BuildVoiceViewer[NIL] will set the voiceRope represented by the rope to NIL, the test used for a currently 'empty' viewer in StopRecording above
Dictation machine creation stuff [the previous procedure is a general one for creating window with no voice in it]:
the procedure behind all DictationMachine buttons: if a recording is in progress then transfer it into another [newly created] window, otherwise make a new window and start recording
the rest of this procedure implements the case of DictationMachine bugged when no recording is in progress: create a new viewer and start recording into it
DictationMachine was bugged when a recording was in progress: create a new voice viewer and alter the recording focus to it. In the case where recording into a voice viewer, leave a marker where the focus was. In the case where recording into a text viewer, place a VoiceWindow marker where the focus was. 
this procedure not only sets the mark but also redraws the viewer, removing the voice input markers
**** this is all a bit dubious - the pointer enables a SAVE of the voice to work, but if the text is SAVED before the voice is either saved or destroyed, the bubble will hang around without voice underneath it

this line places a 'talks bubble' on the selected character - see TalksBubbleImpl
because we are under a tioga lock, it is safe to muck with the 'edited' status of the viewer
philosophically, putting a source marker in a voice viewer does not constitute editing it, so keep the 'edited' status of the viewer constant through this operation
this gets called by TiogaVoicePrivate when a selection [possibly zero length, in which case don't delete it] is to be replaced by new voice input: the selection is assumed to be locked with GetVoiceLock and there is assumed to be no playback or recording in progress at the time of the call

StopRecording may WAIT; don't want to hang up FinchSmarts reporting process

[ nb: NB, cDesc: ConvDesc, remark: ROPE_NIL ]
likely to be ours
restore quiescent state: stop all cues, discard pending requests
[ cDesc: ConvDesc, actionReport: Thrush.ActionReport, actionRequest: REF ] RETURNS [betterActionRequest: REF]
likely to be ours
Initializations
Swinehart, April 10, 1987 9:38:29 am PDT
Merged VoicePlayImpl and VoiceRecordImpl, from Ades. Fixed some nested ML problems resulting from merging two MONITORs.
changes to: AddVoiceProc, DictationMachine, AbortCues, CancelPlayBack
Polle Zellweger (PTZ) May 22, 1987 5:15:23 pm PDT
Split back into VoicePlaybackImpl and VoiceRecordImpl: too many monitor lock difficulties to fix quickly.
changes to: DIRECTORY, AddVoiceProc, DictationMachine
Polle Zellweger (PTZ) June 15, 1987 4:24:18 pm PDT
Avoid blowing up if the user has a point selection (or some other difficulty) at the end of recording; open a voice viewer on the voice rope to allow user to store elsewhere.
changes to: DIRECTORY, StopRecording
Polle Zellweger (PTZ) June 17, 1987 11:13:54 am PDT
Notice when voice viewer has been edited & change icon.
changes to: DIRECTORY, NewRecordingMarker, StopRecording
Polle Zellweger (PTZ) July 17, 1987 6:24:28 pm PDT
Register with FinchSmarts for request/connection reports; base timing of cues on reports; notice when conversation fails & clean up.
changes to: DIRECTORY, timerState, AddVoiceProc, RecordNewRope, StopRecording, RecordInPlaceOfSelection
new: recordingRequestID, recordingNB, convDesc, StartRecording, ConversationReport, RequestReport, initialization
Polle Zellweger (PTZ) July 20, 1987 5:16:41 pm PDT
More of the above.
changes to: CancelProc, RequestReport, StartRecording, RecordNewRope, RecordInPlaceOfSelection, ConversationReport, DIRECTORY
Polle Zellweger (PTZ) July 21, 1987 12:27:42 pm PDT
Create recordingState record.
changes to: recordingState, timerState, RecordingInProgress, AddVoiceProc, PrepareSelection, StartRecording, RecordNewRope, RecordingTimer, IncrementRecordTimer, StopRecording, AddVoice (local of StopRecording), NewDictationWindow, DictationMachine, ChangeVoiceInputFocus, AddVoiceWindowMarker (local of ChangeVoiceInputFocus), RecordInPlaceOfSelection, RequestReport, DIRECTORY, CancelProc, VoiceRecordImpl
Polle Zellweger (PTZ) July 27, 1987 4:52:06 pm PDT
changes to: DIRECTORY, VoiceRecordImpl, StartRecording, RecordNewRope, ForkStopRecording, ConversationReport, RequestReport, RecordingTimer, StopRecording
Polle Zellweger (PTZ) July 27, 1987 5:25:56 pm PDT
changes to: DictationMachine

�Ê‹��˜�šœ™Icode™<Jšœ$Ïk™'Kšœ%™(Kšœœ™2K™�—š	˜	Kšœ
œG˜VJšœœ˜JšœœK˜\Kšœœ
˜Jšœœ˜%JšœœH˜UJšœœ
œ˜Jšœ	œ/˜=Jšœ
œ˜Jšœ	œ˜Jšœ
œ˜!Jšœœ;˜NKšœ	œh˜vJšœ
œ˜$Jšœœ‘˜¨Jšœœ˜Jšœ
œ*˜9Jšœ
œœK˜{J˜—šÐblœœ˜Jšœ¤œ˜È—J˜�™¬J™�—šœœ˜Jšœœœ˜%šœœ˜J™­—˜J™­—šœ!˜!J™;—šœ,˜,J™N—JšœœÏc#˜;šœœ˜J™P—Jšœ˜Jšœœ˜JšœŸ˜;J˜˜J™@—˜J˜�——šœ	œ˜#J˜�—š
Ïnœœœœœ˜7Jšœ(˜.J™�—š 
œœ˜&šœ<œ˜EK˜J˜#J™mJ˜—Jšœ˜J˜�—š œœœ˜-J™)Jšœ$œ˜+šœ-œ˜3J˜Jš˜—Jšœ˜J˜�Jšœ(˜.šœCœ˜IJ˜Jš˜—JšœŸ[˜^Jšœ#˜#J˜�Jšœœœ˜#J˜�Jšœ˜J˜J™�—š œœœœ
œœ˜HJšœû™ûJšœ#˜#Jšœ.˜.Jšœ(œ˜0J˜�Jšœœœ˜$Jšœœœ˜#Jšœœ˜J˜�š œœœ˜0Jšœ˜—˜�JšœÛ˜ÛJ˜�Jšœœ˜Jšœ_œ˜c˜�Jšœ%˜+šœœœ˜šœO˜OJšœO˜OJšœ-˜-JšœœŸ"˜H—J˜J˜�šœ˜Jšœ$œœ
œ˜YJšœ@Ÿ˜XJšœKœ˜OJšœ&˜&Jšœ)˜)JšœœL˜f—J˜—Jšœ˜Jš˜šœ˜Jšœ‹œœ˜®Jšœœœ˜"šœ6Ÿ˜JKšœUœœ˜rJšœœŸ•˜´—Jšœ#œÂŸL™´Jšœz˜z—J˜—J˜—J˜J˜�JšœHœœ˜ZJ™WJšœœ˜šœLœ˜RJšœ˜—J˜Jšœ˜šœœ˜šœOœ˜UJšœ˜—J˜Jšœ˜šœœ˜šœCœ˜IJšœ˜—J˜Jšœ
˜—J˜—J˜J˜J˜�—š œœœ˜!Jšœ%œ˜*Jšœ ˜ Jšœœ˜Jšœ$˜$Jšœ4˜4Jšœ˜J™�Jšœpœz™ìJšœª™ªJ™�Jšœœ˜/J˜J˜�—š 
œœ˜Jš	 œœœ	œŸ˜^Kšœ¶˜¶šœ˜Kšœ0˜0šœ˜Kšœ^Ïtœ'¡™‡——Jšœ˜J˜�—š œœ˜JšœL˜LJ™2Jšœ9˜9Jšœ"˜"Kšœˆ™ˆKšœ7˜7Jšœ˜K˜�š˜Kšœ3˜3Kšœ1Ÿ*˜]K˜�Kšœi˜mKšœœ˜(Jš˜—K˜K˜�—š œœœœœœ˜KKšœ œ˜'šœ#˜#Kš	œ
˜Kšœœ˜—K˜K˜�Kšœ‹˜‹KšœF˜FK˜�šœ&˜,Kšœ˜—K˜K˜�—š œœœ%œ˜NKšœX™Xš œœ˜'K™.Kšœœœ+˜Hšœ›œ%˜ÅKšœ,™,—K˜—Kšœ9˜9K˜K˜�—š 
œœœœ˜$Kšœœ:™\Kšœ%œœ˜3Kšœ œ"˜HKšœœœœœœ˜bKšœ!œœ
œ˜?K˜�Kšœ%œ˜+šœ%œ˜-Kšœœœ˜šœœœ#œ˜KJšœBœ˜HJšœ˜J˜—šœ˜š œœ˜+KšœT˜TJ™QJšœ7œœŸ/˜‰J˜—J˜�Jšœ"˜"Kšœ˜Jšœzœœ˜‹Kšœ2Ÿ˜Jš
œ)œœ8œœ˜~Jšœ|™|Jšœ"˜"KšœZœœ˜eJšœ/˜/Jšœdœ˜jJšœ˜J˜—šœ˜JšœA˜AšœA˜AKšœM™M—K˜—K˜—J˜—šœœEœœ˜VKšœH™Hšœœœ#œ˜KKšœHœ˜NJšœ˜Jšœ=˜=J™VK˜—šœ˜šœH˜HK™Q—Kšœ˜œœ˜¢Kšœ˜—K˜—š
œœœœ#œ˜PKšœI™IKšœœY˜pKšœOœ˜UJšœ˜œ˜«šœQ˜QJš¡œ„¡™†—Kšœ?˜DK˜—šœ˜KšœÉ˜ÉKšœ,œj˜™Kšœ6˜6Kšœ¯™¯šœH˜HK™Q—Kšœbœ˜hKšœ?˜DK˜—K˜J™�—š œœœ˜&Kšœ–™–Kšœ&œ˜,Kšœ œ"˜EKšœœœœ˜ŸKšœœ4œE™Kšœ$˜$K˜K˜�—šœs™sK˜�—š œœœ˜1Kšœ¶™¶J˜�Jšœ$œ˜+šœŸ/˜KJš˜—Jšœ˜J˜�Jšœ›™›šœ(œ˜0Jšœ@œ˜FJ˜Jš˜JšœŸ[˜^—Jšœ#˜#J˜�Jšœ˜JšœL˜LJ˜J˜K˜�—š œœœ˜(Kšœ²™²Kšœ&˜,šœœEœ˜RšœGœ˜MJ˜Jš˜—Jšœ˜K˜�KšœJœ`˜­K™cKšœBœ˜HKšœ?˜D—K˜K˜�šœœ˜$Kšœ
œ(Ÿ&˜bKšœŸN˜dK˜Kšœ
˜˜š œœ˜7Kšœ7œ*œg™ÑK™�Kšœœ(˜>J™QJšœ7œœŸ/˜‰Jšœ;œ˜˜ÖJ˜�J™\J™¤Jšœ˜šœ*œ˜0Jšœ:œ˜@—J˜—J˜—˜�Jšœb˜bKšœ@Ÿ˜XKšœ'œœŸ+˜`JšœA˜AJš	œ8œœœŸI˜”Jšœ>œœœ˜QKšœMŸŒ˜Ù—K˜K˜�K˜�KšœL˜LKš
œœœ&œ/œ˜lKšœ.˜.Kšœgœ
˜y—K˜K˜K˜�—š œœœœ-˜XK™¢Jšœ*˜*Jšœ&œ˜,J˜�Jšœ7Ÿ˜OšœœTœœ˜uJšœœŸ•˜´—J˜�šœz˜zJ™�—Jšœ˜J˜J˜�—š œœœœ˜LJ™KJ™�—š œ-˜?Kšœœœœ™-Jšœ	œœœ˜šœœPœ˜qKšœ™šœ˜&Jšœœ˜šœ˜Jšœ@™@Jšœ#˜#Jšœ˜Jšœ˜—Jšœ˜——K˜K˜�—š 
œ(˜5KšœEœœœ™mJšœ	œœœ˜šœœPœ˜qKšœ™šœ˜$šœ˜šœ1œ˜9šœ˜#Kšœœ˜Kšœœœ˜<Kšœ+˜+Kšœ˜——K˜—šœ˜šœ˜#Kšœœœ˜KšœL˜LKšœX˜XKšœ˜—K˜—KšœœŸ$˜I——KšœœŸ˜*Kšœ˜K˜�—head™KšœPœ˜W—K˜�K˜K˜�šœ%™(KšœGœ.™wKšœÏr9™E—šœœ™1Kšœi™iKšœÐkr	¢ ™5—šœœ™2K™®Kšœ£	¢™$—šœœ™3K™7Kšœ£	¢#™8—™2K™„Kšœ£	¢R™gKšœ¢l™q—™2K™Kšœ¢q™}—™3K™Kšœ¢­œ¢Sœ!¢Q™——™2Kšœ¢Ž™š—™2Kšœ¢™—K™�—�…—����Dê��wc��