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
36 # To supress the ToneList, change to 0
38 if rindex_regex(generate$, "Correct") > 0
53 inputWord$ = replace_regex$(inputWord$, "^\s*(.+)\s*$", "\1", 1)
56 procedure extractTone syllable$
58 currentToneText$ = replace_regex$(syllable$, "^[^\d]+([\d]+)(.*)$", "\1", 0)
59 toneSyllable = extractNumber(currentToneText$, "")
62 procedure convertVoicing voicingSyllable$
64 voicingSyllable$ = replace_regex$(voicingSyllable$, "^([^\d]+)[\d]+", "\1", 0)
65 # Convert voiced consonants
66 voicingSyllable$ = replace_regex$(voicingSyllable$, "(ng|[wrlmny])", "C", 0)
67 # Convert unvoiced consonants
68 voicingSyllable$ = replace_regex$(voicingSyllable$, "(sh|ch|zh|[fsxhktpgqdbzcj])", "U", 0)
70 voicingSyllable$ = replace_regex$(voicingSyllable$, "([aiuoeĆ¼])", "V", 0)
73 # Add a tone movement. The current time point is 'point'
78 segmentDuration = 0.150
83 # start * ?Semit is a fall
84 # start / ?Semit is a rise
88 nineSemit = 0.594603557501361
90 sixSemit = 0.707106781186547
91 # 1/(3 semitones) down
92 threeSemit = 0.840896415253715
93 # 1/(2 semitones) down
94 twoSemit = 0.890898718140339
95 # 1/(1 semitones) down
96 oneSemit = 0.943874313
97 # 1/(4 semitones) down
98 fourSemit = twoSemit * twoSemit
99 # 1/(5 semitones) down
100 fiveSemit = threeSemit * twoSemit
102 frequency_Range = octave
104 frequency_Range = frequency_Range * range_Factor
107 # Get the rules of the tones
108 include ToneRules.praat
110 # Previous end frequency
112 procedure addToneMovement syllable$ topLine prevTone nextTone
115 call extractTone 'syllable$'
116 if toneSyllable = 3 and nextTone = 3
120 # Get voicing pattern
121 voicingSyllable$ = ""
122 call convertVoicing 'syllable$'
124 # Account for tones in duration
126 # Scale the duration of the current syllable
128 toneFactor = toneFactor * durationScale
131 if rindex_regex(voicingSyllable$, "U") = 1
132 point = point + delta
133 Add point... 'point' 0
134 point = point + segmentDuration * toneFactor
135 Add point... 'point' 0
138 voiceLength$ = replace_regex$(voicingSyllable$, "U*([CV]+)U*", "\1", 0)
139 voicedLength = length(voiceLength$)
140 voicedDuration = toneFactor * (segmentDuration*voicedLength + fixedDuration)
141 point = point + delta
143 # Write contour of each tone
144 # Note that tones are influenced by the previous (tone 0) and next (tone 3)
145 # tones. Tone 6 is the Dutch intonation
146 # sqrt(frequency_Range) is the mid point
147 if topLine * frequency_Range < absoluteMinimum
148 frequency_Range = absoluteMinimum / topLine
153 lastFrequency = endPoint
157 # Split input into syllables
160 procedure wordToTones wordInput$ highPitch
161 currentRest$ = wordInput$;
166 while rindex_regex(currentRest$, "^[^\d]+[\d]+") > 0
168 syllable'syllableCount'$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\1", 1)
169 currentSyllable$ = syllable'syllableCount'$
172 call extractTone 'currentSyllable$'
173 toneSyllable'syllableCount' = toneSyllable
174 currentTone = toneSyllable'syllableCount'
176 # Get the Voicing pattern
177 call convertVoicing 'currentSyllable$'
178 voicingSyllable'syllableCount'$ = voicingSyllable$
179 currentVoicing$ = voicingSyllable'syllableCount'$
181 # Calculate new length
182 # Account for tones in duration
184 # Scale the duration of the current syllable
186 toneFactor = toneFactor * durationScale
188 length = length + toneFactor * (length(voicingSyllable'syllableCount'$) * (segmentDuration + delta) + fixedDuration)
191 currentRest$ = replace_regex$(currentRest$, "^([^\d]+[\d]+)(.*)$", "\2", 1)
194 if syllableCount > 2000
199 # Create tone pattern
200 Create PitchTier... 'wordInput$' 0 'length'
205 Add point... 'point' 0
207 Add point... 'point' 0
211 for i from 1 to syllableCount
212 currentSyllable$ = syllable'i'$
213 currentTone = toneSyllable'i'
217 followTone = toneSyllable'j'
220 call addToneMovement 'currentSyllable$' 'highPitch' 'lastTone' 'followTone'
222 lastTone = currentTone
226 point = point + delta
227 Add point... 'point' 0
228 point = point + margin
229 Add point... 'point' 0
232 procedure generateWord whatToGenerate$ theWord$ upperRegister
233 call wordToTones 'theWord$' 'upperRegister'
235 select PitchTier 'theWord$'
236 noprogress To Pitch... 0.0125 60.0 600.0
237 Rename... theOrigWord
240 select Pitch theOrigWord
243 # Generate sound if wanted
244 select Pitch 'theWord$'
245 if rindex_regex(whatToGenerate$, "Sound") > 0
246 noprogress To Sound (hum)
250 select PitchTier 'theWord$'
251 if rindex_regex(whatToGenerate$, "Sound") > 0
252 plus Pitch 'theWord$'
257 # Get a list of items
258 if createToneList = 1
259 Create Table with column names... ToneList 36 Word
262 select Table ToneList
263 Set string value... 'i' Word ------EMPTY
267 syllableCount = length(replace_regex$(inputWord$, "[^\d]+([\d]+)", "1", 0))
273 if rindex(generate$, "Correct") <= 0
274 for first from lowerBound to 6
275 currentWord$ = replace_regex$(inputWord$, "^([^\d]+)([\d]+)(.*)$", "\1'first'\3", 1)
276 for second from 0 to 6
277 if (first <> 5 and second <> 5) and (syllableCount > 1 or second == 1)
278 currentWord$ = replace_regex$(currentWord$, "^([^\d]+)([\d]+)([^\d]+)([\d]+)$", "\1'first'\3'second'", 1)
280 wordNumber = wordNumber+1
281 if createToneList = 1
282 select Table ToneList
283 listLength = Get number of rows
284 listLength = listLength + 1
285 for currLength from listLength to wordNumber
287 Set string value... 'currLength' Word ------EMPTY
289 Set string value... 'wordNumber' Word 'currentWord$'
292 # Actually, generate something
293 call generateWord 'generate$' 'currentWord$' 'upperRegister'
298 call generateWord 'generate$' 'inputWord$' 'upperRegister'