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.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
146 # Calculate Shimmer&Jitter
148 .sound = selected("Sound")
149 call createJitterShimmerContour .sound
150 .jitterShimmer = selected("Sound")
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")
160 select Pitch 'sgc_ToneProt.pinyin$'
161 .generatedWord3 = selected("Pitch")
162 # Lower cutoff is 1/2 semitone above the 0.05 quantile
163 .lowerCutOff = Get quantile: 0, 0, 0.05, "Hertz"
164 .lowerCutOff /= sqrt(toneScript.oneSemit)
166 # Create a tier with low part of the third tone as intervals
167 select .generatedWord3
168 .modelDuration = Get total duration
169 .numFrames = Get number of frames
170 .prevV = Get value in frame: 1, "Hertz"
171 if .prevV = undefined or .prevV <= 0
172 .prevV = sgc_ToneProt.register
176 .pitchTier = Create TextGrid: 0, .modelDuration, "pitch", ""
178 if .prevV > .lowerCutOff
179 Set interval text: 1, .numIntervals, "NotLow"
180 elsif .prevV <= .lowerCutOff
181 Set interval text: 1, .numIntervals, "3Low"
185 select .generatedWord3
186 .frameT = Get time from frame number: .s
187 .value = Get value in frame: .s, "Hertz"
188 if .value = undefined or .value <= 0
189 .value = sgc_ToneProt.register
191 if .value > .lowerCutOff and .prevV <= .lowerCutOff
193 Insert boundary: 1, (.frameT+.prevT)/2
195 Set interval text: 1, .numIntervals, "NotLow"
196 elsif .value <= .lowerCutOff and .prevV > .lowerCutOff
198 Insert boundary: 1, (.frameT+.prevT)/2
200 Set interval text: 1, .numIntervals, "3Low"
207 # DTW of .generatedWord9 with sourcePitch
209 select te.recordedPitch
211 .dtw3 = noprogress To DTW... 24 10 yes yes no restriction
212 Rename... DTW'.generatedWord9'
213 .distance3 = Get distance (weighted)
214 select te.recordedPitch
216 .dtw9 = noprogress To DTW... 24 10 yes yes no restriction
217 Rename... DTW'.generatedWord9'
218 .distance9 = Get distance (weighted)
225 .numPitchIntervals = Get number of intervals: 1
226 for .i to .numPitchIntervals
228 .label$ = Get label of interval: 1, .i
230 .modelStart = Get starting point: 1, .i
231 .modelEnd = Get end point: 1, .i
233 .origStart = Get x time from y time: .modelStart
234 .origEnd = Get x time from y time: .modelEnd
235 select .jitterShimmer
236 .mean = Get mean: 0, .origStart, .origEnd
238 toneProt.creackyThree += 1;
246 # Weigh creacky voice by number of syllables
247 toneProt.creackyThree /= toneScript.syllableCount
251 select .jitterShimmer
257 # Remove all pitch points outside a band around the upper sgc_ToneProt.register
258 select te.recordedPitch
259 upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
260 lowerCutOff = sgc_ToneProt.upperRegisterInput/4
261 Formula... if self > 'upperCutOff' then -1 else self endif
262 Formula... if self < 'lowerCutOff' then -1 else self endif
265 select te.recordedPitch
266 maximumRecFzero = Get quantile... 0 0 0.95 Hertz
267 timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
268 minimumRecFzero = Get quantile... 0 0 0.05 Hertz
269 timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
270 if maximumRecFzero = undefined
271 # Determine what should be told to the student
272 .recognitionText$ = "'sgc_ToneProt.currentTestWord$': ???"
273 for i from 1 to numberOfFeedbackRows
274 select Table ToneFeedback
275 .toneOne$ = Get value... 'i' T1
276 .toneTwo$ = Get value... 'i' T2
277 .toneText$ = Get value... 'i' Feedback
280 if .toneOne$ = "NoSound"
281 .feedbackText$ = .toneText$
285 #exit Error, nothing recorded
289 if minimumRecFzero > 0
290 recPitchRange = maximumRecFzero / minimumRecFzero
292 sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
293 sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
294 if sgc_ToneProt.newUpperRegister = undefined
295 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
297 if sgc_ToneProt.newToneRange = undefined
298 sgc_ToneProt.newToneRange = 1
301 sgc_ToneProt.registerUsed$ = "OK"
303 # Advanced speakers must not speak too High, or too "Dramatic"
304 # Beginning speakers also not too Low or too Narrow ranges
305 if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
306 sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
307 sgc_ToneProt.registerUsed$ = "High"
308 elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
309 sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
310 sgc_ToneProt.registerUsed$ = "Low"
313 if sgc_ToneProt.newToneRange > highBoundaryFactor
314 sgc_ToneProt.newToneRange = highBoundaryFactor
316 elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
317 # Don't do this for advanced speakers
318 sgc_ToneProt.newToneRange = lowBoundaryFactor
319 rangeUsed$ = "Narrow"
323 if sgc_ToneProt.durationModel > spacing
324 speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
328 sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
330 # Remove all pitch points outside a band around the upper sgc_ToneProt.register
331 select te.recordedPitch
332 upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
333 lowerCutOff = sgc_ToneProt.newUpperRegister/3
334 Formula... if self > 'upperCutOff' then -1 else self endif
335 Formula... if self < 'lowerCutOff' then -1 else self endif
337 # It is rather dangerous to kill Octave errors, so be careful
338 if killOctaveJumps > 0
339 select te.recordedPitch
340 Rename... OldSourcePitch
342 Rename... SourcePitch
343 te.recordedPitch = selected("Pitch")
344 select Pitch OldSourcePitch
348 # It is good to have the lowest and highest pitch frequencies
349 select te.recordedPitch
350 timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
351 timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
353 # Clean up the old example pitch
354 select Pitch 'sgc_ToneProt.currentTestWord$'
357 # Do the tone recognition
358 .numSyllables = toneScript.syllableCount
359 sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
361 while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
362 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
365 call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
367 originalRecognizedWord$ = sgc_ToneProt.choiceReference$
368 if sgc_ToneProt.ultraStrict = 0
369 # [23]3 is often misidentified as 23, 20 or 30
370 if rindex_regex(sgc_ToneProt.currentTestWord$, "[23][^0-9]+3") > 0
371 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") > 0
372 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") - 1
373 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
374 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23]([^0-9]+)[023]", "\13\23", 1)
377 if rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") > 0
378 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") - 1
379 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
380 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "([^0-9]+)[23]([^0-9]+)[023]", "\12\23", 1)
385 # First syllable: 2<->3 exchanges
386 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2") > 0
387 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+3") > 0
388 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)[36]", "\12", 0)
390 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+3") > 0
391 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+2") > 0
392 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)2", "\13", 0)
394 # A single second tone is often misidentified as a neutral tone,
395 # A real neutral tone would be too low or too narrow and be discarded
396 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2$") > 0
397 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMinimum < timeMaximum
398 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
400 # A single fourth tone is often misidentified as a neutral tone,
401 # A real neutral tone would be too low or too narrow and be discarded
402 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+4$") > 0
403 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMaximum < timeMinimum
404 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
408 # 32 <-> 30 Sometimes this is recognized as 00
409 # A recognized 0/2 after a 3 can be a 2/0
410 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") > 0
411 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") - 1
412 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
413 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\12", 0)
414 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
415 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\22", 0)
417 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") > 0
418 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") - 1
419 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+2") > 0
420 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)2", "\10", 0)
421 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
422 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\20", 0)
427 # A recognized 0 after a 3 can be a 1
428 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") > 0
429 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") - 1
430 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
431 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\11", 0)
436 # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
437 if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") > 0
438 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") - 1
439 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0") > 0
440 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0", "\12", 0)
445 # A recognized 0 between two tones 4 can be a 1
446 if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") > 0
447 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") - 1
448 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0[^0-9]+4") > 0
449 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0([^0-9]+4)", "\11\2", 0)
455 # If wrong, then undo all changes
456 if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
457 sgc_ToneProt.choiceReference$ = originalRecognizedWord$
460 sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
462 ###############################################
466 ###############################################
467 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$'"
468 if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
469 result$ = "Correct:"+result$
471 result$ = "Wrong:"+result$
474 # Initialize result texts
475 .recognitionText$ = "'sgc_ToneProt.currentTestWord$': "
476 .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
477 .feedbackText$ = "----"
479 # Separate tone from pronunciation errors
480 currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
481 choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
483 # Determine what should be told to the student
484 if sgc_ToneProt.registerUsed$ = "Low"
485 .recognitionText$ = .recognitionText$ + "???"
486 for i from 1 to numberOfFeedbackRows
487 select Table ToneFeedback
488 .toneOne$ = Get value... 'i' T1
489 .toneTwo$ = Get value... 'i' T2
490 .toneText$ = Get value... 'i' Feedback
493 .feedbackText$ = .toneText$
497 elsif rangeUsed$ = "Narrow"
498 .recognitionText$ = .recognitionText$ + "???"
499 for i from 1 to numberOfFeedbackRows
500 select Table ToneFeedback
501 .toneOne$ = Get value... 'i' T1
502 .toneTwo$ = Get value... 'i' T2
503 .toneText$ = Get value... 'i' Feedback
505 if .toneOne$ = "Narrow"
506 .feedbackText$ = .toneText$
510 elsif sgc_ToneProt.registerUsed$ = "High"
511 .recognitionText$ = .recognitionText$ + .choiceText$
512 for i from 1 to numberOfFeedbackRows
513 select Table ToneFeedback
514 .toneOne$ = Get value... 'i' T1
515 .toneTwo$ = Get value... 'i' T2
516 .toneText$ = Get value... 'i' Feedback
518 if .toneOne$ = "High"
519 .feedbackText$ = .toneText$
523 elsif rangeUsed$ = "Wide"
524 .recognitionText$ = .recognitionText$ + .choiceText$
525 for i from 1 to numberOfFeedbackRows
526 select Table ToneFeedback
527 .toneOne$ = Get value... 'i' T1
528 .toneTwo$ = Get value... 'i' T2
529 .toneText$ = Get value... 'i' Feedback
531 if .toneOne$ = "Wide"
532 .feedbackText$ = .toneText$
536 # Bad tones, first handle first syllable
537 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
538 .recognitionText$ = .recognitionText$ + .choiceText$
540 for i from 1 to numberOfFeedbackRows
541 select Table ToneFeedback
542 .toneOne$ = Get value... 'i' T1
543 .toneTwo$ = Get value... 'i' T2
544 .toneText$ = Get value... 'i' Feedback
549 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
551 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
552 .feedbackText$ = .feedbackText$ + .toneText$ + " "
555 # Bad tones, then handle second syllable
556 elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
557 .recognitionText$ = .recognitionText$ + .choiceText$
559 for i from 1 to numberOfFeedbackRows
560 select Table ToneFeedback
561 .toneOne$ = Get value... 'i' T1
562 .toneTwo$ = Get value... 'i' T2
563 .toneText$ = Get value... 'i' Feedback
568 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
570 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
571 .feedbackText$ = .feedbackText$ + .toneText$ + " "
574 # Just plain wrong tones
575 elsif currentToneWord$ <> choiceToneReference$
576 .recognitionText$ = .recognitionText$ + .choiceText$
577 for i from 1 to numberOfFeedbackRows
578 select Table ToneFeedback
579 .toneOne$ = Get value... 'i' T1
580 .toneTwo$ = Get value... 'i' T2
581 .toneText$ = Get value... 'i' Feedback
583 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
584 .feedbackText$ = .toneText$
585 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
586 .feedbackText$ = .toneText$
587 elsif .toneOne$ = "Wrong"
588 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
594 .recognitionText$ = .recognitionText$ + .choiceText$
595 for i from 1 to numberOfFeedbackRows
596 select Table ToneFeedback
597 .toneOne$ = Get value... 'i' T1
598 .toneTwo$ = Get value... 'i' T2
599 .toneText$ = Get value... 'i' Feedback
601 if .toneOne$ = "Correct"
602 .feedbackText$ = .toneText$
611 Create Table with column names... Feedback 3 Text
612 Set string value... 1 Text '.recognitionText$'
613 Set string value... 2 Text '.feedbackText$'
614 Set string value... 3 Text '.label$'
617 select Table ToneFeedback
621 freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
623 # Replace recorded sound with new sound
624 if not fileReadable(sgc_ToneProt.currentSound$)
625 select Sound 'sgc_ToneProt.currentSound$'
628 Copy... 'sgc_ToneProt.currentSound$'
634 plus Pitch 'sgc_ToneProt.currentTestWord$'
640 procedure createJitterShimmerContour .sound
642 .duration = Get total duration
643 .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
645 # Create pointprocess
647 .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
653 while .t < .duration - .dT
655 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
656 if .jitter = undefined
661 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
662 if .shimmer = undefined
666 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
678 procedure createShimmerCPPcontour .sound
679 call createCPPContour .sound
680 .cpp = selected("Sound")
683 .duration = Get total duration
684 .shimmerCPPSound = Create Sound: "ShimmerCPP", 0, .duration, 100, "0"
686 # Create pointprocess
688 .pointProc = noprogress To PointProcess (periodic, cc): 60, 350
694 while .t < .duration - .dT
697 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
698 if .shimmer = undefined
702 .cppValue = Get value at time: 0, .t, "Sinc70"
703 if .cppValue = undefined or .cppValue < 1
706 select .shimmerCPPSound
707 Set value at sample number: 0, .sampleNum, 10* .shimmer / .cppValue
717 select .shimmerCPPSound
722 procedure createShimmerContour .sound
724 .duration = Get total duration
725 .shimmerSound = Create Sound: "Shimmer", 0, .duration, 100, "0"
727 # Create pointprocess
729 .pointProc = To PointProcess (periodic, cc): 60, 350
735 while .t < .duration - .dT
738 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
739 if .shimmer = undefined
743 Set value at sample number: 0, .sampleNum, .shimmer
757 procedure createCPPContour .sound
761 # Create pointprocess
763 .duration = Get total duration
764 .pcg = To PowerCepstrogram: 60, .dT, 5000, 50
765 .cppTable = To Table (peak prominence): 60, 330, 0.05, "Parabolic", 0.001, 0, "Exponential decay", "Robust"
766 .nRows = Get number of rows
767 .lastTime = Get value: .nRows, "time"
769 while .lastTime < .duration - .dT
772 Set numeric value: .nRows, "cpp", 0
775 .firstTime = Get value: 1, "time"
777 while .firstTime >= .dT/2
779 Set numeric value: 1, "cpp", 0
782 Remove column: "quefrency"
785 Remove column: "time"
786 .cppTable2 = Transpose
787 .cppMatrix = Down to Matrix
788 .cppSound = To Sound (slice): 1
789 Override sampling frequency: 1/.dT
790 Shift times to: "start time", 0