3 # SpeakGoodChinese: ToneScript.praat generates synthetic tone contours
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
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.
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.
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
24 form Enter pinyin and tone 1 frequency
26 positive upperRegister_(Hz) 300
28 real toneScript.durationScale 1
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
44 toneScript.absoluteMinimum = 80
46 toneScript.prevTone = -1
47 toneScript.nextTone = -1
50 toneScript.lastFrequency = 0
53 if toneScript.inputWord$ <> ""
54 toneScript.inputWord$ = replace_regex$(toneScript.inputWord$, "^\s*(.+)\s*$", "\1", 1)
57 procedure extractTone .syllable$
58 toneScript.toneSyllable = -1
59 .currentToneText$ = replace_regex$(.syllable$, "^[^\d]+([\d]+)(.*)$", "\1", 0)
60 toneScript.toneSyllable = extractNumber(.currentToneText$, "")
63 procedure convertVoicing toneScript.voicingSyllable$
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)
71 toneScript.voicingSyllable$ = replace_regex$(toneScript.voicingSyllable$, "([aiuoeĆ¼])", "V", 0)
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
79 segmentDuration = 0.150
84 # start * ?Semit is a fall
85 # start / ?Semit is a rise
89 nineSemit = 0.594603557501361
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
105 frequency_Range = frequency_Range * range_Factor
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
115 toneScript.toneSyllable = -1
116 call extractTone 'syllable$'
117 if toneScript.toneSyllable = 3 and toneScript.nextTone = 3
118 toneScript.toneSyllable = 2
121 # Get voicing pattern
122 toneScript.voicingSyllable$ = ""
123 call convertVoicing 'syllable$'
125 # Account for tones in duration
127 # Scale the duration of the current syllable
129 toneFactor = toneFactor * toneScript.durationScale
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
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
154 toneScript.lastFrequency = endPoint
158 # Split input into syllables
161 procedure wordToTones .wordInput$ highPitch
162 currentRest$ = .wordInput$;
167 while rindex_regex(currentRest$, "^[^\d]+[\d]+") > 0
169 syllable'syllableCount'$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\1", 1)
170 currentSyllable$ = syllable'syllableCount'$
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
185 # Scale the duration of the current syllable
187 toneFactor = toneFactor * toneScript.durationScale
189 length = length + toneFactor * (length(voicingSyllable'syllableCount'$) * (segmentDuration + toneScript.delta) + fixedDuration)
192 currentRest$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\2", 1)
195 if syllableCount > 2000
200 # Create tone pattern
201 Create PitchTier... '.wordInput$' 0 'length'
204 toneScript.lastFrequency = 0
206 Add point... 'toneScript.point' 0
207 toneScript.point = margin
208 Add point... 'toneScript.point' 0
212 for i from 1 to syllableCount
213 currentSyllable$ = syllable'i'$
214 currentTone = toneScript.toneSyllable'i'
218 followTone = toneScript.toneSyllable'j'
221 call addToneMovement 'currentSyllable$' 'highPitch' 'lastTone' 'followTone'
223 lastTone = currentTone
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
233 procedure generateWord whatToGenerate$ theWord$ upperRegister
234 call wordToTones 'theWord$' 'upperRegister'
236 select PitchTier 'theWord$'
237 noprogress To Pitch... 0.0125 60.0 600.0
238 Rename... theOrigWord
241 select Pitch theOrigWord
244 # Generate sound if wanted
245 select Pitch 'theWord$'
246 if rindex_regex(whatToGenerate$, "Sound") > 0
247 noprogress To Sound (hum)
251 select PitchTier 'theWord$'
252 if rindex_regex(whatToGenerate$, "Sound") > 0
253 plus Pitch 'theWord$'
258 # Get a list of items
259 if toneScript.createToneList = 1
260 Create Table with column names... ToneList 36 Word
263 select Table ToneList
264 Set string value... 'i' Word ------EMPTY
268 syllableCount = length(replace_regex$(toneScript.inputWord$, "[^\d]+([\d]+)", "1", 0))
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)
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
288 Set string value... 'currLength' Word ------EMPTY
290 Set string value... 'wordNumber' Word 'currentWord$'
293 # Actually, generate something
294 call generateWord 'generate$' 'currentWord$' 'upperRegister'
299 call generateWord 'generate$' 'toneScript.inputWord$' 'upperRegister'