Added segmentation for third tones as part of creaky voice detection, working version
[sgc2.git] / ToneProt / SGC_ToneProt.praat
blob3505fdc466e429438a3f7221917de4e799d054f7
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.creackyThree = 0
141         if index(sgc_ToneProt.pinyin$, "3")
142                 # Lowest point for tone 3 is 1 octave and 3st below the top line
143                 # The cut-off is 0,05 quantile of model + 1/2 semitones
144                 
145                 .creakCO = 0.030
146                 # Calculate Shimmer&Jitter
147                 select Sound Source
148                 .sound = selected("Sound")
149                 call createJitterShimmerContour .sound
150                 .jitterShimmer = selected("Sound")
151                 
152                 # Determine the low F0 part of the 3rd tone
153                 # Generate word with voiceless low 3rd tone
154                 .newPinyin$ = replace_regex$(sgc_ToneProt.pinyin$, "3(?=[^0-9]+3)", "2", 0)
155                 .newPinyin$ = replace$(.newPinyin$, "3", "9", 0)
156                 call generateWord Pitch '.newPinyin$' 'sgc_ToneProt.register'
157                 select Pitch '.newPinyin$'
158                 .generatedWord9 = selected("Pitch")
159                 
160                 select Pitch 'sgc_ToneProt.pinyin$'
161                 .generatedWord3 = selected("Pitch")
162                 
163                 # Create a tier with low part of the third tone as intervals
164                 call generateWord TextGrid 'sgc_ToneProt.pinyin$' 'sgc_ToneProt.register'
165                 .lowPresent = 0 
166                 select TextGrid 'sgc_ToneProt.pinyin$'
167                 .pitchTier = selected("TextGrid")
168                 .numIntervals = Get number of intervals: 1
169                 for .int to .numIntervals
170                         select .pitchTier
171                         .label$ = Get label of interval: 1, .int
172                         if .label$ = "3" or .label$ = "9"
173                                 .lowPresent += 1
174                                 Set interval text: 1, .int, "3onset"
175                                 .start = Get starting point: 1, .int
176                                 .end = Get end point: 1, .int
177                                 .newStart = .start + (.end - .start)/3
178                                 .newEnd = .start + 5*(.end - .start)/6
179                         # Add interval to TextGrid
180                                 select .pitchTier
181                                 Insert boundary: 1, .newStart
182                                 Insert boundary: 1, .newEnd
183                                 Set interval text: 1, .int+1, "3Low"
184                                 Set interval text: 1, .int+2, "3offset"
185                                 # Dangerous stuff!
186                                 .int += 2
187                         endif
188                 endfor
189                                 
190                 # DTW of .generatedWord9 with sourcePitch
191                 if .lowPresent > 0
192                         select te.recordedPitch
193                         plus .generatedWord3
194                         .dtw3 = noprogress To DTW... 24 10 yes yes no restriction
195                         Rename... DTW'.generatedWord9'
196                         .distance3 = Get distance (weighted)
197                         select te.recordedPitch
198                         plus .generatedWord9
199                         .dtw9 = noprogress To DTW... 24 10 yes yes no restriction
200                         Rename... DTW'.generatedWord9'
201                         .distance9 = Get distance (weighted)
202                         
203                         .dtw = .dtw3
204                         if .dtw3 > .dtw9
205                                 .dtw = .dtw9
206                         endif
207                         select .pitchTier
208                         .numPitchIntervals = Get number of intervals: 1
209                         for .i to .numPitchIntervals
210                                 select .pitchTier
211                                 .label$ = Get label of interval: 1, .i
212                                 if .label$ = "3Low"
213                                         .modelStart = Get starting point: 1, .i
214                                         .modelEnd = Get end point: 1, .i
215                                         select .dtw
216                                         .origStart = Get x time from y time: .modelStart
217                                         .origEnd = Get x time from y time: .modelEnd
218                                         select .jitterShimmer
219                                         .mean = Get mean: 0, .origStart, .origEnd
220                                         if .mean > .creakCO
221                                                 toneProt.creackyThree += 1;
222                                         endif
223                                 endif
224                         endfor
225                         select .dtw3
226                         plus .dtw9
227                         Remove
228                         
229                         # Weigh creacky voice by number of syllables
230                         toneProt.creackyThree /= toneScript.syllableCount
231                 endif
233                 # Clean up
234                 select .jitterShimmer
235                 plus .pitchTier
236                 plus .generatedWord9
237                 Remove
238         endif
240         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
241         select te.recordedPitch
242         upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
243         lowerCutOff = sgc_ToneProt.upperRegisterInput/4
244         Formula... if self > 'upperCutOff' then -1 else self endif
245         Formula... if self < 'lowerCutOff' then -1 else self endif
247         # Get range and top
248         select te.recordedPitch
249         maximumRecFzero = Get quantile... 0 0 0.95 Hertz
250         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
251         minimumRecFzero = Get quantile... 0 0 0.05 Hertz
252         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
253         if maximumRecFzero = undefined
254         # Determine what should be told to the student
255         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': ???"
256         for i from 1 to numberOfFeedbackRows
257                 select Table ToneFeedback
258                 .toneOne$ = Get value... 'i' T1
259                 .toneTwo$ = Get value... 'i' T2
260                 .toneText$ = Get value... 'i' Feedback
261                         .label$ = "Unknown"
263                 if .toneOne$ = "NoSound"
264                 .feedbackText$ = .toneText$
265                 endif
266         endfor
268         #exit Error, nothing recorded
269                 goto END
270         endif
271         recPitchRange = 2
272         if minimumRecFzero > 0
273            recPitchRange = maximumRecFzero / minimumRecFzero
274         endif
275         sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
276         sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
277         if sgc_ToneProt.newUpperRegister = undefined
278                 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
279         endif
280         if sgc_ToneProt.newToneRange = undefined
281                 sgc_ToneProt.newToneRange = 1
282         endif
284         sgc_ToneProt.registerUsed$ = "OK"
285         rangeUsed$ = "OK"
286         # Advanced speakers must not speak too High, or too "Dramatic"
287         # Beginning speakers also not too Low or too Narrow ranges
288         if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
289            sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
290            sgc_ToneProt.registerUsed$ = "High"
291         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
292            sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
293            sgc_ToneProt.registerUsed$ = "Low"
294         endif
295         
296         if sgc_ToneProt.newToneRange > highBoundaryFactor
297            sgc_ToneProt.newToneRange = highBoundaryFactor
298            rangeUsed$ = "Wide"
299         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
300                 # Don't do this for advanced speakers
301            sgc_ToneProt.newToneRange = lowBoundaryFactor
302            rangeUsed$ = "Narrow"
303         endif
305         # Duration 
306         if sgc_ToneProt.durationModel > spacing
307            speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
308         endif
310         # Round values
311         sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
313         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
314         select te.recordedPitch
315         upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
316         lowerCutOff = sgc_ToneProt.newUpperRegister/3
317         Formula... if self > 'upperCutOff' then -1 else self endif
318         Formula... if self < 'lowerCutOff' then -1 else self endif
320         # It is rather dangerous to kill Octave errors, so be careful
321         if killOctaveJumps > 0
322                 select te.recordedPitch
323         Rename... OldSourcePitch
324         Kill octave jumps
325         Rename... SourcePitch
326         te.recordedPitch = selected("Pitch")
327         select Pitch OldSourcePitch
328         Remove
329         endif
331         # It is good to have the lowest and highest pitch frequencies
332         select te.recordedPitch
333         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
334         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
336         # Clean up the old example pitch
337         select Pitch 'sgc_ToneProt.currentTestWord$'
338         Remove
340         # Do the tone recognition
341         .numSyllables = toneScript.syllableCount
342         sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
343         .skipSyllables = 0
344         while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
345                 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
346                 .skipSyllables += 1
347         endwhile
348         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
349         # Special cases
350         originalRecognizedWord$ = sgc_ToneProt.choiceReference$
351         if  sgc_ToneProt.ultraStrict = 0
352         # [23]3 is often misidentified as 23, 20 or 30
353         if rindex_regex(sgc_ToneProt.currentTestWord$, "[23][^0-9]+3") > 0
354                 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") > 0
355                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") - 1
356                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
357                         sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23]([^0-9]+)[023]", "\13\23", 1)
358                         endif
359                 endif
360                 if rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") > 0
361                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") - 1
362                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
363                         sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "([^0-9]+)[23]([^0-9]+)[023]", "\12\23", 1)
364                         endif
365                 endif
366             endif
367             
368         # First syllable: 2<->3 exchanges
369         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2") > 0
370                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+3") > 0
371                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)[36]", "\12", 0)
372                 endif
373         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+3") > 0
374                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+2") > 0
375                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)2", "\13", 0)
376                 endif
377         # A single second tone is often misidentified as a neutral tone, 
378         # A real neutral tone would be too low or too narrow and be discarded
379         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2$") > 0
380                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMinimum < timeMaximum
381                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
382                 endif
383         # A single fourth tone is often misidentified as a neutral tone, 
384         # A real neutral tone would be too low or too narrow and be discarded
385         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+4$") > 0
386                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMaximum < timeMinimum
387                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
388                 endif
389         endif
391         # 32 <-> 30 Sometimes this is recognized as 00
392         # A recognized 0/2 after a 3 can be a 2/0
393         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") > 0
394                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") - 1
395                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
396                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\12", 0)
397                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
398                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\22", 0)
399                 endif
400         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") > 0
401                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") - 1
402                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+2") > 0
403                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)2", "\10", 0)
404                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
405                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\20", 0)
406                         endif
407         endif
408         
409         # 31 <-> 30 
410         # A recognized 0 after a 3 can be a 1
411         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") > 0
412                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") - 1
413                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
414                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\11", 0)
415                 endif
416                 endif
417         
418         # 40 <-> 42
419         # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
420         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") > 0
421                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") - 1
422                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0") > 0
423                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0", "\12", 0)
424                 endif
425         endif
426         
427                 # 404 <-> 414
428         # A recognized 0 between two tones 4 can be a 1
429         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") > 0
430                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") - 1
431                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0[^0-9]+4") > 0
432                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0([^0-9]+4)", "\11\2", 0)
433                 endif
434         endif
435         
436         endif
437         
438         # If wrong, then undo all changes
439         if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
440         sgc_ToneProt.choiceReference$ = originalRecognizedWord$
441         endif
443         sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
445         ###############################################
446         #
447         # Report
448         #
449         ###############################################
450         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$'"
451         if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
452            result$ = "Correct:"+result$
453         else
454            result$ = "Wrong:"+result$
455         endif
457         # Initialize result texts
458         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': "
459         .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
460         .feedbackText$ = "----"
462         # Separate tone from pronunciation errors
463         currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
464         choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
466         # Determine what should be told to the student
467         if sgc_ToneProt.registerUsed$ = "Low"
468         .recognitionText$ = .recognitionText$ + "???"
469         for i from 1 to numberOfFeedbackRows
470                 select Table ToneFeedback
471                 .toneOne$ = Get value... 'i' T1
472                 .toneTwo$ = Get value... 'i' T2
473                 .toneText$ = Get value... 'i' Feedback
475                 if .toneOne$ = "Low"
476                 .feedbackText$ = .toneText$
477                                 .label$ = .toneOne$
478                 endif
479         endfor
480         elsif rangeUsed$ = "Narrow"
481         .recognitionText$ = .recognitionText$ + "???"
482         for i from 1 to numberOfFeedbackRows
483                 select Table ToneFeedback
484                 .toneOne$ = Get value... 'i' T1
485                 .toneTwo$ = Get value... 'i' T2
486                 .toneText$ = Get value... 'i' Feedback
488                 if .toneOne$ = "Narrow"
489                 .feedbackText$ = .toneText$
490                                 .label$ = .toneOne$
491                 endif
492         endfor
493         elsif sgc_ToneProt.registerUsed$ = "High"
494         .recognitionText$ = .recognitionText$ + .choiceText$
495         for i from 1 to numberOfFeedbackRows
496                 select Table ToneFeedback
497                 .toneOne$ = Get value... 'i' T1
498                 .toneTwo$ = Get value... 'i' T2
499                 .toneText$ = Get value... 'i' Feedback
501                 if .toneOne$ = "High"
502                 .feedbackText$ = .toneText$
503                                 .label$ = .toneOne$
504                 endif
505         endfor
506         elsif rangeUsed$ = "Wide"
507         .recognitionText$ = .recognitionText$ + .choiceText$
508         for i from 1 to numberOfFeedbackRows
509                 select Table ToneFeedback
510                 .toneOne$ = Get value... 'i' T1
511                 .toneTwo$ = Get value... 'i' T2
512                 .toneText$ = Get value... 'i' Feedback
514                 if .toneOne$ = "Wide"
515                 .feedbackText$ = .toneText$
516                                 .label$ = .toneOne$
517                 endif
518         endfor
519         # Bad tones, first handle first syllable
520         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
521         .recognitionText$ = .recognitionText$ + .choiceText$
522         # First syllable
523         for i from 1 to numberOfFeedbackRows
524                 select Table ToneFeedback
525                 .toneOne$ = Get value... 'i' T1
526                 .toneTwo$ = Get value... 'i' T2
527                 .toneText$ = Get value... 'i' Feedback
529                 # 
530                 .feedbackText$ = ""
531                 if .toneOne$ = "6"
532                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
533                                 .label$ = .toneOne$
534                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
535                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
536                 endif
537         endfor
538         # Bad tones, then handle second syllable
539         elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
540         .recognitionText$ = .recognitionText$ + .choiceText$
541         # Last syllable
542         for i from 1 to numberOfFeedbackRows
543                 select Table ToneFeedback
544                 .toneOne$ = Get value... 'i' T1
545                 .toneTwo$ = Get value... 'i' T2
546                 .toneText$ = Get value... 'i' Feedback
548                 # 
549                 .feedbackText$ = ""
550                 if .toneOne$ = "6"
551                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
552                                 .label$ = .toneOne$
553                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
554                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
555                 endif
556         endfor
557         # Just plain wrong tones
558         elsif currentToneWord$ <> choiceToneReference$
559         .recognitionText$ = .recognitionText$ + .choiceText$
560         for i from 1 to numberOfFeedbackRows
561                 select Table ToneFeedback
562                 .toneOne$ = Get value... 'i' T1
563                 .toneTwo$ = Get value... 'i' T2
564                 .toneText$ = Get value... 'i' Feedback
566                 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
567                 .feedbackText$ = .toneText$
568                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
569                 .feedbackText$ = .toneText$
570                 elsif .toneOne$ = "Wrong"
571                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
572                                 .label$ = .toneOne$
573                 endif
574         endfor
575         # Correct
576         else
577         .recognitionText$ = .recognitionText$ + .choiceText$
578         for i from 1 to numberOfFeedbackRows
579                 select Table ToneFeedback
580                 .toneOne$ = Get value... 'i' T1
581                 .toneTwo$ = Get value... 'i' T2
582                 .toneText$ = Get value... 'i' Feedback
584                 if .toneOne$ = "Correct"
585                 .feedbackText$ = .toneText$
586                                 .label$ = .toneOne$
587                 endif
588         endfor
589         endif
591         label END
593         # Write out result
594         Create Table with column names... Feedback 3 Text
595         Set string value... 1 Text '.recognitionText$'
596         Set string value... 2 Text '.feedbackText$'
597         Set string value... 3 Text '.label$'
599         # Clean up
600         select Table ToneFeedback
601         Remove
603         # Show pitch tracks
604     freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
606         # Replace recorded sound with new sound
607         if not fileReadable(sgc_ToneProt.currentSound$)
608         select Sound 'sgc_ToneProt.currentSound$'
609                 Remove
610                 select Sound Source
611         Copy... 'sgc_ToneProt.currentSound$'
612         endif
615         # Clean up
616         select Sound Source
617         plus Pitch 'sgc_ToneProt.currentTestWord$'
618         Remove
619 endproc
622 # Use ~0.03 cutoff
623 procedure createJitterShimmerContour .sound
624         select .sound
625         .duration = Get total duration
626         .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
627         
628         # Create pointprocess
629         select .sound
630         .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
631         
632         .dT = 0.01
633         .w_half = 0.075/2
634         .t = 0.0
635         .sampleNum = 1
636         while .t < .duration - .dT
637                 select .pointProc
638                 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
639                 if .jitter = undefined
640                         .jitter = 0
641                 endif
642                 select .sound
643                 plus .pointProc
644                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
645                 if .shimmer = undefined
646                         .shimmer = 0
647                 endif
648                 select .jitshimSound
649                 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
650         
651                 .t += .dT
652                 .sampleNum += 1
653         endwhile
654         
655         select .pointProc
656         Remove
657         
658         select .jitshimSound
659 endproc
661 procedure createShimmerCPPcontour .sound
662         call createCPPContour .sound
663         .cpp = selected("Sound")
664         
665         select .sound
666         .duration = Get total duration
667         .shimmerCPPSound = Create Sound: "ShimmerCPP", 0, .duration, 100, "0"
668         
669         # Create pointprocess
670         select .sound
671         .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
672         
673         .dT = 0.01
674         .w_half = 0.075/2
675         .t = 0.0
676         .sampleNum = 1
677         while .t < .duration - .dT
678                 select .sound
679                 plus .pointProc
680                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
681                 if .shimmer = undefined
682                         .shimmer = 0
683                 endif
684                 select .cpp
685                 .cppValue = Get value at time: 0, .t, "Sinc70"
686                 if .cppValue = undefined or .cppValue < 1
687                         .cppValue = 1
688                 endif
689                 select .shimmerCPPSound
690                 Set value at sample number: 0, .sampleNum, 10* .shimmer / .cppValue
691         
692                 .t += .dT
693                 .sampleNum += 1
694         endwhile
695         
696         select .pointProc
697         plus .cpp
698         Remove
699         
700         select .shimmerCPPSound
701         
702 endproc
704 # Use ~? cutoff
705 procedure createShimmerContour .sound
706         select .sound
707         .duration = Get total duration
708         .shimmerSound = Create Sound: "Shimmer", 0, .duration, 100, "0"
709         
710         # Create pointprocess
711         select .sound
712         .pointProc = To PointProcess (periodic, cc): 60, 350
713         
714         .dT = 0.01
715         .w_half = 0.075/2
716         .t = 0.0
717         .sampleNum = 1
718         while .t < .duration - .dT
719                 select .sound
720                 plus .pointProc
721                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
722                 if .shimmer = undefined
723                         .shimmer = 0
724                 endif
725                 select .shimmerSound
726                 Set value at sample number: 0, .sampleNum, .shimmer
727         
728                 .t += .dT
729                 .sampleNum += 1
730         endwhile
731         
732         select .pointProc
733         Remove
734         
735         select .shimmerSound
736 endproc
739 # Use ~? cutoff
740 procedure createCPPContour .sound
741         select .sound
742         .dT = 0.01
743         
744         # Create pointprocess
745         select .sound
746         .duration = Get total duration
747         .pcg = To PowerCepstrogram: 60, .dT, 5000, 50
748         .cppTable = To Table (peak prominence): 60, 330, 0.05, "Parabolic", 0.001, 0, "Exponential decay", "Robust"
749         .nRows = Get number of rows
750         .lastTime = Get value: .nRows, "time"
751         .lastTime += .dT
752         while .lastTime < .duration - .dT
753                 Append row
754                 .nRows += 1
755                 Set numeric value: .nRows, "cpp", 0
756                 .lastTime += .dT
757         endwhile
758         .firstTime = Get value: 1, "time"
759         .firstTime -= .dT
760         while .firstTime >= .dT/2
761                 Insert row: 1
762                 Set numeric value: 1, "cpp", 0
763                 .firstTime -= .dT
764         endwhile
765         Remove column: "quefrency"
766         Remove column: "f0"
767         Remove column: "rnr"
768         Remove column: "time"
769         .cppTable2 = Transpose
770         .cppMatrix = Down to Matrix
771         .cppSound = To Sound (slice): 1
772         Override sampling frequency: 1/.dT
773         Shift times to: "start time", 0
774         Rename: "CPP"
775         
776         select .pcg
777         plus .cppTable
778         plus .cppTable2
779         plus .cppMatrix
780         Remove
781         
782         select .cppSound
783 endproc