Added framework for creaky voice pitch change
[sgc2.git] / ToneProt / SGC_ToneProt.praat
blob4b3b4f0f97156977f3dd8e6b76a89e23d6651085
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         if index(sgc_ToneProt.pinyin$, "3")
141                 .creakCO = 0.03
142                 .delta = 0.0000001
143                 # Reassign creacky voice parts
144                 # Calculate Shimmer&Jitter
145                 select Sound Source
146                 .sound = selected("Sound")
147                 call createJitterShimmerContour .sound
148                 .jitterShimmer = selected("Sound")
149                 .numSamples = Get number of samples
150                 .voiceTier = To TextGrid: "voice", ""
151                 
152                 # Create a tier with creaky voice intervals
153                 select .jitterShimmer
154                 .prevV = Get value at sample number: 0, 1
155                 .prevT = 0
156                 .numIntervals = 1
157                 select .voiceTier
158                 if .prevV > .creakCO
159                         Set interval text: 1, .numIntervals, "creaky"
160                 elsif .prevV <= .creakCO
161                         Set interval text: 1, .numIntervals, "normal"
162                 endif
163                 for .s to .numSamples
164                         select .jitterShimmer
165                         .frameT = Get time from sample number: .s
166                         .value = Get value at sample number: 0, .s
167                         if .value > .creakCO and .prevV <= .creakCO
168                                 select .voiceTier
169                                 Insert boundary: 1, (.frameT+.prevT)/2
170                                 .numIntervals += 1
171                                 Set interval text: 1, .numIntervals, "creaky"
172                         elsif .value <= .creakCO and .prevV > .creakCO
173                                 select .voiceTier
174                                 Insert boundary: 1, (.frameT+.prevT)/2
175                                 .numIntervals += 1
176                                 Set interval text: 1, .numIntervals, "normal"
177                         endif
178                         .prevV = .value
179                         .prevT = .frameT
180                 endfor
181                 
182                 # Create PitchTier and Voicing intervals
183                 select te.recordedPitch
184                 .recordedPitchTier = Down to PitchTier
185                 select te.recordedPitch
186                 .origPointProcess = To PointProcess
187                 .vuvTier = To TextGrid (vuv): 0.02, 0.01
188                 
189                 # Replace creaky voice with low pitch. Low pitch floor is minimumModelFzero
190                 select .voiceTier
191                 .numIntervals = Get number of intervals: 1
192                 for .v to .numIntervals
193                         select .voiceTier
194                         .label$ = Get label of interval: 1, .v
195                         if .label$ = "creaky"
196                                 .start = Get starting point: 1, .v
197                                 .end = Get end point: 1, .v
198                                 if .end > .start
199                                         select .recordedPitchTier
200                                         # Store begin and endvalues of pitch
201                                         .startValue = Get value at time: .start
202                                         .endValue = Get value at time: .end
203                                         
204                                         # Remove all pitch points between start and end
205                                         Remove points between: .start, .end
206                                         
207                                         # Add begin and en values at start and end times
208                                         Add point: .start, .startValue
209                                         Add point: .end, .endValue
210                                         
211                                         # Interpolate or add low pitch values in between start and end times
212                                         #Add point: .start + .delta, minimumModelFzero
213                                         #Add point: .end - .delta, minimumModelFzero
214                                 endif
215                                 
216                         endif
217                 endfor
218                 
219                 # Replace voiceles intervals with 0 Pitch
220                 select .vuvTier
221                 .numIntervals = Get number of intervals: 1
222                 for .v to .numIntervals
223                         select .vuvTier
224                         .label$ = Get label of interval: 1, .v
225                         if .label$ = "U"
226                                 .start = Get starting point: 1, .v
227                                 .end = Get end point: 1, .v
228                                 if .end > .start
229                                         select .recordedPitchTier
230                                         # Store begin and endvalues of pitch
231                                         .startValue = Get value at time: .start
232                                         .endValue = Get value at time: .end
233                                         
234                                         # Remove all pitch points between start and end
235                                         Remove points between: .start, .end
236                                         
237                                         # Add begin and en values at start and end times
238                                         Add point: .start, .startValue
239                                         Add point: .end, .endValue
240                                         
241                                         # Add unvoced values in between start and end times
242                                         Add point: .start + .delta, 0
243                                         Add point: .end - .delta, 0
244                                 endif
245                                 
246                         endif
247                 endfor
248                 
249                 # Convert new Pitchtier to Pitch object (based on old Pitch object)
250                 select te.recordedPitch
251                 plus .recordedPitchTier
252                 .newPitch = To Pitch
253                 
254                 # Move new Pitch object to old Pitch object ID
255                 select te.recordedPitch
256                 .pitchName$ = selected$("Pitch")
257                 Remove
258                 te.recordedPitch = .newPitch
259                 select .newPitch
260                 Rename: .pitchName$
261                 .newPitch = 0
263 pause           
264                 # Clean up
265                 select .jitterShimmer
266                 plus .origPointProcess
267                 plus .vuvTier
268                 plus .recordedPitchTier
269                 Remove
270 pause
271         endif
273         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
274         select te.recordedPitch
275         upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
276         lowerCutOff = sgc_ToneProt.upperRegisterInput/4
277         Formula... if self > 'upperCutOff' then -1 else self endif
278         Formula... if self < 'lowerCutOff' then -1 else self endif
280         # Get range and top
281         select te.recordedPitch
282         maximumRecFzero = Get quantile... 0 0 0.95 Hertz
283         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
284         minimumRecFzero = Get quantile... 0 0 0.05 Hertz
285         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
286         if maximumRecFzero = undefined
287         # Determine what should be told to the student
288         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': ???"
289         for i from 1 to numberOfFeedbackRows
290                 select Table ToneFeedback
291                 .toneOne$ = Get value... 'i' T1
292                 .toneTwo$ = Get value... 'i' T2
293                 .toneText$ = Get value... 'i' Feedback
294                         .label$ = "Unknown"
296                 if .toneOne$ = "NoSound"
297                 .feedbackText$ = .toneText$
298                 endif
299         endfor
301         #exit Error, nothing recorded
302                 goto END
303         endif
304         recPitchRange = 2
305         if minimumRecFzero > 0
306            recPitchRange = maximumRecFzero / minimumRecFzero
307         endif
308         sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
309         sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
310         if sgc_ToneProt.newUpperRegister = undefined
311                 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
312         endif
313         if sgc_ToneProt.newToneRange = undefined
314                 sgc_ToneProt.newToneRange = 1
315         endif
317         sgc_ToneProt.registerUsed$ = "OK"
318         rangeUsed$ = "OK"
319         # Advanced speakers must not speak too High, or too "Dramatic"
320         # Beginning speakers also not too Low or too Narrow ranges
321         if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
322            sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
323            sgc_ToneProt.registerUsed$ = "High"
324         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
325            sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
326            sgc_ToneProt.registerUsed$ = "Low"
327         endif
328         
329         if sgc_ToneProt.newToneRange > highBoundaryFactor
330            sgc_ToneProt.newToneRange = highBoundaryFactor
331            rangeUsed$ = "Wide"
332         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
333                 # Don't do this for advanced speakers
334            sgc_ToneProt.newToneRange = lowBoundaryFactor
335            rangeUsed$ = "Narrow"
336         endif
338         # Duration 
339         if sgc_ToneProt.durationModel > spacing
340            speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
341         endif
343         # Round values
344         sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
346         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
347         select te.recordedPitch
348         upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
349         lowerCutOff = sgc_ToneProt.newUpperRegister/3
350         Formula... if self > 'upperCutOff' then -1 else self endif
351         Formula... if self < 'lowerCutOff' then -1 else self endif
353         # It is rather dangerous to kill Octave errors, so be careful
354         if killOctaveJumps > 0
355                 select te.recordedPitch
356         Rename... OldSourcePitch
357         Kill octave jumps
358         Rename... SourcePitch
359         te.recordedPitch = selected("Pitch")
360         select Pitch OldSourcePitch
361         Remove
362         endif
364         # It is good to have the lowest and highest pitch frequencies
365         select te.recordedPitch
366         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
367         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
369         # Clean up the old example pitch
370         select Pitch 'sgc_ToneProt.currentTestWord$'
371         Remove
373         # Do the tone recognition
374         .numSyllables = toneScript.syllableCount
375         sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
376         .skipSyllables = 0
377         while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
378                 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
379                 .skipSyllables += 1
380         endwhile
381         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
382         # Special cases
383         originalRecognizedWord$ = sgc_ToneProt.choiceReference$
384         if  sgc_ToneProt.ultraStrict = 0
385         # [23]3 is often misidentified as 23, 20 or 30
386         if rindex_regex(sgc_ToneProt.currentTestWord$, "[23][^0-9]+3") > 0
387                 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") > 0
388                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") - 1
389                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
390                         sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23]([^0-9]+)[023]", "\13\23", 1)
391                         endif
392                 endif
393                 if rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") > 0
394                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") - 1
395                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
396                         sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "([^0-9]+)[23]([^0-9]+)[023]", "\12\23", 1)
397                         endif
398                 endif
399             endif
400             
401         # First syllable: 2<->3 exchanges
402         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2") > 0
403                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+3") > 0
404                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)[36]", "\12", 0)
405                 endif
406         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+3") > 0
407                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+2") > 0
408                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)2", "\13", 0)
409                 endif
410         # A single second tone is often misidentified as a neutral tone, 
411         # A real neutral tone would be too low or too narrow and be discarded
412         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2$") > 0
413                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMinimum < timeMaximum
414                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
415                 endif
416         # A single fourth tone is often misidentified as a neutral tone, 
417         # A real neutral tone would be too low or too narrow and be discarded
418         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+4$") > 0
419                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMaximum < timeMinimum
420                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
421                 endif
422         endif
424         # 32 <-> 30 Sometimes this is recognized as 00
425         # A recognized 0/2 after a 3 can be a 2/0
426         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") > 0
427                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") - 1
428                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
429                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\12", 0)
430                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
431                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\22", 0)
432                 endif
433         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") > 0
434                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") - 1
435                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+2") > 0
436                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)2", "\10", 0)
437                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
438                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\20", 0)
439                         endif
440         endif
441         
442         # 31 <-> 30 
443         # A recognized 0 after a 3 can be a 1
444         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") > 0
445                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") - 1
446                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
447                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\11", 0)
448                 endif
449                 endif
450         
451         # 40 <-> 42
452         # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
453         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") > 0
454                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") - 1
455                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0") > 0
456                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0", "\12", 0)
457                 endif
458         endif
459         
460                 # 404 <-> 414
461         # A recognized 0 between two tones 4 can be a 1
462         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") > 0
463                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") - 1
464                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0[^0-9]+4") > 0
465                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0([^0-9]+4)", "\11\2", 0)
466                 endif
467         endif
468         
469         endif
470         
471         # If wrong, then undo all changes
472         if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
473         sgc_ToneProt.choiceReference$ = originalRecognizedWord$
474         endif
476         sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
478         ###############################################
479         #
480         # Report
481         #
482         ###############################################
483         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$'"
484         if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
485            result$ = "Correct:"+result$
486         else
487            result$ = "Wrong:"+result$
488         endif
490         # Initialize result texts
491         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': "
492         .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
493         .feedbackText$ = "----"
495         # Separate tone from pronunciation errors
496         currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
497         choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
499         # Determine what should be told to the student
500         if sgc_ToneProt.registerUsed$ = "Low"
501         .recognitionText$ = .recognitionText$ + "???"
502         for i from 1 to numberOfFeedbackRows
503                 select Table ToneFeedback
504                 .toneOne$ = Get value... 'i' T1
505                 .toneTwo$ = Get value... 'i' T2
506                 .toneText$ = Get value... 'i' Feedback
508                 if .toneOne$ = "Low"
509                 .feedbackText$ = .toneText$
510                                 .label$ = .toneOne$
511                 endif
512         endfor
513         elsif rangeUsed$ = "Narrow"
514         .recognitionText$ = .recognitionText$ + "???"
515         for i from 1 to numberOfFeedbackRows
516                 select Table ToneFeedback
517                 .toneOne$ = Get value... 'i' T1
518                 .toneTwo$ = Get value... 'i' T2
519                 .toneText$ = Get value... 'i' Feedback
521                 if .toneOne$ = "Narrow"
522                 .feedbackText$ = .toneText$
523                                 .label$ = .toneOne$
524                 endif
525         endfor
526         elsif sgc_ToneProt.registerUsed$ = "High"
527         .recognitionText$ = .recognitionText$ + .choiceText$
528         for i from 1 to numberOfFeedbackRows
529                 select Table ToneFeedback
530                 .toneOne$ = Get value... 'i' T1
531                 .toneTwo$ = Get value... 'i' T2
532                 .toneText$ = Get value... 'i' Feedback
534                 if .toneOne$ = "High"
535                 .feedbackText$ = .toneText$
536                                 .label$ = .toneOne$
537                 endif
538         endfor
539         elsif rangeUsed$ = "Wide"
540         .recognitionText$ = .recognitionText$ + .choiceText$
541         for i from 1 to numberOfFeedbackRows
542                 select Table ToneFeedback
543                 .toneOne$ = Get value... 'i' T1
544                 .toneTwo$ = Get value... 'i' T2
545                 .toneText$ = Get value... 'i' Feedback
547                 if .toneOne$ = "Wide"
548                 .feedbackText$ = .toneText$
549                                 .label$ = .toneOne$
550                 endif
551         endfor
552         # Bad tones, first handle first syllable
553         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
554         .recognitionText$ = .recognitionText$ + .choiceText$
555         # First syllable
556         for i from 1 to numberOfFeedbackRows
557                 select Table ToneFeedback
558                 .toneOne$ = Get value... 'i' T1
559                 .toneTwo$ = Get value... 'i' T2
560                 .toneText$ = Get value... 'i' Feedback
562                 # 
563                 .feedbackText$ = ""
564                 if .toneOne$ = "6"
565                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
566                                 .label$ = .toneOne$
567                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
568                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
569                 endif
570         endfor
571         # Bad tones, then handle second syllable
572         elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
573         .recognitionText$ = .recognitionText$ + .choiceText$
574         # Last syllable
575         for i from 1 to numberOfFeedbackRows
576                 select Table ToneFeedback
577                 .toneOne$ = Get value... 'i' T1
578                 .toneTwo$ = Get value... 'i' T2
579                 .toneText$ = Get value... 'i' Feedback
581                 # 
582                 .feedbackText$ = ""
583                 if .toneOne$ = "6"
584                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
585                                 .label$ = .toneOne$
586                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
587                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
588                 endif
589         endfor
590         # Just plain wrong tones
591         elsif currentToneWord$ <> choiceToneReference$
592         .recognitionText$ = .recognitionText$ + .choiceText$
593         for i from 1 to numberOfFeedbackRows
594                 select Table ToneFeedback
595                 .toneOne$ = Get value... 'i' T1
596                 .toneTwo$ = Get value... 'i' T2
597                 .toneText$ = Get value... 'i' Feedback
599                 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
600                 .feedbackText$ = .toneText$
601                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
602                 .feedbackText$ = .toneText$
603                 elsif .toneOne$ = "Wrong"
604                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
605                                 .label$ = .toneOne$
606                 endif
607         endfor
608         # Correct
609         else
610         .recognitionText$ = .recognitionText$ + .choiceText$
611         for i from 1 to numberOfFeedbackRows
612                 select Table ToneFeedback
613                 .toneOne$ = Get value... 'i' T1
614                 .toneTwo$ = Get value... 'i' T2
615                 .toneText$ = Get value... 'i' Feedback
617                 if .toneOne$ = "Correct"
618                 .feedbackText$ = .toneText$
619                                 .label$ = .toneOne$
620                 endif
621         endfor
622         endif
624         label END
626         # Write out result
627         Create Table with column names... Feedback 3 Text
628         Set string value... 1 Text '.recognitionText$'
629         Set string value... 2 Text '.feedbackText$'
630         Set string value... 3 Text '.label$'
632         # Clean up
633         select Table ToneFeedback
634         Remove
636         # Show pitch tracks
637     freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
639         # Replace recorded sound with new sound
640         if not fileReadable(sgc_ToneProt.currentSound$)
641         select Sound 'sgc_ToneProt.currentSound$'
642                 Remove
643                 select Sound Source
644         Copy... 'sgc_ToneProt.currentSound$'
645         endif
648         # Clean up
649         select Sound Source
650         plus Pitch 'sgc_ToneProt.currentTestWord$'
651         Remove
652 endproc
655 # Use ~0.03 cutoff
656 procedure createJitterShimmerContour .sound
657         select .sound
658         .duration = Get total duration
659         .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
660         
661         # Create pointprocess
662         select .sound
663         .pointProc = To PointProcess (periodic, cc): 60, 350
664         
665         .dT = 0.01
666         .w_half = 0.1/2
667         .t = 0.0
668         .sampleNum = 1
669         while .t < .duration - .dT
670                 select .pointProc
671                 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
672                 if .jitter = undefined
673                         .jitter = 0
674                 endif
675                 select .sound
676                 plus .pointProc
677                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
678                 if .shimmer = undefined
679                         .shimmer = 0
680                 endif
681                 select .jitshimSound
682                 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
683         
684                 .t += .dT
685                 .sampleNum += 1
686         endwhile
687         
688         select .pointProc
689         Remove
690         
691         select .jitshimSound
692 endproc