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