Removed persistence of saveAudio directorymemory
[sgc2.git] / ToneProt / SGC_ToneProt.praat
blob70f0275d5fac411a93ee1b9b03fa3e3a019038a3
1 #! praat
3 #     SpeakGoodChinese: SGC_ToneRecognizer.praat processes student utterances 
4 #     and generates a report on their tone production
5 #     
6 #     Copyright (C) 2007-2010  R.J.J.H. van Son
7 #     The SpeakGoodChinese team are:
8 #     Guangqin Chen, Zhonyan Chen, Stefan de Koning, Eveline van Hagen, 
9 #     Rob van Son, Dennis Vierkant, David Weenink
10
11 #     This program is free software; you can redistribute it and/or modify
12 #     it under the terms of the GNU General Public License as published by
13 #     the Free Software Foundation; either version 2 of the License, or
14 #     (at your option) any later version.
15
16 #     This program is distributed in the hope that it will be useful,
17 #     but WITHOUT ANY WARRANTY; without even the implied warranty of
18 #     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 #     GNU General Public License for more details.
20
21 #     You should have received a copy of the GNU General Public License
22 #     along with this program; if not, write to the Free Software
23 #     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24
25 # Needs:
26 # include ToneRecognition.praat
27 # include ToneScript.praat
28 # procedure loadTable
30 procedure sgc_ToneProt sgc_ToneProt.currentSound$ sgc_ToneProt.pinyin$ sgc_ToneProt.register sgc_ToneProt.proficiency sgc_ToneProt.language$
31         # Remove if included in main program!
32         sgc_ToneProt.viewportMargin = 5
34         sgc_ToneProt.precision = 3
35         if sgc_ToneProt.proficiency
36                 sgc_ToneProt.precision = 1.5
37         endif
38         # Stick to the raw recognition results or not
39         sgc_ToneProt.ultraStrict = sgc_ToneProt.proficiency
41         
42         # Read and select the feedbacktext
43         call loadTable ToneFeedback_'sgc_ToneProt.language$'
44         Rename... ToneFeedback
45         numberOfFeedbackRows = Get number of rows
47         # Clean up input
48         if sgc_ToneProt.pinyin$ <> ""           
49         sgc_ToneProt.pinyin$ = replace_regex$(sgc_ToneProt.pinyin$, "^\s*(.+)\s*$", "\1", 1)
50         sgc_ToneProt.pinyin$ = replace_regex$(sgc_ToneProt.pinyin$, "5", "0", 0)
51                 # Missing neutral tones
52                 call add_missing_neutral_tones 'sgc_ToneProt.pinyin$'
53                 sgc_ToneProt.pinyin$ = add_missing_neutral_tones.pinyin$
54         endif
56         # Reduction (lower sgc_ToneProt.register and narrow range) means errors
57         # The oposite mostly not. Asymmetry alows more room upward
58         # than downward (asymmetry = 2 => highBoundaryFactor ^ 2)
59         asymmetry = 2
61         # Kill octave jumps: DANGEROUS
62         killOctaveJumps = 0
64         # Limit pitch range
65         sgc_ToneProt.minimumPitch = 50
66         sgc_ToneProt.maximumPitch = 500
67         if sgc_ToneProt.register > 400
68         sgc_ToneProt.minimumPitch = 60
69         sgc_ToneProt.maximumPitch = 600
70         elsif sgc_ToneProt.register > 250
71         sgc_ToneProt.minimumPitch = 50
72         sgc_ToneProt.maximumPitch = 500
73         else
74         sgc_ToneProt.minimumPitch = 40
75         sgc_ToneProt.maximumPitch = 400
76         endif
78         sgc_ToneProt.currentTestWord$ = sgc_ToneProt.pinyin$
79         spacing = 0.5
80         sgc_ToneProt.precisionFactor = 2^(sgc_ToneProt.precision/12)
81         highBoundaryFactor = sgc_ToneProt.precisionFactor ^ asymmetry
82         lowBoundaryFactor = 1/sgc_ToneProt.precisionFactor
84         # Generate reference example
85         # Start with a range of 1 octave and a speed factor of 1
86         toneRange = 1.0
87         speedFactor = 1.0
88         sgc_ToneProt.upperRegisterInput = sgc_ToneProt.register
89         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 1 1 CorrectPitch
90         # Get range and top
91         select Pitch 'sgc_ToneProt.currentTestWord$'
92         sgc_ToneProt.durationModel = Get total duration
93         maximumModelFzero = Get quantile... 0 0 0.95 Hertz
94         minimumModelFzero = Get quantile... 0 0 0.05 Hertz
95         if maximumModelFzero = undefined
96                 maximumModelFzero = 0
97         endif
98         if minimumModelFzero = undefined
99                 minimumModelFzero = 0
100         endif
101         sgc_ToneProt.modelPitchRange = 2
102         if minimumModelFzero > 0
103         sgc_ToneProt.modelPitchRange = maximumModelFzero / minimumModelFzero
104     else
105                 sgc_ToneProt.modelPitchRange = 0
106         endif
108         # Get the sounds
109         if fileReadable(sgc_ToneProt.currentSound$)
110         Read from file... 'sgc_ToneProt.currentSound$'
111         Rename... Source
112         else
113         select Sound 'sgc_ToneProt.currentSound$'
114         Copy... Source
115         endif
117         # Calculate pitch
118         select Sound Source
119         durationSource = Get total duration
120         call convert2Pitch 'sgc_ToneProt.minimumPitch' 'sgc_ToneProt.maximumPitch'
121         te.recordedPitch = convert2Pitch.object
122         Rename... SourcePitch
124         # It is rather dangerous to kill Octave errors, so be careful
125         if killOctaveJumps > 0
126         Rename... OldSource
127         Kill octave jumps
128         Rename... SourcePitch
129         te.recordedPitch = selected("Pitch")
130         select Pitch OldSource
131         Remove
132         endif
134         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
135         select te.recordedPitch
136         upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
137         lowerCutOff = sgc_ToneProt.upperRegisterInput/4
138         Formula... if self > 'upperCutOff' then -1 else self endif
139         Formula... if self < 'lowerCutOff' then -1 else self endif
141         # Get range and top
142         select te.recordedPitch
143         maximumRecFzero = Get quantile... 0 0 0.95 Hertz
144         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
145         minimumRecFzero = Get quantile... 0 0 0.05 Hertz
146         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
147         if maximumRecFzero = undefined
148         # Determine what should be told to the student
149         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': ???"
150         for i from 1 to numberOfFeedbackRows
151                 select Table ToneFeedback
152                 .toneOne$ = Get value... 'i' T1
153                 .toneTwo$ = Get value... 'i' T2
154                 .toneText$ = Get value... 'i' Feedback
155                         .label$ = "Unknown"
157                 if .toneOne$ = "NoSound"
158                 .feedbackText$ = .toneText$
159                 endif
160         endfor
162         #exit Error, nothing recorded
163                 goto END
164         endif
165         recPitchRange = 2
166         if minimumRecFzero > 0
167            recPitchRange = maximumRecFzero / minimumRecFzero
168         endif
169         sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
170         sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
171         if sgc_ToneProt.newUpperRegister = undefined
172                 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
173         endif
174         if sgc_ToneProt.newToneRange = undefined
175                 sgc_ToneProt.newToneRange = 1
176         endif
178         sgc_ToneProt.registerUsed$ = "OK"
179         rangeUsed$ = "OK"
180         # Advanced speakers must not speak too High, or too "Dramatic"
181         # Beginning speakers also not too Low or too Narrow ranges
182         if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
183            sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
184            sgc_ToneProt.registerUsed$ = "High"
185         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
186            sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
187            sgc_ToneProt.registerUsed$ = "Low"
188         endif
189         
190         if sgc_ToneProt.newToneRange > highBoundaryFactor
191            sgc_ToneProt.newToneRange = highBoundaryFactor
192            rangeUsed$ = "Wide"
193         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
194                 # Don't do this for advanced speakers
195            sgc_ToneProt.newToneRange = lowBoundaryFactor
196            rangeUsed$ = "Narrow"
197         endif
199         # Duration 
200         if sgc_ToneProt.durationModel > spacing
201            speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
202         endif
204         # Round values
205         sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
207         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
208         select te.recordedPitch
209         upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
210         lowerCutOff = sgc_ToneProt.newUpperRegister/3
211         Formula... if self > 'upperCutOff' then -1 else self endif
212         Formula... if self < 'lowerCutOff' then -1 else self endif
214         if killOctaveJumps > 0
215         Rename... OldSourcePitch
216         Kill octave jumps
217         Rename... SourcePitch
218         te.recordedPitch = selected("Pitch")
219         select Pitch OldSourcePitch
220         Remove
221         endif
223         # It is good to have the lowest and highest pitch frequencies
224         select te.recordedPitch
225         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
226         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
228         # Clean up the old example pitch
229         select Pitch 'sgc_ToneProt.currentTestWord$'
230         Remove
232         # Do the tone recognition
233         .numSyllables = toneScript.syllableCount
234         sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
235         .skipSyllables = 0
236         while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
237                 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
238                 .skipSyllables += 1
239         endwhile
240         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
242         # Special cases
243         originalRecognizedWord$ = sgc_ToneProt.choiceReference$
244         if  sgc_ToneProt.ultraStrict = 0
245         # First syllable: 2<->3 (6) exchanges (incl 6)
246         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+2[a-zA-Z]+[0-4]$") > 0
247                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[36][a-zA-Z]+[0-4]$") > 0
248                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[36]([a-zA-Z]+[0-4])$", "2\1", 0)
249                 endif
250         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+3[a-zA-Z]+[0-4]$") > 0
251                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[26][a-zA-Z]+[0-4]$") > 0
252                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[26]([a-zA-Z]+[0-4])$", "3\1", 0)
253                 endif
254         # A single second tone is often misidentified as a neutral tone, 
255         # A real neutral tone would be too low or too narrow and be discarded
256         # Leaves us with erroneous tone 4
257         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+2$") > 0
258                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+0$") > 0 and timeMinimum < timeMaximum
259                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
260                 endif
261         # A single fourth tone is often misidentified as a neutral tone, 
262         # A real neutral tone would be too low or too narrow and be discarded
263         # Leaves us with erroneous tones 2 and 3
264         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+4$") > 0
265                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+0$") > 0 and timeMaximum < timeMinimum
266                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
267                 endif
268         endif
270         # Second (last) syllable, 0<->6 exchanges and 2<->3
271         # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
272         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+[4][a-zA-Z]+2$") > 0
273                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[4][a-zA-Z]+[0]$") > 0
274                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[0]$", "2", 0)
275                 endif
276         endif
277         # A final 6 after a valid tone is often a recognition error
278         # A final 6 can be a 0
279         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+[0-9][a-zA-Z]+0$") > 0
280                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[0-4][a-zA-Z]+6$") > 0
281                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "6$", "0", 0)
282                 endif
283         # Second (last) syllable, 2<->3 exchanges after [23] tones
284         # A recognized 6 (or 3) after a valid tone [1-4] is mostly wrong, can be a 2
285         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+[1-4][a-zA-Z]+2$") > 0
286                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[1-4][a-zA-Z]+[36]$") > 0
287                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[36]$", "2", 0)
288                 endif
289         # A recognized 6 after a [23] is mostly wrong, can be a 3
290         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+[23][a-zA-Z]+3$") > 0
291                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[23][a-zA-Z]+[26]$") > 0
292                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[26]$", "3", 0)
293                 endif
294         # A recognized 6 after a [3] is mostly wrong, can be a 1
295         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+[3][a-zA-Z]+1$") > 0
296                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[3][a-zA-Z]+[6]$") > 0
297                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[6]$", "1", 0)
298                 endif
299         endif
301         # Clean up odd things constructed with special cases
302         # Target is 3-3, but recognized is 2-3, which is CORRECT. Change it into 3-3
303         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+[3][a-zA-Z]+[3]$") > 0
304                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+[2][a-zA-Z]+[3]$") > 0
305                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[2]([a-zA-Z]+[3])$", "3\1", 0)
306                 endif
307         endif
308         endif
310         # If wrong, then undo all changes
311         if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
312         sgc_ToneProt.choiceReference$ = originalRecognizedWord$
313         endif
315         sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
317         ###############################################
318         #
319         # Report
320         #
321         ###############################################
322         result$ = "'tab$''sgc_ToneProt.currentTestWord$''tab$''sgc_ToneProt.choiceReference$''tab$''sgc_ToneProt.newUpperRegister''tab$''sgc_ToneProt.newToneRange''tab$''speedFactor''tab$''sgc_ToneProt.registerUsed$''tab$''rangeUsed$'"
323         if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
324            result$ = "Correct:"+result$
325         else
326            result$ = "Wrong:"+result$
327         endif
329         # Initialize result texts
330         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': "
331         .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
332         .feedbackText$ = "----"
334         # Separate tone from pronunciation errors
335         currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
336         choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
338         # Determine what should be told to the student
339         if sgc_ToneProt.registerUsed$ = "Low"
340         .recognitionText$ = .recognitionText$ + "???"
341         for i from 1 to numberOfFeedbackRows
342                 select Table ToneFeedback
343                 .toneOne$ = Get value... 'i' T1
344                 .toneTwo$ = Get value... 'i' T2
345                 .toneText$ = Get value... 'i' Feedback
347                 if .toneOne$ = "Low"
348                 .feedbackText$ = .toneText$
349                                 .label$ = .toneOne$
350                 endif
351         endfor
352         elsif rangeUsed$ = "Narrow"
353         .recognitionText$ = .recognitionText$ + "???"
354         for i from 1 to numberOfFeedbackRows
355                 select Table ToneFeedback
356                 .toneOne$ = Get value... 'i' T1
357                 .toneTwo$ = Get value... 'i' T2
358                 .toneText$ = Get value... 'i' Feedback
360                 if .toneOne$ = "Narrow"
361                 .feedbackText$ = .toneText$
362                                 .label$ = .toneOne$
363                 endif
364         endfor
365         elsif sgc_ToneProt.registerUsed$ = "High"
366         .recognitionText$ = .recognitionText$ + .choiceText$
367         for i from 1 to numberOfFeedbackRows
368                 select Table ToneFeedback
369                 .toneOne$ = Get value... 'i' T1
370                 .toneTwo$ = Get value... 'i' T2
371                 .toneText$ = Get value... 'i' Feedback
373                 if .toneOne$ = "High"
374                 .feedbackText$ = .toneText$
375                                 .label$ = .toneOne$
376                 endif
377         endfor
378         elsif rangeUsed$ = "Wide"
379         .recognitionText$ = .recognitionText$ + .choiceText$
380         for i from 1 to numberOfFeedbackRows
381                 select Table ToneFeedback
382                 .toneOne$ = Get value... 'i' T1
383                 .toneTwo$ = Get value... 'i' T2
384                 .toneText$ = Get value... 'i' Feedback
386                 if .toneOne$ = "Wide"
387                 .feedbackText$ = .toneText$
388                                 .label$ = .toneOne$
389                 endif
390         endfor
391         # Bad tones, first handle first syllable
392         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
393         .recognitionText$ = .recognitionText$ + .choiceText$
394         # First syllable
395         for i from 1 to numberOfFeedbackRows
396                 select Table ToneFeedback
397                 .toneOne$ = Get value... 'i' T1
398                 .toneTwo$ = Get value... 'i' T2
399                 .toneText$ = Get value... 'i' Feedback
401                 # 
402                 .feedbackText$ = ""
403                 if .toneOne$ = "6"
404                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
405                                 .label$ = .toneOne$
406                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
407                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
408                 endif
409         endfor
410         # Bad tones, then handle second syllable
411         elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
412         .recognitionText$ = .recognitionText$ + .choiceText$
413         # Last syllable
414         for i from 1 to numberOfFeedbackRows
415                 select Table ToneFeedback
416                 .toneOne$ = Get value... 'i' T1
417                 .toneTwo$ = Get value... 'i' T2
418                 .toneText$ = Get value... 'i' Feedback
420                 # 
421                 .feedbackText$ = ""
422                 if .toneOne$ = "6"
423                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
424                                 .label$ = .toneOne$
425                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
426                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
427                 endif
428         endfor
429         # Just plain wrong tones
430         elsif currentToneWord$ <> choiceToneReference$
431         .recognitionText$ = .recognitionText$ + .choiceText$
432         for i from 1 to numberOfFeedbackRows
433                 select Table ToneFeedback
434                 .toneOne$ = Get value... 'i' T1
435                 .toneTwo$ = Get value... 'i' T2
436                 .toneText$ = Get value... 'i' Feedback
438                 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
439                 .feedbackText$ = .toneText$
440                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
441                 .feedbackText$ = .toneText$
442                 elsif .toneOne$ = "Wrong"
443                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
444                                 .label$ = .toneOne$
445                 endif
446         endfor
447         # Correct
448         else
449         .recognitionText$ = .recognitionText$ + .choiceText$
450         for i from 1 to numberOfFeedbackRows
451                 select Table ToneFeedback
452                 .toneOne$ = Get value... 'i' T1
453                 .toneTwo$ = Get value... 'i' T2
454                 .toneText$ = Get value... 'i' Feedback
456                 if .toneOne$ = "Correct"
457                 .feedbackText$ = .toneText$
458                                 .label$ = .toneOne$
459                 endif
460         endfor
461         endif
463         label END
465         # Write out result
466         Create Table with column names... Feedback 3 Text
467         Set string value... 1 Text '.recognitionText$'
468         Set string value... 2 Text '.feedbackText$'
469         Set string value... 3 Text '.label$'
471         # Clean up
472         select Table ToneFeedback
473         Remove
475         # Show pitch tracks
476     freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
478         # Replace recorded sound with new sound
479         if not fileReadable(sgc_ToneProt.currentSound$)
480         select Sound 'sgc_ToneProt.currentSound$'
481                 Remove
482                 select Sound Source
483         Copy... 'sgc_ToneProt.currentSound$'
484         endif
487         # Clean up
488         select Sound Source
489         plus Pitch 'sgc_ToneProt.currentTestWord$'
490         Remove
491 endproc