Reworking creaky voice recognition
[sgc2.git] / ToneProt / SGC_ToneProt.praat
blobe40b9d06e042f9f665a8565884e23b88034ad80b
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
40         
41         # Read and select the feedbacktext
42         call testLoadTable ToneFeedback_'sgc_ToneProt.language$'
43         if testLoadTable.table > 0
44                 call loadTable ToneFeedback_'sgc_ToneProt.language$'
45         else
46                 call loadTable ToneFeedback_EN
47         endif
48         Rename... ToneFeedback
49         numberOfFeedbackRows = Get number of rows
51         # Clean up input
52         if sgc_ToneProt.pinyin$ <> ""           
53         sgc_ToneProt.pinyin$ = replace_regex$(sgc_ToneProt.pinyin$, "^\s*(.+)\s*$", "\1", 1)
54         sgc_ToneProt.pinyin$ = replace_regex$(sgc_ToneProt.pinyin$, "5", "0", 0)
55                 # Missing neutral tones
56                 call add_missing_neutral_tones 'sgc_ToneProt.pinyin$'
57                 sgc_ToneProt.pinyin$ = add_missing_neutral_tones.pinyin$
58         endif
60         # Reduction (lower sgc_ToneProt.register and narrow range) means errors
61         # The oposite mostly not. Asymmetry alows more room upward
62         # than downward (asymmetry = 2 => highBoundaryFactor ^ 2)
63         asymmetry = 2
65         # Kill octave jumps: DANGEROUS
66         killOctaveJumps = 0
68         # Limit pitch range
69         sgc_ToneProt.minimumPitch = 50
70         sgc_ToneProt.maximumPitch = 500
71         if sgc_ToneProt.register > 400
72         sgc_ToneProt.minimumPitch = 60
73         sgc_ToneProt.maximumPitch = 600
74         elsif sgc_ToneProt.register > 250
75         sgc_ToneProt.minimumPitch = 50
76         sgc_ToneProt.maximumPitch = 500
77         else
78         sgc_ToneProt.minimumPitch = 40
79         sgc_ToneProt.maximumPitch = 400
80         endif
82         sgc_ToneProt.currentTestWord$ = sgc_ToneProt.pinyin$
83         spacing = 0.5
84         .interPrecision = sgc_ToneProt.precision;
85         
86         # Allow more "room" when there is a 3rd tone
87         if index(sgc_ToneProt.pinyin$, "3")
88                 .interPrecision *= 4/3 
89         endif
90         sgc_ToneProt.precisionFactor = 2^(.interPrecision/12)
91         highBoundaryFactor = sgc_ToneProt.precisionFactor ^ asymmetry
92         lowBoundaryFactor = 1/sgc_ToneProt.precisionFactor
94         # Generate reference example
95         # Start with a range of 1 octave and a speed factor of 1
96         toneRange = 1.0
97         speedFactor = 1.0
98         sgc_ToneProt.upperRegisterInput = sgc_ToneProt.register
99         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 1 1 CorrectPitch
100         # Get range and top
101         select Pitch 'sgc_ToneProt.currentTestWord$'
102         sgc_ToneProt.durationModel = Get total duration
103         maximumModelFzero = Get quantile... 0 0 0.95 Hertz
104         minimumModelFzero = Get quantile... 0 0 0.05 Hertz
105         if maximumModelFzero = undefined
106                 maximumModelFzero = 0
107         endif
108         if minimumModelFzero = undefined
109                 minimumModelFzero = 0
110         endif
111         sgc_ToneProt.modelPitchRange = 2
112         if minimumModelFzero > 0
113         sgc_ToneProt.modelPitchRange = maximumModelFzero / minimumModelFzero
114     else
115                 sgc_ToneProt.modelPitchRange = 0
116         endif
118         # Get the sounds
119         if fileReadable(sgc_ToneProt.currentSound$)
120         Read from file... 'sgc_ToneProt.currentSound$'
121         Rename... Source
122         else
123         select Sound 'sgc_ToneProt.currentSound$'
124         Copy... Source
125         endif
127         # Calculate pitch
128         select Sound Source
129         durationSource = Get total duration
130         call convert2Pitch 'sgc_ToneProt.minimumPitch' 'sgc_ToneProt.maximumPitch'
131         te.recordedPitch = convert2Pitch.object
132         select te.recordedPitch
133         Rename... SourcePitch
134         
135         ################################################################
136         #
137         # If there is a third tone, replace creaky voice by low pitch
138         # 
139         ################################################################
140         toneProt.creakyThree$ = "" 
141         for .s to toneScript.syllableCount
142                 toneProt.creakyThree$ = toneProt.creakyThree$ + "."
143         endfor
144         if index(sgc_ToneProt.pinyin$, "3")
145                 .creakCO = 0.025
146                 .delta = 0.0000001
147                 
148                 # Lowest point for tone 3 is 1 octave and 3st below the top line
149                 # The cut-off is 0,05 quantile of model + 1/2 semitones
150                 select te.recordedPitch
151                 .recordedPitchTier = Down to PitchTier
152                 
153                 # Calculate Shimmer&Jitter
154                 select Sound Source
155                 .sound = selected("Sound")
156                 call createJitterShimmerContour .sound
157                 .jitterShimmer = selected("Sound")
158                 
159                 # Determine the low F0 part of the 3rd tone
160                 # Generate word with voiceless low 3rd tone
161                 .newPinyin$ = replace$(sgc_ToneProt.pinyin$, "3", "9", 0)
162                 call generateWord Pitch '.newPinyin$' 'sgc_ToneProt.register'
163                 select Pitch '.newPinyin$'
164                 .generatedWord9 = selected("Pitch")
165                 
166                 select Pitch 'sgc_ToneProt.pinyin$'
167                 .generatedWord3 = selected("Pitch")
168                 
169                 # Create a tier with low part of the third tone as intervals
170                 call generateWord TextGrid 'sgc_ToneProt.pinyin$' 'sgc_ToneProt.register'
171                 select TextGrid 'sgc_ToneProt.pinyin$'
172                 .pitchTextGrid = selected("TextGrid")
173                 .lowPresent = Count intervals where: 1, "is equal to", "3"
174                                 
175                 # DTW of .generatedWord9 with sourcePitch
176                 if .lowPresent > 0
177                         select te.recordedPitch
178                         plus .generatedWord3
179                         .dtw3 = noprogress To DTW... 24 10 yes yes no restriction
180                         Rename... DTW'sgc_ToneProt.pinyin$'
181                         .distance3 = Get distance (weighted)
182                         select te.recordedPitch
183                         plus .generatedWord9
184                         .dtw9 = noprogress To DTW... 24 10 yes yes no restriction
185                         Rename... DTW'.newPinyin$'
186                         .distance9 = Get distance (weighted)
187                         
188                         select .pitchTextGrid
189                         if .distance3 <= .distance9
190                                 plus .dtw3
191                         else
192                                 plus .dtw9
193                         endif
194                         .origTextGrid = To TextGrid (warp times)
195                         .numPitchIntervals = Get number of intervals: 1
196                         .syllableNum = 0
197                         for .i to .numPitchIntervals
198                                 select .origTextGrid
199                                 .label$ = Get label of interval: 1, .i
200                                 if index_regex(.label$, "[0-9]")
201                                         .syllableNum += 1
202                                 endif
203                                 if .label$ = "3"
204                                         select .origTextGrid
205                                         .threeStart = Get starting point: 1, .i
206                                         .threeEnd = Get end point: 1, .i
207                                         .lowStart = .threeStart + (.threeEnd - .threeStart)/3
208                                         .lowEnd = .threeStart + 5*(.threeEnd - .threeStart)/6
209                                         select .jitterShimmer
210                                         .mean = Get mean: 0, .lowStart, .lowEnd
211                                         if .mean > .creakCO
212                                                 toneProt.creakyThree$ = left$(toneProt.creakyThree$, .syllableNum-1) + "3" + right$(toneProt.creakyThree$, toneScript.syllableCount-.syllableNum);
213                                         endif
214                                 endif
215                         endfor
216                         select .dtw3
217                         plus .dtw9
218                         Remove
219                 endif
220                 # Clean up
221                 select .jitterShimmer
222                 plus .pitchTextGrid
223                 plus .recordedPitchTier
224                 plus .generatedWord9
225                 Remove
226         endif
228         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
229         select te.recordedPitch
230         upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
231         lowerCutOff = sgc_ToneProt.upperRegisterInput/4
232         Formula... if self > 'upperCutOff' then -1 else self endif
233         Formula... if self < 'lowerCutOff' then -1 else self endif
235         # Get range and top
236         select te.recordedPitch
237         maximumRecFzero = Get quantile... 0 0 0.95 Hertz
238         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
239         minimumRecFzero = Get quantile... 0 0 0.05 Hertz
240         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
241         if maximumRecFzero = undefined
242         # Determine what should be told to the student
243         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': ???"
244         for i from 1 to numberOfFeedbackRows
245                 select Table ToneFeedback
246                 .toneOne$ = Get value... 'i' T1
247                 .toneTwo$ = Get value... 'i' T2
248                 .toneText$ = Get value... 'i' Feedback
249                         .label$ = "Unknown"
251                 if .toneOne$ = "NoSound"
252                 .feedbackText$ = .toneText$
253                 endif
254         endfor
256         #exit Error, nothing recorded
257                 goto END
258         endif
259         recPitchRange = 2
260         if minimumRecFzero > 0
261            recPitchRange = maximumRecFzero / minimumRecFzero
262         endif
263         sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
264         sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
265         if sgc_ToneProt.newUpperRegister = undefined
266                 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
267         endif
268         if sgc_ToneProt.newToneRange = undefined
269                 sgc_ToneProt.newToneRange = 1
270         endif
272         sgc_ToneProt.registerUsed$ = "OK"
273         rangeUsed$ = "OK"
274         # Advanced speakers must not speak too High, or too "Dramatic"
275         # Beginning speakers also not too Low or too Narrow ranges
276         if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
277            sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
278            sgc_ToneProt.registerUsed$ = "High"
279         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
280            sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
281            sgc_ToneProt.registerUsed$ = "Low"
282         endif
283         
284         if sgc_ToneProt.newToneRange > highBoundaryFactor
285            sgc_ToneProt.newToneRange = highBoundaryFactor
286            rangeUsed$ = "Wide"
287         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
288                 # Don't do this for advanced speakers
289            sgc_ToneProt.newToneRange = lowBoundaryFactor
290            rangeUsed$ = "Narrow"
291         endif
293         # Duration 
294         if sgc_ToneProt.durationModel > spacing
295            speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
296         endif
298         # Round values
299         sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
301         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
302         select te.recordedPitch
303         upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
304         lowerCutOff = sgc_ToneProt.newUpperRegister/3
305         Formula... if self > 'upperCutOff' then -1 else self endif
306         Formula... if self < 'lowerCutOff' then -1 else self endif
308         # It is rather dangerous to kill Octave errors, so be careful
309         if killOctaveJumps > 0
310                 select te.recordedPitch
311         Rename... OldSourcePitch
312         Kill octave jumps
313         Rename... SourcePitch
314         te.recordedPitch = selected("Pitch")
315         select Pitch OldSourcePitch
316         Remove
317         endif
319         # It is good to have the lowest and highest pitch frequencies
320         select te.recordedPitch
321         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
322         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
324         # Clean up the old example pitch
325         select Pitch 'sgc_ToneProt.currentTestWord$'
326         Remove
328         # Do the tone recognition
329         .numSyllables = toneScript.syllableCount
330         sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
331         .skipSyllables = 0
332         while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
333                 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
334                 .skipSyllables += 1
335         endwhile
336         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
337         # Special cases
338         originalRecognizedWord$ = sgc_ToneProt.choiceReference$
339         if  sgc_ToneProt.ultraStrict = 0
340         # [23]3 is often misidentified as 23, 20 or 30
341         if rindex_regex(sgc_ToneProt.currentTestWord$, "[23][^0-9]+3") > 0
342                 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") > 0
343                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") - 1
344                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
345                         sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23]([^0-9]+)[023]", "\13\23", 1)
346                         endif
347                 endif
348                 if rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") > 0
349                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") - 1
350                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
351                         sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "([^0-9]+)[23]([^0-9]+)[023]", "\12\23", 1)
352                         endif
353                 endif
354             endif
355             
356         # First syllable: 2<->3 exchanges
357         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2") > 0
358                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+3") > 0
359                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)[36]", "\12", 0)
360                 endif
361         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+3") > 0
362                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+2") > 0
363                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)2", "\13", 0)
364                 endif
365         # A single second tone is often misidentified as a neutral tone, 
366         # A real neutral tone would be too low or too narrow and be discarded
367         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2$") > 0
368                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMinimum < timeMaximum
369                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
370                 endif
371         # A single fourth tone is often misidentified as a neutral tone, 
372         # A real neutral tone would be too low or too narrow and be discarded
373         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+4$") > 0
374                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMaximum < timeMinimum
375                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
376                 endif
377         endif
379         # 32 <-> 30 Sometimes this is recognized as 00
380         # A recognized 0/2 after a 3 can be a 2/0
381         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") > 0
382                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") - 1
383                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
384                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\12", 0)
385                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
386                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\22", 0)
387                 endif
388         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") > 0
389                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") - 1
390                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+2") > 0
391                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)2", "\10", 0)
392                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
393                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\20", 0)
394                         endif
395         endif
396         
397         # 31 <-> 30 
398         # A recognized 0 after a 3 can be a 1
399         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") > 0
400                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") - 1
401                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
402                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\11", 0)
403                 endif
404                 endif
405         
406         # 40 <-> 42
407         # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
408         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") > 0
409                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") - 1
410                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0") > 0
411                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0", "\12", 0)
412                 endif
413         endif
414         
415                 # 404 <-> 414
416         # A recognized 0 between two tones 4 can be a 1
417         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") > 0
418                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") - 1
419                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0[^0-9]+4") > 0
420                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0([^0-9]+4)", "\11\2", 0)
421                 endif
422         endif
423         
424         endif
425         
426         # If wrong, then undo all changes
427         if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
428         sgc_ToneProt.choiceReference$ = originalRecognizedWord$
429         endif
431         sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
433         ###############################################
434         #
435         # Report
436         #
437         ###############################################
438         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$'"
439         if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
440            result$ = "Correct:"+result$
441         else
442            result$ = "Wrong:"+result$
443         endif
445         # Initialize result texts
446         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': "
447         .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
448         .feedbackText$ = "----"
450         # Separate tone from pronunciation errors
451         currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
452         choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
454         # Determine what should be told to the student
455         if sgc_ToneProt.registerUsed$ = "Low"
456         .recognitionText$ = .recognitionText$ + "???"
457         for i from 1 to numberOfFeedbackRows
458                 select Table ToneFeedback
459                 .toneOne$ = Get value... 'i' T1
460                 .toneTwo$ = Get value... 'i' T2
461                 .toneText$ = Get value... 'i' Feedback
463                 if .toneOne$ = "Low"
464                 .feedbackText$ = .toneText$
465                                 .label$ = .toneOne$
466                 endif
467         endfor
468         elsif rangeUsed$ = "Narrow"
469         .recognitionText$ = .recognitionText$ + "???"
470         for i from 1 to numberOfFeedbackRows
471                 select Table ToneFeedback
472                 .toneOne$ = Get value... 'i' T1
473                 .toneTwo$ = Get value... 'i' T2
474                 .toneText$ = Get value... 'i' Feedback
476                 if .toneOne$ = "Narrow"
477                 .feedbackText$ = .toneText$
478                                 .label$ = .toneOne$
479                 endif
480         endfor
481         elsif sgc_ToneProt.registerUsed$ = "High"
482         .recognitionText$ = .recognitionText$ + .choiceText$
483         for i from 1 to numberOfFeedbackRows
484                 select Table ToneFeedback
485                 .toneOne$ = Get value... 'i' T1
486                 .toneTwo$ = Get value... 'i' T2
487                 .toneText$ = Get value... 'i' Feedback
489                 if .toneOne$ = "High"
490                 .feedbackText$ = .toneText$
491                                 .label$ = .toneOne$
492                 endif
493         endfor
494         elsif rangeUsed$ = "Wide"
495         .recognitionText$ = .recognitionText$ + .choiceText$
496         for i from 1 to numberOfFeedbackRows
497                 select Table ToneFeedback
498                 .toneOne$ = Get value... 'i' T1
499                 .toneTwo$ = Get value... 'i' T2
500                 .toneText$ = Get value... 'i' Feedback
502                 if .toneOne$ = "Wide"
503                 .feedbackText$ = .toneText$
504                                 .label$ = .toneOne$
505                 endif
506         endfor
507         # Bad tones, first handle first syllable
508         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
509         .recognitionText$ = .recognitionText$ + .choiceText$
510         # First syllable
511         for i from 1 to numberOfFeedbackRows
512                 select Table ToneFeedback
513                 .toneOne$ = Get value... 'i' T1
514                 .toneTwo$ = Get value... 'i' T2
515                 .toneText$ = Get value... 'i' Feedback
517                 # 
518                 .feedbackText$ = ""
519                 if .toneOne$ = "6"
520                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
521                                 .label$ = .toneOne$
522                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
523                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
524                 endif
525         endfor
526         # Bad tones, then handle second syllable
527         elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
528         .recognitionText$ = .recognitionText$ + .choiceText$
529         # Last syllable
530         for i from 1 to numberOfFeedbackRows
531                 select Table ToneFeedback
532                 .toneOne$ = Get value... 'i' T1
533                 .toneTwo$ = Get value... 'i' T2
534                 .toneText$ = Get value... 'i' Feedback
536                 # 
537                 .feedbackText$ = ""
538                 if .toneOne$ = "6"
539                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
540                                 .label$ = .toneOne$
541                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
542                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
543                 endif
544         endfor
545         # Just plain wrong tones
546         elsif currentToneWord$ <> choiceToneReference$
547         .recognitionText$ = .recognitionText$ + .choiceText$
548         for i from 1 to numberOfFeedbackRows
549                 select Table ToneFeedback
550                 .toneOne$ = Get value... 'i' T1
551                 .toneTwo$ = Get value... 'i' T2
552                 .toneText$ = Get value... 'i' Feedback
554                 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
555                 .feedbackText$ = .toneText$
556                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
557                 .feedbackText$ = .toneText$
558                 elsif .toneOne$ = "Wrong"
559                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
560                                 .label$ = .toneOne$
561                 endif
562         endfor
563         # Correct
564         else
565         .recognitionText$ = .recognitionText$ + .choiceText$
566         for i from 1 to numberOfFeedbackRows
567                 select Table ToneFeedback
568                 .toneOne$ = Get value... 'i' T1
569                 .toneTwo$ = Get value... 'i' T2
570                 .toneText$ = Get value... 'i' Feedback
572                 if .toneOne$ = "Correct"
573                 .feedbackText$ = .toneText$
574                                 .label$ = .toneOne$
575                 endif
576         endfor
577         endif
579         label END
581         # Write out result
582         Create Table with column names... Feedback 3 Text
583         Set string value... 1 Text '.recognitionText$'
584         Set string value... 2 Text '.feedbackText$'
585         Set string value... 3 Text '.label$'
587         # Clean up
588         select Table ToneFeedback
589         Remove
591         # Show pitch tracks
592     freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
594         # Replace recorded sound with new sound
595         if not fileReadable(sgc_ToneProt.currentSound$)
596         select Sound 'sgc_ToneProt.currentSound$'
597                 Remove
598                 select Sound Source
599         Copy... 'sgc_ToneProt.currentSound$'
600         endif
603         # Clean up
604         select Sound Source
605         plus Pitch 'sgc_ToneProt.currentTestWord$'
606         Remove
607 endproc
610 # Use ~0.03 cutoff
611 procedure createJitterShimmerContour .sound
612         select .sound
613         .duration = Get total duration
614         .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
615         
616         # Create pointprocess
617         select .sound
618         .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
619         
620         .dT = 0.01
621         .w_half = 0.075/2
622         .t = 0.0
623         .sampleNum = 1
624         while .t < .duration - .dT
625                 select .pointProc
626                 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
627                 if .jitter = undefined
628                         .jitter = 0
629                 endif
630                 select .sound
631                 plus .pointProc
632                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
633                 if .shimmer = undefined
634                         .shimmer = 0
635                 endif
636                 select .jitshimSound
637                 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
638         
639                 .t += .dT
640                 .sampleNum += 1
641         endwhile
642         
643         select .pointProc
644         Remove
645         
646         select .jitshimSound
647 endproc
649 procedure createShimmerCPPcontour .sound
650         call createCPPContour .sound
651         .cpp = selected("Sound")
652         
653         select .sound
654         .duration = Get total duration
655         .shimmerCPPSound = Create Sound: "ShimmerCPP", 0, .duration, 100, "0"
656         
657         # Create pointprocess
658         select .sound
659         .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
660         
661         .dT = 0.01
662         .w_half = 0.075/2
663         .t = 0.0
664         .sampleNum = 1
665         while .t < .duration - .dT
666                 select .sound
667                 plus .pointProc
668                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
669                 if .shimmer = undefined
670                         .shimmer = 0
671                 endif
672                 select .cpp
673                 .cppValue = Get value at time: 0, .t, "Sinc70"
674                 if .cppValue = undefined or .cppValue < 1
675                         .cppValue = 1
676                 endif
677                 select .shimmerCPPSound
678                 Set value at sample number: 0, .sampleNum, 10* .shimmer / .cppValue
679         
680                 .t += .dT
681                 .sampleNum += 1
682         endwhile
683         
684         select .pointProc
685         plus .cpp
686         Remove
687         
688         select .shimmerCPPSound
689         
690 endproc
692 # Use ~? cutoff
693 procedure createShimmerContour .sound
694         select .sound
695         .duration = Get total duration
696         .shimmerSound = Create Sound: "Shimmer", 0, .duration, 100, "0"
697         
698         # Create pointprocess
699         select .sound
700         .pointProc = To PointProcess (periodic, cc): 60, 350
701         
702         .dT = 0.01
703         .w_half = 0.075/2
704         .t = 0.0
705         .sampleNum = 1
706         while .t < .duration - .dT
707                 select .sound
708                 plus .pointProc
709                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
710                 if .shimmer = undefined
711                         .shimmer = 0
712                 endif
713                 select .shimmerSound
714                 Set value at sample number: 0, .sampleNum, .shimmer
715         
716                 .t += .dT
717                 .sampleNum += 1
718         endwhile
719         
720         select .pointProc
721         Remove
722         
723         select .shimmerSound
724 endproc
727 # Use ~? cutoff
728 procedure createCPPContour .sound
729         select .sound
730         .dT = 0.01
731         
732         # Create pointprocess
733         select .sound
734         .duration = Get total duration
735         .pcg = To PowerCepstrogram: 60, .dT, 5000, 50
736         .cppTable = To Table (peak prominence): 60, 330, 0.05, "Parabolic", 0.001, 0, "Exponential decay", "Robust"
737         .nRows = Get number of rows
738         .lastTime = Get value: .nRows, "time"
739         .lastTime += .dT
740         while .lastTime < .duration - .dT
741                 Append row
742                 .nRows += 1
743                 Set numeric value: .nRows, "cpp", 0
744                 .lastTime += .dT
745         endwhile
746         .firstTime = Get value: 1, "time"
747         .firstTime -= .dT
748         while .firstTime >= .dT/2
749                 Insert row: 1
750                 Set numeric value: 1, "cpp", 0
751                 .firstTime -= .dT
752         endwhile
753         Remove column: "quefrency"
754         Remove column: "f0"
755         Remove column: "rnr"
756         Remove column: "time"
757         .cppTable2 = Transpose
758         .cppMatrix = Down to Matrix
759         .cppSound = To Sound (slice): 1
760         Override sampling frequency: 1/.dT
761         Shift times to: "start time", 0
762         Rename: "CPP"
763         
764         select .pcg
765         plus .cppTable
766         plus .cppTable2
767         plus .cppMatrix
768         Remove
769         
770         select .cppSound
771 endproc