Small typo corrected in SGC_ToneProt/README.txt
[sgc.git] / SGC_ToneProt / ReferenceToneScript.praat
blob3e51360ce82bc00f8e8d36200cd6107f3fd85d65
1 #!praat
3 # Generate a Pitch object from the pinyin
5 # For testing the models.
7 form Enter pinyin and tone 1 frequency
8         word inputWord xiang1gang3
9         positive upperRegister_(Hz) 169
10     real range_Factor 0.8408964152537146
11     real durationScale 0.6511624373537163
12     optionmenu generate 3
13         option Pitch
14         option Sound
15         option CorrectPitch
16         option CorrectSound
17 endform
19 # To supress the ToneList, change to 0
20 createToneList = 1
21 if rindex_regex(generate$, "Correct") > 0
22         createToneList = 0
23 endif
25 # Limit lowest tone
26 absoluteMinimum = 80
28 # Clean up input
29 if inputWord$ <> ""
30     inputWord$ = replace_regex$(inputWord$, "^\s*(.+)\s*$", "\1", 1)
31 endif
33 procedure extractTone syllable$
34         toneSyllable = -1
35         currentToneText$ = replace_regex$(syllable$, "^[^\d]+([\d]+)(.*)$", "\1", 0)
36         toneSyllable = extractNumber(currentToneText$, "")
37 endproc
39 procedure convertVoicing voicingSyllable$
40         # Remove tones
41         voicingSyllable$ = replace_regex$(voicingSyllable$, "^([^\d]+)[\d]+", "\1", 0)
42         # Convert voiced consonants
43         voicingSyllable$ = replace_regex$(voicingSyllable$, "(ng|[wrlmny])", "C", 0)
44         # Convert unvoiced consonants
45         voicingSyllable$ = replace_regex$(voicingSyllable$, "(sh|ch|zh|[fsxhktpgqdbzcj])", "U", 0)
46         # Convert vowels
47         voicingSyllable$ = replace_regex$(voicingSyllable$, "([aiuoeĆ¼])", "V", 0)
48 endproc
50 # Add a tone movement. The current time point is 'point'
51 delta = 0.0000001
52 if durationScale <= 0
53     durationScale = 1.0
54 endif
55 segmentDuration = 0.150
56 fixedDuration = 0.12
57 zeroToneFactor = 0.5
59 # 1/(12 semitones)
60 octave = 0.5
61 # 1/(9 semitones)
62 nineSemit = 0.594603557501361
63 # 1/(6 semitones)
64 sixSemit = 0.707106781186547
65 # 1/(3 semitones) down
66 threeSemit = 0.840896415253715
67 # 1/(2 semitones) down
68 twoSemit = 0.890898718140339
69 # 1/(1 semitones) down
70 oneSemit = 0.943874313
71 # 1/(5 semitones)
72 fiveSemit = threeSemit * twoSemit
74 frequency_Range = octave
75 if range_Factor > 0
76     frequency_Range =  frequency_Range * range_Factor
77 endif
79 # Previous end frequency
80 lastFrequency = 0
81 procedure addToneMovement syllable$ topLine prevTone nextTone
82         # Get tone
83         toneSyllable = -1
84         call extractTone 'syllable$'
85     if toneSyllable = 3 and nextTone = 3
86         toneSyllable = 2
87     elsif syllable$ = "bu4" and nextTone = 4
88         toneSyllable = 2
89     endif
91         # Get voicing pattern
92         voicingSyllable$ = ""
93         call convertVoicing 'syllable$'
95         # Account for tones in duration
96         toneFactor = 1
97         if toneSyllable = 0
98                 toneFactor = 0.5
99         elsif toneSyllable = 2
100                 toneFactor = 0.8
101         elsif toneSyllable = 4
102                 toneFactor = 0.8
103         endif
104         toneFactor = toneFactor * durationScale
106         # Unvoiced part
107         if rindex_regex(voicingSyllable$, "U") = 1
108                 point = point + delta
109         Add point... 'point' 0
110                 point = point + segmentDuration * toneFactor
111         Add point... 'point' 0
112         endif
113         # Voiced part
114         voiceLength$ = replace_regex$(voicingSyllable$, "U*([CV]+)U*", "\1", 0)
115         voicedLength = length(voiceLength$)
116         voicedDuration = toneFactor * (segmentDuration*voicedLength + fixedDuration)
117         point = point + delta
118     
119     # Write contour of each tone
120     # Note that tones are influenced by the previous (tone 0) and next (tone 3)
121     # tones. Tone 6 is the Dutch intonation
122     # sqrt(frequency_Range) is the mid point
123     if topLine * frequency_Range < absoluteMinimum
124         frequency_Range = absoluteMinimum / topLine
125     endif
126     
127         if toneSyllable = 1
128         Add point... 'point' 'topLine'
129         point = point + voicedDuration
130         Add point... 'point' 'topLine'
131                 lastFrequency = topLine
132         elsif toneSyllable = 2
133         # Halfway of the range
134         startPoint = topLine * sqrt(frequency_Range)
135         endPoint = topLine
136         # Special case: 2 followed by 1, stop short of the top-line
137         if nextTone = 1
138             endPoint = startPoint / threeSemit
139             endif
140             # Go lower if previous tone is 1
141             if prevTone = 1
142                 startPoint = startPoint * oneSemit
143         elsif prevTone = 4
144         # Special case: 2 following 4, stop short of top-line
145             endPoint = startPoint / threeSemit
146         endif
148         Add point... 'point' 'startPoint'
149         point = point + voicedDuration
150         Add point... 'point' 'endPoint'
151                 lastFrequency = topLine
152         elsif toneSyllable = 3
153         # Halfway the range
154         startPoint = topLine * sqrt(frequency_Range)
155         lowestPoint = topLine * frequency_Range * threeSemit
156         # Protect pitch against "underflow"
157         if lowestPoint < absoluteMinimum
158             lowestPoint = absoluteMinimum
159         endif
160         if nextTone <= 0
161 #            endPoint = topLine
162             endPoint = startPoint
163         elsif nextTone = 1 or nextTone = 4
164             lowestPoint = topLine * frequency_Range / twoSemit
165             endPoint = startPoint
166         elsif nextTone = 2
167             endPoint = topLine * frequency_Range / twoSemit
168         else
169             endPoint = startPoint
170         endif
172         Add point... 'point' 'startPoint'
173             point = point + (voicedDuration)/3
174         Add point... 'point' 'lowestPoint'
175             point = point + (voicedDuration)/3
176         Add point... 'point' 'lowestPoint'
177             point = point + (voicedDuration)/3
178         Add point... 'point' 'endPoint' 
179                 lastFrequency = topLine
180         elsif toneSyllable = 4
181         startPoint = topLine / twoSemit
182         endPoint = startPoint * frequency_Range
183         Add point... 'point' 'startPoint'
184         point = point + voicedDuration/3
185         Add point... 'point' 'startPoint'
186         point = point + voicedDuration*2/3
187         Add point... 'point' 'endPoint'
188                 lastFrequency = endPoint
189         elsif toneSyllable = 0
190         if prevTone = 1 or prevTone = 2
191                         zeroPoint = topLine * sixSemit * oneSemit
192         elsif prevTone = 3
193             zeroPoint = topLine * threeSemit
194         elsif prevTone = 4
195                         zeroPoint = topLine * frequency_Range / threeSemit
196         else
197                         zeroPoint = topLine * sixSemit * oneSemit     
198         endif
199         
200         Add point... 'point' 'zeroPoint'
201                 point = point + voicedDuration
202             Add point... 'point' 'zeroPoint'
203                 lastFrequency = zeroPoint
204         else
205         dutchInton = topLine * sqrt(frequency_Range)
206         if prevTone = 6
207             dutchInton = lastFrequency
208         endif
209                 
210         endPoint = dutchInton * oneSemit
211         Add point... 'point' 'dutchInton'
212         point = point + voicedDuration
213         Add point... 'point' 'endPoint'
214                 lastFrequency = endPoint
215         endif
217 endproc
219 # Split input into syllables
220 margin = 0.25
222 procedure wordToTones wordInput$ highPitch
223         currentRest$ = wordInput$;
224         syllableCount = 0
225         length = 2 * margin
226     
227     # Split syllables
228         while rindex_regex(currentRest$, "^[^\d]+[\d]+") > 0
229         syllableCount += 1
230         syllable'syllableCount'$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\1", 1)
231                 currentSyllable$ = syllable'syllableCount'$
233                 # Get the tone
234                 call extractTone 'currentSyllable$'
235                 toneSyllable'syllableCount' = toneSyllable
236                 currentTone = toneSyllable'syllableCount'
238                 # Get the Voicing pattern
239                 call convertVoicing 'currentSyllable$'
240                 voicingSyllable'syllableCount'$ = voicingSyllable$
241                 currentVoicing$ = voicingSyllable'syllableCount'$
243                 # Calculate new length
244                 toneFactor = 1
245                 if currentTone = 0
246                         toneFactor = 0.5
247                 elsif currentTone = 4
248                         toneFactor = 0.8
249                 endif
250             toneFactor = toneFactor * durationScale
252                 length = length + toneFactor * (length(voicingSyllable'syllableCount'$) * (segmentDuration + delta) + fixedDuration)
254                 # Next round
255                 currentRest$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\2", 1)
256    
257                 # Safety valve
258                 if syllableCount > 2000
259                         exit
260                 endif
261         endwhile
263         # Create tone pattern
264         Create PitchTier... 'wordInput$' 0 'length'
266         # Add start margin
267         lastFrequency = 0
268         point = margin
269         Add point... 'point' 0
271     lastTone = -1
272     followTone = -1
273         for i from 1 to syllableCount
274                 currentSyllable$ = syllable'i'$
275         currentTone = toneSyllable'i'
276         followTone = -1
277         if i < syllableCount
278             j = i+1
279             followTone = toneSyllable'j'
280         endif
281         
282                 call addToneMovement 'currentSyllable$' 'highPitch' 'lastTone' 'followTone'
283         
284         lastTone = currentTone
285         endfor
287         # Add end margin
288         point = point + delta
289         Add point... 'point' 0
290         point = point + margin
291         Add point... 'point' 0
292 endproc
294 procedure generateWord whatToGenerate$ theWord$ upperRegister
295         call wordToTones 'theWord$' 'upperRegister'
296         # Generate pitch
297     select PitchTier 'theWord$'
298     noprogress To Pitch... 0.0125 60.0 600.0
299         Rename... theOrigWord
300         Smooth... 10
301         Rename... 'theWord$'
302         select Pitch theOrigWord
303         Remove
305     # Generate sound if wanted
306     select Pitch 'theWord$'
307     if rindex_regex(whatToGenerate$, "Sound") > 0
308             noprogress To Sound (hum)
309     endif
311     # Clean up
312     select PitchTier 'theWord$'
313     if rindex_regex(whatToGenerate$, "Sound") > 0
314         plus Pitch 'theWord$'
315     endif
316     Remove
317 endproc
319 # Horrible clutch to get an empty Strings list
320 if createToneList = 1
321     Read Strings from raw text file... ToneScript.praat
322     Rename... ToneList
323     listLength = Get number of strings
324     for i from 1 to listLength
325         select Strings ToneList
326         Set string... 'i' ------EMPTY
327     endfor
328 endif
330 syllableCount = length(replace_regex$(inputWord$, "[^\d]+([\d]+)", "1", 0))
331 wordNumber = 0
332 if rindex(generate$, "Correct") <= 0
333     for first from 1 to 6
334             currentWord$ = replace_regex$(inputWord$, "^([^\d]+)([\d]+)(.*)$", "\1'first'\3", 1)
335             for second from 0 to 6
336                     if (first <> 5 and second <> 5) and (syllableCount > 1 or second == 1)
337                             currentWord$ = replace_regex$(currentWord$, "^([^\d]+)([\d]+)([^\d]+)([\d]+)$", "\1'first'\3'second'", 1)
338                 # Write name in list
339                 wordNumber = wordNumber+1
340                 if createToneList = 1
341                     select Strings ToneList
342                     Set string... 'wordNumber' 'currentWord$'
343                 endif
345                 # Actually, generate something
346                 call generateWord 'generate$' 'currentWord$' 'upperRegister'
347                     endif
348             endfor
349     endfor
350 else
351     call generateWord 'generate$' 'inputWord$' 'upperRegister'
352 endif