pelican-nlp 0.3.7__py3-none-any.whl → 0.3.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1224 @@
1
+ # segment.praat ---
2
+ # This file is included (indirectly) by prosogram.praat. It isn't a stand-alone script. Use prosogram.praat instead.
3
+ # Author: Piet Mertens
4
+
5
+ # Last modification: 2021-08-03
6
+ # 2018-03-31 added segmentation method "specsim"
7
+ # 2018-04-01 added segmentation method "pitchchange"
8
+ # 2018-08-09 added segmentation method "pitchterrace"
9
+ # 2019-01-29 debugged voiced_portions
10
+ # 2019-02-12 changed to new Praat syntax
11
+ # 2019-02-12 handle double length diacritic in @is_vowel
12
+ # 2019-12-04 changed @get_boundary
13
+ # 2021-08-03 added some vowels in @is_vowel
14
+
15
+
16
+ # Procedure hierarchy
17
+ # make_segmentation
18
+ # local_peaks_duo (**)
19
+ # pseudo_syllables (**) for segmentation mode "asyll"
20
+ # local_peaks_vowels (*) for segmentation mode "vow-nucl"
21
+ # local_peaks_syllables (*) for segmentation mode "syll"
22
+ # local_peaks_syllables_vowels (*)for segmentation mode "syll+vow"
23
+ # local_peaks_rhyme (*) for segmentation mode "rhyme"
24
+ # local_peaks_loudness (*) for segmentation mode "loudness"
25
+ #
26
+ # convexhull
27
+ # get_local_peak (*)
28
+ # is_unvoiced_region
29
+ # get_boundary
30
+ # voiced_intersection
31
+ # add_boundary
32
+ # set_boundary_label
33
+ # get_peak_duo (**)
34
+
35
+
36
+
37
+ procedure make_segmentation: segm_method, .at1, .at2, destID, mindiff
38
+ # Make a segmentation based on a parameter (which can be intensity, loudness, etc, stored in Intensity object),
39
+ # in time range <.at1> to <.at2>.
40
+ # The resulting interval tier is written in textgrid <destID>, created before calling this procedure.
41
+ # mindiff intensity difference threshold for local dips in convex hull (default = 3)
42
+ @debug_msg: "make_segmentation: entry"
43
+
44
+ mindur_nucl = 0.025 ; minimum duration of nucleus (otherwise rejected)
45
+ if (segm_method == segm_vnucl)
46
+ @local_peaks_vowels: destID, intensityID, .at1, .at2
47
+ elsif (segm_method == segm_aloudness)
48
+ @convexhull: destID, dip_tier, loudnessID, .at1, .at2, mindiff
49
+ @tier_point_to_interval: destID, dip_tier, syllable_tier, .at1, .at2
50
+ @local_peaks_loudness: destID, loudnessID, .at1, .at2
51
+ elsif (segm_method == segm_anucl)
52
+ @convexhull: destID, dip_tier, intbpID, .at1, .at2, mindiff
53
+ @tier_point_to_interval: destID, dip_tier, syllable_tier, .at1, .at2
54
+ @local_peaks_duo: destID, intensityID, intbpID, .at1, .at2
55
+ elsif (segm_method == segm_asyll)
56
+ @pseudo_syllables: destID, intensityID, intbpID, .at1, .at2, mindiff
57
+ elsif (segm_method == segm_msyllvow)
58
+ @local_peaks_syllables_vowels: destID, intensityID, .at1, .at2
59
+ elsif (segm_method == segm_msyllpeak)
60
+ @local_peaks_syllables: destID, intensityID, .at1, .at2
61
+ elsif (segm_method == segm_mrhyme)
62
+ @local_peaks_rhyme: destID, intensityID, .at1, .at2
63
+ elsif (segm_method == segm_voiced)
64
+ @voiced_portions: destID, intensityID, .at1, .at2
65
+ elsif (segm_method == segm_specsim)
66
+ if (not signal_available)
67
+ @read_signal
68
+ endif
69
+ if (not intensity_available)
70
+ .intensityID = To Intensity: 100, time_step
71
+ endif
72
+ @spectral_similarity: soundID, destID, intensityID, .at1, .at2, 0.02
73
+ removeObject: soundID
74
+ signal_available = 0
75
+ elsif (segm_method == segm_pitchchange)
76
+ @segmentation_pitch: pitchID, pc_threshold, intensityID, intbpID, .at1, .at2, destID, mindiff
77
+ elsif (segm_method == segm_pitchterrace)
78
+ @segmentation_pitch2: pitchID, pc_threshold, intensityID, .at1, .at2, mindur_terrace, destID
79
+ ;selectObject: destID
80
+ ;Copy: "Copy nucleiID " + fname$
81
+ endif
82
+ @debug_msg: "make_segmentation: exit"
83
+ endproc
84
+
85
+
86
+ procedure local_peaks_loudness: .grid, .paramID, .t1, .t2
87
+ # Find nucleus as local peak in loudness function
88
+ # <.grid> ID of destination textgrid where nuclei will be stored
89
+ # <.paramID> parameter for which local peak is to be found
90
+ # <.t1>..<.t2> time range of analysis
91
+ selectObject: grid
92
+ .t = .t1
93
+ while (.t < .t2)
94
+ .j = Get interval at time.: syllable_tier, .t
95
+ .x1 = Get start time of interval: syllable_tier, .j
96
+ .x2 = Get end time of interval: syllable_tier, .j
97
+ @get_local_peak: .grid, .paramID, .x1, .x2, 1, .x1, .x2
98
+ .t = .x2
99
+ endwhile
100
+ endproc
101
+
102
+
103
+ procedure local_peaks_vowels: .grid, .paramID, .t1, .t2
104
+ # Find nucleus as local peak in intensity of vowel
105
+ # <.grid> ID of destination textgrid where nuclei will be stored
106
+ # <.paramID> parameter for which local peak is to be found
107
+ # <.t1>..<.t2> time range of analysis
108
+ @intervals_from_time_range: .grid, phone_tier, .t1, .t2, "first_interval", "last_interval"
109
+ selectObject: .grid
110
+ for .j from first_interval to last_interval
111
+ .x1 = Get start time of interval: phone_tier, .j
112
+ .x2 = Get end time of interval: phone_tier, .j
113
+ .label$ = Get label of interval: phone_tier, .j
114
+ @is_vowel: .label$
115
+ if (is_vowel)
116
+ @get_local_peak: .grid, .paramID, .x1, .x2, 1, .x1, .x2
117
+ endif
118
+ endfor
119
+ endproc
120
+
121
+
122
+ procedure local_peaks_syllables: .grid, .paramID, .t1, .t2
123
+ # Find nucleus as local peak in intensity of syllable
124
+ # <.grid> ID of destination textgrid where nuclei will be stored
125
+ # <.paramID> parameter for which local peak is to be found
126
+ # <.t1>..<.t2> time range of analysis
127
+ @intervals_from_time_range: .grid, syllable_tier, .t1, .t2, "first_interval", "last_interval"
128
+ selectObject: .grid
129
+ for .j from first_interval to last_interval
130
+ .x1 = Get start time of interval: syllable_tier, .j
131
+ .x2 = Get end time of interval: syllable_tier, .j
132
+ .label$ = Get label of interval: syllable_tier, .j
133
+ .label$ = replace_regex$ (.label$, "^ *", "", 1)
134
+ .label$ = replace_regex$ (.label$, " *$", "", 1)
135
+ if (.label$ <> "PAUSE" and .label$ <> "_")
136
+ @get_local_peak: .grid, .paramID, .x1, .x2, 1, .x1, .x2
137
+ endif
138
+ endfor
139
+ endproc
140
+
141
+
142
+ procedure local_peaks_syllables_vowels: .grid .paramID, .t1, .t2
143
+ # Find nucleus as local peak in intensity of syllable, starting from local peak in vowel
144
+ # <.grid> ID of destination textgrid where nuclei will be stored
145
+ # <.paramID> parameter for which local peak is to be found
146
+ # <.t1>..<.t2> time range of analysis
147
+ @intervals_from_time_range: .grid, syllable_tier, .t1, .t2, "first_interval", "last_interval"
148
+ selectObject: .grid
149
+ for .j from first_interval to last_interval
150
+ .x1 = Get start time of interval: syllable_tier, .j
151
+ .x2 = Get end time of interval: syllable_tier, .j
152
+ .syll$ = Get label of interval: syllable_tier, .j
153
+ ; @intervals_from_time_range: .grid, phone_tier, .x1, .x2-0.00001, "ph1", "ph2"
154
+ @intervals_from_time_range_excl: .grid, phone_tier, .x1, .x2, "ph1", "ph2"
155
+ nrof_vowels = 0
156
+ nrof_syllabics = 0
157
+ for phon from ph1 to ph2
158
+ .label$ = Get label of interval: phone_tier, phon
159
+ .phon_x1 = Get start time of interval: phone_tier, phon
160
+ .phon_x2 = Get end time of interval: phone_tier, phon
161
+ @is_syllabic: .label$
162
+ if (result)
163
+ nrof_syllabics += 1
164
+ if (is_vowel)
165
+ nrof_vowels += 1
166
+ endif
167
+ if (nrof_syllabics == 1)
168
+ @get_local_peak: .grid, .paramID, .x1, .x2, 1, .phon_x1, .phon_x2
169
+ else
170
+ @msg: "Warning: Multiple vowels (or syllabics) in syllable <'.syll$'> at time '.phon_x1:3'"
171
+ endif
172
+ endif
173
+ endfor
174
+ endfor
175
+ endproc
176
+
177
+
178
+ procedure local_peaks_rhyme: .grid, .paramID, .t1, .t2
179
+ # Find nucleus as local peak in intensity of rhyme, starting from local intensity peak in vowel
180
+ # <.grid> ID of destination textgrid where nuclei will be stored
181
+ # <.paramID> parameter for which local peak is to be found
182
+ # <.t1>..<.t2> time range of analysis
183
+ @debug_msg: "local_peaks_rhyme: entry ('.t1:4'-'.t2:4')"
184
+ @intervals_from_time_range: .grid, syllable_tier, .t1, .t2, "first_interval", "last_interval"
185
+ selectObject: .grid
186
+ for .j from first_interval to last_interval
187
+ syll_x1 = Get start time of interval: syllable_tier, .j
188
+ syll_x2 = Get end time of interval: syllable_tier, .j
189
+ syll_label$ = Get label of interval: syllable_tier, .j
190
+ ; @intervals_from_time_range: .grid, phone_tier, syll_x1, syll_x2-0.00001, "ph1", "ph2"
191
+ @intervals_from_time_range_excl: .grid, phone_tier, syll_x1, syll_x2, "ph1", "ph2"
192
+ nrof_vowels = 0
193
+ nrof_syllabics = 0
194
+ for phon from ph1 to ph2
195
+ phon_x1 = Get start time of interval: phone_tier, phon
196
+ phon_x2 = Get end time of interval: phone_tier, phon
197
+ label$ = Get label of interval: phone_tier, phon
198
+ if (phon == ph1 and phon_x1 <> syll_x1)
199
+ @msg: "Syllable ('syll_label$') starting at 'syll_x1:4' not aligned with phoneme ('label$') start ('phon_x1:4')"
200
+ endif
201
+ if (phon == ph2 and phon_x2 <> syll_x2)
202
+ @msg: "Syllable ('syll_label$') ending at 'syll_x2:4' not aligned with phoneme ('label$') end ('phon_x2:4')"
203
+ endif
204
+ @is_syllabic: label$
205
+ if (result)
206
+ nrof_syllabics += 1
207
+ if (is_vowel)
208
+ nrof_vowels += 1
209
+ endif
210
+ if (nrof_syllabics == 1)
211
+ @get_local_peak: .grid, .paramID, phon_x1, syll_x2, 1, phon_x1, phon_x2
212
+ else
213
+ @msg: "Warning: Multiple vowels (or syllabics) in syllable at time 'phon_x1:3'"
214
+ endif
215
+ endif
216
+ endfor
217
+ endfor
218
+ @debug_msg: "local_peaks_rhyme: exit"
219
+ endproc
220
+
221
+
222
+ procedure local_peaks_duo: .dstID, .paramID, .ifilID, .t1, .t2
223
+ # <.dstID> ID of destination textgrid where nuclei will be stored
224
+ # <.paramID> parameter for which local peak is to be found (intensity)
225
+ # <.ifilID> intensity of bandpass filtered signal
226
+ # <.t1>..<.t2> time range of analysis
227
+ #
228
+ # Find syllabic nucleus within syllable-like interval
229
+ # - For a voiced portion...
230
+ # - Evaluate importance of difference between maximum of global intensity
231
+ # and dip at right end of segment. This affects right side of nucleus.
232
+ # - From intensity peak (of filtered), go left/right until max difference reached.
233
+ @debug_msg: "local_peaks_duo: entry"
234
+
235
+ .time = .t1
236
+ while (.time < .t2)
237
+ selectObject: .dstID
238
+ .j = Get interval at time: syllable_tier, .time
239
+ .x1 = Get start point: syllable_tier, .j
240
+ .x2 = Get end point: syllable_tier, .j
241
+ @is_unvoiced_region: dstID, .x1, .x2
242
+ # default values used in case of error
243
+ left = .x1
244
+ right = .x2
245
+ if (result = 1) ; fully unvoiced
246
+ @set_boundary_label: .dstID, syllable_tier, .x1, .x2, "U"
247
+ else ; fully or partly voiced
248
+ @get_peak_duo: .x1, .x2
249
+ ; get_peak_duo sets variables: left, right, valid
250
+ @add_boundary: .dstID, nucleus_tier, left
251
+ @add_boundary: .dstID, nucleus_tier, right
252
+ if (valid) ; variable set by get_peak_duo
253
+ @add_boundary: .dstID, syllable_tier, left
254
+ @add_boundary: .dstID, syllable_tier, right
255
+ if (left-.x1 > time_step)
256
+ @set_boundary_label: .dstID, syllable_tier, .x1, left, "<"
257
+ endif
258
+ if (.x2-right > time_step)
259
+ @set_boundary_label: .dstID, syllable_tier, right, .x2, ">"
260
+ endif
261
+ @set_boundary_label: .dstID, nucleus_tier, left, right, "a"
262
+ @set_boundary_label: .dstID, syllable_tier, left, right, "a"
263
+ else ; invalid nucleus (too short)
264
+ @set_boundary_label: .dstID, nucleus_tier, left, right, "reject"
265
+ @set_boundary_label: .dstID, syllable_tier, .x1, .x2, "<>"
266
+ endif ; valid
267
+ endif ; voiced
268
+ .time = .x2
269
+ endwhile
270
+ @debug_msg: "local_peaks_duo: exit"
271
+ endproc
272
+
273
+
274
+ procedure get_peak_duo: .x1p, .x2p
275
+ ; Find syllabic nucleus boundaries using full-band and BP-filtered intensity
276
+ ; Returns values in <valid>, <left>, <right>
277
+ valid = 0
278
+ selectObject: intbpID
279
+ .tmaxfil = Get time of maximum: .x1p, .x2p, "Parabolic"
280
+ selectObject: intensityID ; intensity full bandwidth
281
+ .tmax = Get time of maximum: .x1p, .x2p, "Parabolic"
282
+ .max = Get maximum: .x1p, .x2p, "Parabolic"
283
+ if (.max == undefined) ; can happen at end of signal, where intensity is undefined
284
+ @msg: "get_peak_duo: max undefined at time x1='.x1p:3', x2='.x2p:3'"
285
+ else
286
+ repeat ; can happen at end of signal, where intensity is undefined
287
+ selectObject: intensityID
288
+ .dip_int = Get value at time: .x2p, "Nearest"
289
+ selectObject: intbpID
290
+ .dip_intbp = Get value at time: .x2p, "Nearest"
291
+ if (.dip_int == undefined or .dip_intbp == undefined)
292
+ @debug_msg: "get_peak_duo: dip undefined at time '.x2p:3'"
293
+ .x2p -= time_step
294
+ endif
295
+ until (.dip_int <> undefined and .dip_intbp <> undefined)
296
+ ; <diff_left> is initialized in @initialization_multiple_files (prosomain.praat)
297
+ @get_boundary: intbpID, .tmaxfil, .x1p, -1, diff_left
298
+ .left_filt = result
299
+ @get_boundary: intensityID, .tmax, .x1p, -1, diff_left
300
+ left = max (.left_filt, result) ; select rightmost of both candidates
301
+ diff_right = max (3, (.max-.dip_intbp)/2 )
302
+ @get_boundary: intbpID, .tmaxfil, .x2p, 1, diff_right
303
+ .right_filt = result
304
+ diff_right = max (3, (.max-.dip_int)/2 )
305
+ @get_boundary: intensityID, .tmax, .x2p, 1, diff_right
306
+ right = max (.right_filt, result) ; select rightmost of both candidates
307
+ right = min (.x2p, right) ; different time unit in get_boundary
308
+ @voiced_intersection: left, right
309
+ if (result > 0 and right-left >= mindur_nucl)
310
+ valid = 1
311
+ endif
312
+ endif
313
+ endproc
314
+
315
+
316
+ procedure find_silences: paramID_, dstID, dst_tier
317
+ ; Use Praat's procedure to identify silent pauses on the basis of <paramID_> (usu. intensity)
318
+ ; and copy results to tier <dst_tier> of <dstID>
319
+ @debug_msg: "find_silences: entry"
320
+ selectObject: dstID
321
+ min_silent_interval = 0.15 ; Praat standard = 0.1
322
+ min_sounding_interval = 0.05 ; Praat standard = 0.1
323
+ silence_threshold = -30.0 ; Praat standard = -25.0
324
+ selectObject: paramID_
325
+ .silencesID = To TextGrid (silences): silence_threshold, min_silent_interval, min_sounding_interval, "_", "a"
326
+ Rename: "silences"
327
+ .n = Get number of intervals: 1
328
+ ; Copy boundaries from silences TextGrid to dstID in time range
329
+ for .j to .n
330
+ selectObject: .silencesID
331
+ .label$ = Get label of interval: 1, .j
332
+ .x1 = Get start point: 1, .j
333
+ .x2 = Get end point: 1, .j
334
+ @add_boundary: dstID, dst_tier, .x1
335
+ @add_boundary: dstID, dst_tier, .x2
336
+ @set_boundary_label: dstID, dst_tier, .x1, .x2, .label$
337
+ endfor
338
+ removeObject: .silencesID
339
+ @debug_msg: "find_silences: exit"
340
+ endproc
341
+
342
+
343
+ procedure voiced_portions: .dstID, .intID, .at1, .at2
344
+ # Find voiced portions in range <.at1>..<.at2> of signal and store corresponding intervals in nucleus_tier of <.dstID> TextGrid.
345
+ @debug_msg: "voiced_portions: entry"
346
+ @find_silences: .intID, .dstID, syllable_tier
347
+ selectObject: .dstID
348
+ # number of intervals grows during loop
349
+ .time = .at1
350
+ @add_boundary: .dstID, nucleus_tier, .time
351
+ while (.time + time_step < .at2) ; avoid rounding error
352
+ selectObject: .dstID
353
+ .j = Get interval at time: syllable_tier, .time
354
+ .label$ = Get label of interval: syllable_tier, .j
355
+ .x1 = Get start time of interval: syllable_tier, .j
356
+ .x2 = Get end time of interval: syllable_tier, .j
357
+ .nexttime = .x2
358
+ ;@debug_msg: "voiced_portions: x1='.x1:4' x2='.x2:4'"
359
+ @add_boundary: .dstID, nucleus_tier, .x2
360
+ if (.label$ == "_") ; pause
361
+ @set_boundary_label: .dstID, nucleus_tier, .x1, .x2, .label$
362
+ else
363
+ repeat
364
+ @voiced_intersection: .x1, .x2
365
+ ; returns result, left, right
366
+ if (result) ; it contains a voiced part
367
+ if (left-.x1 > time_step)
368
+ @add_boundary: .dstID, nucleus_tier, left
369
+ @set_boundary_label: .dstID, nucleus_tier, .x1, left, "U"
370
+ endif
371
+ @add_boundary: .dstID, nucleus_tier, right
372
+ @set_boundary_label: .dstID, nucleus_tier, left, right, "a"
373
+ .x1 = right
374
+ else
375
+ .x1 = .x2
376
+ endif
377
+ until (.x1 >= .x2)
378
+ endif
379
+ .time = .nexttime
380
+ endwhile
381
+ @debug_msg: "voiced_portions: exit"
382
+ endproc
383
+
384
+
385
+ procedure pseudo_syllables: .dstID, .intID, .ifilID, .at1, .at2, .mindiff
386
+ @debug_msg: "pseudo_syllables: entry"
387
+ @find_silences: .intID, .dstID, syllable_tier ; silent intervals are stored as intervals on syllabe tier
388
+ @mark_unvoiced: .dstID, dip_tier, .at1, .at2 ; voiced-unvoiced transitions are stored as points on dip tier
389
+ # number of intervals grows during following loop
390
+ # for each interval in syllable tier (either a silence or a speech fragment)
391
+ .time = .at1
392
+ while (.time + time_step < .at2) ; avoid rounding error
393
+ selectObject: .dstID
394
+ .j = Get interval at time: syllable_tier, .time
395
+ .label$ = Get label of interval: syllable_tier, .j
396
+ .x1 = Get start time of interval: syllable_tier, .j
397
+ .x2 = Get end time of interval: syllable_tier, .j
398
+ .nexttime = .x2
399
+ if (.label$ = "a")
400
+ @convexhull: .dstID, dip_tier, .ifilID, .x1, .x2, .mindiff ; major dips are stored as points on dip tier
401
+ if (result)
402
+ @tier_point_to_interval: .dstID, dip_tier, syllable_tier, .x1, .x2
403
+ @local_peaks_duo: .dstID, .intID, .ifilID, .x1, .x2
404
+ endif
405
+ endif
406
+ .time = .nexttime
407
+ endwhile
408
+ @pseudo_syll_pass4: .intID, .dstID, syllable_tier
409
+ @debug_msg: "pseudo_syllables: exit"
410
+ endproc
411
+
412
+
413
+ procedure segmentation_pitch: .pitchID, .thresholdST, .intID, .intbpID, .t1, .t2, .dstID, .mindiff
414
+ @debug_msg: "segmentation_pitch: entry"
415
+ @find_silences: .intID, .dstID, syllable_tier ; silence and speech are stored as intervals on syllable tier
416
+ @mark_unvoiced: .dstID, dip_tier, .t1, .t2 ; voiced-unvoiced transitions are stored as points on dip tier
417
+ ; @all_convexhull: .dstID, syllable_tier, .intID, .intbpID, .t1, .t2, .mindiff
418
+ @pitch_changes: .pitchID, .t1, .t2, .thresholdST, .dstID, dip_tier
419
+ @tier_point_to_interval: .dstID, dip_tier, syllable_tier, .t1, .t2
420
+ @label_syllable_tier: .dstID
421
+ ; @local_peaks_syllables: .dstID, .intID, .t1, .t2
422
+ @debug_msg: "segmentation_pitch: exit"
423
+ endproc
424
+
425
+
426
+ procedure segmentation_pitch2: .pitchID, .thresholdST, .intID, .t1, .t2, .mindur, .dstID
427
+ @debug_msg: "segmentation_pitch2: entry"
428
+ ; dip tier stores end times of terraces, slopes, unvoiced intervals:
429
+ @pitch_terraces: .pitchID, .t1, .t2, .thresholdST, .mindur, .dstID, dip_tier
430
+
431
+ selectObject: .dstID
432
+
433
+ ; Merge adjacent slope parts
434
+ .j = Get number of points: dip_tier
435
+ while (.j > 1)
436
+ .label$ = Get label of point: dip_tier, .j
437
+ .prev$ = Get label of point: dip_tier, .j-1
438
+ if (.label$ == "S" and .prev$ == "S")
439
+ Remove point: dip_tier, .j-1
440
+ endif
441
+ .j -= 1
442
+ endwhile
443
+
444
+ ; Convert point tier ("dip") to interval tier (syllables) and label pitch terraces as "a"
445
+ .n = Get number of points: dip_tier
446
+ for .j to .n
447
+ .time = Get time of point: dip_tier, .j
448
+ .label$ = Get label of point: dip_tier, .j
449
+ Insert boundary: syllable_tier, .time
450
+ if (.label$ == "T")
451
+ .k = Get low interval at time: syllable_tier, .time
452
+ Set interval text: syllable_tier, .k, "a"
453
+ endif
454
+ endfor
455
+ @debug_msg: "segmentation_pitch2: exit"
456
+ endproc
457
+
458
+
459
+ procedure spectral_similarity: .soundID, .dstID, .intID, .at1, .at2, .dcorr
460
+ @debug_msg: "spectral_similarity: entry"
461
+ @find_silences: .intID, .dstID, syllable_tier ; silence and speech are stored as intervals on syllabe tier
462
+ @mark_unvoiced: .dstID, dip_tier, .at1, .at2 ; voiced-unvoiced transitions are stored as points on dip tier
463
+ @spectral_correlation: .soundID, .at1, .at2, 0.03 ; compute correlation between spectral slices; time interval between spectral slices being correlated = 30ms
464
+ .corrID = result
465
+ # number of intervals grows during loop
466
+ .time = .at1
467
+ while (.time + time_step < .at2) ; avoid rounding error
468
+ selectObject: .dstID
469
+ .j = Get interval at time: syllable_tier, .time
470
+ .label$ = Get label of interval: syllable_tier, .j
471
+ .x1 = Get start point: syllable_tier, .j
472
+ .x2 = Get end point: syllable_tier, .j
473
+ .nexttime = .x2
474
+ if (.label$ = "a")
475
+ @convexhull: .dstID, dip_tier, .corrID, .x1, .x2, .dcorr ; major dips are stored as points on dip tier
476
+ if (result)
477
+ @tier_point_to_interval: .dstID, dip_tier, syllable_tier, .x1, .x2
478
+ .t = .x1
479
+ repeat
480
+ .i = Get interval at time: syllable_tier, .t
481
+ Set interval text: syllable_tier, .i, "a"
482
+ .t = Get end point: syllable_tier, .i
483
+ until (.t >= .x2)
484
+ endif
485
+ endif
486
+ .time = .nexttime
487
+ endwhile
488
+ .ns = Get number of intervals: syllable_tier
489
+ for .j to .ns
490
+ selectObject: .dstID
491
+ .label$ = Get label of interval: syllable_tier, .j
492
+ if (.label$ = "a")
493
+ .x1 = Get start point: syllable_tier, .j
494
+ .x2 = Get end point: syllable_tier, .j
495
+ @get_local_peak: .dstID, .intID, .x1, .x2, 0, .x1, .x2
496
+ endif
497
+ endfor
498
+ removeObject: .corrID
499
+ @debug_msg: "spectral_similarity: exit"
500
+ endproc
501
+
502
+
503
+ procedure spectral_correlation: .soundID, .t1, .t2, .timegap
504
+ ; Compute correlation between successive spectral slices to find point of spectral change.
505
+ ; <.timegap> the time between spectral slices being correlated.
506
+ ; The array of correlation values is casted to an intensity object (the ID od which is returned in <result>,
507
+ ; for later use in convex hull.
508
+ ncols = (.t2-.t1)/time_step + 1
509
+ ; Create Matrix: name$, xmin, xmax, ncols, dx, x1, ymin, ymax, nrows, dy, y1, formula$
510
+ .corr = Create Matrix: "correlation", .t1, .t2, ncols, time_step, .t1, 0, 1, 1, 1, 0, "0"
511
+ selectObject: .soundID
512
+ ; To Cochleagram: timestep, freqresolution(Bark), windowlength, forward_masking_time
513
+ .cochleaID = To Cochleagram: 0.01, 0.1, 0.03, 0.03
514
+ selectObject: .soundID
515
+
516
+ .time = .t1
517
+ while (.time < .t2)
518
+ @cochl_slice: .cochleaID, .time - .timegap/2
519
+ .tmp1 = result
520
+ @cochl_slice: .cochleaID, .time + .timegap/2
521
+ .tmp2 = result
522
+ selectObject: .tmp1, .tmp2
523
+ .tmp3 = Append columns
524
+ .corID = To Correlation
525
+ .r = Get value: 1, 2
526
+ selectObject: .corr
527
+ .col = ((.time-.t1)/time_step)+1
528
+ Set value: 1, .col, .r
529
+ removeObject: .tmp1, .tmp2, .tmp3, .corID
530
+ .time += time_step
531
+ endwhile
532
+ selectObject: .corr
533
+ result = To Intensity
534
+ removeObject: .cochleaID, .corr
535
+ endproc
536
+
537
+
538
+ procedure cochl_slice: .cochleaID, .time
539
+ selectObject: .cochleaID
540
+ excID = To Excitation (slice): .time
541
+ matID = To Matrix
542
+ mattID = Transpose
543
+ result = To TableOfReal
544
+ removeObject: excID, matID, mattID
545
+ endproc
546
+
547
+
548
+ procedure mark_unvoiced: dstID, tier_, at1, at2
549
+ # add boundaries to <tier_> (point tier) in dstID, at voiced-unvoiced transitions
550
+ @debug_msg: "mark_unvoiced: entry"
551
+ selectObject: dstID
552
+ n_ = Get number of intervals: vuv_tier
553
+ prev$ = ""
554
+ time_ = at1
555
+ while (time_ < at2)
556
+ j_ = Get interval at time: vuv_tier, time_
557
+ label_$ = Get label of interval: vuv_tier, j_
558
+ x2_ = Get end point: vuv_tier, j_
559
+ if (label_$ = "U" and prev$ = "V")
560
+ x1_ = Get start point: vuv_tier, j_
561
+ @tier_point_add: dstID, tier_, x1_, "0"
562
+ endif
563
+ prev$ = label_$
564
+ time_ = x2_
565
+ if (j_ == n_)
566
+ time_ = at2 ; exit loop
567
+ endif
568
+ endwhile
569
+ @debug_msg: "mark_unvoiced: exit"
570
+ endproc
571
+
572
+
573
+ procedure pseudo_syll_pass4: .intID, .dstID, .tier
574
+ ; 1. Group sequences with pattern: [<] a [>]
575
+ @debug_msg: "pseudo_syll_pass4: entry"
576
+ selectObject: .dstID
577
+ .n = Get number of intervals: .tier
578
+ .j = 2
579
+ while (.j <= .n)
580
+ selectObject: .dstID
581
+ .label$ = Get label of interval: .tier, .j
582
+ if (.label$ = "a")
583
+ .x1 = Get start time of interval: .tier, .j
584
+ .x2 = Get end time of interval: .tier, .j
585
+ selectObject: .intID
586
+ .ymax = Get maximum: .x1, .x2, "Parabolic"
587
+ selectObject: .dstID
588
+ Set interval text: .tier, .j, "syl"
589
+ if (.j > 1)
590
+ .prevlabel$ = Get label of interval: .tier, .j-1
591
+ if (.prevlabel$ = "<")
592
+ Remove left boundary: .tier, .j
593
+ .j -= 1
594
+ .n -= 1
595
+ Set interval text: .tier, .j, "syl"
596
+ endif
597
+ endif
598
+ if (.j < .n)
599
+ .nextlabel$ = Get label of interval: .tier, .j+1
600
+ .x = Get end time of interval: .tier, .j
601
+ selectObject: .intID
602
+ .y = Get value at time: .x, "Nearest"
603
+ if (.nextlabel$ = ">" and .ymax - .y < 25)
604
+ selectObject: .dstID
605
+ Remove right boundary: .tier, .j
606
+ .n -= 1
607
+ Set interval text: .tier, .j, "syl"
608
+ endif
609
+ endif
610
+ endif
611
+ .j += 1
612
+ endwhile
613
+ ; 2. Group unvoiced rejected nuclei with next syllable
614
+ .j = 1
615
+ while (.j <= .n)
616
+ selectObject: .dstID
617
+ .label$ = Get label of interval: .tier, .j
618
+ if (.label$ = "syl")
619
+ if (.j > 1)
620
+ .prevlabel$ = Get label of interval: .tier, .j-1
621
+ if (.prevlabel$ = "U")
622
+ Remove left boundary: .tier, .j
623
+ .n -= 1
624
+ .j -= 1
625
+ Set interval text: .tier, .j, "syl"
626
+ endif
627
+ endif
628
+ endif
629
+ .j += 1
630
+ endwhile
631
+ @debug_msg: "pseudo_syll_pass4: exit"
632
+ endproc
633
+
634
+
635
+ procedure get_local_peak: dstID, .paramID, x1, x2, dyn_threshold, seed_x1, seed_x2
636
+ # Find maximum in interval <seed_x1>..<seed_x2>. Find boundaries inside <x1>..<x2>.
637
+ # Time ranges <x1>..<x2> and <seed_x1>..<seed_x2> may be identical.
638
+ # <dyn_threshold> Use dynamic intensity threshold for right boundary
639
+ @debug_msg: "get_local_peak: entry x1='x1:3' x2='x2:3'"
640
+ selectObject: .paramID
641
+ time_step = Get time step
642
+ ; default values used in case of error
643
+ left = x1 ; left boundary of peak
644
+ right = x2 ; right boundary of peak
645
+ label2$ = "-" ; not a nucleus
646
+ valid = 0 ; not a valid nucleus
647
+ @is_unvoiced_region: dstID, x1, x2
648
+ if (result = 0) ; not fully unvoiced, i.e. partly voiced
649
+ selectObject: .paramID
650
+ maxtime = Get time of maximum: seed_x1, seed_x2, "Parabolic"
651
+ max = Get maximum: seed_x1, seed_x2, "Parabolic"
652
+ if (max = undefined) ; can happen at both ends of signal, where intensity is undefined
653
+ @msg: "get_local_peak: max undefined at 'maxtime:3', seed_x1='seed_x1:3' seed_x2='seed_x2:3'"
654
+ # use defaults
655
+ else ; max is defined
656
+ # find left boundary
657
+ dipL = Get value at time: x1, "Nearest"
658
+ if (dipL = undefined)
659
+ @msg: "get_local_peak: dip left undefined at time 'x1:3'"
660
+ else
661
+ ; <diff_left> is initialized in @initialization_multiple_files (prosomain.praat)
662
+ ; diff_left = 3
663
+ @get_boundary: .paramID, maxtime, x1, -1, diff_left
664
+ left = result
665
+ if ((segm_method == segm_vnucl or segm_method == segm_mrhyme) and left-seed_x1 >= 0.075)
666
+ left = seed_x1 + 0.02
667
+ endif
668
+ endif
669
+ # find right boundary
670
+ dip = Get minimum: maxtime, x2, "Parabolic"
671
+ if (dip = undefined)
672
+ @msg: "get_local_peak: dip right undefined at time 'x2:3'"
673
+ else
674
+ diff_right = 9
675
+ if (dyn_threshold)
676
+ diff_right = max(3, (max - dip)*0.80)
677
+ endif
678
+ @get_boundary: .paramID, maxtime, x2, 1, diff_right
679
+ right = result
680
+ endif
681
+ if (dipL != undefined and dip != undefined)
682
+ @voiced_intersection: left, right
683
+ if (result > 0 and right-left >= mindur_nucl)
684
+ label2$ = "a"
685
+ valid = 1
686
+ endif
687
+ endif
688
+ endif
689
+ endif ; voiced
690
+ ;@msg: "get_local_peak: ['seed_x1:3' 'seed_x2:3'] peak='maxtime:3' ADDING boundaries L='left:3' R='right:3' label='label2$'"
691
+ @add_boundary: dstID, nucleus_tier, left
692
+ @add_boundary: dstID, nucleus_tier, right
693
+ @set_boundary_label: dstID, nucleus_tier, left, right, label2$
694
+ if (valid)
695
+ @add_boundary: dstID, nucleus_tier, x1
696
+ @add_boundary: dstID, nucleus_tier, x2
697
+ if (left-x1 > time_step)
698
+ @set_boundary_label: dstID, nucleus_tier, x1, left, "<"
699
+ endif
700
+ if (x2-right > time_step)
701
+ @set_boundary_label: dstID, nucleus_tier, right, x2, ">"
702
+ endif
703
+ endif ; valid
704
+ endproc
705
+
706
+
707
+ procedure all_convexhull: .gridID, .tier, .paramID, .t1, .t2, .mindiff
708
+ # Apply convexhull to each interval of tier in grid, storing major dips in dip_tier
709
+ .time = .t1
710
+ while (.time + time_step < .t2) ; avoid rounding error
711
+ selectObject: .gridID
712
+ .j = Get interval at time: .tier, .time
713
+ .label$ = Get label of interval: .tier, .j
714
+ .x1 = Get start point: .tier, .j
715
+ .x2 = Get end point: .tier, .j
716
+ if (.label$ = "a")
717
+ @convexhull: .gridID, dip_tier, .paramID, .x1, .x2, .mindiff
718
+ endif
719
+ .time = .x2
720
+ endwhile
721
+ endproc
722
+
723
+
724
+ procedure convexhull: .gridID, .tier, .paramID, .x1, .x2, .mindiff
725
+ # <.gridID> textgrid in which segmentation points are stored
726
+ # <.tier> point tier where segmentation points are stored
727
+ # <.paramID> parameter on which segmentation is based
728
+ # <.x1> start time of analysis
729
+ # <.x2> end time of analysis
730
+ # <.mindiff> difference threshold for segmentation
731
+ # <result>
732
+ @debug_msg: "convexhull: entry"
733
+ convexhull_winlen = 0.75
734
+ selectObject: .paramID
735
+ .dx = Get time step
736
+
737
+ # Skip part at start of signal for which parameter is undefined
738
+ .ok_L = 0
739
+ .xL = .x1
740
+ repeat
741
+ .y = Get value at time: .xL, "Nearest"
742
+ if (.y == undefined)
743
+ .xL += .dx
744
+ else
745
+ .ok_L = 1
746
+ endif
747
+ until (.ok_L or .xL > .x2)
748
+
749
+ # Skip part at end of signal for which parameter is undefined
750
+ .ok_R = 0
751
+ .xR = .x2
752
+ repeat
753
+ .y = Get value at time: .xR, "Nearest"
754
+ if (.y == undefined)
755
+ .xR -= .dx
756
+ else
757
+ .ok_R = 1
758
+ endif
759
+ until (.ok_R or .xR < .x1)
760
+
761
+ result = 0
762
+ if (.ok_L and .ok_R)
763
+ while (.xL < .xR)
764
+ .xlast = min (.xL + convexhull_winlen, .xR)
765
+ .dip = 0
766
+ repeat
767
+ @time_maxdiff: .paramID, .xL, .xlast, .dx, .mindiff
768
+ if (tmaxdif >= 0)
769
+ .xlast = tmaxdif
770
+ .dip = maxdif
771
+ endif
772
+ until (tmaxdif < 0)
773
+ if (.dip > .mindiff)
774
+ ;@msg: "ADDING DIP at '.xlast:4', diff='.dip:3'"
775
+ @tier_point_add: .gridID, .tier, .xlast, "'.dip:1'"
776
+ endif
777
+ .xL = .xlast ; shift start of analysis window to end of previous
778
+ endwhile
779
+ result = 1
780
+ endif
781
+ @debug_msg: "convexhull: exit"
782
+ endproc
783
+
784
+
785
+ procedure time_maxdiff: paramID, xh1, xh2, dx, mindif
786
+ # returns <tmaxdif>, <maxdif>, <result>
787
+ # <result> (boolean) true if dip found (dif >= mindif)
788
+ selectObject: paramID
789
+ maxdif = 0.0
790
+ tmax_ = Get time of maximum... xh1 xh2 Parabolic
791
+ if (tmax_ > xh1)
792
+ x = xh1
793
+ tmaxdif = xh1
794
+ h = Get value at time... x Nearest
795
+ while (x < tmax_) ; locate max diff while going up hull to peak
796
+ y = Get value at time... x Nearest
797
+ if (h-y > maxdif)
798
+ maxdif = h-y
799
+ tmaxdif = x
800
+ endif
801
+ h = max (y,h)
802
+ x += dx
803
+ endwhile
804
+ endif
805
+ if (tmax_ < xh2)
806
+ x = xh2
807
+ h = Get value at time... x Nearest
808
+ while (x > tmax_)
809
+ y = Get value at time... x Nearest
810
+ if (h-y > maxdif)
811
+ maxdif = h-y
812
+ tmaxdif = x
813
+ endif
814
+ h = max (y,h)
815
+ x -= dx
816
+ endwhile
817
+ endif
818
+ if (maxdif >= mindif)
819
+ result = 1
820
+ else
821
+ result = 0
822
+ tmaxdif = -1
823
+ endif
824
+ endproc
825
+
826
+
827
+ procedure add_boundary: .destID, .tier, .xbound
828
+ # Add a boundary, but
829
+ # - avoid adding left boundary at right boundary of previous segment
830
+ # - avoid adding boundary at starttime of TextGrid
831
+ # - avoid adding boundary at endtime of TextGrid
832
+ # <.xbound> time of boundary
833
+ ;@debug_msg: "add_boundary: entry"
834
+ selectObject: .destID
835
+ .xstop = Get end time
836
+ .xstart = Get start time
837
+ .i = Get interval at time: .tier, .xbound
838
+ if (.i <= 0)
839
+ @msg: "add_boundary: i<=0 xbound='.xbound'"
840
+ endif
841
+ .t1 = Get start time of interval: .tier, .i
842
+ .t2 = Get end time of interval: .tier, .i
843
+ if (abs(.t1-.xbound) > time_step and abs(.t2-.xbound) > time_step and .xbound > .xstart and .xbound < .xstop)
844
+ Insert boundary: .tier, .xbound
845
+ endif
846
+ ;@debug_msg: "add_boundary: exit"
847
+ endproc
848
+
849
+
850
+ procedure tier_point_add: .destID, .tier, .x, .text$
851
+ selectObject: .destID
852
+ .i = Get nearest index from time: .tier, .x
853
+ if (.i == 0) ; no points in tier
854
+ Insert point: .tier, .x, .text$
855
+ else
856
+ .t = Get time of point: .tier, .i
857
+ if (.t == .x)
858
+ @msg: "tier_point_add: already a point at time '.x:3'"
859
+ else
860
+ Insert point: .tier, .x, .text$
861
+ endif
862
+ endif
863
+ endproc
864
+
865
+
866
+ procedure set_boundary_label: .destID, .tier, .xleft, .xright, .label$
867
+ ;call debug_msg set_boundary_label: entry
868
+ select .destID
869
+ .i = Get interval at time: .tier, .xleft+(.xright-.xleft)/2
870
+ Set interval text: .tier, .i, .label$
871
+ ;call debug_msg set_boundary_label: exit
872
+ endproc
873
+
874
+
875
+ procedure get_boundary_old: .paramID, .peaktime, .xlimit, .incr, .diff
876
+ # Get time of left or right boundary.
877
+ # <.peaktime> time of peak in interval
878
+ # <.xlimit> time limit for left or right boundary
879
+ # <.incr> -1 for left boundary, 1 for right boundary
880
+ # <.diff> max difference between peak and value at boundary
881
+ # Returns time of left or right boundary in <result>
882
+ result = 0
883
+ selectObject: .paramID
884
+ .peakframe = Get frame number from time: .peaktime
885
+ .i = round (.peakframe)
886
+ .max = Get value in frame: .i
887
+ .limit = Get frame number from time: .xlimit
888
+ .limit = round (.limit)
889
+ ;@msg: "get_boundary: t='.peaktime:4' xlimit='.xlimit:5' limit='.limit'"
890
+ .ok = 1
891
+ while (.ok)
892
+ .nexti = .i + .incr
893
+ .y = Get value in frame: .i
894
+ if ((.incr < 0 and .nexti < .limit) or (.incr > 0 and .nexti > .limit) or (.max-.y > .diff))
895
+ .ok = 0
896
+ else
897
+ .i = .nexti
898
+ if (.incr < 0 and .i <= 0)
899
+ .ok = 0
900
+ .i = 1
901
+ endif
902
+ endif
903
+ endwhile
904
+ result = Get time from frame: .i
905
+ if (.incr > 0) ; frame is real number -> rounding errors
906
+ result = min (result, .xlimit)
907
+ else
908
+ result = max (result, .xlimit)
909
+ endif
910
+ endproc
911
+
912
+
913
+ procedure get_boundary: .paramID, .peaktime, .xlimit, .incr, .diff
914
+ # Get time of left or right boundary.
915
+ # <.peaktime> time of peak in interval
916
+ # <.xlimit> time limit for left or right boundary
917
+ # <.incr> -1 for left boundary, 1 for right boundary
918
+ # <.diff> max difference between peak and value at boundary
919
+ # Returns time of left or right boundary in <result>
920
+ selectObject: .paramID
921
+ .dx = Get time step
922
+ .t = .peaktime
923
+ .max = Get value at time: .t, "Nearest"
924
+ .ok = 1
925
+ while (.ok)
926
+ .y = Get value at time: .t, "Nearest"
927
+ .tn = .t + (.incr * .dx)
928
+ if ((.incr < 0 and .tn < .xlimit) or (.incr > 0 and .tn > .xlimit) or (.max-.y > .diff))
929
+ .ok = 0
930
+ else
931
+ .t = .tn
932
+ if (.incr < 0 and .t <= 0)
933
+ .ok = 0
934
+ .t = 0
935
+ endif
936
+ endif
937
+ endwhile
938
+ ;@msg: "get_boundary: tp='.peaktime:4' xlimit='.xlimit:5' t='.t:5' incr='.incr'"
939
+ if (.incr > 0)
940
+ result = min (.t, .xlimit)
941
+ else
942
+ result = max (.t, .xlimit)
943
+ endif
944
+ endproc
945
+
946
+
947
+
948
+ procedure tier_point_to_interval: .gridID, .ptier, .itier, .t1, .t2
949
+ # Convert points in PointTier <ptier> to intervals in <itier>
950
+ @debug_msg: "tier_point_to_interval: entry"
951
+ selectObject: .gridID
952
+ .n = Get number of points: .ptier
953
+ for .j to .n
954
+ .time = Get time of point: .ptier, .j
955
+ if (.time >= .t1 and .time <= .t2)
956
+ @add_boundary: .gridID, .itier, .time
957
+ endif
958
+ endfor
959
+ @debug_msg: "tier_point_to_interval: exit"
960
+ endproc
961
+
962
+
963
+ procedure is_unvoiced_region: .gridID, .x1, .x2
964
+ # Returns 1 in variable <result> if <.x1> and <.x2> are within same unvoiced interval of the VUV grid
965
+ @debug_msg: "is_unvoiced_region: entry"
966
+ result = 0
967
+ selectObject: .gridID
968
+ .i1 = Get interval at time: vuv_tier, .x1
969
+ if (.i1 == 0) ; peeking before analysed signal; return unvoiced
970
+ @msg: "is_unvoiced_region: i1='.i1' x1='x1'"
971
+ result = 1
972
+ else
973
+ .i2 = Get interval at time: vuv_tier, .x2
974
+ .label$ = Get label of interval: vuv_tier, .i1
975
+ if (.i1 == .i2 and .label$ = "U")
976
+ result = 1
977
+ endif
978
+ endif
979
+ @debug_msg: "is_unvoiced_region: exit"
980
+ endproc
981
+
982
+
983
+ procedure unvoiced_proportion x1_ x2_
984
+ # returns proportion of unvoiced part inside <x1_>..<x2_>
985
+ # returns left, right: the unvoiced part inside <x1_>..<x2_>
986
+ result = 0
987
+ select nucleiID
988
+ ni_ = Get number of intervals... vuv_tier
989
+ i_ = Get interval at time... vuv_tier x1_
990
+ if (i_ == 0)
991
+ # peeking outside analysed signal; return unvoiced
992
+ call msg error in unvoiced_proportion i='i_' x1='x1_'
993
+ result = 0
994
+ else
995
+ ux1 = x2_
996
+ ux2 = ux1
997
+ repeat
998
+ label_$ = Get label of interval... vuv_tier i_
999
+ t1_ = Get start point... vuv_tier i_
1000
+ t2_ = Get end point... vuv_tier i_
1001
+ if (label_$ = "U")
1002
+ ux1 = max(x1_, t1_)
1003
+ endif
1004
+ i_ += 1
1005
+ until (ux1 < x2_ or t1_ >= x2_ or i_ >= ni_)
1006
+ ux2 = min (t2_, x2_)
1007
+ result = (ux2 - ux1) / (x2_ - x1_)
1008
+ endif
1009
+ endproc
1010
+
1011
+
1012
+ procedure voiced_intersection: .x1, .x2
1013
+ # returns 1 in <result> if there is a voiced part inside <.x1>..<.x2>
1014
+ # returns <left>, <right>: the voiced part inside <.x1>..<.x2>
1015
+ result = 0
1016
+ selectObject: nucleiID
1017
+ .i1 = Get interval at time: vuv_tier, .x1
1018
+ .i2 = Get interval at time: vuv_tier, .x2
1019
+ if (.i1 == 0 or .i2 == 0) ; peeking outside analysed signal; return unvoiced
1020
+ @error_msg: "voiced_intersection: .i1='.i1' x1='.x1'"
1021
+ else
1022
+ while (.i1 <= .i2)
1023
+ .label$ = Get label of interval: vuv_tier, .i1
1024
+ if (.label$ = "V")
1025
+ if (result == 0)
1026
+ .t = Get start time of interval: vuv_tier, .i1
1027
+ left = max (.t, .x1)
1028
+ endif
1029
+ .t = Get end time of interval: vuv_tier, .i1
1030
+ right = min (.x2, .t)
1031
+ result = 1
1032
+ else
1033
+ if (result == 1)
1034
+ .i1 = .i2+1
1035
+ endif
1036
+ endif
1037
+ .i1 += 1
1038
+ endwhile
1039
+ endif
1040
+ endproc
1041
+
1042
+
1043
+ procedure is_vowel: s$
1044
+ # Sets global variable <is_vowel> to 1 if <s$> is a vowel (in SAMPA or Praat Phonetic symbols conventions)
1045
+ # and to 0 otherwise
1046
+ .input$ = s$
1047
+ is_vowel = 0
1048
+ ; remove some symbols, mainly diacritics
1049
+ s$ = replace_regex$ (s$, "^""", "", 1) ; primary stress (at start of label) in SAMPA
1050
+ s$ = replace$ (s$, "`", "", 0) ; remove all rhoticity diacritics
1051
+ s$ = replace_regex$ (s$, "^i_d$", "i", 1) ; dental i in X-SAMPA
1052
+ s$ = replace_regex$ (s$, ":+$", "", 1) ; diacritic(s) indicating lengthening (at end of label)
1053
+ s$ = replace$ (s$, "\:f", "", 0) ; diacritic indicating lengthening
1054
+ len = length (s$)
1055
+ first$ = left$ (s$, 1)
1056
+ if (index_regex (s$, "^[aeiouyAEIOUYOQV@23679&\{\}]+$") ) ; SAMPA, 1 or more vocalic elements
1057
+ is_vowel = 1
1058
+ elsif (len = 1 and s$ = "V") ; ad hoc convention (on special request)
1059
+ is_vowel = 1
1060
+ elsif (len = 2)
1061
+ if (index ("~a~e~o~E~O~A~9~U~", s$)) ; SAMPA nasal vowels; U~ used by some
1062
+ is_vowel = 1
1063
+ endif
1064
+ elsif (len = 3 and first$ = "\" ) ; Praat phonetic symbols
1065
+ z$ = mid$ (s$,1,3) ; first three characters
1066
+ z2$ = mid$ (s$,1,2) ; first two characters
1067
+ third$ = mid$ (s$, 3, 1)
1068
+ if (third$ = """" and (index (":\a:\e:\i:\o:\u:\y:", ":'z2$':")))
1069
+ is_vowel = 1
1070
+ elsif (third$ = "-" and (index (":\e:\i:\o:\u:", ":'z2$':")))
1071
+ is_vowel = 1
1072
+ elsif (index (":\a~:\o~:", ":'s$':")) ; nasal vowels
1073
+ is_vowel = 1
1074
+ elsif (index (":\o/:\ab:\as:\ae:\at:\ep:\ef:\er:\oe:\Oe:\ct:\vt:\ic:\yc:\sw:\sr:\rh:\hs:\kb:\mt:\u-:", ":'z$':"))
1075
+ is_vowel = 1
1076
+ endif
1077
+ elsif (len > 3) ; Praat symbols
1078
+ if (index (":a\~^:\ep~:\as\~^:\ep\~^:\ct\~^:", ":'s$':")) ; nasalized
1079
+ is_vowel = 1
1080
+ elsif (index (":a\ic:\ct\ic:a\hs:o\hs:\ct\hs:", ":'s$':")) ; diphthongs aI, OI, aU, oU, OU
1081
+ is_vowel = 1
1082
+ endif
1083
+ endif
1084
+ endproc
1085
+
1086
+
1087
+ procedure is_syllabic: s$
1088
+ ; sets <result> = true is string is vowel or syllabic consonant
1089
+ ; sets <is_vowel> = true if string is a vowel
1090
+ @is_vowel: s$
1091
+ if (is_vowel)
1092
+ result = 1
1093
+ ; elsif (index_regex (s$, "^[mnJNlrR]\\\|v$")) ; Praat phonetic symbol "\|v"
1094
+ elsif (index_regex (s$, "^[mnJNlrR]=$")) ; X-SAMPA symbol "="
1095
+ result = 1
1096
+ else
1097
+ result = 0
1098
+ endif
1099
+ endproc
1100
+
1101
+
1102
+ procedure pitch_changes: .pitchID, .t1, .t2, .thresholdST, .grid, .tier
1103
+ # Detect pitch changes exceeding <.thresholdST> (in semitones) and store them as points in tier <.tier> of <.grid>
1104
+ selectObject: .pitchID
1105
+ .n = Get number of frames
1106
+ .j = Get frame number from time: .t1
1107
+ .j = max (.j, 1)
1108
+ .j2 = Get frame number from time: .t2
1109
+ .j2 = min (floor(.j2), .n)
1110
+ .prevHz = Get value in frame: .j, "Hertz"
1111
+ while (.j < .j2)
1112
+ .pitchHz = Get value in frame: .j, "Hertz"
1113
+ if (.pitchHz == undefined)
1114
+ repeat ; find end of part with undefined pitch
1115
+ .right = .j
1116
+ .j += 1
1117
+ .nextHz = Get value in frame: .j, "Hertz"
1118
+ until (.j == .j2 or not (.nextHz == undefined))
1119
+ else
1120
+ repeat ; find point where pitch change exceeds threshold or where pitch is undefined
1121
+ .right = .j
1122
+ .j += 1
1123
+ .nextHz = Get value in frame: .j, "Hertz"
1124
+ .distST = 12 * log2 (.nextHz/.prevHz)
1125
+ until (.nextHz == undefined or .j == .j2 or abs(.distST) > .thresholdST)
1126
+ endif
1127
+ .t = Get time from frame number: .right
1128
+ selectObject: .grid
1129
+ Insert point: .tier, .t, ""
1130
+ selectObject: .pitchID
1131
+ .prevHz = Get value in frame: .j, "Hertz"
1132
+ endwhile
1133
+ endproc
1134
+
1135
+
1136
+ procedure pitch_terraces: .pitchID, .t1, .t2, .thresholdST, .mindur, .grid, .tier
1137
+ # Detect pitch changes exceeding <.thresholdST> (in semitones per frame period) and store them as points in tier <.tier> of <.grid>
1138
+ selectObject: .pitchID
1139
+ .n = Get number of frames
1140
+ ; .prevt = Get start time
1141
+ ; Synchronize times with frame times
1142
+ .j = Get frame number from time: .t1
1143
+ .j = max (.j, 1)
1144
+ .j2 = Get frame number from time: .t2
1145
+ .j2 = min (floor(.j2), .n)
1146
+ .yHz = Get value in frame: .j, "Hertz"
1147
+ ; possible states: U=unvoiced, T=(in )terrace, S=(in )slope
1148
+ ; initial states: U, T
1149
+ if (.yHz == undefined)
1150
+ .state$ = "U"
1151
+ else
1152
+ .state$ = "T"
1153
+ endif
1154
+ while (.j < .j2)
1155
+ .t = Get time from frame number: .j
1156
+ .switch = 0 ; 1 when change state
1157
+ .yHz = Get value in frame: .j, "Hertz"
1158
+ .j += 1
1159
+ .nextHz = Get value in frame: .j, "Hertz"
1160
+ .dist = 12 * log2 (.nextHz/.yHz)
1161
+ ; printline *** t='.t:3' state='.state$' j='.j' nextF0='.nextHz:0' dist='.dist:2'
1162
+ if (.state$ == "T")
1163
+ if (.nextHz == undefined)
1164
+ .switch = 1
1165
+ .newstate$ = "U"
1166
+ elsif (abs(.dist) > .thresholdST)
1167
+ .switch = 1
1168
+ .newstate$ = "S"
1169
+ endif
1170
+ elsif (.state$ == "S")
1171
+ if (.nextHz == undefined)
1172
+ .switch = 1
1173
+ .newstate$ = "U"
1174
+ elsif (abs(.dist) <= .thresholdST)
1175
+ .switch = 1
1176
+ .newstate$ = "T"
1177
+ endif
1178
+ elsif (.state$ == "U")
1179
+ if not (.nextHz == undefined)
1180
+ .switch = 1
1181
+ .newstate$ = "T"
1182
+ endif
1183
+ endif
1184
+ if (.switch)
1185
+ .t = Get time from frame number: .j-1
1186
+ selectObject: .grid
1187
+ Insert point: .tier, .t, .state$
1188
+ .state$ = .newstate$
1189
+ selectObject: .pitchID
1190
+ endif
1191
+ endwhile
1192
+ selectObject: .pitchID
1193
+ .t = Get time from frame number: .j2
1194
+ selectObject: .grid
1195
+ Insert point: .tier, .t, .state$
1196
+
1197
+ ; Remove short duration pitch terraces (< .mindur)
1198
+ selectObject: .grid
1199
+ .j = Get number of points: dip_tier
1200
+ while (.j > 1)
1201
+ .label$ = Get label of point: dip_tier, .j
1202
+ .x2 = Get time of point: dip_tier, .j
1203
+ .x1 = Get time of point: dip_tier, .j-1
1204
+ if (.label$ == "T" and .x2-.x1 < .mindur)
1205
+ Set point text: dip_tier, .j, "D"
1206
+ endif
1207
+ .j -= 1
1208
+ endwhile
1209
+ endproc
1210
+
1211
+
1212
+ procedure label_syllable_tier: .gridID
1213
+ selectObject: .gridID
1214
+ .n = Get number of intervals: syllable_tier
1215
+ for .j to .n
1216
+ .x1 = Get start point: syllable_tier, .j
1217
+ .x2 = Get end point: syllable_tier, .j
1218
+ @is_unvoiced_region: .gridID, .x1, .x2
1219
+ if (not result)
1220
+ Set interval text: syllable_tier, .j, "a"
1221
+ endif
1222
+ endfor
1223
+ endproc
1224
+