3 # SpeakGoodChinese: SGC_ToneRecognizer.praat processes student utterances
4 # and generates a report on their tone production
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
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.
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.
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
26 # include ToneRecognition.praat
27 # include ToneScript.praat
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
38 # Stick to the raw recognition results or not
39 sgc_ToneProt.ultraStrict = sgc_ToneProt.proficiency
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$'
46 call loadTable ToneFeedback_EN
48 Rename... ToneFeedback
49 numberOfFeedbackRows = Get number of rows
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$
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)
65 # Kill octave jumps: DANGEROUS
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
78 sgc_ToneProt.minimumPitch = 40
79 sgc_ToneProt.maximumPitch = 400
82 sgc_ToneProt.currentTestWord$ = sgc_ToneProt.pinyin$
84 .interPrecision = sgc_ToneProt.precision;
86 # Allow more "room" when there is a 3rd tone
87 if index(sgc_ToneProt.pinyin$, "3")
88 .interPrecision *= 4/3
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
98 sgc_ToneProt.upperRegisterInput = sgc_ToneProt.register
99 call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 1 1 CorrectPitch
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
108 if minimumModelFzero = undefined
109 minimumModelFzero = 0
111 sgc_ToneProt.modelPitchRange = 2
112 if minimumModelFzero > 0
113 sgc_ToneProt.modelPitchRange = maximumModelFzero / minimumModelFzero
115 sgc_ToneProt.modelPitchRange = 0
119 if fileReadable(sgc_ToneProt.currentSound$)
120 Read from file... 'sgc_ToneProt.currentSound$'
123 select Sound 'sgc_ToneProt.currentSound$'
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
135 ################################################################
137 # If there is a third tone, replace creaky voice by low pitch
139 ################################################################
140 toneProt.creakyThree$ = ""
141 for .s to toneScript.syllableCount
142 toneProt.creakyThree$ = toneProt.creakyThree$ + "."
144 if index(sgc_ToneProt.pinyin$, "3")
148 # Lowest point for tone 3 is 1 octave and 3st below the top line
149 # The cut-off is 0,05 quantile of model + 1/2 semitones
150 select te.recordedPitch
151 .recordedPitchTier = Down to PitchTier
153 # Calculate Shimmer&Jitter
155 .sound = selected("Sound")
156 call createJitterShimmerContour .sound
157 .jitterShimmer = selected("Sound")
159 # Determine the low F0 part of the 3rd tone
160 # Generate word with voiceless low 3rd tone
161 .newPinyin$ = replace$(sgc_ToneProt.pinyin$, "3", "9", 0)
162 call generateWord Pitch '.newPinyin$' 'sgc_ToneProt.register'
163 select Pitch '.newPinyin$'
164 .generatedWord9 = selected("Pitch")
166 select Pitch 'sgc_ToneProt.pinyin$'
167 .generatedWord3 = selected("Pitch")
169 # Create a tier with low part of the third tone as intervals
170 call generateWord TextGrid 'sgc_ToneProt.pinyin$' 'sgc_ToneProt.register'
171 select TextGrid 'sgc_ToneProt.pinyin$'
172 .pitchTextGrid = selected("TextGrid")
173 .lowPresent = Count intervals where: 1, "is equal to", "3"
175 # DTW of .generatedWord9 with sourcePitch
177 select te.recordedPitch
179 .dtw3 = noprogress To DTW... 24 10 yes yes no restriction
180 Rename... DTW'sgc_ToneProt.pinyin$'
181 .distance3 = Get distance (weighted)
182 select te.recordedPitch
184 .dtw9 = noprogress To DTW... 24 10 yes yes no restriction
185 Rename... DTW'.newPinyin$'
186 .distance9 = Get distance (weighted)
188 select .pitchTextGrid
189 if .distance3 <= .distance9
194 .origTextGrid = To TextGrid (warp times)
195 .numPitchIntervals = Get number of intervals: 1
197 for .i to .numPitchIntervals
199 .label$ = Get label of interval: 1, .i
200 if index_regex(.label$, "[0-9]")
205 .threeStart = Get starting point: 1, .i
206 .threeEnd = Get end point: 1, .i
207 .lowStart = .threeStart + (.threeEnd - .threeStart)/3
208 .lowEnd = .threeStart + 5*(.threeEnd - .threeStart)/6
209 select .jitterShimmer
210 .mean = Get mean: 0, .lowStart, .lowEnd
212 toneProt.creakyThree$ = left$(toneProt.creakyThree$, .syllableNum-1) + "3" + right$(toneProt.creakyThree$, toneScript.syllableCount-.syllableNum);
221 select .jitterShimmer
223 plus .recordedPitchTier
228 # Remove all pitch points outside a band around the upper sgc_ToneProt.register
229 select te.recordedPitch
230 upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
231 lowerCutOff = sgc_ToneProt.upperRegisterInput/4
232 Formula... if self > 'upperCutOff' then -1 else self endif
233 Formula... if self < 'lowerCutOff' then -1 else self endif
236 select te.recordedPitch
237 maximumRecFzero = Get quantile... 0 0 0.95 Hertz
238 timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
239 minimumRecFzero = Get quantile... 0 0 0.05 Hertz
240 timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
241 if maximumRecFzero = undefined
242 # Determine what should be told to the student
243 .recognitionText$ = "'sgc_ToneProt.currentTestWord$': ???"
244 for i from 1 to numberOfFeedbackRows
245 select Table ToneFeedback
246 .toneOne$ = Get value... 'i' T1
247 .toneTwo$ = Get value... 'i' T2
248 .toneText$ = Get value... 'i' Feedback
251 if .toneOne$ = "NoSound"
252 .feedbackText$ = .toneText$
256 #exit Error, nothing recorded
260 if minimumRecFzero > 0
261 recPitchRange = maximumRecFzero / minimumRecFzero
263 sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
264 sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
265 if sgc_ToneProt.newUpperRegister = undefined
266 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
268 if sgc_ToneProt.newToneRange = undefined
269 sgc_ToneProt.newToneRange = 1
272 sgc_ToneProt.registerUsed$ = "OK"
274 # Advanced speakers must not speak too High, or too "Dramatic"
275 # Beginning speakers also not too Low or too Narrow ranges
276 if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
277 sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
278 sgc_ToneProt.registerUsed$ = "High"
279 elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
280 sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
281 sgc_ToneProt.registerUsed$ = "Low"
284 if sgc_ToneProt.newToneRange > highBoundaryFactor
285 sgc_ToneProt.newToneRange = highBoundaryFactor
287 elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
288 # Don't do this for advanced speakers
289 sgc_ToneProt.newToneRange = lowBoundaryFactor
290 rangeUsed$ = "Narrow"
294 if sgc_ToneProt.durationModel > spacing
295 speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
299 sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
301 # Remove all pitch points outside a band around the upper sgc_ToneProt.register
302 select te.recordedPitch
303 upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
304 lowerCutOff = sgc_ToneProt.newUpperRegister/3
305 Formula... if self > 'upperCutOff' then -1 else self endif
306 Formula... if self < 'lowerCutOff' then -1 else self endif
308 # It is rather dangerous to kill Octave errors, so be careful
309 if killOctaveJumps > 0
310 select te.recordedPitch
311 Rename... OldSourcePitch
313 Rename... SourcePitch
314 te.recordedPitch = selected("Pitch")
315 select Pitch OldSourcePitch
319 # It is good to have the lowest and highest pitch frequencies
320 select te.recordedPitch
321 timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
322 timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
324 # Clean up the old example pitch
325 select Pitch 'sgc_ToneProt.currentTestWord$'
328 # Do the tone recognition
329 .numSyllables = toneScript.syllableCount
330 sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
332 while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
333 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
336 call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
338 originalRecognizedWord$ = sgc_ToneProt.choiceReference$
339 if sgc_ToneProt.ultraStrict = 0
340 # [23]3 is often misidentified as 23, 20 or 30
341 if rindex_regex(sgc_ToneProt.currentTestWord$, "[23][^0-9]+3") > 0
342 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") > 0
343 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") - 1
344 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
345 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23]([^0-9]+)[023]", "\13\23", 1)
348 if rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") > 0
349 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") - 1
350 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
351 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "([^0-9]+)[23]([^0-9]+)[023]", "\12\23", 1)
356 # First syllable: 2<->3 exchanges
357 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2") > 0
358 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+3") > 0
359 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)[36]", "\12", 0)
361 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+3") > 0
362 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+2") > 0
363 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)2", "\13", 0)
365 # A single second tone is often misidentified as a neutral tone,
366 # A real neutral tone would be too low or too narrow and be discarded
367 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2$") > 0
368 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMinimum < timeMaximum
369 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
371 # A single fourth tone is often misidentified as a neutral tone,
372 # A real neutral tone would be too low or too narrow and be discarded
373 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+4$") > 0
374 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMaximum < timeMinimum
375 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
379 # 32 <-> 30 Sometimes this is recognized as 00
380 # A recognized 0/2 after a 3 can be a 2/0
381 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") > 0
382 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") - 1
383 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
384 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\12", 0)
385 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
386 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\22", 0)
388 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") > 0
389 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") - 1
390 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+2") > 0
391 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)2", "\10", 0)
392 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
393 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\20", 0)
398 # A recognized 0 after a 3 can be a 1
399 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") > 0
400 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") - 1
401 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
402 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\11", 0)
407 # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
408 if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") > 0
409 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") - 1
410 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0") > 0
411 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0", "\12", 0)
416 # A recognized 0 between two tones 4 can be a 1
417 if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") > 0
418 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") - 1
419 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0[^0-9]+4") > 0
420 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0([^0-9]+4)", "\11\2", 0)
426 # If wrong, then undo all changes
427 if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
428 sgc_ToneProt.choiceReference$ = originalRecognizedWord$
431 sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
433 ###############################################
437 ###############################################
438 result$ = "'tab$''sgc_ToneProt.currentTestWord$''tab$''sgc_ToneProt.choiceReference$''tab$''sgc_ToneProt.newUpperRegister''tab$''sgc_ToneProt.newToneRange''tab$''speedFactor''tab$''sgc_ToneProt.registerUsed$''tab$''rangeUsed$'"
439 if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
440 result$ = "Correct:"+result$
442 result$ = "Wrong:"+result$
445 # Initialize result texts
446 .recognitionText$ = "'sgc_ToneProt.currentTestWord$': "
447 .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
448 .feedbackText$ = "----"
450 # Separate tone from pronunciation errors
451 currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
452 choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
454 # Determine what should be told to the student
455 if sgc_ToneProt.registerUsed$ = "Low"
456 .recognitionText$ = .recognitionText$ + "???"
457 for i from 1 to numberOfFeedbackRows
458 select Table ToneFeedback
459 .toneOne$ = Get value... 'i' T1
460 .toneTwo$ = Get value... 'i' T2
461 .toneText$ = Get value... 'i' Feedback
464 .feedbackText$ = .toneText$
468 elsif rangeUsed$ = "Narrow"
469 .recognitionText$ = .recognitionText$ + "???"
470 for i from 1 to numberOfFeedbackRows
471 select Table ToneFeedback
472 .toneOne$ = Get value... 'i' T1
473 .toneTwo$ = Get value... 'i' T2
474 .toneText$ = Get value... 'i' Feedback
476 if .toneOne$ = "Narrow"
477 .feedbackText$ = .toneText$
481 elsif sgc_ToneProt.registerUsed$ = "High"
482 .recognitionText$ = .recognitionText$ + .choiceText$
483 for i from 1 to numberOfFeedbackRows
484 select Table ToneFeedback
485 .toneOne$ = Get value... 'i' T1
486 .toneTwo$ = Get value... 'i' T2
487 .toneText$ = Get value... 'i' Feedback
489 if .toneOne$ = "High"
490 .feedbackText$ = .toneText$
494 elsif rangeUsed$ = "Wide"
495 .recognitionText$ = .recognitionText$ + .choiceText$
496 for i from 1 to numberOfFeedbackRows
497 select Table ToneFeedback
498 .toneOne$ = Get value... 'i' T1
499 .toneTwo$ = Get value... 'i' T2
500 .toneText$ = Get value... 'i' Feedback
502 if .toneOne$ = "Wide"
503 .feedbackText$ = .toneText$
507 # Bad tones, first handle first syllable
508 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
509 .recognitionText$ = .recognitionText$ + .choiceText$
511 for i from 1 to numberOfFeedbackRows
512 select Table ToneFeedback
513 .toneOne$ = Get value... 'i' T1
514 .toneTwo$ = Get value... 'i' T2
515 .toneText$ = Get value... 'i' Feedback
520 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
522 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
523 .feedbackText$ = .feedbackText$ + .toneText$ + " "
526 # Bad tones, then handle second syllable
527 elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
528 .recognitionText$ = .recognitionText$ + .choiceText$
530 for i from 1 to numberOfFeedbackRows
531 select Table ToneFeedback
532 .toneOne$ = Get value... 'i' T1
533 .toneTwo$ = Get value... 'i' T2
534 .toneText$ = Get value... 'i' Feedback
539 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
541 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
542 .feedbackText$ = .feedbackText$ + .toneText$ + " "
545 # Just plain wrong tones
546 elsif currentToneWord$ <> choiceToneReference$
547 .recognitionText$ = .recognitionText$ + .choiceText$
548 for i from 1 to numberOfFeedbackRows
549 select Table ToneFeedback
550 .toneOne$ = Get value... 'i' T1
551 .toneTwo$ = Get value... 'i' T2
552 .toneText$ = Get value... 'i' Feedback
554 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
555 .feedbackText$ = .toneText$
556 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
557 .feedbackText$ = .toneText$
558 elsif .toneOne$ = "Wrong"
559 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
565 .recognitionText$ = .recognitionText$ + .choiceText$
566 for i from 1 to numberOfFeedbackRows
567 select Table ToneFeedback
568 .toneOne$ = Get value... 'i' T1
569 .toneTwo$ = Get value... 'i' T2
570 .toneText$ = Get value... 'i' Feedback
572 if .toneOne$ = "Correct"
573 .feedbackText$ = .toneText$
582 Create Table with column names... Feedback 3 Text
583 Set string value... 1 Text '.recognitionText$'
584 Set string value... 2 Text '.feedbackText$'
585 Set string value... 3 Text '.label$'
588 select Table ToneFeedback
592 freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
594 # Replace recorded sound with new sound
595 if not fileReadable(sgc_ToneProt.currentSound$)
596 select Sound 'sgc_ToneProt.currentSound$'
599 Copy... 'sgc_ToneProt.currentSound$'
605 plus Pitch 'sgc_ToneProt.currentTestWord$'
611 procedure createJitterShimmerContour .sound
613 .duration = Get total duration
614 .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
616 # Create pointprocess
618 .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
624 while .t < .duration - .dT
626 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
627 if .jitter = undefined
632 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
633 if .shimmer = undefined
637 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
649 procedure createShimmerCPPcontour .sound
650 call createCPPContour .sound
651 .cpp = selected("Sound")
654 .duration = Get total duration
655 .shimmerCPPSound = Create Sound: "ShimmerCPP", 0, .duration, 100, "0"
657 # Create pointprocess
659 .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
665 while .t < .duration - .dT
668 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
669 if .shimmer = undefined
673 .cppValue = Get value at time: 0, .t, "Sinc70"
674 if .cppValue = undefined or .cppValue < 1
677 select .shimmerCPPSound
678 Set value at sample number: 0, .sampleNum, 10* .shimmer / .cppValue
688 select .shimmerCPPSound
693 procedure createShimmerContour .sound
695 .duration = Get total duration
696 .shimmerSound = Create Sound: "Shimmer", 0, .duration, 100, "0"
698 # Create pointprocess
700 .pointProc = To PointProcess (periodic, cc): 60, 350
706 while .t < .duration - .dT
709 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
710 if .shimmer = undefined
714 Set value at sample number: 0, .sampleNum, .shimmer
728 procedure createCPPContour .sound
732 # Create pointprocess
734 .duration = Get total duration
735 .pcg = To PowerCepstrogram: 60, .dT, 5000, 50
736 .cppTable = To Table (peak prominence): 60, 330, 0.05, "Parabolic", 0.001, 0, "Exponential decay", "Robust"
737 .nRows = Get number of rows
738 .lastTime = Get value: .nRows, "time"
740 while .lastTime < .duration - .dT
743 Set numeric value: .nRows, "cpp", 0
746 .firstTime = Get value: 1, "time"
748 while .firstTime >= .dT/2
750 Set numeric value: 1, "cpp", 0
753 Remove column: "quefrency"
756 Remove column: "time"
757 .cppTable2 = Transpose
758 .cppMatrix = Down to Matrix
759 .cppSound = To Sound (slice): 1
760 Override sampling frequency: 1/.dT
761 Shift times to: "start time", 0