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 if index(sgc_ToneProt.pinyin$, "3")
143 # Reassign creacky voice parts
144 # Calculate Shimmer&Jitter
146 .sound = selected("Sound")
147 call createJitterShimmerContour .sound
148 .jitterShimmer = selected("Sound")
149 .numSamples = Get number of samples
150 .voiceTier = To TextGrid: "voice", ""
152 call createShimmerContour .sound
153 .shimmer = selected("Sound")
154 call createCPPContour .sound
155 .cpp = selected("Sound")
157 # Create a tier with creaky voice intervals
158 select .jitterShimmer
159 .prevV = Get value at sample number: 0, 1
164 Set interval text: 1, .numIntervals, "creaky"
165 elsif .prevV <= .creakCO
166 Set interval text: 1, .numIntervals, "normal"
168 for .s to .numSamples
169 select .jitterShimmer
170 .frameT = Get time from sample number: .s
171 .value = Get value at sample number: 0, .s
172 if .value > .creakCO and .prevV <= .creakCO
174 Insert boundary: 1, (.frameT+.prevT)/2
176 Set interval text: 1, .numIntervals, "creaky"
177 elsif .value <= .creakCO and .prevV > .creakCO
179 Insert boundary: 1, (.frameT+.prevT)/2
181 Set interval text: 1, .numIntervals, "normal"
187 # Create PitchTier and Voicing intervals
188 select te.recordedPitch
189 .origPointProcess = To PointProcess
190 .vuvTier = To TextGrid (vuv): 0.02, 0.01
192 select te.recordedPitch
193 .recordedPitchTier = Down to PitchTier
195 # Devoice creaky voice parts
197 .numIntervals = Get number of intervals: 1
198 for .v to .numIntervals
200 .label$ = Get label of interval: 1, .v
201 if .label$ = "creaky"
202 .start = Get starting point: 1, .v
203 .end = Get end point: 1, .v
205 select .recordedPitchTier
206 # Store begin and endvalues of pitch
207 .startValue = Get value at time: .start
208 .endValue = Get value at time: .end
210 # Remove all pitch points between start and end
211 Remove points between: .start, .end
213 # Add begin and en values at start and end times
214 Add point: .start, .startValue
215 Add point: .end, .endValue
217 # Interpolate or add low pitch values in between start and end times if the interval is long
218 if .end - .start > 0.040
219 Add point: .start + .delta, minimumModelFzero
220 Add point: .end - .delta, minimumModelFzero
227 # Convert new Pitchtier to Pitch object (based on old Pitch object)
228 select te.recordedPitch
229 plus .recordedPitchTier
234 # Move new Pitch object to old Pitch object ID
235 select te.recordedPitch
236 .pitchName$ = selected$("Pitch")
238 te.recordedPitch = .newPitch
244 select .origPointProcess
248 plus .recordedPitchTier
252 # Remove all pitch points outside a band around the upper sgc_ToneProt.register
253 select te.recordedPitch
254 upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
255 lowerCutOff = sgc_ToneProt.upperRegisterInput/4
256 Formula... if self > 'upperCutOff' then -1 else self endif
257 Formula... if self < 'lowerCutOff' then -1 else self endif
260 select te.recordedPitch
261 maximumRecFzero = Get quantile... 0 0 0.95 Hertz
262 timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
263 minimumRecFzero = Get quantile... 0 0 0.05 Hertz
264 timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
265 if maximumRecFzero = undefined
266 # Determine what should be told to the student
267 .recognitionText$ = "'sgc_ToneProt.currentTestWord$': ???"
268 for i from 1 to numberOfFeedbackRows
269 select Table ToneFeedback
270 .toneOne$ = Get value... 'i' T1
271 .toneTwo$ = Get value... 'i' T2
272 .toneText$ = Get value... 'i' Feedback
275 if .toneOne$ = "NoSound"
276 .feedbackText$ = .toneText$
280 #exit Error, nothing recorded
284 if minimumRecFzero > 0
285 recPitchRange = maximumRecFzero / minimumRecFzero
287 sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
288 sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
289 if sgc_ToneProt.newUpperRegister = undefined
290 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
292 if sgc_ToneProt.newToneRange = undefined
293 sgc_ToneProt.newToneRange = 1
296 sgc_ToneProt.registerUsed$ = "OK"
298 # Advanced speakers must not speak too High, or too "Dramatic"
299 # Beginning speakers also not too Low or too Narrow ranges
300 if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
301 sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
302 sgc_ToneProt.registerUsed$ = "High"
303 elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
304 sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
305 sgc_ToneProt.registerUsed$ = "Low"
308 if sgc_ToneProt.newToneRange > highBoundaryFactor
309 sgc_ToneProt.newToneRange = highBoundaryFactor
311 elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
312 # Don't do this for advanced speakers
313 sgc_ToneProt.newToneRange = lowBoundaryFactor
314 rangeUsed$ = "Narrow"
318 if sgc_ToneProt.durationModel > spacing
319 speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
323 sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
325 # Remove all pitch points outside a band around the upper sgc_ToneProt.register
326 select te.recordedPitch
327 upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
328 lowerCutOff = sgc_ToneProt.newUpperRegister/3
329 Formula... if self > 'upperCutOff' then -1 else self endif
330 Formula... if self < 'lowerCutOff' then -1 else self endif
332 # It is rather dangerous to kill Octave errors, so be careful
333 if killOctaveJumps > 0
334 select te.recordedPitch
335 Rename... OldSourcePitch
337 Rename... SourcePitch
338 te.recordedPitch = selected("Pitch")
339 select Pitch OldSourcePitch
343 # It is good to have the lowest and highest pitch frequencies
344 select te.recordedPitch
345 timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
346 timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
348 # Clean up the old example pitch
349 select Pitch 'sgc_ToneProt.currentTestWord$'
352 # Do the tone recognition
353 .numSyllables = toneScript.syllableCount
354 sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
356 while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
357 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
360 call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
362 originalRecognizedWord$ = sgc_ToneProt.choiceReference$
363 if sgc_ToneProt.ultraStrict = 0
364 # [23]3 is often misidentified as 23, 20 or 30
365 if rindex_regex(sgc_ToneProt.currentTestWord$, "[23][^0-9]+3") > 0
366 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") > 0
367 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") - 1
368 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
369 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23]([^0-9]+)[023]", "\13\23", 1)
372 if rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") > 0
373 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") - 1
374 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
375 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "([^0-9]+)[23]([^0-9]+)[023]", "\12\23", 1)
380 # First syllable: 2<->3 exchanges
381 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2") > 0
382 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+3") > 0
383 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)[36]", "\12", 0)
385 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+3") > 0
386 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+2") > 0
387 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)2", "\13", 0)
389 # A single second tone is often misidentified as a neutral tone,
390 # A real neutral tone would be too low or too narrow and be discarded
391 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2$") > 0
392 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMinimum < timeMaximum
393 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
395 # A single fourth tone is often misidentified as a neutral tone,
396 # A real neutral tone would be too low or too narrow and be discarded
397 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+4$") > 0
398 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMaximum < timeMinimum
399 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
403 # 32 <-> 30 Sometimes this is recognized as 00
404 # A recognized 0/2 after a 3 can be a 2/0
405 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") > 0
406 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") - 1
407 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
408 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\12", 0)
409 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
410 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\22", 0)
412 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") > 0
413 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") - 1
414 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+2") > 0
415 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)2", "\10", 0)
416 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
417 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\20", 0)
422 # A recognized 0 after a 3 can be a 1
423 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") > 0
424 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") - 1
425 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
426 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\11", 0)
431 # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
432 if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") > 0
433 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") - 1
434 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0") > 0
435 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0", "\12", 0)
440 # A recognized 0 between two tones 4 can be a 1
441 if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") > 0
442 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") - 1
443 if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0[^0-9]+4") > 0
444 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0([^0-9]+4)", "\11\2", 0)
450 # If wrong, then undo all changes
451 if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
452 sgc_ToneProt.choiceReference$ = originalRecognizedWord$
455 sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
457 ###############################################
461 ###############################################
462 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$'"
463 if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
464 result$ = "Correct:"+result$
466 result$ = "Wrong:"+result$
469 # Initialize result texts
470 .recognitionText$ = "'sgc_ToneProt.currentTestWord$': "
471 .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
472 .feedbackText$ = "----"
474 # Separate tone from pronunciation errors
475 currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
476 choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
478 # Determine what should be told to the student
479 if sgc_ToneProt.registerUsed$ = "Low"
480 .recognitionText$ = .recognitionText$ + "???"
481 for i from 1 to numberOfFeedbackRows
482 select Table ToneFeedback
483 .toneOne$ = Get value... 'i' T1
484 .toneTwo$ = Get value... 'i' T2
485 .toneText$ = Get value... 'i' Feedback
488 .feedbackText$ = .toneText$
492 elsif rangeUsed$ = "Narrow"
493 .recognitionText$ = .recognitionText$ + "???"
494 for i from 1 to numberOfFeedbackRows
495 select Table ToneFeedback
496 .toneOne$ = Get value... 'i' T1
497 .toneTwo$ = Get value... 'i' T2
498 .toneText$ = Get value... 'i' Feedback
500 if .toneOne$ = "Narrow"
501 .feedbackText$ = .toneText$
505 elsif sgc_ToneProt.registerUsed$ = "High"
506 .recognitionText$ = .recognitionText$ + .choiceText$
507 for i from 1 to numberOfFeedbackRows
508 select Table ToneFeedback
509 .toneOne$ = Get value... 'i' T1
510 .toneTwo$ = Get value... 'i' T2
511 .toneText$ = Get value... 'i' Feedback
513 if .toneOne$ = "High"
514 .feedbackText$ = .toneText$
518 elsif rangeUsed$ = "Wide"
519 .recognitionText$ = .recognitionText$ + .choiceText$
520 for i from 1 to numberOfFeedbackRows
521 select Table ToneFeedback
522 .toneOne$ = Get value... 'i' T1
523 .toneTwo$ = Get value... 'i' T2
524 .toneText$ = Get value... 'i' Feedback
526 if .toneOne$ = "Wide"
527 .feedbackText$ = .toneText$
531 # Bad tones, first handle first syllable
532 elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
533 .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
544 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
546 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
547 .feedbackText$ = .feedbackText$ + .toneText$ + " "
550 # Bad tones, then handle second syllable
551 elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
552 .recognitionText$ = .recognitionText$ + .choiceText$
554 for i from 1 to numberOfFeedbackRows
555 select Table ToneFeedback
556 .toneOne$ = Get value... 'i' T1
557 .toneTwo$ = Get value... 'i' T2
558 .toneText$ = Get value... 'i' Feedback
563 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
565 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
566 .feedbackText$ = .feedbackText$ + .toneText$ + " "
569 # Just plain wrong tones
570 elsif currentToneWord$ <> choiceToneReference$
571 .recognitionText$ = .recognitionText$ + .choiceText$
572 for i from 1 to numberOfFeedbackRows
573 select Table ToneFeedback
574 .toneOne$ = Get value... 'i' T1
575 .toneTwo$ = Get value... 'i' T2
576 .toneText$ = Get value... 'i' Feedback
578 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
579 .feedbackText$ = .toneText$
580 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
581 .feedbackText$ = .toneText$
582 elsif .toneOne$ = "Wrong"
583 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
589 .recognitionText$ = .recognitionText$ + .choiceText$
590 for i from 1 to numberOfFeedbackRows
591 select Table ToneFeedback
592 .toneOne$ = Get value... 'i' T1
593 .toneTwo$ = Get value... 'i' T2
594 .toneText$ = Get value... 'i' Feedback
596 if .toneOne$ = "Correct"
597 .feedbackText$ = .toneText$
606 Create Table with column names... Feedback 3 Text
607 Set string value... 1 Text '.recognitionText$'
608 Set string value... 2 Text '.feedbackText$'
609 Set string value... 3 Text '.label$'
612 select Table ToneFeedback
616 freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
618 # Replace recorded sound with new sound
619 if not fileReadable(sgc_ToneProt.currentSound$)
620 select Sound 'sgc_ToneProt.currentSound$'
623 Copy... 'sgc_ToneProt.currentSound$'
629 plus Pitch 'sgc_ToneProt.currentTestWord$'
635 procedure createJitterShimmerContour .sound
637 .duration = Get total duration
638 .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
640 # Create pointprocess
642 .pointProc = To PointProcess (periodic, cc): 60, 350
648 while .t < .duration - .dT
650 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
651 if .jitter = undefined
656 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
657 if .shimmer = undefined
661 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
673 procedure createShimmerCPPcontour .sound
674 call createCPPContour .sound
675 .cpp = selected("Sound")
678 .duration = Get total duration
679 .shimmerCPPSound = Create Sound: "ShimmerCPP", 0, .duration, 100, "0"
681 # Create pointprocess
683 .pointProc = To PointProcess (periodic, cc): 60, 350
689 while .t < .duration - .dT
692 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
693 if .shimmer = undefined
697 .cppValue = Get value at time: 0, .t, "Sinc70"
698 if .cppValue = undefined or .cppValue < 1
701 select .shimmerCPPSound
702 Set value at sample number: 0, .sampleNum, 10* .shimmer / .cppValue
712 select .shimmerCPPSound
717 procedure createShimmerContour .sound
719 .duration = Get total duration
720 .shimmerSound = Create Sound: "Shimmer", 0, .duration, 100, "0"
722 # Create pointprocess
724 .pointProc = To PointProcess (periodic, cc): 60, 350
730 while .t < .duration - .dT
733 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
734 if .shimmer = undefined
738 Set value at sample number: 0, .sampleNum, .shimmer
752 procedure createCPPContour .sound
756 # Create pointprocess
758 .duration = Get total duration
759 .pcg = To PowerCepstrogram: 60, .dT, 5000, 50
760 .cppTable = To Table (peak prominence): 60, 330, 0.05, "Parabolic", 0.001, 0, "Exponential decay", "Robust"
761 .nRows = Get number of rows
762 .lastTime = Get value: .nRows, "time"
764 while .lastTime < .duration - .dT
767 Set numeric value: .nRows, "cpp", 0
770 .firstTime = Get value: 1, "time"
772 while .firstTime >= .dT/2
774 Set numeric value: 1, "cpp", 0
777 Remove column: "quefrency"
780 Remove column: "time"
781 .cppTable2 = Transpose
782 .cppMatrix = Down to Matrix
783 .cppSound = To Sound (slice): 1
784 Override sampling frequency: 1/.dT
785 Shift times to: "start time", 0