Rewriting process start codes to use call process<table><label> .... procedures
[sgc2.git] / ToneProt / ToneScript.praat
blob981b3bd106c301e08dbc18927420e06b678532e4
1 #! praat
3 #     SpeakGoodChinese: ToneScript.praat generates synthetic tone contours
4 #     for Mandarin Chinese
5 #     Copyright (C) 2007  R.J.J.H. van Son
6 #     The SpeakGoodChinese team are:
7 #     Guangqin Chen, Zhonyan Chen, Stefan de Konink, Eveline van Hagen, 
8 #     Rob van Son, Dennis Vierkant, David Weenink
9
10 #     This program is free software; you can redistribute it and/or modify
11 #     it under the terms of the GNU General Public License as published by
12 #     the Free Software Foundation; either version 2 of the License, or
13 #     (at your option) any later version.
14
15 #     This program is distributed in the hope that it will be useful,
16 #     but WITHOUT ANY WARRANTY; without even the implied warranty of
17 #     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 #     GNU General Public License for more details.
19
20 #     You should have received a copy of the GNU General Public License
21 #     along with this program; if not, write to the Free Software
22 #     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23
24 form Enter pinyin and tone 1 frequency
25         word inputWord ba1ba1
26         positive upperRegister_(Hz) 300
27     real range_Factor 1
28     real toneScript.durationScale 1
29     optionmenu generate 1
30         option Pitch
31         option Sound
32         option CorrectPitch
33         option CorrectSound
34 endform
36 toneScript.inputWord$ = inputWord$
37 # To supress the ToneList, change to 0
38 toneScript.createToneList = 1
39 if rindex_regex(generate$, "Correct") > 0
40         toneScript.createToneList = 0
41 endif
43 # Limit lowest tone
44 toneScript.absoluteMinimum = 80
46 toneScript.prevTone = -1
47 toneScript.nextTone = -1
49 toneScript.point = 0
50 toneScript.lastFrequency = 0
52 # Clean up input
53 if toneScript.inputWord$ <> ""
54     toneScript.inputWord$ = replace_regex$(toneScript.inputWord$, "^\s*(.+)\s*$", "\1", 1)
55 endif
57 procedure extractTone .syllable$
58         toneScript.toneSyllable = -1
59         .currentToneText$ = replace_regex$(.syllable$, "^[^\d]+([\d]+)(.*)$", "\1", 0)
60         toneScript.toneSyllable = extractNumber(.currentToneText$, "")
61 endproc
63 procedure convertVoicing toneScript.voicingSyllable$
64         # Remove tones
65         toneScript.voicingSyllable$ = replace_regex$(toneScript.voicingSyllable$, "^([^\d]+)[\d]+", "\1", 0)
66         # Convert voiced consonants
67         toneScript.voicingSyllable$ = replace_regex$(toneScript.voicingSyllable$, "(ng|[wrlmny])", "C", 0)
68         # Convert unvoiced consonants
69         toneScript.voicingSyllable$ = replace_regex$(toneScript.voicingSyllable$, "(sh|ch|zh|[fsxhktpgqdbzcj])", "U", 0)
70         # Convert vowels
71         toneScript.voicingSyllable$ = replace_regex$(toneScript.voicingSyllable$, "([aiuoeĆ¼])", "V", 0)
72 endproc
74 # Add a tone movement. The current time toneScript.point is 'toneScript.point'
75 toneScript.delta = 0.0000001
76 if toneScript.durationScale <= 0
77     toneScript.durationScale = 1.0
78 endif
79 segmentDuration = 0.150
80 fixedDuration = 0.12
83 # Movements
84 # start * ?Semit is a fall
85 # start / ?Semit is a rise
86 # 1/(12 semitones)
87 octave = 0.5
88 # 1/(9 semitones)
89 nineSemit = 0.594603557501361
90 # 1/(6 semitones)
91 sixSemit = 0.707106781186547
92 # 1/(3 semitones) down
93 threeSemit = 0.840896415253715
94 # 1/(2 semitones) down
95 twoSemit = 0.890898718140339
96 # 1/(1 semitones) down
97 oneSemit = 0.943874313
98 # 1/(4 semitones) down
99 fourSemit = twoSemit * twoSemit
100 # 1/(5 semitones) down
101 fiveSemit = threeSemit * twoSemit
103 frequency_Range = octave
104 if range_Factor > 0
105     frequency_Range =  frequency_Range * range_Factor
106 endif
108 # Get the rules of the tones
109 include ToneRules.praat
111 # Previous end frequency
112 toneScript.lastFrequency = 0
113 procedure addToneMovement syllable$ topLine toneScript.prevTone toneScript.nextTone
114         # Get tone
115         toneScript.toneSyllable = -1
116         call extractTone 'syllable$'
117     if toneScript.toneSyllable = 3 and toneScript.nextTone = 3
118         toneScript.toneSyllable = 2
119     endif
121         # Get voicing pattern
122         toneScript.voicingSyllable$ = ""
123         call convertVoicing 'syllable$'
125         # Account for tones in duration
126         toneFactor = 1
127     # Scale the duration of the current syllable
128     call toneDuration
129         toneFactor = toneFactor * toneScript.durationScale
131         # Unvoiced part
132         if rindex_regex(toneScript.voicingSyllable$, "U") = 1
133                 toneScript.point = toneScript.point + toneScript.delta
134         Add point... 'toneScript.point' 0
135                 toneScript.point = toneScript.point + segmentDuration * toneFactor
136         Add point... 'toneScript.point' 0
137         endif
138         # Voiced part
139         voiceLength$ = replace_regex$(toneScript.voicingSyllable$, "U*([CV]+)U*", "\1", 0)
140         voicedLength = length(voiceLength$)
141         voicedDuration = toneFactor * (segmentDuration*voicedLength + fixedDuration)
142         toneScript.point = toneScript.point + toneScript.delta
144     # Write contour of each tone
145     # Note that tones are influenced by the previous (tone 0) and next (tone 3)
146     # tones. Tone 6 is the Dutch intonation
147     # sqrt(frequency_Range) is the mid toneScript.point
148     if topLine * frequency_Range < toneScript.absoluteMinimum
149         frequency_Range = toneScript.absoluteMinimum / topLine
150     endif
152     call toneRules
153         
154     toneScript.lastFrequency = endPoint
156 endproc
158 # Split input into syllables
159 margin = 0.25
161 procedure wordToTones .wordInput$ highPitch
162         currentRest$ = .wordInput$;
163         syllableCount = 0
164         length = 2 * margin
166     # Split syllables
167         while rindex_regex(currentRest$, "^[^\d]+[\d]+") > 0
168         syllableCount += 1
169         syllable'syllableCount'$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\1", 1)
170                 currentSyllable$ = syllable'syllableCount'$
172                 # Get the tone
173                 call extractTone 'currentSyllable$'
174                 toneScript.toneSyllable'syllableCount' = toneScript.toneSyllable
175                 currentTone = toneScript.toneSyllable'syllableCount'
177                 # Get the Voicing pattern
178                 call convertVoicing 'currentSyllable$'
179                 voicingSyllable'syllableCount'$ = toneScript.voicingSyllable$
180                 currentVoicing$ = voicingSyllable'syllableCount'$
182                 # Calculate new length
183             # Account for tones in duration
184             toneFactor = 1
185         # Scale the duration of the current syllable
186         call toneDuration
187             toneFactor = toneFactor * toneScript.durationScale
189                 length = length + toneFactor * (length(voicingSyllable'syllableCount'$) * (segmentDuration + toneScript.delta) + fixedDuration)
191                 # Next round
192                 currentRest$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\2", 1)
194                 # Safety valve
195                 if syllableCount > 2000
196                         exit
197                 endif
198         endwhile
200         # Create tone pattern
201         Create PitchTier... '.wordInput$' 0 'length'
203         # Add start margin
204         toneScript.lastFrequency = 0
205     toneScript.point = 0
206         Add point... 'toneScript.point' 0
207         toneScript.point = margin
208         Add point... 'toneScript.point' 0
210     lastTone = -1
211     followTone = -1
212         for i from 1 to syllableCount
213                 currentSyllable$ = syllable'i'$
214         currentTone = toneScript.toneSyllable'i'
215         followTone = -1
216         if i < syllableCount
217             j = i+1
218             followTone = toneScript.toneSyllable'j'
219         endif
221                 call addToneMovement 'currentSyllable$' 'highPitch' 'lastTone' 'followTone'
223         lastTone = currentTone
224         endfor
226         # Add end margin
227         toneScript.point = toneScript.point + toneScript.delta
228         Add point... 'toneScript.point' 0
229         toneScript.point = toneScript.point + margin
230         Add point... 'toneScript.point' 0
231 endproc
233 procedure generateWord whatToGenerate$ theWord$ upperRegister
234         call wordToTones 'theWord$' 'upperRegister'
235         # Generate pitch
236     select PitchTier 'theWord$'
237     noprogress To Pitch... 0.0125 60.0 600.0
238         Rename... theOrigWord
239         Smooth... 10
240         Rename... 'theWord$'
241         select Pitch theOrigWord
242         Remove
244     # Generate sound if wanted
245     select Pitch 'theWord$'
246     if rindex_regex(whatToGenerate$, "Sound") > 0
247             noprogress To Sound (hum)
248     endif
250     # Clean up
251     select PitchTier 'theWord$'
252     if rindex_regex(whatToGenerate$, "Sound") > 0
253         plus Pitch 'theWord$'
254     endif
255     Remove
256 endproc
258 # Get a list of items
259 if toneScript.createToneList = 1
260     Create Table with column names... ToneList 36 Word
262     for i from 1 to 36
263             select Table ToneList
264         Set string value... 'i' Word ------EMPTY
265     endfor
266 endif
268 syllableCount = length(replace_regex$(toneScript.inputWord$, "[^\d]+([\d]+)", "1", 0))
269 wordNumber = 0
270 lowerBound = 1
271 if syllableCount = 1
272      lowerBound = 0
273 endif
274 if rindex(generate$, "Correct") <= 0
275     for first from lowerBound to 6
276             currentWord$ = replace_regex$(toneScript.inputWord$, "^([^\d]+)([\d]+)(.*)$", "\1'first'\3", 1)
277             for second from 0 to 6
278                     if (first <> 5 and second <> 5) and (syllableCount > 1 or second == 1)
279                             currentWord$ = replace_regex$(currentWord$, "^([^\d]+)([\d]+)([^\d]+)([\d]+)$", "\1'first'\3'second'", 1)
280                 # Write name in list
281                 wordNumber = wordNumber+1
282                 if toneScript.createToneList = 1
283                         select Table ToneList
284                     listLength = Get number of rows
285                     listLength = listLength + 1
286                     for currLength from listLength to wordNumber
287                         Append row
288                         Set string value... 'currLength' Word ------EMPTY
289                     endfor
290                     Set string value... 'wordNumber' Word 'currentWord$'
291                 endif
293                 # Actually, generate something
294                 call generateWord 'generate$' 'currentWord$' 'upperRegister'
295                     endif
296             endfor
297     endfor
298 else
299     call generateWord 'generate$' 'toneScript.inputWord$' 'upperRegister'
300 endif