Test of creaky voice detector
[sgc2.git] / ToneProt / SGC_ToneProt.praat
blobbaa02e64b342d1ef9992ab75360033b7cc28c18d
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                 # Determine the low F0 part of the 3rd tone
153                 # Generate word
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                 .generatedWord = selected("Pitch");
159 pause
160                 
161                 # Create a tier with creaky voice intervals
162                 select .jitterShimmer
163                 .prevV = Get value at sample number: 0, 1
164                 .prevT = 0
165                 .numIntervals = 1
166                 select .voiceTier
167                 if .prevV > .creakCO
168                         Set interval text: 1, .numIntervals, "creaky"
169                 elsif .prevV <= .creakCO
170                         Set interval text: 1, .numIntervals, "normal"
171                 endif
172                 for .s to .numSamples
173                         select .jitterShimmer
174                         .frameT = Get time from sample number: .s
175                         .value = Get value at sample number: 0, .s
176                         if .value > .creakCO and .prevV <= .creakCO
177                                 select .voiceTier
178                                 Insert boundary: 1, (.frameT+.prevT)/2
179                                 .numIntervals += 1
180                                 Set interval text: 1, .numIntervals, "creaky"
181                         elsif .value <= .creakCO and .prevV > .creakCO
182                                 select .voiceTier
183                                 Insert boundary: 1, (.frameT+.prevT)/2
184                                 .numIntervals += 1
185                                 Set interval text: 1, .numIntervals, "normal"
186                         endif
187                         .prevV = .value
188                         .prevT = .frameT
189                 endfor
190                 
191                 # Create PitchTier and Voicing intervals
192                 select te.recordedPitch
193                 .origPointProcess = To PointProcess
194                 .vuvTier = To TextGrid (vuv): 0.02, 0.01
195                 
196                 select te.recordedPitch
197                 .recordedPitchTier = Down to PitchTier
198                 
199                 # Devoice creaky voice parts
200                 select .voiceTier
201                 .numIntervals = Get number of intervals: 1
202 .numIntervals = 0
203                 for .v to .numIntervals
204                         select .voiceTier
205                         .label$ = Get label of interval: 1, .v
206                         if .label$ = "creaky"
207                                 .start = Get starting point: 1, .v
208                                 .end = Get end point: 1, .v
209                                 if .end > .start
210                                         select .recordedPitchTier
211                                         # Store begin and endvalues of pitch
212                                         .startValue = Get value at time: .start
213                                         .endValue = Get value at time: .end
214                                         
215                                         # Remove all pitch points between start and end
216                                         Remove points between: .start, .end
217                                         
218                                         # Add begin and en values at start and end times
219                                         Add point: .start, .startValue
220                                         Add point: .end, .endValue
221                                         
222                                         # Interpolate or add low pitch values in between start and end times if the interval is long
223                                         if .end - .start > 0.040
224                                                 Add point: .start + .delta, minimumModelFzero
225                                                 Add point: .end - .delta, minimumModelFzero
226                                         endif
227                                 endif
228                                 
229                         endif
230                 endfor
231                 
232                 # Convert new Pitchtier to Pitch object (based on old Pitch object)
233                 select te.recordedPitch
234                 plus .recordedPitchTier
235                 .newPitch = To Pitch
236                 
237 # REMOVE
238 #pause
239                 # Move new Pitch object to old Pitch object ID
240                 select te.recordedPitch
241                 .pitchName$ = selected$("Pitch")
242                 Remove
243                 te.recordedPitch = .newPitch
244                 select .newPitch
245                 Rename: .pitchName$
246                 .newPitch = 0
248                 # Clean up
249                 select .origPointProcess
250                 #plus .jitterShimmer
251                 plus .vuvTier
252                 plus .voiceTier
253                 plus .recordedPitchTier
254                 plus .generatedWord
255                 Remove
256         endif
258         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
259         select te.recordedPitch
260         upperCutOff = 1.7*sgc_ToneProt.upperRegisterInput
261         lowerCutOff = sgc_ToneProt.upperRegisterInput/4
262         Formula... if self > 'upperCutOff' then -1 else self endif
263         Formula... if self < 'lowerCutOff' then -1 else self endif
265         # Get range and top
266         select te.recordedPitch
267         maximumRecFzero = Get quantile... 0 0 0.95 Hertz
268         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
269         minimumRecFzero = Get quantile... 0 0 0.05 Hertz
270         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
271         if maximumRecFzero = undefined
272         # Determine what should be told to the student
273         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': ???"
274         for i from 1 to numberOfFeedbackRows
275                 select Table ToneFeedback
276                 .toneOne$ = Get value... 'i' T1
277                 .toneTwo$ = Get value... 'i' T2
278                 .toneText$ = Get value... 'i' Feedback
279                         .label$ = "Unknown"
281                 if .toneOne$ = "NoSound"
282                 .feedbackText$ = .toneText$
283                 endif
284         endfor
286         #exit Error, nothing recorded
287                 goto END
288         endif
289         recPitchRange = 2
290         if minimumRecFzero > 0
291            recPitchRange = maximumRecFzero / minimumRecFzero
292         endif
293         sgc_ToneProt.newUpperRegister = maximumRecFzero / maximumModelFzero * sgc_ToneProt.upperRegisterInput
294         sgc_ToneProt.newToneRange = recPitchRange / sgc_ToneProt.modelPitchRange
295         if sgc_ToneProt.newUpperRegister = undefined
296                 sgc_ToneProt.newUpperRegister = sgc_ToneProt.upperRegisterInput
297         endif
298         if sgc_ToneProt.newToneRange = undefined
299                 sgc_ToneProt.newToneRange = 1
300         endif
302         sgc_ToneProt.registerUsed$ = "OK"
303         rangeUsed$ = "OK"
304         # Advanced speakers must not speak too High, or too "Dramatic"
305         # Beginning speakers also not too Low or too Narrow ranges
306         if sgc_ToneProt.newUpperRegister > highBoundaryFactor * sgc_ToneProt.upperRegisterInput
307            sgc_ToneProt.newUpperRegister = highBoundaryFactor * sgc_ToneProt.upperRegisterInput
308            sgc_ToneProt.registerUsed$ = "High"
309         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newUpperRegister < lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
310            sgc_ToneProt.newUpperRegister = lowBoundaryFactor * sgc_ToneProt.upperRegisterInput
311            sgc_ToneProt.registerUsed$ = "Low"
312         endif
313         
314         if sgc_ToneProt.newToneRange > highBoundaryFactor
315            sgc_ToneProt.newToneRange = highBoundaryFactor
316            rangeUsed$ = "Wide"
317         elsif not sgc_ToneProt.proficiency and sgc_ToneProt.newToneRange < lowBoundaryFactor and not sgc_ToneProt.proficiency
318                 # Don't do this for advanced speakers
319            sgc_ToneProt.newToneRange = lowBoundaryFactor
320            rangeUsed$ = "Narrow"
321         endif
323         # Duration 
324         if sgc_ToneProt.durationModel > spacing
325            speedFactor = (durationSource - spacing) / (sgc_ToneProt.durationModel - spacing)
326         endif
328         # Round values
329         sgc_ToneProt.newUpperRegister = round(sgc_ToneProt.newUpperRegister)
331         # Remove all pitch points outside a band around the upper sgc_ToneProt.register
332         select te.recordedPitch
333         upperCutOff = 1.5*sgc_ToneProt.newUpperRegister
334         lowerCutOff = sgc_ToneProt.newUpperRegister/3
335         Formula... if self > 'upperCutOff' then -1 else self endif
336         Formula... if self < 'lowerCutOff' then -1 else self endif
338         # It is rather dangerous to kill Octave errors, so be careful
339         if killOctaveJumps > 0
340                 select te.recordedPitch
341         Rename... OldSourcePitch
342         Kill octave jumps
343         Rename... SourcePitch
344         te.recordedPitch = selected("Pitch")
345         select Pitch OldSourcePitch
346         Remove
347         endif
349         # It is good to have the lowest and highest pitch frequencies
350         select te.recordedPitch
351         timeMaximum = Get time of maximum... 0 0 Hertz Parabolic
352         timeMinimum = Get time of minimum... 0 0 Hertz Parabolic
354         # Clean up the old example pitch
355         select Pitch 'sgc_ToneProt.currentTestWord$'
356         Remove
358         # Do the tone recognition
359         .numSyllables = toneScript.syllableCount
360         sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$
361         .skipSyllables = 0
362         while sgc_ToneProt.choiceReference$ = sgc_ToneProt.currentTestWord$ and .skipSyllables < .numSyllables
363                 call FreeToneRecognition 'sgc_ToneProt.choiceReference$' "REUSEPITCH" "" 'sgc_ToneProt.newUpperRegister' 'sgc_ToneProt.newToneRange' 'speedFactor' '.skipSyllables'
364                 .skipSyllables += 1
365         endwhile
366         call toneScript 'sgc_ToneProt.currentTestWord$' 'sgc_ToneProt.upperRegisterInput' 'sgc_ToneProt.newToneRange' 'speedFactor' CorrectPitch
367         # Special cases
368         originalRecognizedWord$ = sgc_ToneProt.choiceReference$
369         if  sgc_ToneProt.ultraStrict = 0
370         # [23]3 is often misidentified as 23, 20 or 30
371         if rindex_regex(sgc_ToneProt.currentTestWord$, "[23][^0-9]+3") > 0
372                 if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+3") > 0
373                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^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$, "^(.{'.c'})[23]([^0-9]+)[023]", "\13\23", 1)
376                         endif
377                 endif
378                 if rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") > 0
379                                 .c = rindex_regex(sgc_ToneProt.currentTestWord$, "2[^0-9]+3") - 1
380                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})[23][^0-9]+[023]") > 0
381                         sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "([^0-9]+)[23]([^0-9]+)[023]", "\12\23", 1)
382                         endif
383                 endif
384             endif
385             
386         # First syllable: 2<->3 exchanges
387         if rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+2") > 0
388                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+3") > 0
389                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)[36]", "\12", 0)
390                 endif
391         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+3") > 0
392                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+2") > 0
393                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^([^0-9]+)2", "\13", 0)
394                 endif
395         # A single second 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]+2$") > 0
398                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMinimum < timeMaximum
399                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "2", 0)
400                 endif
401         # A single fourth tone is often misidentified as a neutral tone, 
402         # A real neutral tone would be too low or too narrow and be discarded
403         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[^0-9]+4$") > 0
404                 if rindex_regex(sgc_ToneProt.choiceReference$, "^[^0-9]+0$") > 0 and timeMaximum < timeMinimum
405                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "0", "4", 0)
406                 endif
407         endif
409         # 32 <-> 30 Sometimes this is recognized as 00
410         # A recognized 0/2 after a 3 can be a 2/0
411         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") > 0
412                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+2") - 1
413                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
414                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\12", 0)
415                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
416                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\22", 0)
417                 endif
418         elsif rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") > 0
419                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+0") - 1
420                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+2") > 0
421                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)2", "\10", 0)
422                         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})0[^0-9]+0") > 0
423                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'})0([^0-9]+)0", "\13\20", 0)
424                         endif
425         endif
426         
427         # 31 <-> 30 
428         # A recognized 0 after a 3 can be a 1
429         if rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") > 0
430                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "3[^0-9]+1") - 1
431                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})3[^0-9]+0") > 0
432                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}3[^0-9]+)0", "\11", 0)
433                 endif
434                 endif
435         
436         # 40 <-> 42
437         # A recognized 0 after a 4 can be a 2: 4-0 => 4-2
438         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") > 0
439                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+2") - 1
440                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0") > 0
441                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0", "\12", 0)
442                 endif
443         endif
444         
445                 # 404 <-> 414
446         # A recognized 0 between two tones 4 can be a 1
447         if rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") > 0
448                         .c = rindex_regex(sgc_ToneProt.currentTestWord$, "4[^0-9]+1[^0-9]+4") - 1
449                         if rindex_regex(sgc_ToneProt.choiceReference$, "^(.{'.c'})4[^0-9]+0[^0-9]+4") > 0
450                 sgc_ToneProt.choiceReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "^(.{'.c'}4[^0-9]+)0([^0-9]+4)", "\11\2", 0)
451                 endif
452         endif
453         
454         endif
455         
456         # If wrong, then undo all changes
457         if sgc_ToneProt.currentTestWord$ != sgc_ToneProt.choiceReference$
458         sgc_ToneProt.choiceReference$ = originalRecognizedWord$
459         endif
461         sgc_ToneProt.toneChoiceReference$ = sgc_ToneProt.choiceReference$
463         ###############################################
464         #
465         # Report
466         #
467         ###############################################
468         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$'"
469         if sgc_ToneProt.currentTestWord$ = sgc_ToneProt.toneChoiceReference$
470            result$ = "Correct:"+result$
471         else
472            result$ = "Wrong:"+result$
473         endif
475         # Initialize result texts
476         .recognitionText$ =  "'sgc_ToneProt.currentTestWord$': "
477         .choiceText$ = replace_regex$(sgc_ToneProt.choiceReference$, "6", "\?", 0)
478         .feedbackText$ = "----"
480         # Separate tone from pronunciation errors
481         currentToneWord$ = replace_regex$(sgc_ToneProt.currentTestWord$, "[a-z]+", "\*", 0)
482         choiceToneReference$ = replace_regex$(sgc_ToneProt.choiceReference$, "[a-z]+", "\*", 0)
484         # Determine what should be told to the student
485         if sgc_ToneProt.registerUsed$ = "Low"
486         .recognitionText$ = .recognitionText$ + "???"
487         for i from 1 to numberOfFeedbackRows
488                 select Table ToneFeedback
489                 .toneOne$ = Get value... 'i' T1
490                 .toneTwo$ = Get value... 'i' T2
491                 .toneText$ = Get value... 'i' Feedback
493                 if .toneOne$ = "Low"
494                 .feedbackText$ = .toneText$
495                                 .label$ = .toneOne$
496                 endif
497         endfor
498         elsif rangeUsed$ = "Narrow"
499         .recognitionText$ = .recognitionText$ + "???"
500         for i from 1 to numberOfFeedbackRows
501                 select Table ToneFeedback
502                 .toneOne$ = Get value... 'i' T1
503                 .toneTwo$ = Get value... 'i' T2
504                 .toneText$ = Get value... 'i' Feedback
506                 if .toneOne$ = "Narrow"
507                 .feedbackText$ = .toneText$
508                                 .label$ = .toneOne$
509                 endif
510         endfor
511         elsif sgc_ToneProt.registerUsed$ = "High"
512         .recognitionText$ = .recognitionText$ + .choiceText$
513         for i from 1 to numberOfFeedbackRows
514                 select Table ToneFeedback
515                 .toneOne$ = Get value... 'i' T1
516                 .toneTwo$ = Get value... 'i' T2
517                 .toneText$ = Get value... 'i' Feedback
519                 if .toneOne$ = "High"
520                 .feedbackText$ = .toneText$
521                                 .label$ = .toneOne$
522                 endif
523         endfor
524         elsif rangeUsed$ = "Wide"
525         .recognitionText$ = .recognitionText$ + .choiceText$
526         for i from 1 to numberOfFeedbackRows
527                 select Table ToneFeedback
528                 .toneOne$ = Get value... 'i' T1
529                 .toneTwo$ = Get value... 'i' T2
530                 .toneText$ = Get value... 'i' Feedback
532                 if .toneOne$ = "Wide"
533                 .feedbackText$ = .toneText$
534                                 .label$ = .toneOne$
535                 endif
536         endfor
537         # Bad tones, first handle first syllable
538         elsif rindex_regex(sgc_ToneProt.choiceReference$, "^[a-zA-Z]+6") > 0
539         .recognitionText$ = .recognitionText$ + .choiceText$
540         # First syllable
541         for i from 1 to numberOfFeedbackRows
542                 select Table ToneFeedback
543                 .toneOne$ = Get value... 'i' T1
544                 .toneTwo$ = Get value... 'i' T2
545                 .toneText$ = Get value... 'i' Feedback
547                 # 
548                 .feedbackText$ = ""
549                 if .toneOne$ = "6"
550                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
551                                 .label$ = .toneOne$
552                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'") > 0 and .toneTwo$ = "-"
553                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
554                 endif
555         endfor
556         # Bad tones, then handle second syllable
557         elsif rindex_regex(sgc_ToneProt.choiceReference$, "[a-zA-Z]+6$") > 0
558         .recognitionText$ = .recognitionText$ + .choiceText$
559         # Last syllable
560         for i from 1 to numberOfFeedbackRows
561                 select Table ToneFeedback
562                 .toneOne$ = Get value... 'i' T1
563                 .toneTwo$ = Get value... 'i' T2
564                 .toneText$ = Get value... 'i' Feedback
566                 # 
567                 .feedbackText$ = ""
568                 if .toneOne$ = "6"
569                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
570                                 .label$ = .toneOne$
571                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
572                 .feedbackText$ = .feedbackText$ + .toneText$ + " "
573                 endif
574         endfor
575         # Just plain wrong tones
576         elsif currentToneWord$ <> choiceToneReference$
577         .recognitionText$ = .recognitionText$ + .choiceText$
578         for i from 1 to numberOfFeedbackRows
579                 select Table ToneFeedback
580                 .toneOne$ = Get value... 'i' T1
581                 .toneTwo$ = Get value... 'i' T2
582                 .toneText$ = Get value... 'i' Feedback
584                 if rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'$") > 0 and .toneTwo$ = "-"
585                 .feedbackText$ = .toneText$
586                 elsif rindex_regex(sgc_ToneProt.currentTestWord$, "^[a-zA-Z]+'.toneOne$'[a-zA-Z]+'.toneTwo$'$") > 0
587                 .feedbackText$ = .toneText$
588                 elsif .toneOne$ = "Wrong"
589                 .recognitionText$ = .recognitionText$ + " ('.toneText$')"
590                                 .label$ = .toneOne$
591                 endif
592         endfor
593         # Correct
594         else
595         .recognitionText$ = .recognitionText$ + .choiceText$
596         for i from 1 to numberOfFeedbackRows
597                 select Table ToneFeedback
598                 .toneOne$ = Get value... 'i' T1
599                 .toneTwo$ = Get value... 'i' T2
600                 .toneText$ = Get value... 'i' Feedback
602                 if .toneOne$ = "Correct"
603                 .feedbackText$ = .toneText$
604                                 .label$ = .toneOne$
605                 endif
606         endfor
607         endif
609         label END
611         # Write out result
612         Create Table with column names... Feedback 3 Text
613         Set string value... 1 Text '.recognitionText$'
614         Set string value... 2 Text '.feedbackText$'
615         Set string value... 3 Text '.label$'
617         # Clean up
618         select Table ToneFeedback
619         Remove
621         # Show pitch tracks
622     freqTop = 1.5 * sgc_ToneProt.upperRegisterInput
624         # Replace recorded sound with new sound
625         if not fileReadable(sgc_ToneProt.currentSound$)
626         select Sound 'sgc_ToneProt.currentSound$'
627                 Remove
628                 select Sound Source
629         Copy... 'sgc_ToneProt.currentSound$'
630         endif
633         # Clean up
634         select Sound Source
635         plus Pitch 'sgc_ToneProt.currentTestWord$'
636         Remove
637 endproc
640 # Use ~0.03 cutoff
641 procedure createJitterShimmerContour .sound
642         select .sound
643         .duration = Get total duration
644         .jitshimSound = Create Sound: "JitterShimmer", 0, .duration, 100, "0"
645         
646         # Create pointprocess
647         select .sound
648         .pointProc = To PointProcess (periodic, cc): 60, 350
649         
650         .dT = 0.01
651         .w_half = 0.075/2
652         .t = 0.0
653         .sampleNum = 1
654         while .t < .duration - .dT
655                 select .pointProc
656                 .jitter = Get jitter (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3
657                 if .jitter = undefined
658                         .jitter = 0
659                 endif
660                 select .sound
661                 plus .pointProc
662                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
663                 if .shimmer = undefined
664                         .shimmer = 0
665                 endif
666                 select .jitshimSound
667                 Set value at sample number: 0, .sampleNum, 10 * .jitter * .shimmer
668         
669                 .t += .dT
670                 .sampleNum += 1
671         endwhile
672         
673         select .pointProc
674         Remove
675         
676         select .jitshimSound
677 endproc
679 procedure createShimmerCPPcontour .sound
680         call createCPPContour .sound
681         .cpp = selected("Sound")
682         
683         select .sound
684         .duration = Get total duration
685         .shimmerCPPSound = Create Sound: "ShimmerCPP", 0, .duration, 100, "0"
686         
687         # Create pointprocess
688         select .sound
689         .pointProc = To PointProcess (periodic, cc): 60, 350
690         
691         .dT = 0.01
692         .w_half = 0.075/2
693         .t = 0.0
694         .sampleNum = 1
695         while .t < .duration - .dT
696                 select .sound
697                 plus .pointProc
698                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
699                 if .shimmer = undefined
700                         .shimmer = 0
701                 endif
702                 select .cpp
703                 .cppValue = Get value at time: 0, .t, "Sinc70"
704                 if .cppValue = undefined or .cppValue < 1
705                         .cppValue = 1
706                 endif
707                 select .shimmerCPPSound
708                 Set value at sample number: 0, .sampleNum, 10* .shimmer / .cppValue
709         
710                 .t += .dT
711                 .sampleNum += 1
712         endwhile
713         
714         select .pointProc
715         plus .cpp
716         Remove
717         
718         select .shimmerCPPSound
719         
720 endproc
722 # Use ~? cutoff
723 procedure createShimmerContour .sound
724         select .sound
725         .duration = Get total duration
726         .shimmerSound = Create Sound: "Shimmer", 0, .duration, 100, "0"
727         
728         # Create pointprocess
729         select .sound
730         .pointProc = To PointProcess (periodic, cc): 60, 350
731         
732         .dT = 0.01
733         .w_half = 0.075/2
734         .t = 0.0
735         .sampleNum = 1
736         while .t < .duration - .dT
737                 select .sound
738                 plus .pointProc
739                 .shimmer = Get shimmer (local): .t-.w_half, .t+.w_half, 0.0001, 0.02, 1.3, 1.6
740                 if .shimmer = undefined
741                         .shimmer = 0
742                 endif
743                 select .shimmerSound
744                 Set value at sample number: 0, .sampleNum, .shimmer
745         
746                 .t += .dT
747                 .sampleNum += 1
748         endwhile
749         
750         select .pointProc
751         Remove
752         
753         select .shimmerSound
754 endproc
757 # Use ~? cutoff
758 procedure createCPPContour .sound
759         select .sound
760         .dT = 0.01
761         
762         # Create pointprocess
763         select .sound
764         .duration = Get total duration
765         .pcg = To PowerCepstrogram: 60, .dT, 5000, 50
766         .cppTable = To Table (peak prominence): 60, 330, 0.05, "Parabolic", 0.001, 0, "Exponential decay", "Robust"
767         .nRows = Get number of rows
768         .lastTime = Get value: .nRows, "time"
769         .lastTime += .dT
770         while .lastTime < .duration - .dT
771                 Append row
772                 .nRows += 1
773                 Set numeric value: .nRows, "cpp", 0
774                 .lastTime += .dT
775         endwhile
776         .firstTime = Get value: 1, "time"
777         .firstTime -= .dT
778         while .firstTime >= .dT/2
779                 Insert row: 1
780                 Set numeric value: 1, "cpp", 0
781                 .firstTime -= .dT
782         endwhile
783         Remove column: "quefrency"
784         Remove column: "f0"
785         Remove column: "rnr"
786         Remove column: "time"
787         .cppTable2 = Transpose
788         .cppMatrix = Down to Matrix
789         .cppSound = To Sound (slice): 1
790         Override sampling frequency: 1/.dT
791         Shift times to: "start time", 0
792         Rename: "CPP"
793         
794         select .pcg
795         plus .cppTable
796         plus .cppTable2
797         plus .cppMatrix
798         Remove
799         
800         select .cppSound
801 endproc