Test of creaky voice detector
[sgc2.git] / ToneProt / SGC_ToneProt.praat
bloba44501a4eb2bb641256aa278d854d33b9720e501
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                 call createShimmerContour .sound
153                 .shimmer = selected("Sound")
154                 call createCPPContour .sound
155                 .cpp = selected("Sound")
156                 
157                 # Create a tier with creaky voice intervals
158                 select .jitterShimmer
159                 .prevV = Get value at sample number: 0, 1
160                 .prevT = 0
161                 .numIntervals = 1
162                 select .voiceTier
163                 if .prevV > .creakCO
164                         Set interval text: 1, .numIntervals, "creaky"
165                 elsif .prevV <= .creakCO
166                         Set interval text: 1, .numIntervals, "normal"
167                 endif
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
173                                 select .voiceTier
174                                 Insert boundary: 1, (.frameT+.prevT)/2
175                                 .numIntervals += 1
176                                 Set interval text: 1, .numIntervals, "creaky"
177                         elsif .value <= .creakCO and .prevV > .creakCO
178                                 select .voiceTier
179                                 Insert boundary: 1, (.frameT+.prevT)/2
180                                 .numIntervals += 1
181                                 Set interval text: 1, .numIntervals, "normal"
182                         endif
183                         .prevV = .value
184                         .prevT = .frameT
185                 endfor
186                 
187                 # Create PitchTier and Voicing intervals
188                 select te.recordedPitch
189                 .origPointProcess = To PointProcess
190                 .vuvTier = To TextGrid (vuv): 0.02, 0.01
191                 
192                 select te.recordedPitch
193                 .recordedPitchTier = Down to PitchTier
194                 
195                 # Devoice creaky voice parts
196                 select .voiceTier
197                 .numIntervals = Get number of intervals: 1
198                 for .v to .numIntervals
199                         select .voiceTier
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
204                                 if .end > .start
205                                         select .recordedPitchTier
206                                         # Store begin and endvalues of pitch
207                                         .startValue = Get value at time: .start
208                                         .endValue = Get value at time: .end
209                                         
210                                         # Remove all pitch points between start and end
211                                         Remove points between: .start, .end
212                                         
213                                         # Add begin and en values at start and end times
214                                         Add point: .start, .startValue
215                                         Add point: .end, .endValue
216                                         
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
221                                         endif
222                                 endif
223                                 
224                         endif
225                 endfor
226                 
227                 # Convert new Pitchtier to Pitch object (based on old Pitch object)
228                 select te.recordedPitch
229                 plus .recordedPitchTier
230                 .newPitch = To Pitch
231                 
232 # REMOVE
233 #pause
234                 # Move new Pitch object to old Pitch object ID
235                 select te.recordedPitch
236                 .pitchName$ = selected$("Pitch")
237                 Remove
238                 te.recordedPitch = .newPitch
239                 select .newPitch
240                 Rename: .pitchName$
241                 .newPitch = 0
243                 # Clean up
244                 select .origPointProcess
245                 #plus .jitterShimmer
246                 plus .vuvTier
247                 plus .voiceTier
248                 plus .recordedPitchTier
249                 Remove
250         endif
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
259         # Get range and top
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
273                         .label$ = "Unknown"
275                 if .toneOne$ = "NoSound"
276                 .feedbackText$ = .toneText$
277                 endif
278         endfor
280         #exit Error, nothing recorded
281                 goto END
282         endif
283         recPitchRange = 2
284         if minimumRecFzero > 0
285            recPitchRange = maximumRecFzero / minimumRecFzero
286         endif
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
291         endif
292         if sgc_ToneProt.newToneRange = undefined
293                 sgc_ToneProt.newToneRange = 1
294         endif
296         sgc_ToneProt.registerUsed$ = "OK"
297         rangeUsed$ = "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"
306         endif
307         
308         if sgc_ToneProt.newToneRange > highBoundaryFactor
309            sgc_ToneProt.newToneRange = highBoundaryFactor
310            rangeUsed$ = "Wide"
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"
315         endif
317         # Duration 
318         if sgc_ToneProt.durationModel > spacing
319            speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
320         endif
322         # Round values
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
336         Kill octave jumps
337         Rename... SourcePitch
338         te.recordedPitch = selected("Pitch")
339         select Pitch OldSourcePitch
340         Remove
341         endif
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$'
350         Remove
352         # Do the tone recognition
353         .numSyllables = toneScript.syllableCount
354         sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
355         .skipSyllables = 0
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'
358                 .skipSyllables += 1
359         endwhile
360         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
361         # Special cases
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)
370                         endif
371                 endif
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)
376                         endif
377                 endif
378             endif
379             
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)
384                 endif
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)
388                 endif
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)
394                 endif
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)
400                 endif
401         endif
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)
411                 endif
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)
418                         endif
419         endif
420         
421         # 31 <-> 30 
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)
427                 endif
428                 endif
429         
430         # 40 <-> 42
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)
436                 endif
437         endif
438         
439                 # 404 <-> 414
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)
445                 endif
446         endif
447         
448         endif
449         
450         # If wrong, then undo all changes
451         if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
452         sgc_ToneProt.choiceReference$ = originalRecognizedWord$
453         endif
455         sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
457         ###############################################
458         #
459         # Report
460         #
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$
465         else
466            result$ = "Wrong:"+result$
467         endif
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
487                 if .toneOne$ = "Low"
488                 .feedbackText$ = .toneText$
489                                 .label$ = .toneOne$
490                 endif
491         endfor
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$
502                                 .label$ = .toneOne$
503                 endif
504         endfor
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$
515                                 .label$ = .toneOne$
516                 endif
517         endfor
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$
528                                 .label$ = .toneOne$
529                 endif
530         endfor
531         # Bad tones, first handle first syllable
532         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
533         .recognitionText$ = .recognitionText$ + .choiceText$
534         # First syllable
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                 # 
542                 .feedbackText$ = ""
543                 if .toneOne$ = "6"
544                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
545                                 .label$ = .toneOne$
546                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
547                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
548                 endif
549         endfor
550         # Bad tones, then handle second syllable
551         elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
552         .recognitionText$ = .recognitionText$ + .choiceText$
553         # Last syllable
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
560                 # 
561                 .feedbackText$ = ""
562                 if .toneOne$ = "6"
563                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
564                                 .label$ = .toneOne$
565                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
566                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
567                 endif
568         endfor
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$')"
584                                 .label$ = .toneOne$
585                 endif
586         endfor
587         # Correct
588         else
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$
598                                 .label$ = .toneOne$
599                 endif
600         endfor
601         endif
603         label END
605         # Write out result
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$'
611         # Clean up
612         select Table ToneFeedback
613         Remove
615         # Show pitch tracks
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$'
621                 Remove
622                 select Sound Source
623         Copy... 'sgc_ToneProt.currentSound$'
624         endif
627         # Clean up
628         select Sound Source
629         plus Pitch 'sgc_ToneProt.currentTestWord$'
630         Remove
631 endproc
634 # Use ~0.03 cutoff
635 procedure createJitterShimmerContour .sound
636         select .sound
637         .duration = Get total duration
638         .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
639         
640         # Create pointprocess
641         select .sound
642         .pointProc = To PointProcess (periodic, cc): 60, 350
643         
644         .dT = 0.01
645         .w_half = 0.075/2
646         .t = 0.0
647         .sampleNum = 1
648         while .t < .duration - .dT
649                 select .pointProc
650                 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
651                 if .jitter = undefined
652                         .jitter = 0
653                 endif
654                 select .sound
655                 plus .pointProc
656                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
657                 if .shimmer = undefined
658                         .shimmer = 0
659                 endif
660                 select .jitshimSound
661                 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
662         
663                 .t += .dT
664                 .sampleNum += 1
665         endwhile
666         
667         select .pointProc
668         Remove
669         
670         select .jitshimSound
671 endproc
673 procedure createShimmerCPPcontour .sound
674         call createCPPContour .sound
675         .cpp = selected("Sound")
676         
677         select .sound
678         .duration = Get total duration
679         .shimmerCPPSound = Create Sound: "ShimmerCPP", 0, .duration, 100, "0"
680         
681         # Create pointprocess
682         select .sound
683         .pointProc = To PointProcess (periodic, cc): 60, 350
684         
685         .dT = 0.01
686         .w_half = 0.075/2
687         .t = 0.0
688         .sampleNum = 1
689         while .t < .duration - .dT
690                 select .sound
691                 plus .pointProc
692                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
693                 if .shimmer = undefined
694                         .shimmer = 0
695                 endif
696                 select .cpp
697                 .cppValue = Get value at time: 0, .t, "Sinc70"
698                 if .cppValue = undefined or .cppValue < 1
699                         .cppValue = 1
700                 endif
701                 select .shimmerCPPSound
702                 Set value at sample number: 0, .sampleNum, 10* .shimmer / .cppValue
703         
704                 .t += .dT
705                 .sampleNum += 1
706         endwhile
707         
708         select .pointProc
709         plus .cpp
710         Remove
711         
712         select .shimmerCPPSound
713         
714 endproc
716 # Use ~? cutoff
717 procedure createShimmerContour .sound
718         select .sound
719         .duration = Get total duration
720         .shimmerSound = Create Sound: "Shimmer", 0, .duration, 100, "0"
721         
722         # Create pointprocess
723         select .sound
724         .pointProc = To PointProcess (periodic, cc): 60, 350
725         
726         .dT = 0.01
727         .w_half = 0.075/2
728         .t = 0.0
729         .sampleNum = 1
730         while .t < .duration - .dT
731                 select .sound
732                 plus .pointProc
733                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
734                 if .shimmer = undefined
735                         .shimmer = 0
736                 endif
737                 select .shimmerSound
738                 Set value at sample number: 0, .sampleNum, .shimmer
739         
740                 .t += .dT
741                 .sampleNum += 1
742         endwhile
743         
744         select .pointProc
745         Remove
746         
747         select .shimmerSound
748 endproc
751 # Use ~? cutoff
752 procedure createCPPContour .sound
753         select .sound
754         .dT = 0.01
755         
756         # Create pointprocess
757         select .sound
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"
763         .lastTime += .dT
764         while .lastTime < .duration - .dT
765                 Append row
766                 .nRows += 1
767                 Set numeric value: .nRows, "cpp", 0
768                 .lastTime += .dT
769         endwhile
770         .firstTime = Get value: 1, "time"
771         .firstTime -= .dT
772         while .firstTime >= .dT/2
773                 Insert row: 1
774                 Set numeric value: 1, "cpp", 0
775                 .firstTime -= .dT
776         endwhile
777         Remove column: "quefrency"
778         Remove column: "f0"
779         Remove column: "rnr"
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
786         Rename: "CPP"
787         
788         select .pcg
789         plus .cppTable
790         plus .cppTable2
791         plus .cppMatrix
792         Remove
793         
794         select .cppSound
795 endproc