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,2766 @@
1
+ # stylize.praat -- Praat include file
2
+ # Pitch contour stylization.
3
+ # This file is included (indirectly) by prosogram.praat. It isn't a stand-alone script. Use "prosogram.praat" instead.
4
+ # Author: Piet Mertens
5
+ # For documentation see http://sites.google.com/site/prosogram/
6
+ # Last modification: 2020-07-15
7
+
8
+ # 2018-10-29 extended prosodic profile
9
+ # 2018-10-31 improved handling of octave jumps
10
+ # 2018-11-01 improved handling of undefined pitch frames at nucleus boundaries and inside syllabic nucleus
11
+ # 2018-11-06 adopt current Praat scripting syntax
12
+ # 2018-11-14 add PropLevel in globalsheet
13
+ # 2018-11-15 all pitch movements (intra- and intersyllabic) and trajectory values are now after stylization
14
+ # 2018-11-30 added pauses in extended prosodic profile
15
+ # 2018-12-08 added segmentation type in extended prosodic profile
16
+ # 2019-01-22 handle histogram for data without glissando
17
+ # 2019-03-01 deal with non-integer Pitch frame number in "Get frame number from time"
18
+ # 2019-03-25 corrected precision error occurring on some hardware
19
+ # 2019-03-26 added some rows to reportID
20
+ # 2019-04-01 improved decision for octavejump
21
+ # 2019-04-26 separate tables for prosodic profile and globalsheet
22
+ # 2019-04-28 changed prosodic_profile_new, draw_histogram, and draw_column_as_distribution
23
+ # 2019-04-28 improved draw_column_as_distribution
24
+ # 2019-05-09 removed variable min_pause_duration, which conflicted with mindur_pause_gap
25
+ # 2019-09-04 corrected bug in pause duration calculation in @initialize_nucldat
26
+ # 2019-09-04 corrected bug for stddev of nucleus duration when there's only one syllable in the speech signal
27
+ # 2019-12-04 improved handling of pauses in @initialize_nucldat
28
+ # 2020-06-11 corrected bug when left boundary of first nucleus earlier than first pitch frame
29
+ # 2020-07-15 corrected bug when octavejump correction results in zero-duration nucleus
30
+
31
+
32
+ # Procedure hierarchy
33
+ #
34
+ # create_table_of_nuclei create TableOfReal with values for each nucleus
35
+ # af ("add field") set column label and store column number in named variable
36
+ # profile_table_create create TableOfReal for prosodic profile and associated variables
37
+ # profile_table_prepare add lines for speakers in prosodic profile table
38
+ # profile_table_load read prosodic profile table from file
39
+ # store_features write spreadsheet/table of syllable features
40
+ # safe_nuclei adjust nucleus boundaries: pitch defined for entire nucleus
41
+ # defined_intersection
42
+ # octavejump
43
+ # initialize_nucldat link TextGrid nucleiID with TableOfReal nucldatID, using pointer_tier in TG
44
+ # stylize_nuclei
45
+ # stylize_nucleus
46
+ # turning_point
47
+ # slopeSTs calculate slope in ST/s and audibility (glissando threshold) of pitch change
48
+ # calc_localrate
49
+ # calc_vowel_duration calculate vowel duration
50
+ # calc_rhyme_duration calculate rhyme duration
51
+ # calc_onset_duration calculate onset duration
52
+ # speakers_prosodic_parms calculate statistics of prosodic features per speaker
53
+ # calc_nPVI
54
+ # pitchrange_normalized_pitch
55
+ # prosodic_profile write prosodic profile report text file
56
+ # pitchrange_speakers_report
57
+ # raw_pitchrange_speakers_report
58
+ # pitchprofile_speakers_report
59
+ # duration_profile_speakers_report
60
+ # update_global_report write TableOfReal of global report for all processed input files
61
+ # prosodic_profile_new
62
+ # draw_table
63
+ # draw_histogram
64
+ # draw_column_as_distribution
65
+
66
+
67
+ spreadsheet_times_reduced_precision = 0 ; if true, reduce precision (3 digits) for nucleus t1, t2, and duration, in output spreadsheet (useful for table readability by human)
68
+ ; reduced precision was default before version 2.14
69
+ mindur_pause_gap = 0.35 ; min duration for gap between nuclei for pause.
70
+ ; Also stored in _nucl.TextGrid settings tier.
71
+
72
+
73
+ # Flexible handling of tables for syllabic nuclei, prosodic features, etc.
74
+ # A table is created using procedure "create_table_of_nuclei", "create_table_global_report".
75
+ # Columns may be added at any time by procedure "af" (short for "add_field").
76
+ # Column order is determined by the order of the calls to "af".
77
+ # The column index is stored in a variable specified in the call to "af".
78
+ # Rows may be added at any time by Praat primitives.
79
+
80
+
81
+ procedure af: label$, indexname$, comment$
82
+ # Add a column to the table (the object id of which is in <af_object>), using <label> as the column label.
83
+ # Return the number of the column in the variable named <indexname>
84
+ # label$ label for the column in the table
85
+ # indexname$ name of the variable holding the column number
86
+ selectObject: 'af_object$'
87
+ .nc = Get number of columns
88
+ Insert column (index): .nc+1
89
+ if (not af_object_init)
90
+ Remove column (index): 1
91
+ af_object_init = 1
92
+ endif
93
+ .nc = Get number of columns
94
+ Set column label (index): .nc, label$
95
+ 'af_object$'_colname_index$ [.nc] = "'label$'_'.nc'"
96
+ 'indexname$' = .nc
97
+ endproc
98
+
99
+
100
+ procedure create_table_of_nuclei
101
+ @debug_msg: "create_table_of_nuclei: entry"
102
+ .nrows = nrof_nuclei_analysed
103
+ if (.nrows == 0)
104
+ @error_msg: "No nuclei were found."
105
+ .nrows = 1 ; avoid crash
106
+ endif
107
+ nucldat_ncols = 1 ; dummy: at least one column needed at creation of TableOfReal
108
+ nucldatID = Create TableOfReal: "nucl_data", .nrows, nucldat_ncols
109
+ nucldat_available = 1
110
+ af_object$ = "nucldatID"
111
+ af_object_init = 0
112
+ ; col_label ,indexname ,comment
113
+ @af: "nucl_t1" ,"j_nucl_t1" ,"starttime of nucleus"
114
+ @af: "nucl_t2" ,"j_nucl_t2" ,"endtime of nucleus"
115
+ @af: "f0_min" ,"f0_min" ,"f0 min (Hz) within nucleus before stylization"
116
+ @af: "f0_max" ,"f0_max" ,"f0 max (Hz) within nucleus before stylization"
117
+ @af: "f0_median" ,"f0_median" ,"f0 median (Hz) within nucleus before stylization"
118
+ @af: "f0_mean" ,"j_f0_mean" ,"f0 mean (Hz) within nucleus before stylization"
119
+ @af: "f0_meanST" ,"j_f0_meanST" ,"f0 mean (ST) within nucleus before stylization"
120
+ @af: "f0_start" ,"j_f0_start" ,"f0 value (Hz) at start of nucleus after stylization"
121
+ @af: "f0_end" ,"j_f0_end" ,"f0 value (Hz) at end of nucleus after stylization"
122
+ @af: "lopitch" ,"lopitch" ,"f0 min (Hz) within nucleus after stylization"
123
+ @af: "hipitch" ,"hipitch" ,"f0 max (Hz) within nucleus after stylization"
124
+ @af: "hipitchST" ,"j_hipitchST" ,"f0 max (ST) within nucleus after stylization"
125
+ @af: "dynamic" ,"j_dynamic" ,"0 = static, 1 = rising, -1 = falling"
126
+ @af: "intrasyllab","j_intrasyl" ,"sum of pitch interval (ST) of tonal segments in nucleus (rises and falls compensate)"
127
+ @af: "intersyllab","j_intersyl" ,"intersyllabic interval (ST) between end of previous nucleus and start of current one"
128
+ @af: "up" ,"j_intrasylup" ,"sum of upward pitch interval (ST) of tonal segments in nucleus, after stylization"
129
+ @af: "down" ,"j_intrasyldown" ,"sum of downward pitch interval (ST) of tonal segments in nucleus, after stylization"
130
+ @af: "trajectory","j_traj" ,"sum of absolute pitch interval (ST) of tonal segments in nucleus (rises and falls add up), after stylization"
131
+ @af: "f0_discont","j_f0_discont" ,"f0 contains discontinuity"
132
+ @af: "prnp_start","j_prnp_start" ,"pitch-range normalized pitch at start of nucleus after stylization"
133
+ @af: "prnp_end" ,"j_prnp_end" ,"pitch-range normalized pitch at end of nucleus after stylization"
134
+ @af: "prnp_intra","j_prnp_intra" ,"pitch-range normalized pitch of intranucleus variation after stylization"
135
+ @af: "nucl_dur" ,"j_nucldur" ,"nucleus duration"
136
+ @af: "syll_dur" ,"j_syllabledur" ,"syllable duration (only for appropriate segmentation methods)"
137
+ @af: "vowel_dur" ,"j_voweldur" ,"vowel duration (only for appropriate segmentation methods)"
138
+ @af: "rhyme_dur" ,"j_rhymedur" ,"rhyme duration (only for appropriate segmentation methods)"
139
+ @af: "gap_left" ,"j_internucldur" ,"time between end of previous nucleus and start of current one"
140
+ @af: "loudness" ,"j_loudness" ,"loudness peak in nucleus (only if parameter available)"
141
+ @af: "int_peak" ,"j_int_peak" ,"peak intensity in nucleus"
142
+ if (use_duration_model)
143
+ @af: "en_sylldur","j_en_sylldur" ,"elasticity normalized syllable duration (only for appropriate segmentation methods)"
144
+ @af: "en_rhymedur" ,"j_en_rhymedur" ,"elasticity normalized rhyme duration (only for appropriate segmentation methods)"
145
+ endif
146
+ @af: "promL2D_nucldur" ,"j_promL2D_nucldur" ,"prominence of nucleus duration wrt dynamic left context of 2 units"
147
+ if (calc_prominence)
148
+ @af: "promL2R1D_f0_mean" ,"j_promL2R1D_f0_mean" ,"prominence of mean pitch (Hz) wrt dynamic left/right context of 2+1 units"
149
+ ; @af: "promL2R1D_f0_max" ,"j_promL2R1D_f0_max" ,"prominence of max pitch (Hz) wrt dynamic left/right context of 2+1 units"
150
+ @af: "promL2R1D_hipitch" ,"j_promL2R1D_hipitch" ,"prominence of max pitch (Hz) wrt dynamic left/right context of 2+1 units"
151
+ @af: "promL2R1D_hipitchST" ,"j_promL2R1D_hipitchST" ,"prominence of max pitch (ST) wrt dynamic left/right context of 2+1 units"
152
+ @af: "promL2R1D_f0_meanST" ,"j_promL2R1D_f0_meanST" ,"prominence of mean pitch (ST) wrt dynamic left/right context of 2+1 units"
153
+ ; @af: "promL2R1D_nucldur" ,"j_promL2R1D_nucldur" ,"prominence of nucleus duration wrt dynamic left/right context of 2+1 units"
154
+ ; @af: "promL2R1D_sylldur" ,"j_promL2R1D_sylldur" ,"prominence of syllable duration wrt dynamic left/right context of 2+1 units"
155
+ ; @af: "promL2R1D_rhymedur" ,"j_promL2R1D_rhymedur","prominence of rhyme duration wrt dynamic left/right context of 2+1 units"
156
+ ; @af: "promL2R1D_int_peak" ,"j_promL2R1D_int_peak","prominence of peak intensity wrt dynamic left/right context of 2+1 units"
157
+ ; @af: "promL2R1D_loudness" ,"j_promL2R1D_loudness" ,"prominence of loudness wrt dynamic left/right context of 2+1 units"
158
+ ; @af: "endtime_syll" ,"endtime_syll" ,"endtime of syllable; used by plot_salience (only for appropriate segmentation method)"
159
+ endif
160
+ if (use_duration_model and calc_prominence)
161
+ @af: "promL2R1D_en_sylldur" ,"j_promL2R1D_en_sylldur", ""
162
+ @af: "promL2R1D_en_rhymedur" ,"j_promL2R1D_en_rhymedur", ""
163
+ endif
164
+ @af: "hesitation" ,"j_hesitation" ,"hesitation (required!)"
165
+ @af: "speaker_id" ,"j_speaker_id" ,"speaker ID number, from tier 'speaker' in annotation file"
166
+ @af: "before_pause" ,"j_before_pause" ,"syllable is followed by pause"
167
+ @af: "after_pause" ,"j_after_pause" ,"syllable is preceded by pause"
168
+ @af: "pause_dur" ,"j_pause_dur" ,"duration of pause following current syllable"
169
+ if (show_localrate)
170
+ @af: "local_rate_nucl" ,"j_localrate_nucl" ,"local speech rate in complete nuclei per second"
171
+ endif
172
+ ; @af: "iso_dur" ,"j_isodur" ,"inter syllable onset duration, for current onset to next onset"
173
+ ; @af: "pc_gap_left" ,"j_pcgapleft" ,"time since previous pcenter (except when pause)"
174
+ ; @af: "nucl_length" ,"j_nucl_len" ,""
175
+ @af: "onset_dur" ,"j_onsetdur" ,""
176
+
177
+ @debug_msg: "create_table_of_nuclei: exit"
178
+ endproc
179
+
180
+
181
+ procedure profile_table_create
182
+ # Create table and variables for prosodic profile (showing prosodic features for each speaker)
183
+ profileID = Create TableOfReal: "prosodic_profile", 1, 1
184
+ af_object$ = "profileID"
185
+ af_object_init = 0
186
+
187
+ profileID_empty = 1
188
+ profileID_row_offset = 0
189
+ @af: "SpeakerNr" ,"j_speaker_nr" ,"speaker number within input file"
190
+ @af: "SpeechRate" ,"j_speech_rate" ,"speech rate (= nrofnucl/(TotNuclDur+TotInternuclDur))"
191
+ @af: "NrofNuclei" ,"j_nrofnucl" ,"number of all nuclei, for current speaker"
192
+ @af: "NrofSafe" ,"j_nrofvalid" ,"number of nuclei, without outliers and discontinuities"
193
+ @af: "SpeechTime" ,"j_speech_time" ,"sum of TotNuclDur + TotInternuclDur + TotPauseDur, for current speaker"
194
+ @af: "TotNuclDur" ,"j_tot_nucldur" ,"total nucleus duration, for current speaker"
195
+ @af: "TotInternuclDur","j_tot_internucldur","total internucleus duration, for current speaker"
196
+ @af: "TotPauseDur" ,"j_tot_pausedur" ,"total pause duration, for current speaker"
197
+ @af: "PropPhon" ,"j_propphon" ,"(nucldur+internucldur)/(nucldur+internucldur+pausedur)"
198
+ @af: "PropPause" ,"j_proppause" ,"pausedur/(nucldur+internucldur+pausedur)"
199
+ @af: "F0MedianHz" ,"j_pitch_median_Hz","median in Hz of F0 values in Hz, 2 per nucleus: low and high"
200
+ @af: "F0MedianInST" ,"j_pitch_median_ST","median in ST of F0 values in Hz"
201
+ @af: "F0MeanHz" ,"j_pitch_mean_Hz" ,"mean in Hz of F0 values in Hz, 2 per nucleus: low and high"
202
+ @af: "F0MeanInST" ,"j_pitch_mean_ST" ,"mean in ST of F0 values in Hz"
203
+ @af: "F0StdevHz" ,"j_pitch_stdev_of_Hz","stdev of pitch values in Hz, 2 per nucleus: low and high"
204
+ @af: "PitchMeanST" ,"j_pitch_mean_of_ST","mean in ST of F0 values in ST, 2 per nucleus: low and high"
205
+ @af: "PitchStdevST" ,"j_pitch_stdev_of_ST","stdev of pitch values in ST, 2 per nucleus: low and high"
206
+ @af: "PitchRange" ,"j_pitch_range" ,"pitch range (span), in ST"
207
+ @af: "PitchTopST" ,"j_pitch_top_ST" ,"top of pitch range, in ST"
208
+ @af: "PitchBottomST" ,"j_pitch_bottom_ST","bottom of pitch range, in ST"
209
+ @af: "PitchTopHz" ,"j_pitch_top_Hz" ,"top of pitch range, in Hz"
210
+ @af: "PitchBottomHz" ,"j_pitch_bottom_Hz","bottom of pitch range, in Hz"
211
+ @af: "RawF0_p02" ,"j_rawf0_p02" ,"2 percentile of raw F0 values in nuclei"
212
+ @af: "RawF0_p25" ,"j_rawf0_p25" ,"25 percentile of raw F0 values in nuclei"
213
+ @af: "RawF0_p50" ,"j_rawf0_p50" ,"50 percentile of raw F0 values in nuclei"
214
+ @af: "RawF0_p75" ,"j_rawf0_p75" ,"75 percentile of raw F0 values in nuclei"
215
+ @af: "RawF0_p98" ,"j_rawf0_p98" ,"98 percentile of raw F0 values in nuclei"
216
+ @af: "RawF0_mean" ,"j_rawf0_mean" ,"mean of raw F0 values in nuclei"
217
+ @af: "PropLevel" ,"j_prop_level" ,"Proportion of nuclei with level pitch (stylized)"
218
+ @af: "Gliss" ,"j_prop_gliss" ,"Proportion of nuclei with abs pitch change >= 4 ST"
219
+ @af: "Rises" ,"j_prop_rises" ,"Proportion of nuclei with pitch change >= 4 ST"
220
+ @af: "Falls" ,"j_prop_falls" ,"Proportion of nuclei with pitch change <= -4 ST"
221
+ @af: "TrajIntra" ,"j_traj_intra" ,"Time-normalized pitch trajectory of intrasyllabic variations"
222
+ @af: "TrajInter" ,"j_traj_inter" ,"Time-normalized pitch trajectory of intrasyllabic variations"
223
+ @af: "TrajPhon" ,"j_traj_phon" ,"Time-normalized pitch trajectory of all pitch variations"
224
+ @af: "TrajIntraZ" ,"j_traj_intra_z" ,"Pitch range normalized TrajIntra"
225
+ @af: "TrajInterZ" ,"j_traj_inter_z" ,"Pitch range normalized TrajInter"
226
+ @af: "TrajPhonZ" ,"j_traj_phon_z" ,"Pitch range normalized TrajPhon"
227
+ @af: "NuclDurMean" ,"j_nucldur_mean" ,"mean nucleus duration"
228
+ @af: "NuclDurStdev" ,"j_nucldur_stdev" ,"stdev of nucleus duration"
229
+ @af: "nPVI_nucldur" ,"j_nPVI_nucldur" ,"nPVI of nucleus duration"
230
+ @af: "nPVI_voweldur" ,"j_nPVI_voweldur" ,"nPVI of vowel duration"
231
+ @af: "nPVI_sylldur" ,"j_nPVI_sylldur" ,"nPVI of syllable duration"
232
+ @af: "NuclDurMedian" ,"j_nucldur_median" ,"median nucleus duration"
233
+ endproc
234
+
235
+
236
+ procedure profile_table_prepare
237
+ # Is called once after speakers have been identified for the current input file
238
+ # Adds rows for speakers and stores speaker labels in row labels of table
239
+ @debug_msg: "profile_table_prepare: entry"
240
+ if (nrof_nuclei_analysed > 0)
241
+ ; Append 1 row for each speaker
242
+ selectObject: profileID
243
+ for .speaker from 1 to speakers ; nrofspeakers in current file
244
+ .nrows = Get number of rows
245
+ Insert row (index): .nrows+1
246
+ if (profileID_empty) ; for first input file, report contains 1 empty row
247
+ Remove row (index): 1
248
+ profileID_empty = 0
249
+ endif
250
+ .row = Get number of rows
251
+ .label$ = speaker_label'.speaker'$
252
+ Set row label (index): .row, .label$
253
+ .ncols = Get number of columns
254
+ for .col to .ncols
255
+ Set value: .row, .col, 0
256
+ endfor
257
+ Set value: .row, j_speaker_nr, .speaker
258
+ endfor
259
+ endif
260
+ @debug_msg: "profile_table_prepare: exit"
261
+ endproc
262
+
263
+
264
+ procedure profile_table_load
265
+ # Read headerless spreadsheet file containing prosodic profile data into TableOfReal
266
+ profileID = Read TableOfReal from headerless spreadsheet file: profile_file$
267
+ .nrcols = Get number of columns
268
+ for .col to .nrcols ; the following row indices are used by procedures in interactive prosogram
269
+ .label$ = Get column label: .col
270
+ if (.label$ == "PitchBottomST")
271
+ j_pitch_bottom_ST = .col
272
+ elsif (.label$ == "PitchTopST")
273
+ j_pitch_top_ST = .col
274
+ elsif (.label$ == "F0MedianInST")
275
+ j_pitch_median_ST = .col
276
+ endif
277
+ endfor
278
+ profile_available = 1
279
+ endproc
280
+
281
+
282
+ procedure store_features: .filecounter, .long, .outfname$
283
+ # Stores nuclei data (TableOfReal) to a file in "headerless spreadsheet file" format, possibly adding extra columns ("long" format)
284
+ ; <.filecounter> counter of input speech files (used to decide when to write table header with column names
285
+ ; <.long> long output format adds columns: syll, rhyme, prom, contour, pitchlevel, corpus
286
+ ; and attemps to provide the appropriate content starting from tiers for phoneme, syllable,
287
+ ; contour, prominence
288
+ ; <.outfname$> filename of output file
289
+ @debug_msg: "store_features: entry"
290
+ if (.filecounter == 1)
291
+ deleteFile: .outfname$
292
+ endif
293
+ selectObject: nucldatID
294
+ .nrows = Get number of rows
295
+ if (spreadsheet_times_reduced_precision)
296
+ for .row from 1 to .nrows
297
+ .t1 = Get value: .row, j_nucl_t1
298
+ .t2 = Get value: .row, j_nucl_t2
299
+ Set value: .row, j_nucl_t1, number(fixed$(.t1,3))
300
+ Set value: .row, j_nucl_t2, number(fixed$(.t2,3))
301
+ Set value: .row, j_nucldur, number(fixed$(.t2-.t1,3))
302
+ endfor
303
+ endif
304
+ if (not .long)
305
+ Write to headerless spreadsheet file: .outfname$
306
+ elsif (not segfile_available)
307
+ Write to headerless spreadsheet file: .outfname$
308
+ else ; long format
309
+ # Store TableOfReal in a temporary file
310
+ @fname_parts: .outfname$
311
+ .tmpfile$ = result4$ + "_tmp_.txt"
312
+ Write to headerless spreadsheet file: .tmpfile$
313
+ # Find tier number of phoneme, syllable, contour and prominence tiers
314
+ .grid = nucleiID
315
+ @tier_get: .grid, "^phon", "l_phon_tier", "No phon tier", 0
316
+ @tier_get: .grid, "^syll", "l_syll_tier", "No syll tier", 0
317
+ @tier_get: .grid, "^speaker", "l_speaker_tier", "No speaker tier", 0
318
+ @tier_get: .grid, "^contour", "l_contour_tier", "No contour tier", 0
319
+ @tier_get: .grid, "^prom", "l_prom_tier", "No prom tier", 0
320
+ # Read tmp file into a string
321
+ text$ = readFile$ (.tmpfile$)
322
+ deleteFile: .tmpfile$
323
+ # For each row, add some categorical data not available inside the TableOfReal
324
+ # Handle first line of file, which contains header
325
+ @next_line: "text", "line"
326
+ if (.filecounter = 1) ; we need to construct a header line
327
+ line$ = replace_regex$ (line$, "\n$", "", 1) ; remove end of line from header
328
+ line$ = right$ (line$, length(line$)-index(line$, tab$)) ; remove first field
329
+ if (corpus$ = "cprom")
330
+ line$ = "syll rhyme " + line$ + " speaker prom contour pitchlevel corpus discourse country"
331
+ else
332
+ line$ = "syll rhyme " + line$ + " speaker prom contour pitchlevel corpus"
333
+ endif
334
+ line$ = replace_regex$(line$, " ", "'tab$'", 0) ; tab is field delimiter
335
+ appendFileLine: .outfname$, line$ ; write header line
336
+ endif
337
+ # For all lines other than header: add categorical data
338
+ .syl$ = "NA"
339
+ .rhyme$ = "NA"
340
+ .speaker$ = "NA"
341
+ .prom$ = "NA"
342
+ .contour$ = "NA"
343
+ .pitchlevel$ = "NA"
344
+ .country$ = "NA"
345
+ .discourse$ = "NA"
346
+ if (corpus$ = "cprom")
347
+ .discourse$ = replace_regex$(basename$, "\-\w*$", "", 1)
348
+ .country$ = replace_regex$(basename$, "^\w*\-", "", 1)
349
+ .corpus$ = corpus$
350
+ elsif (corpus$ = "")
351
+ .corpus$ = "NA"
352
+ endif
353
+ for .j to .nrows
354
+ @next_line: "text", "line"
355
+ line$ = replace_regex$ (line$, "\n$", "", 1) ; remove end of line
356
+ selectObject: nucldatID
357
+ .t1n = Get value: .j, j_nucl_t1
358
+ .t2n = Get value: .j, j_nucl_t2
359
+ .midt = .t1n+(.t2n-.t1n)/2
360
+ selectObject: .grid
361
+ if (l_syll_tier) ; syllable tier present
362
+ .k = Get interval at time: l_syll_tier, .midt
363
+ .syl$ = Get label of interval: l_syll_tier, .k
364
+ .t1s = Get start time of interval: l_syll_tier, .k
365
+ .t2s = Get end time of interval: l_syll_tier, .k
366
+ if (l_phon_tier)
367
+ .t = .t1s ; start time of syllable
368
+ .rhyme$ = "" ; sequence of sounds in rhyme
369
+ .t2r = .t2s ; end time of rhyme
370
+ .pos = -1 ; assume we start in onset of syllable
371
+ repeat ; concatenate the sequence of sounds in the syllable; obtain label of rhyme
372
+ .k = Get interval at time: l_phon_tier, .t
373
+ .phon$ = Get label of interval: l_phon_tier, .k
374
+ @is_syllabic: .phon$
375
+ if (result)
376
+ .pos = 0 ; in syllable peak
377
+ .t1r = Get start time of interval: l_phon_tier, .k
378
+ endif
379
+ if (.pos >= 0)
380
+ .rhyme$ = .rhyme$ + .phon$
381
+ endif
382
+ .t = Get end time of interval: l_phon_tier, .k
383
+ until (.t >= .t2s)
384
+ endif
385
+ endif
386
+ if (l_speaker_tier) ; speaker tier present
387
+ .k = Get interval at time: speaker_tier, .midt
388
+ .speaker$ = Get label of interval: l_speaker_tier, .k
389
+ .speaker$ = replace_regex$ (.speaker$, "^ +(.*) +$", "\1", 1) ; trim left and right
390
+ endif
391
+ if (l_prom_tier)
392
+ .k = Get interval at time: l_prom_tier, .midt
393
+ .prom$ = Get label of interval: l_prom_tier, .k
394
+ endif
395
+ if (l_contour_tier)
396
+ .k = Get interval at time: l_contour_tier, .midt
397
+ .contour$ = Get label of interval: l_contour_tier, .k
398
+ .pitchlevel$ = .contour$
399
+ .pitchlevel$ = replace_regex$ (.pitchlevel$, "[rRfFSC_]", "", 0)
400
+ if (length (.pitchlevel$) < 1)
401
+ .pitchlevel$ = "NA"
402
+ endif
403
+ .contour$ = replace_regex$ (.contour$, "[BLMHT]", "", 0)
404
+ if (index (.contour$, "C"))
405
+ .contour$ = "NA"
406
+ endif
407
+ if (length (.contour$) < 1)
408
+ .contour$ = "_"
409
+ endif
410
+ endif
411
+ ; Finally write line/row
412
+ line$ = right$ (line$, length(line$)-index(line$, tab$)) ; remove first field (RowLabel)
413
+ if (corpus$ = "cprom")
414
+ line$ = "'.syl$' '.rhyme$' " + line$ + " '.speaker$' '.prom$' '.contour$' '.pitchlevel$' '.corpus$' '.discourse$' '.country$'"
415
+ else
416
+ line$ = "'.syl$' '.rhyme$' " + line$ + " '.speaker$' '.prom$' '.contour$' '.pitchlevel$' '.corpus$'"
417
+ endif
418
+ line$ = replace_regex$(line$, " ", "'tab$'", 0)
419
+ appendFileLine: .outfname$, line$
420
+ endfor
421
+ endif
422
+ @debug_msg: "store_features: exit"
423
+ endproc
424
+
425
+
426
+ procedure safe_nuclei: .t1, .t2
427
+ # Adjust nucleus boundaries such that pitch is defined throughout the nucleus, without octave jumps
428
+ # Return in <result> the number of valid nuclei in analysis interval
429
+ # <.t1>..<.t2> time range in which procedure is applied
430
+ # Time instants: t1 <= st1 <= cx1 <= ox1 <= ox2 <= cx2 <= st2 <= t2
431
+ # t1 .. t2 input time interval
432
+ # st1 .. st2 pitch frame synchronized time interval
433
+ # cx1 .. cx2 time interval where pitch frames are continuously defined
434
+ # ox1 .. ox2 resulting time interval without octave jumps
435
+ @debug_msg: "safe_nuclei: entry, t1='.t1:4' t2='.t2:4'"
436
+ mindur_syl = 0.01 ; minimum duration for syllable, otherwise skipped
437
+ selectObject: pitchID
438
+ .f0_median_Hz = Get quantile: 0, 0, 0.50, "Hertz"
439
+ .dx = Get time step
440
+ @interval_from_time: nucleiID, nucleus_tier, .t1, "first_interval"
441
+ selectObject: nucleiID
442
+ prev_boundary = Get start time of interval: safe_tier, 1
443
+ .x1 = Get start time of interval: nucleus_tier, first_interval
444
+ repeat
445
+ selectObject: nucleiID
446
+ .i = Get interval at time: nucleus_tier, .x1 ; nrof intervals may change during process
447
+ .x1 = Get start time of interval: nucleus_tier, .i
448
+ .x2 = Get end time of interval: nucleus_tier, .i
449
+ @is_nucleus: .i
450
+ if (result)
451
+ ; synchronize left boundary time from TextGrid nucleus tier with pitch frame times
452
+ ; time of synchronized left boundary will be >= time of pitch frame
453
+ .st1 = .x1
454
+ selectObject: pitchID
455
+ .jf = Get frame number from time: .st1
456
+ .jf = floor (.jf)
457
+ if (.jf < 1) ; left boundary earlier than first pitch frame
458
+ .jf = 1
459
+ endif
460
+ .t = Get time from frame number: .jf
461
+ if (.t < .st1)
462
+ .st1 = .t + .dx
463
+ else
464
+ .st1 = .t
465
+ endif
466
+ ; synchronize right boundary time from TextGrid nucleus tier with pitch frame times
467
+ ; time of synchronized right boundary will be <= time of pitch frame
468
+ .st2 = .x2
469
+ .jf = Get frame number from time: .st2
470
+ .jf = ceiling (.jf)
471
+ if (.jf < 1) ; right boundary earlier than first pitch frame
472
+ .jf = 1
473
+ endif
474
+ .t = Get time from frame number: .jf
475
+ if (.t > .st2)
476
+ .st2 = .t - .dx
477
+ else
478
+ .st2 = .t
479
+ endif
480
+ ; find time interval within .st1 .. .st2, where pitch is continuously defined
481
+ @defined_intersection: pitchID, .st1, .st2
482
+ .cx1 = result1
483
+ .cx2 = result2
484
+ ; @debug_msg: "safe_nuclei: defined: cx1='.cx1:6' cx2='.cx2:6'"
485
+ selectObject: nucleiID
486
+ if (result == 0) ; nucleus fully undefined
487
+ Set interval text: nucleus_tier, .i, "undef"
488
+ else
489
+ if (.cx1 > .x1) ; undefined section at start of nucleus
490
+ Insert boundary: nucleus_tier, .cx1
491
+ Set interval text: nucleus_tier, .i, "xL"
492
+ .i += 1
493
+ Set interval text: nucleus_tier, .i, "a"
494
+ endif
495
+ if (.cx2 < .x2) ; undefined section at end of nucleus
496
+ Insert boundary: nucleus_tier, .cx2
497
+ .j = Get interval at time: nucleus_tier, .cx2
498
+ Set interval text: nucleus_tier, .j, "xR"
499
+ endif
500
+ @octavejump: nucleiID, pitchID, .f0_median_Hz, .cx1, .cx2
501
+ .ox1 = result1
502
+ .ox2 = result2
503
+ .dur = .ox2 - .ox1
504
+ ; @debug_msg: "safe_nuclei: after octavejump, ox1='.ox1:6' ox2='.ox2:6' dur='.dur:6'"
505
+ selectObject: nucleiID
506
+ if (.ox2-.ox1 < mindur_syl)
507
+ Set interval text: nucleus_tier, .i, "short"
508
+ elsif (result3 == 1) ; discontinuity found
509
+ if (.ox1 > .cx1) ; skipped left part
510
+ ; @debug_msg: "st1='.st1:6' cx1='.cx1:6' ox1='.ox1:6' ox2='.ox2:6' cx2='.cx2:6' st2='.st2:6' dur='.dur:6'"
511
+ Insert boundary: nucleus_tier, .ox1
512
+ Set interval text: nucleus_tier, .i, "skip"
513
+ .i += 1
514
+ Set interval text: nucleus_tier, .i, "a"
515
+ endif
516
+ if (.ox2 < .cx2) ; skipped right part
517
+ Insert boundary: nucleus_tier, .ox2
518
+ Set interval text: nucleus_tier, .i, "a"
519
+ .i += 1
520
+ Set interval text: nucleus_tier, .i, "skip"
521
+ endif
522
+ ; elsif (.ox2-.ox1 < mindur_syl)
523
+ ; Set interval text: nucleus_tier, .i, "short"
524
+ elsif (.ox2 < .cx2)
525
+ Insert boundary: nucleus_tier, .ox2
526
+ .j = Get interval at time: nucleus_tier, .ox2
527
+ Set interval text: nucleus_tier, .j, "xR"
528
+ Set interval text: nucleus_tier, .j-1, "a"
529
+ endif
530
+ if (.ox2-.ox1 >= mindur_syl) ; safe_tier
531
+ if (.ox1 > prev_boundary)
532
+ Insert boundary: safe_tier, .ox1
533
+ endif
534
+ Insert boundary: safe_tier, .ox2
535
+ prev_boundary = .ox2
536
+ .j = Get interval at time: safe_tier, .ox1+(.ox2-.ox1)/2
537
+ Set interval text: safe_tier, .j, "a"
538
+ endif
539
+ endif
540
+ endif
541
+ .x1 = .x2
542
+ .intervals = Get number of intervals: nucleus_tier
543
+ until (.x2 >= .t2 or .i == .intervals)
544
+ selectObject: nucleiID
545
+ result = Count intervals where: nucleus_tier, "is equal to", "a"
546
+ @debug_msg: "safe_nuclei: exit, result='result'"
547
+ endproc
548
+
549
+
550
+ procedure is_nucleus: .inucl
551
+ selectObject: nucleiID
552
+ .label$ = Get label of interval: nucleus_tier, .inucl
553
+ result = 0
554
+ if (.label$ == "a")
555
+ result = 1
556
+ endif
557
+ endproc
558
+
559
+
560
+ procedure initialize_nucldat: .at1, .at2
561
+ # Initialize the table of nucleus data. This involves 2 steps:
562
+ # 1. Link TextGrid nucleiID and table nucldatID, by storing index of row into pointer tier of TextGrid
563
+ # 2. Initialize some columns in table nucldatID
564
+
565
+ @debug_msg: "initialize_nucldat: entry"
566
+
567
+ @intervals_from_time_range: nucleiID, nucleus_tier, .at1, .at2, "first_interval", "last_interval"
568
+
569
+ ; Connect TextGrid nucleiID and table nucldatID, by storing index of row into tier of TextGrid
570
+ selectObject: nucleiID
571
+ nrofnuclei = Get number of intervals: nucleus_tier
572
+ if (not reuse_nucl)
573
+ @copy_tier: nucleiID, nucleus_tier, nucleiID, pointer_tier
574
+ @tier_clear_text: nucleiID, pointer_tier
575
+ endif
576
+
577
+ ; Initialize some columns in table nucldatID: nucleus starttime, nucleus endtime and nucleus duration
578
+ .row = 0
579
+ for .i from first_interval to last_interval
580
+ selectObject: nucleiID
581
+ @is_nucleus: .i
582
+ if (result)
583
+ .x1 = Get start time of interval: nucleus_tier, .i
584
+ .x2 = Get end time of interval: nucleus_tier, .i
585
+ .row += 1
586
+ selectObject: nucleiID
587
+ Set interval text: pointer_tier, .i, "'.row'"
588
+ selectObject: nucldatID
589
+ Set row label (index): .row, "'.x1:3'"
590
+ Set value: .row, j_nucl_t1, .x1
591
+ Set value: .row, j_nucl_t2, .x2
592
+ Set value: .row, j_nucldur, .x2-.x1
593
+ endif
594
+ endfor
595
+
596
+ ; Initialize some columns in table nucldatID: vowel duration, syllable duration, rhyme duration, hesitation
597
+ selectObject: nucldatID
598
+ .nrows = Get number of rows
599
+ for .row to .nrows
600
+ Set value: .row, j_voweldur, 0
601
+ Set value: .row, j_syllabledur, 0
602
+ Set value: .row, j_rhymedur, 0
603
+ if (use_duration_model)
604
+ Set value: .row, j_en_sylldur, 0
605
+ Set value: .row, j_en_rhymedur, 0
606
+ endif
607
+ Set value: .row, j_hesitation, 0
608
+ endfor
609
+
610
+ ; Initialize some columns in table nucldatID: locate pauses
611
+ selectObject: nucleiID
612
+ .t0 = Get start time
613
+ for .j from 1 to nrof_nuclei_analysed ; assign "before_pause" and "pause_dur"
614
+ selectObject: nucldatID
615
+ .before_pause = 0
616
+ .pause_dur = 0
617
+ .t2 = Get value: .j, j_nucl_t2 ; end of current nucleus
618
+ if (.j == nrof_nuclei_analysed) ; last nucleus in speech signal
619
+ .before_pause = 1
620
+ .pause_dur = signal_finish-.t2
621
+ else
622
+ .t = Get value: .j+1, j_nucl_t1 ; start of next nucleus
623
+ if (.t-.t2 >= mindur_pause_gap) ; potential pause
624
+ @find_nucleus: "-", .t2, .t, 1 ; find rejected nucleus in gap after current nucleus
625
+ if (result)
626
+ .t3 = Get start time of interval: nucleus_tier, result
627
+ if (.t3-.t2 >= mindur_pause_gap) ; actual pause
628
+ .before_pause = 1
629
+ .pause_dur = .t3-.t2
630
+ endif
631
+ else
632
+ .before_pause = 1 ; actual pause
633
+ .pause_dur = .t-.t2
634
+ endif
635
+ endif
636
+ endif
637
+ selectObject: nucldatID
638
+ Set value: .j, j_before_pause, .before_pause
639
+ Set value: .j, j_pause_dur, number(fixed$(.pause_dur, 3))
640
+ endfor
641
+ for .j from 1 to nrof_nuclei_analysed ; assign "after_pause"
642
+ selectObject: nucldatID
643
+ .t = Get value: .j, j_nucl_t1
644
+ if (.j > 1)
645
+ .t2 = Get value: .j-1, j_nucl_t2 ; end of previous valid nucleus
646
+ else
647
+ .t2 = .t0 ; .t0 = start time of signal
648
+ endif
649
+ .after_pause = 0
650
+ if (.t-.t2 >= mindur_pause_gap) ; potential pause
651
+ @find_nucleus: "-", .t2, .t, 0 ; find rejected nucleus in gap before current nucleus
652
+ if (result)
653
+ .t3 = Get end time of interval: nucleus_tier, result
654
+ if (.t-.t3 >= mindur_pause_gap) ; actual pause
655
+ .after_pause = 1
656
+ endif
657
+ else
658
+ .after_pause = 1 ; actual pause
659
+ endif
660
+ else
661
+ .after_pause = 0
662
+ endif
663
+ selectObject: nucldatID
664
+ Set value: .j, j_after_pause, .after_pause
665
+ endfor
666
+ if variableExists ("j_pcgapleft")
667
+ for .j from 1 to nrof_nuclei_analysed ; assign "pc_gap_left"
668
+ selectObject: nucldatID
669
+ .t1 = Get value: .j, j_nucl_t1 ; start of current nucleus
670
+ .sp = Get value: .j, j_speaker_id
671
+ .after_pause = Get value: .j, j_after_pause
672
+ .gap = 0
673
+ if (.j > 1)
674
+ .t = Get value: .j-1, j_nucl_t1 ; start of previous nucleus
675
+ .sp2 = Get value: .j-1, j_speaker_id
676
+ if (not .after_pause and .sp = .sp2)
677
+ .gap = .t1 - .t
678
+ endif
679
+ endif
680
+ selectObject: nucldatID
681
+ Set value: .j, j_pcgapleft, number(fixed$(.gap,3))
682
+ endfor
683
+ endif
684
+ @debug_msg: "initialize_nucldat: exit"
685
+ endproc
686
+
687
+
688
+ procedure stylize_nuclei: .t1, .t2
689
+ # Stylize all nuclei within the specified time interval
690
+ @debug_msg: "stylize_nuclei: entry"
691
+ .tier = nucleus_tier
692
+ mindur_syl = 0.01 ; minimum duration for syllable, otherwise skipped
693
+ @interval_from_time: nucleiID, .tier, .t1, "first_interval"
694
+ @interval_from_time: nucleiID, .tier, .t2, "last_interval"
695
+
696
+ .prev_nucleus = 0 ; index (into textgrid tier) of previous valid nucleus; 0 indicates "not found yet"
697
+ for .i from first_interval to last_interval
698
+ selectObject: nucleiID
699
+ @is_nucleus: .i
700
+ if (result)
701
+ @stylize_nucleus: .tier, .i, .prev_nucleus
702
+ .prev_nucleus = .i
703
+ endif
704
+ endfor
705
+
706
+ if (show_localrate)
707
+ @calc_localrate: nucldatID, j_localrate_nucl, 2.5, 2.5
708
+ endif
709
+ ; @calc_isodur: nucldatID
710
+
711
+ @debug_msg: "stylize_nuclei: phones_available='phones_available'"
712
+ if (phones_available and segm_type <> segm_asyll) ; calculate vowel duration for spreadsheet
713
+ @calc_vowel_duration: nucldatID, j_voweldur
714
+ if (syllables_available) ; calculate syllable duration for spreadsheet
715
+ @calc_rhyme_duration: nucldatID, j_rhymedur
716
+ ; @calc_onset_duration: nucldatID, j_onsetdur
717
+ if (use_duration_model and fileReadable (duration_model_filename$))
718
+ @calc_norm_duration: "syllable", j_en_sylldur, duration_model_filename$
719
+ @calc_norm_duration: "rhyme", j_en_rhymedur, duration_model_filename$
720
+ endif
721
+ endif
722
+ endif
723
+ @debug_msg: "stylize_nuclei: exit"
724
+ endproc
725
+
726
+
727
+ procedure defined_intersection: .paramID, .t1, .t2
728
+ # Find region within <.t1>..<.t2> for which parameter is defined
729
+ # <.t1>..<.t2> pitch-frame-synchronized times of region
730
+ # Returns <result> = 0 when fully undefined
731
+ # Returns <result1> and <result2>, the resulting defined interval
732
+ @debug_msg: "defined_intersection: entry, t1='.t1:6' t2='.t2:6'"
733
+ selectObject: .paramID
734
+ .dx = Get time step
735
+ repeat
736
+ ; Trim undefined pitch frames at start and end of initial nucleus
737
+ .ok = 0
738
+ while (.ok == 0 and .t1 <= .t2) ; skip undefined frames at start
739
+ .v = Get value at time: .t1, "Hertz", "Linear"
740
+ if (.v == undefined)
741
+ ; @debug_msg: "defined_intersection: undefined frame at start of nucleus, t1='.t1:6'"
742
+ .t1 += .dx
743
+ else
744
+ .ok = 1
745
+ endif
746
+ endwhile
747
+ .ok = 0
748
+ while (.ok == 0 and .t2 > .t1) ; skip undefined frames at end
749
+ .v = Get value at time: .t2, "Hertz", "Linear"
750
+ if (.v == undefined)
751
+ ; @debug_msg: "defined_intersection: undefined frame at end of nucleus, t2='.t2:6'"
752
+ .t2 -= .dx
753
+ else
754
+ .ok = 1
755
+ endif
756
+ endwhile
757
+ ; Find undefined pitch frames inside the initial nucleus
758
+ if (.t2-.t1 > .dx)
759
+ .ok = 1
760
+ .t = .t1
761
+ while (.ok and .t < .t2) ; find undefined frame somewhere between updated .t1 and .t2
762
+ .v = Get value at time: .t, "Hertz", "Linear"
763
+ if (.v == undefined)
764
+ ; @debug_msg: "defined_intersection: undefined frame in middle of nucleus, t='.t:6'"
765
+ .ok = 0
766
+ else
767
+ .t += .dx
768
+ endif
769
+ endwhile
770
+ if (.ok == 0) ; found undefined frame
771
+ if (.t-.t1 < .t2-.t) ; select longest part
772
+ .t1 = .t+.dx ; prepare for next repeat-until loop starting at .t+.dx
773
+ else
774
+ .t2 = max(.t-.dx,.t1) ; use first part of interval
775
+ .ok = 1 ; exit repeat-until loop
776
+ endif
777
+ ; @debug_msg: "defined_intersection: kept interval: t1='.t1:6' t2='.t2:6'"
778
+ endif
779
+ endif
780
+ until (.ok and .t1 < .t2) or (.t2-.t1 <= .dx)
781
+ result1 = .t1
782
+ result2 = .t2
783
+ if (.t2-.t1 >= .dx) ; result = 0 when too short
784
+ result = 1
785
+ else
786
+ result = 0
787
+ .dur = .t2-.t1
788
+ ; @debug_msg: "defined_intersection: too short, dur='.dur:6' t1='.t1:6' t2='.t2:6' result='result'"
789
+ endif
790
+ @debug_msg: "defined_intersection: end, t1='.t1:6' t2='.t2:6' result='result'"
791
+ endproc
792
+
793
+
794
+ procedure octavejump: .grid, .param, .f0_median_Hz, .t1, .t2
795
+ # Find region within <t1>..<t2> for which pitch does not present discontinuities such as octave jumps
796
+ # Stores the position of discontinuity in point tier <discontinuity_tier> of <.grid>
797
+ # <.t1>..<.t2> pitch-frame-synchronized times of region
798
+ # Return values:
799
+ # <result1>.. <result2> the interval without octave jump
800
+ # <result3> = 1 when discontinuity found, in which case <result2> is end of safe interval
801
+ @debug_msg: "octavejump: entry, t1='.t1:3'"
802
+ result3 = 0 ; no discontinuity found
803
+ selectObject: .param
804
+ .dx = Get time step
805
+ ; Find an octave jump. Select the part of nucleus whose F0 is closer to median F0,
806
+ ; provided it has sufficient duration.
807
+ ; Repeat procedure for selected time interval, to deal with multiple octave jumps in same nucleus.
808
+ repeat
809
+ selectObject: .param
810
+ result1 = .t1
811
+ .ok = 1
812
+ .t = .t1
813
+ .f1 = Get value at time: .t, "Hertz", "Linear"
814
+ while (.ok == 1 and .t <= .t2)
815
+ selectObject: .param
816
+ .f2 = Get value at time: .t, "Hertz", "Linear"
817
+ if (abs(.f2-.f1)/min(.f1,.f2) > 0.3) ; was 0.2 initially and 0.5 in v2.7g
818
+ .tdisc = .t-(.dx/2)
819
+ ; @debug_msg: "octavejump: discontinuity at t='.tdisc:6'"
820
+ .ok = 0
821
+ else
822
+ result2 = .t
823
+ .t += .dx
824
+ .f1 = .f2
825
+ endif
826
+ endwhile
827
+ if (not .ok)
828
+ result3 = 1 ; discontinuity found
829
+ selectObject: .grid
830
+ Insert point: discontinuity_tier, .t-(.dx/2), ""
831
+ selectObject: .param
832
+ .durL = .t-.dx-.t1
833
+ .durR = .t2-.t
834
+ selectObject: intensityID
835
+ .intL = Get mean: .t1, .t-.dx, "dB"
836
+ .intR = Get mean: .t, .t2, "dB"
837
+ ; Take into account (1) duration, (2) intensity and (3) deviation from median pitch of speech signal
838
+ ; @msg: "octavejump: discontinuity at t='.tdisc:6' intensity L='.intL:2' R='.intR:2' duration L='.durL:4' R='.durR:6'"
839
+ if (.durL >= mindur_syl and .durR >= mindur_syl) ; both parts are sufficiently long
840
+ if (.intL - .intR >= 5) ; left part clearly higher intensity
841
+ ; @msg: "octavejump: t='.tdisc:6', left part wins by intensity"
842
+ result2 = .t-.dx
843
+ .t = .t2+1 ; force end of repeat-until loop
844
+ elsif (.durL >= 0.5*(.t2-.t1))
845
+ ; @msg: "octavejump: t='.tdisc:6', left part wins by duration"
846
+ result2 = .t-.dx
847
+ .t = .t2+1 ; force end of repeat-until loop
848
+ elsif (abs(.f1-.f0_median_Hz)/.f0_median_Hz < abs(.f2-.f0_median_Hz)/.f0_median_Hz) ; left part closer to median pitch
849
+ ; @msg: "octavejump: t='.tdisc:6', left part wins: closer to mean F0"
850
+ result2 = .t-.dx
851
+ .t = .t2+1 ; force end of repeat-until loop
852
+ else
853
+ ; @msg: "octavejump: t='.tdisc:6', right part wins: closer to mean F0"
854
+ result1 = .t
855
+ result2 = .t2
856
+ .t1 = .t
857
+ endif
858
+ elsif (.durL >= mindur_syl)
859
+ result2 = .t-.dx
860
+ .t = .t2+1 ; force end of repeat-until loop
861
+ else ; (.durL < mindur_syl)
862
+ result1 = .t
863
+ result2 = .t2
864
+ .t1 = .t
865
+ endif
866
+ endif
867
+ until (.t >= .t2)
868
+ @debug_msg: "octavejump: exit"
869
+ endproc
870
+
871
+
872
+ procedure stylize_nucleus: .tier, .i, .prev_nucleus
873
+ ; <.tier> tier where boundaries are stored
874
+ ; <.i> index of nucleus to be stylized
875
+ ; <.prev_nucleus> index (of interval in TextGrid) of previous syllabic nucleus (i.e. where label == "a")
876
+ @debug_msg: "stylize_nucleus: entry, i='.i'"
877
+
878
+ ; Find row index in nucldatID for nucleus to be stylized
879
+ selectObject: nucleiID
880
+ x1 = Get start time of interval: .tier, .i
881
+ x2 = Get end time of interval: .tier, .i
882
+ .i = Get interval at time: pointer_tier, x1+(x2-x1)/2
883
+ .s$ = Get label of interval: pointer_tier, .i
884
+ .row = number(.s$) ; index of row in table nucldatID
885
+
886
+ ; Set adaptive glissando value
887
+ selectObject: nucldatID
888
+ pause_follows = Get value: .row, j_before_pause
889
+ glissando_local = glissando
890
+ if (adaptive_glissando and pause_follows)
891
+ glissando_local = glissando_low
892
+ endif
893
+
894
+ ; Find pitch frame index for start and end of nucleus
895
+ selectObject: pitchID
896
+ .dx = Get time step
897
+ .f = Get frame number from time: x1
898
+ frame1 = round(.f)
899
+ .t = Get time from frame number: frame1
900
+ if (abs(.t-x1) > .dx)
901
+ @debug_msg: "stylize_nucleus: x1='x1' f='.f' frame1='frame1' dx='.dx' time(round(frame))='.t'"
902
+ frame1 += 1
903
+ endif
904
+ .f = Get frame number from time: x2
905
+ frame2 = round(.f)
906
+ .t = Get time from frame number: frame2
907
+ if (abs(.t-x2) > .dx)
908
+ @debug_msg: "stylize_nucleus: x2='x2' f='.f' frame2='frame2' dx='.dx' time(round(frame))='.t'"
909
+ frame2 -= 1
910
+ endif
911
+ if (frame2 <= frame1)
912
+ @fatal_msg: "stylize_nucleus: frame2 (='frame2') <= frame1 (='frame1')"
913
+ endif
914
+
915
+
916
+ # Step 1. Segmentation of pitch contour into tonal segments.
917
+ # Find turning points (TP) in contour, by order of importance.
918
+ # A TP is the point of largest distance between the raw F0 and the linear fit between F0 values at start and end.
919
+ # A TP is kept only
920
+ # - if the difference in slope between the parts before and after the TP exceeds the differential glissando threshold, and
921
+ # - if at least 1 of the parts is an audible pitch movement.
922
+ # When a TP is found, additional TPs are searched for in the left part, until none are found.
923
+ # Then the search continues for the interval between the last TP and the end of the nucleus interval.
924
+ nrofts = 1 ; number of tonal segments
925
+ selectObject: stylID
926
+ Add point: x1, 1
927
+ Add point: x2, 1
928
+ i1 = Get nearest index from time: x1
929
+ xL = x1 ; xL..xR is time interval where TP may be found
930
+ xR = x2
931
+ repeat
932
+ nrofsplit = 0 ; nrof turning points found in repeat loop
933
+ repeat ; find turning points
934
+ split = 0 ; nrof times split at turning point
935
+ @slopeSTs: xL, xR, "g", "aud_A"
936
+ if (aud_A) ; time interval contains audible pitch change
937
+ @turning_point: xL, xR
938
+ if (maxdiftime >= 0) ; found a candidate turning point
939
+ if ((maxdiftime - xL >= mindur_ts) and (xR - maxdiftime >= mindur_ts))
940
+ @slopeSTs: xL, maxdiftime, "g1", "aud_L"
941
+ @slopeSTs: maxdiftime, xR, "g2", "aud_R"
942
+ if ((abs (g2-g1) > diffgt) and (aud_L or aud_R) )
943
+ split = 1 ; found a valid turning point
944
+ endif
945
+ endif
946
+ endif ; (maxdiftime >= 0)
947
+ if (split)
948
+ selectObject: stylID
949
+ Add point: maxdiftime, 1
950
+ xR = maxdiftime
951
+ nrofsplit += 1 ; turning points inserted in this loop
952
+ nrofts += 1 ; additional tonal segment found
953
+ endif
954
+ endif
955
+ until (split = 0)
956
+ if (xR < x2) ; interval was split; continue segmentation for right side
957
+ i1 += 1 ; adjust xL..xR analysis window
958
+ selectObject: stylID
959
+ xL = Get time from index: i1
960
+ xR = Get time from index: i1+1
961
+ else ; no split...
962
+ nrofsplit = 0 ; prepare for end of repeat loop
963
+ xL = x2
964
+ endif
965
+ until (nrofsplit == 0 and xL >= x2)
966
+
967
+ # Step 2. Actual stylization. Also calculates prosodic features.
968
+ ; values before stylization:
969
+ sum_intra = 0 ; sum of intrasyllabic pitch variation, before stylization
970
+ sum_intra_up = 0 ; sum of intrasyllabic pitch rises, before stylization
971
+ sum_intra_down = 0 ; sum of intrasyllabic pitch falls, before stylization
972
+ sum_abs_intra = 0 ; sum of absolute intrasyllabic pitch variation, before stylization
973
+ ; values after stylization:
974
+ sum_intra_styl = 0 ; sum of intrasyllabic pitch variation, after stylization
975
+ sum_intra_up_styl = 0 ; sum of intrasyllabic pitch rises, after stylization
976
+ sum_intra_down_styl = 0 ; sum of intrasyllabic pitch falls, after stylization
977
+ sum_abs_intra_styl = 0 ; sum of absolute intrasyllabic pitch variation, after stylization
978
+ dynamic_type = 0 ; type: 0 = static, 1 = rising, -1 = falling
979
+ selectObject: stylID
980
+ i = Get nearest index from time: x1
981
+ i2 = Get nearest index from time: x2
982
+ ts = 1 ; index of tonal segment under analysis
983
+ while (i < i2) ; for each tonal segment
984
+ selectObject: stylID
985
+ xL = Get time from index: i
986
+ xR = Get time from index: i+1
987
+ @slopeSTs: xL, xR, "g", "aud_A"
988
+ intST = dist ; pitch interval (in ST) in current tonal segment
989
+ sum_intra += intST
990
+ sum_abs_intra += abs (intST)
991
+ sum_intra_up += max (intST, 0)
992
+ sum_intra_down += min (intST, 0)
993
+ # Check special case of two inaudible parts. e.g. bell-shaped contour
994
+ if (aud_A = 1 and nrofts = 1)
995
+ @turning_point: xL, xR
996
+ if (maxdiftime >= 0) ; turning point found
997
+ @slopeSTs: xL, maxdiftime, "g1", "aud_L"
998
+ @slopeSTs: maxdiftime, xR, "g2", "aud_R"
999
+ d1 = maxdiftime - xL
1000
+ d2 = xR - maxdiftime
1001
+ if (aud_L == 0 and aud_R == 0)
1002
+ if ((g1 > 0 and g2 < 0) or (g1 < 0 and g2 > 0))
1003
+ aud_A = 0 ; consider inaudible
1004
+ intST = 0
1005
+ endif
1006
+ endif
1007
+ endif
1008
+ endif ; special case
1009
+ selectObject: pitchID
1010
+ yR = Get value at time: xR, "Hertz", "Linear"
1011
+ yL = Get value at time: xL, "Hertz", "Linear"
1012
+ yM = Get quantile: xL, xR, 0.5, "Hertz"
1013
+ selectObject: stylID
1014
+ if (ts == 1) ; first tonal segment of nucleus => also set value at xL, start of tonal segment
1015
+ if (aud_A = 0)
1016
+ yR = yM ; normalize pitch to median pitch of tonal segment
1017
+ yL = yM
1018
+ endif
1019
+ Remove point: i ; to replace value of point at xL
1020
+ Add point: xL, yL ; set Y value of turning point at xL
1021
+ pv_lo = min (yL, yR) ; initialize pv_lo
1022
+ pv_hi = max (yL, yR) ; initialize pv_hi
1023
+ pv_start = yL
1024
+ endif
1025
+ ; Update Y value of turning point at time xR
1026
+ Remove point: i+1 ; to replace value of point at xR
1027
+ Add point: xR, yR ; set Y value of turning point at xR
1028
+ ; Following lines use values after stylization
1029
+ intST = 12 * log2 (yR/yL)
1030
+ if (aud_A)
1031
+ sum_intra_styl += intST
1032
+ sum_abs_intra_styl += abs (intST)
1033
+ sum_intra_up_styl += max (intST, 0)
1034
+ sum_intra_down_styl += min (intST, 0)
1035
+ dynamic_type = 1
1036
+ endif
1037
+ pv_lo = min (pv_lo, yR)
1038
+ pv_hi = max (pv_hi, yR)
1039
+ ts += 1
1040
+ i += 1
1041
+ endwhile ; for each tonal segment
1042
+ if (dynamic_type == 1)
1043
+ if (abs(sum_intra_down_styl) > sum_intra_up_styl)
1044
+ dynamic_type = -1
1045
+ endif
1046
+ endif
1047
+ selectObject: pitchID
1048
+ v_f0_min = Get minimum: x1, x2, "Hertz", "Parabolic"
1049
+ v_f0_max = Get maximum: x1, x2, "Hertz", "Parabolic"
1050
+ pv_median = Get quantile: x1, x2, 0.50, "Hertz"
1051
+ pv_mean = Get mean: x1, x2, "Hertz"
1052
+ if (.prev_nucleus == 0) ; first nucleus in analysis window
1053
+ .prev_x2 = anal_t1 ; start of analysis window
1054
+ pv_intersyllab = 0
1055
+ else
1056
+ selectObject: nucldatID
1057
+ .prev_x2 = Get value: .row-1, j_nucl_t2 ; endtime of previous nucleus
1058
+ .v = Get value: .row-1, j_f0_end ; F0 at end of previous nucleus
1059
+ pv_intersyllab = 12 * log2 (pv_start/.v)
1060
+ selectObject: nucleiID
1061
+ endif
1062
+
1063
+ if (syllables_available)
1064
+ selectObject: nucleiID
1065
+ .imid = Get interval at time: syllable_tier, x1+(x2-x1)/2
1066
+ syllt1 = Get start time of interval: syllable_tier, .imid
1067
+ syllt2 = Get end time of interval: syllable_tier, .imid
1068
+ sylldur = syllt2 - syllt1
1069
+ else
1070
+ sylldur = undefined
1071
+ syllt2 = undefined
1072
+ endif
1073
+
1074
+ ; Determine whether the original nucleus contains an F0 discontinuity, which was detected by @octavejump (called by safe_nuclei)
1075
+ selectObject: nucleiID
1076
+ .k = Get number of points: discontinuity_tier
1077
+ .i = Get high index from time: discontinuity_tier, x1
1078
+ if (.i > 0 and .i <= .k) ; time x1 > time of last discontinuity
1079
+ .t = Get time of point: discontinuity_tier, .i
1080
+ else
1081
+ .t = -1
1082
+ endif
1083
+ v_f0_discont = 0
1084
+ if (.t >= x1-time_step and .t <= x2+time_step) ; discontinuity within nucleus
1085
+ v_f0_discont = 1
1086
+ endif
1087
+
1088
+ ; Store all parameters for syllable in table (some post-editing for speaker turns is done in @speakers_prosodic_parms)
1089
+ selectObject: nucldatID
1090
+ Set value: .row, j_f0_start, floor(pv_start)
1091
+ Set value: .row, j_f0_end, floor(yR)
1092
+ Set value: .row, f0_min, floor(v_f0_min)
1093
+ Set value: .row, f0_max, floor(v_f0_max)
1094
+ Set value: .row, f0_median, floor(pv_median)
1095
+ Set value: .row, j_f0_mean, floor(pv_mean)
1096
+ Set value: .row, j_f0_meanST, number(fixed$(12 * log2 (pv_mean),2))
1097
+ Set value: .row, lopitch, floor(pv_lo)
1098
+ Set value: .row, hipitch, floor(pv_hi)
1099
+ Set value: .row, j_hipitchST, number(fixed$(12 * log2 (pv_hi),2))
1100
+ Set value: .row, j_dynamic, dynamic_type
1101
+ Set value: .row, j_intrasyl, number(fixed$(sum_intra_styl,2))
1102
+ Set value: .row, j_traj, number(fixed$(sum_abs_intra_styl,2))
1103
+ Set value: .row, j_intrasylup, number(fixed$(sum_intra_up_styl,2))
1104
+ Set value: .row, j_intrasyldown, number(fixed$(sum_intra_down_styl,2))
1105
+ Set value: .row, j_intersyl, number(fixed$(pv_intersyllab,2))
1106
+ Set value: .row, j_f0_discont, v_f0_discont
1107
+ ; j_internucldur = time between end of previous nucleus and start of current one
1108
+ Set value: .row, j_internucldur, number(fixed$(x1-.prev_x2,4))
1109
+ if (syllables_available)
1110
+ Set value: .row, j_syllabledur, number(fixed$(sylldur,4))
1111
+ endif
1112
+ ; endtime_syll: used by plot_salience (only for appropriate segmentation method)
1113
+ ; if (calc_prominence)
1114
+ ; Set value: .row, endtime_syll, syllt2
1115
+ ; endif
1116
+ if (needs_loudness and loudness_available)
1117
+ selectObject: loudnessID
1118
+ v = Get maximum: x1, x2, "None"
1119
+ selectObject: nucldatID
1120
+ Set value: .row, j_loudness, number(fixed$(v,3))
1121
+ endif
1122
+ selectObject: intensityID
1123
+ .v = Get maximum: x1, x2, "None"
1124
+ if (.v == undefined)
1125
+ @msg: "Warning: (stylize_nucleus:) maximum intensity undefined for time interval 'x1:3'..'x2:3'"
1126
+ .v = 0
1127
+ endif
1128
+ selectObject: nucldatID
1129
+ Set value: .row, j_int_peak, number(fixed$(.v,1))
1130
+
1131
+ @debug_msg: "stylize_nucleus: exit"
1132
+ endproc
1133
+
1134
+
1135
+ procedure slopeSTs: .t1, .t2, varname1$, varname2$
1136
+ # Calculate slope of F0 variation (in ST/s) in time interval <.t1>..<.t2>.
1137
+ # Return slope in global variable named in <varname1$>.
1138
+ # Determine whether pitch change is audible, i.e. above glissando threshold.
1139
+ # Return audibility in global variable named in <varname2$>.
1140
+ # Return values:
1141
+ # <slopeSTs> slope
1142
+ # <dist> pitch interval (in ST)
1143
+ selectObject: pitchID
1144
+ .max = Get maximum: .t1, .t2, "Hertz", "None"
1145
+ .min = Get minimum: .t1, .t2, "Hertz", "None"
1146
+ .tmax = Get time of maximum: .t1, .t2, "Hertz", "None"
1147
+ .tmin = Get time of minimum: .t1, .t2, "Hertz", "None"
1148
+ if (.tmin <= .tmax)
1149
+ dist = 12 * log2 (.max/.min)
1150
+ else
1151
+ dist = 12 * log2 (.min/.max)
1152
+ endif
1153
+ .dur = .t1-.t2
1154
+ .slopeSTs = dist/.dur
1155
+ 'varname1$' = .slopeSTs
1156
+ if (abs (.slopeSTs) >= glissando_local/(.dur*.dur))
1157
+ 'varname2$' = 1
1158
+ else
1159
+ 'varname2$' = 0
1160
+ endif
1161
+ endproc
1162
+
1163
+
1164
+ procedure turning_point: .t1, .t2
1165
+ # Find most important turning point.
1166
+ # Returns:
1167
+ # <maxdiftime> = time of turning point in time interval <.t1>..<.t2> OR -1 if max difference is too small ( < 1 ST )
1168
+ selectObject: pitchID
1169
+ .dx = Get time step
1170
+ .jf1 = Get frame number from time: .t1
1171
+ .jf1 = round(.jf1)
1172
+ if (.jf1 < frame1)
1173
+ @msg: "turning_point: jf1='.jf1' < frame1='frame1', at t1='.t1:4'"
1174
+ .jf1 = frame1
1175
+ endif
1176
+ .f01 = Get value in frame: .jf1, "Hertz"
1177
+ if (.f01 == undefined)
1178
+ .t = Get time from frame number: .jf1
1179
+ @fatal_error: "turning_point: Pitch undefined at (left boundary) time='.t:6' .t1='.t1:6' .t2='.t2:6' .jf1='.jf1' .jf2='.jf2'"
1180
+ endif
1181
+ .jf2 = Get frame number from time: .t2
1182
+ .jf2 = round(.jf2)
1183
+ if (.jf2 > frame2)
1184
+ @msg: "turning_point: jf2='.jf2' > frame2='frame2' at t2='.t2:4'"
1185
+ .jf2 = frame2
1186
+ endif
1187
+ .f02 = Get value in frame: .jf2, "Hertz"
1188
+ if (.f02 == undefined)
1189
+ .t = Get time from frame: .jf2
1190
+ @fatal_error: "turning_point: Pitch undefined at (right boundary) time='.t:6' .t1='.t1:6' .t2='.t2:6' .jf1='.jf1' .jf2='.jf2'"
1191
+ endif
1192
+ .b = (.f02 - .f01) / ((.jf2-.jf1)*.dx)
1193
+ .maxdif = 0
1194
+ .maxdiffit = 1
1195
+ maxdiftime = .t1
1196
+ .jmaxdif = .jf1
1197
+ for .j from .jf1 to .jf2
1198
+ .f0 = Get value in frame: .j, "Hertz"
1199
+ if (.f0 == undefined)
1200
+ .t = Get time from frame: .j
1201
+ @fatal_error: "turning_point: Pitch undefined at '.t:6' .t1='.t1:6' .t2='.t2:6' .jf1='.jf1' .jf2='.jf2'"
1202
+ endif
1203
+ .fit = .f01 + .b * ((.j-.jf1)*.dx)
1204
+ .dy = abs (.f0 - .fit)
1205
+ if (.dy > .maxdif)
1206
+ .maxdif = .dy
1207
+ .jmaxdif = .j
1208
+ .maxdiffit = .fit
1209
+ endif
1210
+ endfor
1211
+ if (.maxdif == 0)
1212
+ maxdiftime = -1
1213
+ elsif (abs(12 * log2 (.maxdif/.maxdiffit)) < 1) ; smaller than 1 ST
1214
+ maxdiftime = -1
1215
+ else
1216
+ maxdiftime = Get time from frame: .jmaxdif
1217
+ endif
1218
+ endproc
1219
+
1220
+
1221
+ procedure calc_vowel_duration: .table, .dst
1222
+ # Calculate vowel duration, using times of nucleus and alignment in TextGrid.
1223
+ # <.dst> column where results are stored
1224
+ .nrerr = 0
1225
+ selectObject: .table
1226
+ .rows = Get number of rows
1227
+ for .row to .rows ; for each nuclei in the signal
1228
+ selectObject: .table
1229
+ Set value: .row, .dst, 0
1230
+ .t1 = Get value: .row, j_nucl_t1
1231
+ .t2 = Get value: .row, j_nucl_t2
1232
+ .xmid = .t1+(.t2-.t1)/2
1233
+ ; @msg: "calc_vowel_duration: row='.row' nucl_t1='.t1:5' nucl_t2='.t2:5' xmid='.xmid:5'"
1234
+ selectObject: nucleiID
1235
+ if (syllables_available and phones_available)
1236
+ .i = Get interval at time: syllable_tier, .xmid
1237
+ .syll$ = Get label of interval: syllable_tier, .i
1238
+ .x1 = Get start time of interval: syllable_tier, .i
1239
+ .x2 = Get end time of interval: syllable_tier, .i
1240
+ .i = Get interval at time: phone_tier, .x1
1241
+ .i2 = Get interval at time: phone_tier, .x2
1242
+ repeat
1243
+ selectObject: nucleiID
1244
+ .label$ = Get label of interval: phone_tier, .i
1245
+ @is_vowel: .label$
1246
+ if (is_vowel)
1247
+ .x1 = Get start time of interval: phone_tier, .i
1248
+ .x2 = Get end time of interval: phone_tier, .i
1249
+ elsif (.i+1 >= .i2)
1250
+ @msg: "No vowel in syllable <'.syll$'> at '.xmid:3'"
1251
+ .nrerr += 1
1252
+ ; use syllable duration (.x1, .x2)
1253
+ endif
1254
+ .i += 1
1255
+ until (is_vowel or .i >= .i2)
1256
+ elsif (phones_available)
1257
+ .i = Get interval at time: phone_tier, .t1
1258
+ .i2 = Get interval at time: phone_tier, .t2
1259
+ repeat
1260
+ selectObject: nucleiID
1261
+ .label$ = Get label of interval: phone_tier, .i
1262
+ @is_vowel: .label$
1263
+ ; @msg: "calc_vowel_duration: repeat: i='.i' label='.label$' is_vowel='is_vowel'"
1264
+ if (is_vowel)
1265
+ .x1 = Get start time of interval: phone_tier, .i
1266
+ .x2 = Get end time of interval: phone_tier, .i
1267
+ elsif (.i+1 >= .i2)
1268
+ @debug_msg: "No vowel in nucleus at '.xmid:3'"
1269
+ .nrerr += 1
1270
+ ; use nucleus duration
1271
+ .x1 = .t1
1272
+ .x2 = .t2
1273
+ endif
1274
+ .i += 1
1275
+ until (is_vowel or .i >= .i2)
1276
+ endif
1277
+ selectObject: .table
1278
+ Set value: .row, .dst, number(fixed$(.x2-.x1,4))
1279
+ endfor
1280
+ if (.nrerr)
1281
+ @msg: "Warning: calc_vowel_duration: No vowel label found for '.nrerr' nuclei"
1282
+ endif
1283
+ endproc
1284
+
1285
+
1286
+ procedure calc_rhyme_duration: .table, .dst
1287
+ # Calculate duration of rhyme using intervals in the syllable and phoneme tiers
1288
+ # <.dst> column where results are stored
1289
+ selectObject: .table
1290
+ .rows = Get number of rows
1291
+ for .j to .rows ; nrof_nuclei_analysed
1292
+ Set value: .j, .dst, undefined ; prepare for possible error in annotation or lacking annotation
1293
+ endfor
1294
+ @tier_number_by_name: segmentationID, "^phon"
1295
+ if (result)
1296
+ for .j to nrof_nuclei_analysed
1297
+ selectObject: .table
1298
+ .x1 = Get value: .j, j_nucl_t1
1299
+ .x2 = Get value: .j, j_nucl_t2
1300
+ @interval_from_time: nucleiID, syllable_tier, .x1+(.x2-.x1)/2, "syll"
1301
+ .syll_x1 = Get start time of interval: syllable_tier, syll
1302
+ .syll_x2 = Get end time of interval: syllable_tier, syll
1303
+ @interval_from_time: nucleiID, phone_tier, .syll_x1, "ph1"
1304
+ @interval_from_time: nucleiID, phone_tier, .syll_x2-0.001, "ph2"
1305
+ .phon = ph1
1306
+ .nrof_syllabics = 0
1307
+ repeat
1308
+ selectObject: nucleiID
1309
+ .label$ = Get label of interval: phone_tier, .phon
1310
+ @is_syllabic: .label$
1311
+ if (result)
1312
+ .t = Get start time of interval: phone_tier, .phon
1313
+ selectObject: .table
1314
+ Set value: .j, .dst, number(fixed$(.syll_x2-.t,4)) ; duration of rhyme
1315
+ .nrof_syllabics += 1
1316
+ endif
1317
+ .phon += 1
1318
+ until (result or .phon > ph2)
1319
+ if (.nrof_syllabics == 0)
1320
+ @msg: "calc_rhyme_duration: Syllable without syllabic sound at time 'syll_x1:3'"
1321
+ endif
1322
+ endfor
1323
+ endif
1324
+ endproc
1325
+
1326
+
1327
+ procedure calc_onset_duration: .table, .dst
1328
+ # Calculate duration of onset using intervals in the syllable and phoneme tiers
1329
+ # <.dst> column where results are stored
1330
+ selectObject: .table
1331
+ .rows = Get number of rows
1332
+ for .j to .rows ; nrof_nuclei_analysed
1333
+ Set value: .j, .dst, undefined ; prepare for possible error in annotation or lacking annotation
1334
+ endfor
1335
+ @tier_number_by_name: segmentationID, "^phon"
1336
+ if (result)
1337
+ for .j to nrof_nuclei_analysed
1338
+ selectObject: .table
1339
+ .x1 = Get value: .j, j_nucl_t1
1340
+ .x2 = Get value: .j, j_nucl_t2
1341
+ @interval_from_time: nucleiID, syllable_tier, .x1+(.x2-.x1)/2, "syll"
1342
+ .syll_x1 = Get start time of interval: syllable_tier, syll
1343
+ .syll_x2 = Get end time of interval: syllable_tier, syll
1344
+ @interval_from_time: nucleiID, phone_tier, .syll_x1, "ph1"
1345
+ @interval_from_time: nucleiID, phone_tier, .syll_x2-0.001, "ph2"
1346
+ .phon = ph1
1347
+ .nrof_syllabics = 0
1348
+ repeat
1349
+ selectObject: nucleiID
1350
+ .label$ = Get label of interval: phone_tier, .phon
1351
+ @is_syllabic: .label$
1352
+ if (result)
1353
+ .t = Get start time of interval: phone_tier, .phon
1354
+ selectObject: .table
1355
+ Set value: .j, .dst, number(fixed$(.t-.syll_x1,4)) ; duration of onset
1356
+ .nrof_syllabics += 1
1357
+ endif
1358
+ .phon += 1
1359
+ until (result or .phon > ph2)
1360
+ if (.nrof_syllabics == 0)
1361
+ @msg: "calc_onset_duration: Syllable without syllabic sound at time 'syll_x1:3'"
1362
+ endif
1363
+ endfor
1364
+ endif
1365
+ endproc
1366
+
1367
+
1368
+ procedure calc_nPVI: .table, .src
1369
+ # Calculate Normalized Pairwise Variability Index on data in <table>
1370
+ # <.src> column where values are taken from
1371
+ # <result> nPVI for all data in src
1372
+ result = 0
1373
+ selectObject: .table
1374
+ .rows = Get number of rows
1375
+ .sum = 0
1376
+ if (.rows > 1) ; need at least 2 syllables
1377
+ for .row from 2 to .rows ; for each nucleus in the table
1378
+ .y = Get value: .row, .src
1379
+ .y1 = Get value: .row-1, .src
1380
+ .sum += abs( (.y1-.y) / ((.y1+.y)/2) )
1381
+ endfor
1382
+ result = 100*.sum/(.rows-1)
1383
+ endif
1384
+ endproc
1385
+
1386
+
1387
+ procedure speakers_prosodic_parms
1388
+ ; Compute
1389
+ ; - pitch range for each speaker using high and low pitch values of each syllable
1390
+ ; - "pitch profile": pitch trajectory, proportion of glissandos, rises, falls...
1391
+ ; - temporal profile (speech_rate, pause duration, nucleus duration, nPVI...)
1392
+ ; Should be called after stylization.
1393
+ .fn$ = "speakers_prosodic_parms"
1394
+ @debug_msg: "'.fn$': entry"
1395
+ if (speakers < 1) ; speaker are numbered from 1 to N
1396
+ @error_msg: "'.fn$': Expected >= 1 speaker"
1397
+ endif
1398
+ if (needs_pitchrange < 1)
1399
+ @fatal_error: "'.fn$': no pitch range measurement activated"
1400
+ endif
1401
+ selectObject: nucldatID
1402
+ .rows = Get number of rows
1403
+ if (.rows < 1)
1404
+ @fatal_error: "'.fn$': 0 rows in nucldatID"
1405
+ endif
1406
+ @debug_msg: "'.fn$': '.rows' rows in nucldatID"
1407
+
1408
+ ; For each speaker:
1409
+ for speaker_j from 1 to speakers
1410
+ label$ = speaker_label'speaker_j'$
1411
+ speaker_nnucl [speaker_j] = 0 ; nrof nuclei for this speaker
1412
+
1413
+ @debug_msg: "'.fn$': speaker_range_'speaker_j'"
1414
+
1415
+ ; (1) For speaker X, compute quantiles of low and high pitch values of each nucleus after stylization.
1416
+ ; To select syllables from speaker, create temporary table from which other speakers are discarded.
1417
+ ; Also discard nuclei containing a pitch discontinuity.
1418
+ selectObject: nucleiID
1419
+ k = Get number of points: discontinuity_tier
1420
+ selectObject: nucldatID
1421
+ rows = Get number of rows
1422
+ rows2 = rows * 2 ; uses 2 pitch values per syllable
1423
+ ; TableOfReal object does not have command "Get quantile...", so we use "Table without column names" object instead
1424
+ .tmptableID = Create Table without column names: "pitchvalues", rows2, 2
1425
+ Set column label (index): 1, "pitch_ST"
1426
+ Set column label (index): 2, "f0_Hz"
1427
+ n = 0 ; nrof data points
1428
+ for j from 1 to rows
1429
+ selectObject: nucldatID
1430
+ id = Get value: j, j_speaker_id
1431
+ if (id == speaker_j)
1432
+ speaker_nnucl [speaker_j] += 1
1433
+ .t1 = Get value: j, j_nucl_t1
1434
+ .t2 = Get value: j, j_nucl_t2
1435
+ .vlo = Get value: j, lopitch
1436
+ .vhi = Get value: j, hipitch
1437
+ .f0_hi = Get value: j, f0_max
1438
+ .f0_lo = Get value: j, f0_min
1439
+ selectObject: nucleiID
1440
+ .i = Get high index from time: discontinuity_tier, .t1
1441
+ if (.i > 0 and .i <= k) ; time .t1 > time of last discontinuity
1442
+ .t = Get time of point: discontinuity_tier, .i
1443
+ else
1444
+ .t = -1
1445
+ endif
1446
+ if (.t >= .t1 and .t <= .t2) ; avoid data at discontinuity
1447
+ @debug_msg: "'.fn$': discontinuity at '.t:5', nucleus '.t1:5'-'.t2:5' skipped"
1448
+ else
1449
+ selectObject: .tmptableID
1450
+ n += 1
1451
+ Set numeric value: n, "pitch_ST", hertzToSemitones (.vlo) - hertzToSemitones(1)
1452
+ Set numeric value: n, "f0_Hz", .f0_lo
1453
+ n += 1
1454
+ Set numeric value: n, "pitch_ST", hertzToSemitones (.vhi) - hertzToSemitones(1)
1455
+ Set numeric value: n, "f0_Hz", .f0_hi
1456
+ endif
1457
+ endif
1458
+ endfor
1459
+ if (n = 0) ; no valid points are available for this speaker, use default values
1460
+ @msg: "Warning: pitchrange measurement: no pitch data for speaker 'speaker_j' <'label$'>"
1461
+ mean = 150
1462
+ median = 150
1463
+ bottom = 150
1464
+ top = 150
1465
+ stdev = 0
1466
+ .mean_of_ST = 0
1467
+ .stdev_of_ST = 0
1468
+ .stdev_of_Hz = 0
1469
+ p02_rf0 = 0
1470
+ p50_rf0 = 0
1471
+ p98_rf0 = 0
1472
+ mean_rf0 = 0
1473
+ quartile1 = 0
1474
+ quartile3 = 0
1475
+ else
1476
+ if (n < 200)
1477
+ @msg: "Warning: too few syllables for robust pitchrange detection of speaker <'label$'>"
1478
+ endif
1479
+ selectObject: .tmptableID
1480
+ if (n < rows2) ; number of row in table should match number of values
1481
+ .row = rows2
1482
+ while (.row > n) ; remove unused rows at end of table
1483
+ Remove row: .row
1484
+ .row -= 1
1485
+ endwhile
1486
+ endif
1487
+ medianST = Get quantile: "pitch_ST", 0.5
1488
+ .rows = Get number of rows
1489
+ .row = .rows
1490
+ while (.row >= 2) ; remove PAIRS of rows if outsiders
1491
+ .v2 = Get value: .row, "pitch_ST"
1492
+ .v1 = Get value: .row-1, "pitch_ST"
1493
+ if (abs(.v2 - medianST) > 18 or abs(.v1 - medianST) > 18) ; discard manifest errors
1494
+ Remove row: .row
1495
+ Remove row: .row-1
1496
+ endif
1497
+ .row -= 2
1498
+ endwhile
1499
+ n = Get number of rows
1500
+ mean = Get mean: "f0_Hz"
1501
+ median = Get quantile: "f0_Hz", 0.5
1502
+ bottom = Get quantile: "f0_Hz", 0.02
1503
+ top = Get quantile: "f0_Hz", 0.98
1504
+ quartile1 = Get quantile: "f0_Hz", 0.25
1505
+ quartile3 = Get quantile: "f0_Hz", 0.75
1506
+ .mean_of_ST = Get mean: "pitch_ST"
1507
+ .stdev_of_Hz = Get standard deviation: "f0_Hz"
1508
+ .stdev_of_ST = Get standard deviation: "pitch_ST"
1509
+ mean_rf0 = Get mean: "f0_Hz" ; rf0 = raw F0
1510
+ p50_rf0 = Get quantile: "f0_Hz", 0.5
1511
+ p02_rf0 = Get quantile: "f0_Hz", 0.02
1512
+ p98_rf0 = Get quantile: "f0_Hz", 0.98
1513
+ if (.stdev_of_Hz == undefined or .stdev_of_ST == undefined) ; when insufficient data
1514
+ .stdev_of_Hz = 0
1515
+ .stdev_of_ST = 0
1516
+ endif
1517
+ endif ; >= 1 datapoints
1518
+ .nrofvalid = n/2 ; nrof nuclei after removal of outliers and discontinuities
1519
+ meanST = hertzToSemitones (mean) - hertzToSemitones(1)
1520
+ medianST = hertzToSemitones (median) - hertzToSemitones(1)
1521
+ bottomST = hertzToSemitones (bottom) - hertzToSemitones(1)
1522
+ topST = hertzToSemitones (top) - hertzToSemitones(1)
1523
+ removeObject: .tmptableID
1524
+ label$ = speaker_label'speaker_j'$
1525
+ range = topST - bottomST
1526
+ upper_range = 12 * log2 (top/median)
1527
+ lower_range = range - upper_range
1528
+
1529
+ ; Following line needed for prosoplot.praat
1530
+ speaker_range_'speaker_j'$ = "TOP_ST='topST:1' BOTTOM_ST='bottomST:1' MEDIAN_ST='medianST:1' "
1531
+
1532
+
1533
+ ; To select syllables from speaker X, create temporary TableOfReal, from which other speakers are discarded.
1534
+ ; Computed values include mean and stdev for intrasyllabic and intersyllabic pitch variation,
1535
+ ; temporal profile (speech_rate, pause duration, nucleus duration, nPVI...), etc.
1536
+ selectObject: nucldatID
1537
+ .tmptableID = Copy: "tmptable2"
1538
+ selectObject: .tmptableID
1539
+ Rename: "tmptable2"
1540
+ ; Select syllables (i.e. rows) for current speaker
1541
+ .j = Get number of rows
1542
+ repeat ; Discard info (i.e. rows) from other speakers
1543
+ .spkr = Get value: .j, j_speaker_id
1544
+ if (speaker_j <> .spkr)
1545
+ Remove row (index): .j
1546
+ endif
1547
+ .j -= 1
1548
+ until (.j = 0)
1549
+ .rows = Get number of rows
1550
+ ; @debug_msg: "'.fn$': '.rows' rows in tmptable2"
1551
+ ; Calculate prosodic parameters for current speaker
1552
+ .sum_traj_intra = 0 ; intrasyllabic trajectory, after stylization
1553
+ .sum_traj_inter = 0 ; intersyllabic trajectory, after stylization
1554
+ .sum_nucldur = 0
1555
+ .sum_internucldur = 0 ; time between nuclei, corrected for pauses
1556
+ .sum_pausedur = 0 ; time of pauses for current speaker
1557
+ .nsyll = 0 ; number of syllables for current speaker
1558
+ .nrises = 0
1559
+ .nfalls = 0
1560
+ .ngliss = 0
1561
+ .nflat = 0
1562
+ for .j from 1 to .rows
1563
+ .nsyll += 1
1564
+ .sum_nucldur += Get value: .j, j_nucldur
1565
+ .v_traj_inter = Get value: .j, j_intersyl
1566
+ .v_internucldur = Get value: .j, j_internucldur
1567
+ if (.v_internucldur < mindur_pause_gap) ; gap is not a pause
1568
+ .sum_internucldur += .v_internucldur
1569
+ .sum_traj_inter += abs(.v_traj_inter)
1570
+ else ; pauses are not counted for total internucleus duration and internucleus trajectory
1571
+ .sum_pausedur += .v_internucldur
1572
+ Set value: .j, j_intersyl, 0 ; in .tmptableID !! used later for mean and stddev
1573
+ endif
1574
+ .v = Get value: .j, j_dynamic
1575
+ if (.v == 0)
1576
+ .nflat += 1
1577
+ endif
1578
+ .sum_traj_intra += Get value: .j, j_traj
1579
+ .v_up = Get value: .j, j_intrasylup
1580
+ if (.v_up >= 4)
1581
+ .nrises += 1
1582
+ endif
1583
+ .v_down = Get value: .j, j_intrasyldown
1584
+ if (.v_down <= -4)
1585
+ .nfalls += 1
1586
+ endif
1587
+ if (.v_up >= 4 or .v_down <= -4) ; either rise or fall, but counted once
1588
+ .ngliss += 1
1589
+ endif
1590
+ endfor
1591
+ .sum_speechdur = .sum_nucldur + .sum_internucldur + .sum_pausedur
1592
+ .rate_j = 0
1593
+ if (.sum_nucldur + .sum_internucldur > 0)
1594
+ .rate_j = .nsyll / (.sum_nucldur + .sum_internucldur) ; speech rate for current speaker
1595
+ endif
1596
+ .traj_intra_rate = 0 ; time normalized trajectory values; initialize
1597
+ .traj_inter_rate = 0
1598
+ .traj_phon_rate = 0
1599
+ if (.sum_nucldur > 0)
1600
+ .traj_intra_rate = .sum_traj_intra/.sum_nucldur
1601
+ endif
1602
+ if (.sum_internucldur > 0)
1603
+ .traj_inter_rate = .sum_traj_inter/.sum_internucldur
1604
+ .traj_phon_rate = (.sum_traj_inter + .sum_traj_intra)/(.sum_internucldur + .sum_nucldur)
1605
+ endif
1606
+ ; .mean_intra_up = Get column mean (index): j_intrasylup
1607
+ ; .stdev_intra_up = Get column stdev (index): j_intrasylup
1608
+ ; .mean_abs_inter = Get column mean (index): j_intersyl
1609
+ ; .stdev_abs_inter = Get column stdev (index): j_intersyl
1610
+ .mean_nucldur = Get column mean (index): j_nucldur
1611
+ if (.rows > 1)
1612
+ .stdev_nucldur = Get column stdev (index): j_nucldur
1613
+ else ; otherwise undefined
1614
+ .stdev_nucldur = 0
1615
+ endif
1616
+ @calc_nPVI: .tmptableID, j_nucldur
1617
+ .nPVI_nucldur = result
1618
+ .nPVI_voweldur = 0
1619
+ if (phones_available and segm_type <> segm_asyll)
1620
+ @calc_nPVI: .tmptableID, j_voweldur
1621
+ .nPVI_voweldur = result
1622
+ endif
1623
+ .nPVI_sylldur = 0
1624
+ if (syllables_available and segm_type <> segm_asyll)
1625
+ @calc_nPVI: .tmptableID, j_syllabledur
1626
+ .nPVI_sylldur = result
1627
+ endif
1628
+ if (.ngliss > 0)
1629
+ .prises = 100*.nrises/.rows
1630
+ .pfalls = 100*.nfalls/.rows
1631
+ .pgliss = 100*.ngliss/.rows
1632
+ else
1633
+ .prises = 0
1634
+ .pfalls = 0
1635
+ .pgliss = 0
1636
+ endif
1637
+ .pstatic = 100*.nflat/.rows
1638
+ .v1 = .traj_intra_rate/.stdev_of_ST ; time-normalised intrasyllabic trajectory in z-score
1639
+ .v2 = .traj_inter_rate/.stdev_of_ST ; time-normalised intersyllabic trajectory in z-score
1640
+ .v3 = .traj_phon_rate/.stdev_of_ST ; time-normalised total trajectory in z-score
1641
+ if (.v1 == undefined) ; when insufficient nrof data
1642
+ .v1 = 0
1643
+ .v2 = 0
1644
+ .v3 = 0
1645
+ endif
1646
+ .ppauses = 100*.sum_pausedur/(.sum_internucldur+.sum_nucldur+.sum_pausedur)
1647
+ .pphon = 100*(.sum_nucldur+.sum_internucldur)/(.sum_internucldur+.sum_nucldur+.sum_pausedur)
1648
+ ; .est_phon_time = .sum_internucldur + .sum_nucldur
1649
+ .nnucl = speaker_nnucl [speaker_j]
1650
+ ; TableOfReal object does not have "Get quantile" command, so we convert it to a Table object
1651
+ selectObject: .tmptableID
1652
+ .tmp = To Table: "rowLabel"
1653
+ .median_nucldur = Get quantile: "nucl_dur", 0.50
1654
+ removeObject: .tmptableID, .tmp
1655
+
1656
+ selectObject: profileID
1657
+ .row = speaker_j
1658
+ Set value: .row, j_speaker_nr, speaker_j
1659
+ Set value: .row, j_speech_rate, '.rate_j:3'
1660
+ Set value: .row, j_speech_time, '.sum_speechdur:3'
1661
+ Set value: .row, j_nrofnucl, '.nnucl'
1662
+ Set value: .row, j_nrofvalid, '.nrofvalid'
1663
+ Set value: .row, j_tot_nucldur, '.sum_nucldur:3'
1664
+ Set value: .row, j_tot_internucldur, '.sum_internucldur:3'
1665
+ Set value: .row, j_tot_pausedur, '.sum_pausedur:3'
1666
+ Set value: .row, j_propphon, '.pphon:2'
1667
+ Set value: .row, j_proppause, '.ppauses:2'
1668
+ Set value: .row, j_pitch_median_Hz, 'median:0'
1669
+ Set value: .row, j_pitch_median_ST, 'medianST:1'
1670
+ Set value: .row, j_pitch_mean_Hz, 'mean:0'
1671
+ Set value: .row, j_pitch_mean_ST, 'meanST:1'
1672
+ Set value: .row, j_pitch_mean_of_ST, '.mean_of_ST:2'
1673
+ Set value: .row, j_pitch_stdev_of_Hz, '.stdev_of_Hz:3'
1674
+ Set value: .row, j_pitch_stdev_of_ST, '.stdev_of_ST:3'
1675
+ Set value: .row, j_pitch_range, 'range:1'
1676
+ Set value: .row, j_pitch_top_ST, 'topST:1'
1677
+ Set value: .row, j_pitch_bottom_ST, 'bottomST:1'
1678
+ Set value: .row, j_pitch_top_Hz, 'top:1'
1679
+ Set value: .row, j_pitch_bottom_Hz, 'bottom:1'
1680
+ Set value: .row, j_rawf0_p02, 'p02_rf0:0'
1681
+ Set value: .row, j_rawf0_p25, 'quartile1:0'
1682
+ Set value: .row, j_rawf0_p50, 'p50_rf0:0'
1683
+ Set value: .row, j_rawf0_p75, 'quartile3:0'
1684
+ Set value: .row, j_rawf0_p98, 'p98_rf0:0'
1685
+ Set value: .row, j_rawf0_mean, 'mean_rf0:0'
1686
+ Set value: .row, j_prop_level, '.pstatic:1'
1687
+ Set value: .row, j_prop_gliss, '.pgliss:1'
1688
+ Set value: .row, j_prop_rises, '.prises:1'
1689
+ Set value: .row, j_prop_falls, '.pfalls:1'
1690
+ Set value: .row, j_traj_intra, '.traj_intra_rate:2'
1691
+ Set value: .row, j_traj_inter, '.traj_inter_rate:2'
1692
+ Set value: .row, j_traj_phon, '.traj_phon_rate:2'
1693
+ Set value: .row, j_traj_intra_z, '.v1:2'
1694
+ Set value: .row, j_traj_inter_z, '.v2:2'
1695
+ Set value: .row, j_traj_phon_z, '.v3:2'
1696
+ Set value: .row, j_nucldur_mean, '.mean_nucldur:4'
1697
+ Set value: .row, j_nucldur_stdev, '.stdev_nucldur:4'
1698
+ Set value: .row, j_nPVI_nucldur, '.nPVI_nucldur:2'
1699
+ if (phones_available)
1700
+ Set value: .row, j_nPVI_voweldur, '.nPVI_voweldur:2'
1701
+ endif
1702
+ if (syllables_available)
1703
+ Set value: .row, j_nPVI_sylldur, '.nPVI_sylldur:2'
1704
+ endif
1705
+ Set value: .row, j_nucldur_median, '.median_nucldur:4'
1706
+ ;@msg: "'.fn$': speaker='speaker_j' NuclDur mean='.mean_nucldur:4' stdev='.stdev_nucldur:4' median='.median_nucldur:4'"
1707
+
1708
+ endfor ; for speaker_j
1709
+ @debug_msg: "'.fn$': exit"
1710
+ endproc
1711
+
1712
+
1713
+ procedure pitchrange_speakers_report
1714
+ selectObject: profileID
1715
+ @length_longest_speaker_label
1716
+ leading$ = " "
1717
+ .buf$ = "Pitch range of speaker(s): (based on 2 stylization values per nucleus)'newline$'"
1718
+ @sprint_fs: len, "Speaker label"
1719
+ .buf$ += leading$ + result$ + ": Range, Bottom, Mean, Median, Top, MeanOfST, StdevOfST" + newline$
1720
+ for .speaker from 1 to speakers
1721
+ ;.row = reportID_row_offset + .speaker
1722
+ .row = .speaker
1723
+ .label$ = speaker_label'.speaker'$
1724
+ @sprint_fs: len, .label$
1725
+ .buf$ += leading$ + result$ + ": "
1726
+ .v = Get value: .row, j_pitch_range
1727
+ .buf$ += "'.v:1'ST, "
1728
+ .v = Get value: .row, j_pitch_bottom_Hz
1729
+ .buf$ += "'.v:0'Hz "
1730
+ .v = Get value: .row, j_pitch_bottom_ST
1731
+ .buf$ += "('.v:1'ST), "
1732
+ .v = Get value: .row, j_pitch_mean_Hz
1733
+ .buf$ += "'.v:0'Hz "
1734
+ .v = Get value: .row, j_pitch_mean_ST
1735
+ .buf$ += "('.v:1'ST), "
1736
+ .v = Get value: .row, j_pitch_median_Hz
1737
+ .buf$ += "'.v:0'Hz "
1738
+ .v = Get value: .row, j_pitch_median_ST
1739
+ .buf$ += "('.v:1'ST), "
1740
+ .v = Get value: .row, j_pitch_top_Hz
1741
+ .buf$ += "'.v:0'Hz "
1742
+ .v = Get value: .row, j_pitch_top_ST
1743
+ .buf$ += "('.v:1'ST), "
1744
+ .v = Get value: .row, j_pitch_mean_of_ST
1745
+ .buf$ += "'.v:1'ST, "
1746
+ .v = Get value: .row, j_pitch_stdev_of_ST
1747
+ .buf$ += "'.v:1'ST"
1748
+ .buf$ += newline$
1749
+ endfor
1750
+ result$ = .buf$
1751
+ endproc
1752
+
1753
+
1754
+ procedure raw_pitchrange_speakers_report
1755
+ selectObject: profileID
1756
+ @length_longest_speaker_label
1757
+ leading$ = " "
1758
+ .buf$ = "Pitch range of speaker(s): (based on 2 raw F0 values per nucleus)'newline$'"
1759
+ @sprint_fs: len, "Speaker label"
1760
+ .buf$ += leading$ + result$ + ": P02, Mean, Median, P98" + newline$
1761
+ for .speaker from 1 to speakers
1762
+ ;.row = reportID_row_offset + .speaker
1763
+ .row = .speaker
1764
+ .label$ = speaker_label'.speaker'$
1765
+ @sprint_fs: len, .label$
1766
+ .buf$ += leading$ + result$ + ": "
1767
+ .v = Get value: .row, j_rawf0_p02
1768
+ .buf$ += "'.v:0'Hz, "
1769
+ .v = Get value: .row, j_rawf0_mean
1770
+ .buf$ += "'.v:0'Hz, "
1771
+ .v = Get value: .row, j_rawf0_p50
1772
+ .buf$ += "'.v:0'Hz, "
1773
+ .v = Get value: .row, j_rawf0_p98
1774
+ .buf$ += "'.v:0'Hz"
1775
+ .buf$ += newline$
1776
+ endfor
1777
+ result$ = .buf$
1778
+ endproc
1779
+
1780
+
1781
+ procedure pitchprofile_speakers_report
1782
+ selectObject: profileID
1783
+ @length_longest_speaker_label
1784
+ leading$ = " "
1785
+ .buf$ = "Pitch variability of speaker(s):'newline$'"
1786
+ @sprint_fs: len, "Speaker label"
1787
+ .buf$ += leading$ + result$ + ": TrajIntra, TrajInter, TrajPhon, TrajIntraZ, TrajInterZ, TrajPhonZ, PropLevel, Gliss, Rises, Falls" + newline$
1788
+ for .speaker from 1 to speakers
1789
+ ;.row = reportID_row_offset + .speaker
1790
+ .row = .speaker
1791
+ .label$ = speaker_label'.speaker'$
1792
+ @sprint_fs: len, .label$
1793
+ .buf$ += leading$ + result$ + ": "
1794
+ .v = Get value: .row, j_traj_intra
1795
+ .buf$ += "'.v:1' ST/s, "
1796
+ .v = Get value: .row, j_traj_inter
1797
+ .buf$ += "'.v:1' ST/s, "
1798
+ .v = Get value: .row, j_traj_phon
1799
+ .buf$ += "'.v:1' ST/s, "
1800
+ .v = Get value: .row, j_traj_intra_z
1801
+ .buf$ += "'.v:1' sd/s, "
1802
+ .v = Get value: .row, j_traj_inter_z
1803
+ .buf$ += "'.v:1' sd/s, "
1804
+ .v = Get value: .row, j_traj_phon_z
1805
+ .buf$ += "'.v:1' sd/s, "
1806
+ .v = Get value: .row, j_prop_level
1807
+ .buf$ += "'.v:1'%, "
1808
+ .v = Get value: .row, j_prop_gliss
1809
+ .buf$ += "'.v:1'%, "
1810
+ .v = Get value: .row, j_prop_rises
1811
+ .buf$ += "'.v:1'%, "
1812
+ .v = Get value: .row, j_prop_falls
1813
+ .buf$ += "'.v:1'% "
1814
+ .buf$ += newline$
1815
+ endfor
1816
+ result$ = .buf$
1817
+ endproc
1818
+
1819
+
1820
+ procedure duration_profile_speakers_report
1821
+ @length_longest_speaker_label
1822
+ leading$ = " "
1823
+ selectObject: profileID
1824
+ .buf$ = "Temporal profile of speaker(s):" + newline$
1825
+ @sprint_fs: len, "Speaker label"
1826
+ .buf$ += leading$ + result$ + ": SpeechRate, TotalDur, %Phonation, %Pauses, PhonTime, NuclDur, InterNuclDur, PauseDur" + newline$
1827
+ for .speaker from 1 to speakers
1828
+ ;.row = reportID_row_offset + .speaker
1829
+ .row = .speaker
1830
+ .label$ = speaker_label'.speaker'$
1831
+ @sprint_fs: len, .label$
1832
+ .buf$ += leading$ + result$ + ": "
1833
+ .v = Get value: .row, j_speech_rate
1834
+ .buf$ += "'.v:2' syll/s, "
1835
+ .v_nucldur = Get value: .row, j_nucldur
1836
+ .v_internucldur = Get value: .row, j_internucldur
1837
+ .v_pausedur = Get value: .row, j_tot_pausedur
1838
+ .v_total = .v_nucldur + .v_internucldur + .v_pausedur
1839
+ .buf$ += "'.v_total:3' s, "
1840
+ .p = 100*(.v_nucldur + .v_internucldur)/.v_total
1841
+ .buf$ += "'.p:1'%, "
1842
+ .p = 100*.v_pausedur/.v_total
1843
+ .buf$ += "'.p:1'%, "
1844
+ .v = .v_nucldur + .v_internucldur
1845
+ .buf$ += "'.v:3' s, "
1846
+ .buf$ += "'.v_nucldur:3' s, "
1847
+ .buf$ += "'.v_internucldur:3' s, "
1848
+ .buf$ += "'.v_pausedur:2' s "
1849
+ .buf$ += newline$
1850
+ endfor
1851
+ .buf$ += newline$ + "Duration variability of speaker(s):" + newline$
1852
+ @sprint_fs: len, "Speaker label"
1853
+ .buf$ += leading$ + result$ + ": NuclDurMean, NuclDurStdev, nPVI_nucldur, nPVI_voweldur, nPVI_sylldur" + newline$
1854
+ for .speaker from 1 to speakers
1855
+ ;.row = reportID_row_offset + .speaker
1856
+ .row = .speaker
1857
+ .label$ = speaker_label'.speaker'$
1858
+ @sprint_fs: len, .label$
1859
+ .buf$ += leading$ + result$ + ": "
1860
+ .v = Get value: .row, j_nucldur_mean
1861
+ .buf$ += "'.v:3' s, "
1862
+ .v = Get value: .row, j_nucldur_stdev
1863
+ .buf$ += "'.v:3', "
1864
+ .v = Get value: .row, j_nPVI_nucldur
1865
+ .buf$ += "'.v:2', "
1866
+ .v = Get value: .row, j_nPVI_voweldur
1867
+ .buf$ += "'.v:2', "
1868
+ .v = Get value: .row, j_nPVI_sylldur
1869
+ .buf$ += "'.v:2' "
1870
+ .buf$ += newline$
1871
+ endfor
1872
+ result$ = .buf$
1873
+ endproc
1874
+
1875
+
1876
+ procedure length_longest_speaker_label
1877
+ ; Return in variable <len> the length of longest speaker label
1878
+ len = length ("Speaker label")
1879
+ for .speaker from 1 to speakers
1880
+ len = max (len, length (speaker_label'.speaker'$))
1881
+ endfor
1882
+ endproc
1883
+
1884
+
1885
+ procedure sprint_fs: .len, .text$
1886
+ ; print string using fixed length, appending blanks if necessary
1887
+ .j = .len - length(.text$)
1888
+ if (.j > 0)
1889
+ for .k to .j
1890
+ .text$ = .text$ + " "
1891
+ endfor
1892
+ endif
1893
+ result$ = .text$
1894
+ endproc
1895
+
1896
+
1897
+ procedure pitchrange_normalized_pitch
1898
+ @debug_msg: "pitchrange_normalized_pitch: entry"
1899
+ .current_speaker = 0 ; force initialization
1900
+ selectObject: nucldatID
1901
+ .nrows = Get number of rows
1902
+ for .j to .nrows
1903
+ .speaker = Get value: .j, j_speaker_id
1904
+ if (.speaker <> .current_speaker)
1905
+ selectObject: profileID
1906
+ ; .row = reportID_row_offset + .speaker
1907
+ .row = .speaker
1908
+ .bottomST = Get value: .row, j_pitch_bottom_ST
1909
+ .topST = Get value: .row, j_pitch_top_ST
1910
+ .rangeST = .topST - .bottomST
1911
+ .current_speaker = .speaker
1912
+ endif
1913
+ selectObject: nucldatID
1914
+ .v = Get value: .j, j_f0_start
1915
+ .v = hertzToSemitones(.v) - hertzToSemitones(1) ; convert to ST rel 1Hz
1916
+ .v = 100 * (.v - .bottomST) / .rangeST
1917
+ ; normalize to range 0..100 (arbitrary units: percentage of pitch range)
1918
+ Set value: .j, j_prnp_start, min(100, max(0, floor(.v)))
1919
+ .v = Get value: .j, j_f0_end
1920
+ .v = hertzToSemitones(.v) - hertzToSemitones(1) ; convert to ST rel 1Hz
1921
+ .v = 100 * (.v - .bottomST) / .rangeST
1922
+ ; normalize to range 0..100 (arbitrary units)
1923
+ Set value: .j, j_prnp_end, min(100, max(0, floor(.v)))
1924
+ .dyn = Get value: .j, j_dynamic
1925
+ .hi = Get value: .j, hipitch
1926
+ .hi = hertzToSemitones(.hi) - hertzToSemitones(1)
1927
+ .lo = Get value: .j, lopitch
1928
+ .lo = hertzToSemitones(.lo) - hertzToSemitones(1)
1929
+ if (.dyn == 0)
1930
+ .v = 0
1931
+ elsif (.dyn < 0)
1932
+ .v = 100 * (.lo - .hi) / .rangeST
1933
+ elsif (.dyn > 0)
1934
+ .v = 100 * (.hi - .lo) / .rangeST
1935
+ endif
1936
+ Set value: .j, j_prnp_intra, floor(.v)
1937
+ endfor
1938
+ @debug_msg: "pitchrange_normalized_pitch: exit"
1939
+ endproc
1940
+
1941
+
1942
+ procedure calc_localrate: .table, .dst, .sizeleft, .sizeright
1943
+ ; Calculate the local speech rate in nuclei/s for a local window of (sizeleft + sizeright) seconds
1944
+ ; <.dst> column where results are stored
1945
+ ; <.sizeleft> size of window on left (in seconds)
1946
+ ; <.sizeright> size of window on right (in seconds)
1947
+ @debug_msg: "calc_localrate: entry"
1948
+ selectObject: .table
1949
+ .rows = Get number of rows
1950
+ for .row to .rows ; for each nucleus/syllable in the signal
1951
+ .x1 = Get value: .row, j_nucl_t1
1952
+ .x2 = Get value: .row, j_nucl_t2
1953
+ .xmid = .x1+(.x2-.x1)/2
1954
+ .n = 0 ; nrof elements in context
1955
+ .tleft = 0 ; total time of left context
1956
+ if (.sizeleft > 0 and .row > 1)
1957
+ .j = 1
1958
+ repeat ; extend left context
1959
+ if (.row - .j >= 1)
1960
+ .t = Get value: .row-.j, j_nucl_t1
1961
+ .t2 = Get value: .row-.j, j_nucl_t2
1962
+ if (.xmid - .t <= .sizeleft)
1963
+ .n += 1
1964
+ .tleft = .xmid - .t
1965
+ elsif (.xmid - .t2 <= .sizeleft)
1966
+ .tleft = .xmid - .t2
1967
+ endif
1968
+ if (.row - .j == 1)
1969
+ .j = .row ; prepare for end of loop
1970
+ endif
1971
+ endif
1972
+ .j += 1
1973
+ until (.row - .j < 1 or .xmid - .t >= .sizeleft)
1974
+ endif
1975
+ .tright = 0 ; total time of right context
1976
+ if (.sizeright > 0 and .row < .rows)
1977
+ .j = 1
1978
+ repeat ; extend right context
1979
+ if (.row + .j <= .rows)
1980
+ .t1 = Get value: .row+.j, j_nucl_t1
1981
+ .t = Get value: .row+.j, j_nucl_t2
1982
+ if (.t - .xmid <= .sizeright)
1983
+ .n += 1
1984
+ .tright = .t - .xmid
1985
+ elsif (.t - .x2 <= .sizeright)
1986
+ .tright = .t1 - .xmid
1987
+ endif
1988
+ endif
1989
+ .j += 1
1990
+ until (.row + .j > .rows or .t - .xmid >= .sizeright)
1991
+ endif
1992
+ Set value: .row, .dst, (.n + 1)/(.tleft + .tright)
1993
+ endfor
1994
+ @debug_msg: "calc_localrate: exit"
1995
+ endproc
1996
+
1997
+
1998
+ procedure w: .text$
1999
+ appendFileLine: statsfile$, .text$
2000
+ endproc
2001
+
2002
+
2003
+ procedure prosodic_profile
2004
+ @msg: "Writing prosodic profile report for current input file to: 'statsfile$'"
2005
+ deleteFile: statsfile$
2006
+ @w: "Prosodic profile for input file: 'signalfile$'"
2007
+ @w: "Prosogram version: 'version$', (c) Piet Mertens"
2008
+ .date$ = date$ ()
2009
+ @w: "Date (of analysis): '.date$'"
2010
+ @w: ""
2011
+
2012
+ if (nrof_nuclei_analysed == 0)
2013
+ @w: "The prosodic profile could not be calculated because no nuclei were detected."
2014
+ @msg: "Prosodic profile not calculated: no nuclei detected in speech signal."
2015
+ elsif (not needs_pitchrange)
2016
+ @w: "Prosodic profile calculation requires selection of full time range of speech signal."
2017
+ @msg: "Prosodic profile not calculated: requires selection of full time range of signal."
2018
+ else ; nrof_nuclei_analysed > 0 and pitchrange calculated
2019
+
2020
+ @w: "Segmentation type: 'segmentation_name$'"
2021
+ @w: "Nucleus: 'nrof_nuclei_analysed' nuclei in signal"
2022
+ if (nrof_nuclei_analysed < 100)
2023
+ @w: "'newline$' WARNING: The global measures below are only meaningful for speech samples of at least 100 nuclei (syllables).'newline$'"
2024
+ endif
2025
+ if (nrof_nuclei_analysed <= 1)
2026
+ @w: "'newline$' ERROR: Insufficient number of syllables ('nrof_nucl') for calculation of measures.'newline$'"
2027
+ endif
2028
+
2029
+ @w: ""
2030
+ @pitchrange_speakers_report
2031
+ @w: result$
2032
+
2033
+ @raw_pitchrange_speakers_report
2034
+ @w: result$
2035
+
2036
+ @pitchprofile_speakers_report
2037
+ @w: result$
2038
+
2039
+ @duration_profile_speakers_report
2040
+ @w: result$
2041
+
2042
+ @w: ""
2043
+ @w: "TotalDur = total speech time (in s) = internucleus time + intranucleus time + pause time"
2044
+ @w: "PhonTime = phonation time (in s) = without pauses = internucleus time + intranucleus time"
2045
+ @w: "%Phonation = proportion (%) of estimated phonation time (= internucleus time + intranucleus time) to speech time"
2046
+ @w: "%Pauses = proportion (%) of estimated pause time (= when internucleus time >= 0.3) to speech time"
2047
+ @w: "SpeechRate = estimated speech rate (in syll/s) = nrof_nuclei/phonation_time"
2048
+ @w: "MeanOfST = mean of pitch values, where values are min and max pitch in ST for each syllable"
2049
+ @w: "StdevOfST = stdev of pitch values, where values are min and max pitch in ST for each syllable"
2050
+ @w: "PitchRange = estimated pitch range (in ST) (2%-98% percentiles of data in nuclei without discontinuities)"
2051
+ @w: "Gliss = proportion (%) of syllables with large pitch movement (abs(distance) >= 4ST)"
2052
+ @w: "Rises = proportion (%) of syllables with pitch rise (>= 4ST)"
2053
+ @w: "Falls = proportion (%) of syllables with pitch fall (<= -4ST)"
2054
+ @w: "NuclDur = sum of durations for nuclei for this speaker"
2055
+ @w: "InterNuclDur = sum of durations between successive nuclei for this speaker"
2056
+ @w: "TrajIntra = pitch trajectory (sum of absolute intervals) within syllabic nuclei, divided by duration (in ST/s)"
2057
+ @w: "TrajInter = pitch trajectory (sum of absolute intervals) between syllabic nuclei (except pauses or speaker turns), divided by duration (in ST/s)"
2058
+ @w: "TrajPhon = sum of TrajIntra and TrajInter, divided by phonation time (in ST/s)"
2059
+ @w: "TrajIntraZ = as TrajIntra, but for pitch trajectory in standard deviation units on ST scale (z-score) (in sd/s)"
2060
+ @w: "TrajInterZ = as TrajInter, but for pitch trajectory in standard deviation units on ST scale (z-score) (in sd/s)"
2061
+ @w: "TrajPhonZ = as TrajPhon, but for pitch trajectory in standard deviation units on ST scale (z-score) (in sd/s)"
2062
+
2063
+ endif ; nrof_nuclei_analysed > 0 and ...
2064
+ endproc
2065
+
2066
+
2067
+ procedure update_global_report
2068
+ # Store feature values for the global profile spreadsheet (covering multiple speech files)
2069
+ @debug_msg: "update_global_report: entry"
2070
+ if (nrof_nuclei_analysed > 0)
2071
+ globalsheet_empty = 0
2072
+ if (not globalsheet_available)
2073
+ selectObject: profileID
2074
+ .ncols = Get number of columns
2075
+ globalprofileID = Create TableOfReal: "globalsheet", 1, .ncols
2076
+ globalsheet_available = 1
2077
+ globalsheet_empty = 1
2078
+ for .col to .ncols ; copy column labels
2079
+ selectObject: profileID
2080
+ .label$ = Get column label: .col
2081
+ selectObject: globalprofileID
2082
+ Set column label (index): .col, .label$
2083
+ endfor
2084
+ endif
2085
+ for .speaker from 1 to speakers ; copy all values from profile to globalprofile
2086
+ selectObject: globalprofileID
2087
+ .nrows = Get number of rows
2088
+ Insert row (index): .nrows+1
2089
+ if (globalsheet_empty) ; contains 1 empty row
2090
+ Remove row (index): 1
2091
+ globalsheet_empty = 0
2092
+ endif
2093
+ selectObject: profileID
2094
+ .label$ = Get row label: .speaker
2095
+ selectObject: globalprofileID
2096
+ .row = Get number of rows
2097
+ Set row label (index): .row, basename$ + "_" + .label$
2098
+ for .col to .ncols
2099
+ selectObject: profileID
2100
+ .v = Get value: .speaker, .col
2101
+ selectObject: globalprofileID
2102
+ Set value: .row, .col, .v
2103
+ endfor
2104
+ endfor
2105
+ ; overwrite the global report file, saved after processing previous speech input file(s) in run
2106
+ deleteFile: globalfile$
2107
+ selectObject: globalprofileID
2108
+ Write to headerless spreadsheet file: globalfile$
2109
+ endif
2110
+ @debug_msg: "update_global_report: exit"
2111
+ endproc
2112
+
2113
+
2114
+ procedure draw_table: .wx1, .wx2, .wy1, .wy2, .nrows, .ncols, .format$, .data$
2115
+ ; Draw a table with cell content as in <.data$>
2116
+ ; <.wx1>..<.wx2> left and right in world coordinates of drawable area
2117
+ ; <.wy1>..<.wy2> top and bottom in world coordinates of drawable area
2118
+ ; <.data$> cell values in rows and colums, separated by ";"
2119
+ ; <.format$> width of colums (in % of table width), separated by ";" Optional specification of border and header lines.
2120
+ ; viewport dimensions of table in world of page
2121
+ .mx1 = ppvp_x1+(ppvp_x2-ppvp_x1)*(.wx1-ppw_x1)/(ppw_x2-ppw_x1)
2122
+ .mx2 = ppvp_x1+(ppvp_x2-ppvp_x1)*(.wx2-ppw_x1)/(ppw_x2-ppw_x1)
2123
+ .my1 = ppvp_y1+(ppvp_y2-ppvp_y1)*(ytop-.wy1)/(ytop-ybot) ; top
2124
+ .my2 = ppvp_y1+(ppvp_y2-ppvp_y1)*(ytop-.wy2)/(ytop-ybot) ; bottom
2125
+ Select inner viewport: .mx1, .mx2, .my1, .my2
2126
+ Axes: 0, 1, 0, 1
2127
+ if (show_area)
2128
+ Yellow
2129
+ Draw rectangle: 0, 1, 0, 1
2130
+ Black
2131
+ endif
2132
+ for .col to .ncols ; read format specification
2133
+ .pos = index(.format$, ";")
2134
+ .field$ = left$(.format$, .pos-1)
2135
+ .justif'.col'$ = "left" ; left justification by default
2136
+ .just$ = left$(.field$, 1)
2137
+ if (.just$ == "R")
2138
+ .justif'.col'$ = "right"
2139
+ elsif (.just$ == "C")
2140
+ .justif'.col'$ = "centre"
2141
+ endif
2142
+ .field$ = replace_regex$(.field$, "^[LRC]", "", 1)
2143
+ .width'.col' = number(.field$) ; width as percentage of table width
2144
+ .format$ = mid$(.format$, .pos+1, length(.format$)-.pos) ; rest of format
2145
+ endfor
2146
+ if (index_regex(extractWord$(.format$, "border="),"y"))
2147
+ Grey
2148
+ Draw rectangle: 0, 1, 0, 1
2149
+ endif
2150
+ if (index_regex(extractWord$(.format$, "header="),"y"))
2151
+ Grey
2152
+ .y = (.nrows-1)/.nrows
2153
+ Draw line: 0, .y, 1, .y
2154
+ endif
2155
+ Black
2156
+ .cell_margin = 0.01 ; 1% of table width, on both sides (left and right) of cell
2157
+ for .row to .nrows
2158
+ .y = (.nrows-.row)/.nrows
2159
+ .xoffs = 0
2160
+ for .col to .ncols
2161
+ .pos = index(.data$, ";") ; end of current cell/column
2162
+ .elem$ = left$(.data$, .pos-1) ; cell content
2163
+ .data$ = mid$(.data$, .pos+1, length(.data$)-.pos) ; prepare for next cell
2164
+ .col_width = (.width'.col'/100) ; from percentage to fraction
2165
+ .just$ =.justif'.col'$ ; justification for current column
2166
+ if (.just$ == "left")
2167
+ .x = .xoffs + .cell_margin
2168
+ elsif (.just$ == "right")
2169
+ .x = .xoffs + .col_width - .cell_margin
2170
+ elsif (.just$ == "centre")
2171
+ .x = .xoffs + .col_width/2 + .cell_margin
2172
+ endif
2173
+ Text: .x, .just$, .y, "Bottom", .elem$
2174
+ .xoffs += .col_width ; prepare xoffset for next column
2175
+ endfor
2176
+ endfor
2177
+ ; Restore mother window
2178
+ Select inner viewport: ppvp_x1, ppvp_x2, ppvp_y1, ppvp_y2
2179
+ Axes: ppw_x1, ppw_x2, ybot, ytop
2180
+ endproc
2181
+
2182
+
2183
+ procedure draw_histogram: .table, .col, .wx1, .wx2, .wy1, .wy2, .xr1, .xr2, .nrbins, .xstep, .xcenter, .top$, .bottom$
2184
+ ; Draw a column in a table as a histogram.
2185
+ ; Specify position of window in world coordinates of page (<.wy1> is botton)
2186
+ ; <.wx1>..<.wx2> left and right of histogram in world coordinates of drawable area
2187
+ ; <.wy1>..<.wy2> bottom and top of histogram in world coordinates of drawable area
2188
+ ; <.xr1>..<.xr2> value range of x-axis of histogram
2189
+ ; <.xstep> step for marks at bottom
2190
+ ; <.xcenter> x position of central value, shown in red
2191
+ @debug_msg: "draw_histogram: entry, table='.table', nrbins='.nrbins'"
2192
+ .mx1 = ppvp_x1+(ppvp_x2-ppvp_x1)*(.wx1-ppw_x1)/(ppw_x2-ppw_x1)
2193
+ .mx2 = ppvp_x1+(ppvp_x2-ppvp_x1)*(.wx2-ppw_x1)/(ppw_x2-ppw_x1)
2194
+ .my1 = ppvp_y1+(ppvp_y2-ppvp_y1)*(ytop-.wy1)/(ytop-ybot)
2195
+ .my2 = ppvp_y1+(ppvp_y2-ppvp_y1)*(ytop-.wy2)/(ytop-ybot)
2196
+ if (show_area)
2197
+ Select inner viewport: .mx1, .mx2, .my1, .my2
2198
+ Axes: 0, 1, 0, 1
2199
+ Yellow
2200
+ Draw rectangle: 0, 1, 0, 1
2201
+ Black
2202
+ endif
2203
+ Select outer viewport: .mx1, .mx2, .my1, .my2
2204
+ .xmargin = (.mx2 - .mx1) * 0.14
2205
+ .ymargin = (.my2 - .my1) * 0.2
2206
+ Select inner viewport: .mx1+.xmargin, .mx2-.xmargin, .my1+.ymargin, .my2-.ymargin
2207
+ Solid line
2208
+ Black
2209
+ if (.table != undefined)
2210
+ selectObject: .table
2211
+ ; Draw column as distribution: column, value range 1, 2, frequency range 1, 2, nrbins, Garnish
2212
+ ; Draw column as distribution: .col, .xr1, .xr2, 0, 0, .nrbins, "no"
2213
+ @draw_column_as_distribution: .table, .col, .xr1, .xr2, .nrbins
2214
+ else ; when there is no data
2215
+ Axes: .xr1, .xr2, 0, 1
2216
+ endif
2217
+ Marks bottom every: 1, .xstep, "yes", "yes", "no"
2218
+ if (.xstep >= 100)
2219
+ Marks bottom every: 1, .xstep/2, "no", "yes", "no"
2220
+ endif
2221
+ Text top: "no", .top$
2222
+ Text bottom: "yes", .bottom$
2223
+ Draw inner box
2224
+ if (.xcenter != undefined)
2225
+ Axes: .xr1, .xr2, 0, 1
2226
+ Dotted line
2227
+ Red
2228
+ Draw line: .xcenter, 0, .xcenter, 1
2229
+ endif
2230
+ Solid line
2231
+ Black
2232
+ ; Restore mother window
2233
+ Select inner viewport: ppvp_x1, ppvp_x2, ppvp_y1, ppvp_y2
2234
+ Axes: ppw_x1, ppw_x2, ybot, ytop
2235
+ @debug_msg: "draw_histogram: exit"
2236
+ endproc
2237
+
2238
+
2239
+ procedure draw_xline: .wx1, .wx2, .wy1, .wy2, .xr1, .xr2, .x, .color$
2240
+ ; Draw a vertical line at position x of area
2241
+ ; Specify position of window in world coordinates of page (<.wy1> is botton)
2242
+ ; <.wx1>..<.wx2> left and right of area in world coordinates of drawable area
2243
+ ; <.wy1>..<.wy2> bottom and top of area in world coordinates of drawable area
2244
+ ; <.xr1>..<.xr2> value range of x-axis of area
2245
+ @debug_msg: "draw_xline: entry"
2246
+ .mx1 = ppvp_x1+(ppvp_x2-ppvp_x1)*(.wx1-ppw_x1)/(ppw_x2-ppw_x1)
2247
+ .mx2 = ppvp_x1+(ppvp_x2-ppvp_x1)*(.wx2-ppw_x1)/(ppw_x2-ppw_x1)
2248
+ .my1 = ppvp_y1+(ppvp_y2-ppvp_y1)*(ytop-.wy1)/(ytop-ybot)
2249
+ .my2 = ppvp_y1+(ppvp_y2-ppvp_y1)*(ytop-.wy2)/(ytop-ybot)
2250
+ Select outer viewport: .mx1, .mx2, .my1, .my2
2251
+ .xmargin = (.mx2 - .mx1) * 0.14
2252
+ .ymargin = (.my2 - .my1) * 0.2
2253
+ Select inner viewport: .mx1+.xmargin, .mx2-.xmargin, .my1+.ymargin, .my2-.ymargin
2254
+ Axes: .xr1, .xr2, 0, 1
2255
+ Dotted line
2256
+ '.color$'
2257
+ Draw line: .x, 0, .x, 1
2258
+ ; Restore mother window
2259
+ Select inner viewport: ppvp_x1, ppvp_x2, ppvp_y1, ppvp_y2
2260
+ Axes: ppw_x1, ppw_x2, ybot, ytop
2261
+ Solid line
2262
+ Black
2263
+ @debug_msg: "draw_xline: exit"
2264
+ endproc
2265
+
2266
+
2267
+ procedure draw_column_as_distribution: .table, .col, .xrange1, .xrange2, .nbins
2268
+ ; Draw histogram of values in column <.col> of TableOfReal <.table>, using <.nbins> for value range <.xrange1>..<.xrange2>
2269
+ @debug_msg: "draw_column_as_distribution: entry"
2270
+ ; Create temporay table for histogram bins and fill them
2271
+ .histo = Create TableOfReal: "histo", .nbins, 1
2272
+ .maxcount = 0
2273
+ .dxbin = (.xrange2-.xrange1)/.nbins
2274
+ selectObject: .table
2275
+ .nrows = Get number of rows
2276
+ for .row to .nrows
2277
+ selectObject: .table
2278
+ .v = Get value: .row, .col
2279
+ if (.v >= .xrange1 and .v <= .xrange2)
2280
+ .bin = floor((.v - .xrange1)/ .dxbin) + 1 ; get index of bin
2281
+ if (.bin <= .nbins)
2282
+ selectObject: .histo
2283
+ .count = Get value: .bin, 1
2284
+ .count += 1
2285
+ Set value: .bin, 1, .count
2286
+ .maxcount = max (.maxcount, .count)
2287
+ else
2288
+ @debug_msg: "draw_column_as_distribution: row='.row' v='.v:4' bin='.bin' > nbins='.nbins'"
2289
+ endif
2290
+ endif
2291
+ endfor
2292
+ ; Select appropriate range for Y-axis of histogram
2293
+ .r = 10
2294
+ repeat
2295
+ .top = ceiling(.maxcount/.r)*.r
2296
+ .r = .r*10
2297
+ until (.top >= .maxcount)
2298
+ .top = max(.top, 1) ; top should not equal 0
2299
+ @debug_msg: "draw_column_as_distribution: Select appropriate range top='.top' maxcount='.maxcount'"
2300
+ Axes: .xrange1, .xrange2, 0, .top
2301
+ selectObject: .histo
2302
+ for .bin to .nbins
2303
+ .count = Get value: .bin, 1
2304
+ Draw rectangle: .xrange1+(.bin-1)*.dxbin, .xrange1+.bin*.dxbin, 0, .count
2305
+ endfor
2306
+ ; Select appropriate step for marks on Y-axis of histogram
2307
+ ; .step = ceiling(.top/50)*10
2308
+ .nsteps = 6
2309
+ .d = 10000
2310
+ repeat
2311
+ .r = .top div .d
2312
+ if (.r < 1)
2313
+ .d = .d / 10
2314
+ endif
2315
+ until (.r >= 1 or .d <= 1)
2316
+ .nm = .top/.d/10 ; range 0..1
2317
+ .p = .nm/.nsteps
2318
+ if (.p < 0.01)
2319
+ .step = 0.1 * .d
2320
+ elsif (.p < 0.022)
2321
+ .step = 0.2 * .d
2322
+ elsif (.p < 0.05)
2323
+ .step = 0.5 * .d
2324
+ elsif (.p < 0.1)
2325
+ .step = 1 * .d
2326
+ else
2327
+ .step = 2 * .d
2328
+ endif
2329
+ @debug_msg: "draw_column_as_distribution: Select appropriate step for marks top='.top' step='.step'"
2330
+ Marks left every: 1, .step, "yes", "yes", "no"
2331
+ removeObject: .histo
2332
+ @debug_msg: "draw_column_as_distribution: exit"
2333
+ endproc
2334
+
2335
+
2336
+ procedure prosodic_profile_new
2337
+ @debug_msg: "prosodic_profile_new: entry, speakers='speakers'"
2338
+ for .j from 1 to speakers
2339
+ .speaker_label$ = speaker_label'.j'$
2340
+ .row_speaker = .j
2341
+ ; Get number of nuclei
2342
+ selectObject: nucldatID
2343
+ .nrows = Get number of rows
2344
+ ; Prepare window
2345
+ Erase all
2346
+ ; .fontsize_std = 10
2347
+ ; Font size (points): .fontsize_std
2348
+ ; Size of A4 paper in inches: 8.27 x 11.69
2349
+ .margin = 0.5
2350
+ ppvp_x1 = .margin ; left (inches)
2351
+ ppvp_x2 = 8.27 - .margin ; right (inches)
2352
+ ppvp_y1 = .margin ; top (inches)
2353
+ ppvp_y2 = 11.69 - .margin ; bottom (inches)
2354
+ Select inner viewport: ppvp_x1, ppvp_x2, ppvp_y1, ppvp_y2
2355
+ show_area = 0
2356
+ if (show_area)
2357
+ Axes: 0, 1, 0, 1
2358
+ Red
2359
+ Dashed line
2360
+ Draw rectangle: 0, 1, 0, 1
2361
+ endif
2362
+ Black
2363
+ Solid line
2364
+ ppw_x1 = 0 ; world coordinates, left of page
2365
+ ppw_x2 = 100 ; world coordinates, right of page
2366
+ ytop = 0 ; world coordinates, top of page
2367
+ ybot = 100 ; world coordinates, bottom of page
2368
+ Axes: ppw_x1, ppw_x2, ybot, ytop
2369
+ ; Axes: left, right, bottom, top
2370
+ ypos = ytop ; current y position on page
2371
+ ystep = 1.65
2372
+ .x = 0
2373
+ Font size: fontsize
2374
+ .date$ = date$ ()
2375
+
2376
+ .t$ = "Prosodic Profile, by 'version$', \co Piet Mertens; ;"
2377
+ @draw_table: 0, 60, ypos, ypos+ystep, 1, 2, "50;50;", .t$
2378
+ ypos += ystep
2379
+
2380
+ selectObject: profileID
2381
+ .nrofnuclei = Get value: .row_speaker, j_nrofnucl
2382
+ .nrofvalid = Get value: .row_speaker, j_nrofvalid
2383
+ .t$ = "Input file:;'signalfile$';"
2384
+ .t$ += "Date (of analysis):;'.date$';"
2385
+ .t$ += "Speaker:;'.j' (of 'speakers'), with label ""'.speaker_label$'"";"
2386
+ .t$ += "Number of nuclei:;'.nrofvalid' (of '.nrofnuclei', incl. outliers & discontinuities);"
2387
+ @draw_table: 5, 90, ypos, ypos+4*ystep, 4, 2, "25;75;border=y", .t$
2388
+ .t$ ="Segmentation type: 'segmentation_name$'"
2389
+ @draw_table: 60, 90, ypos+ystep, ypos+2*ystep, 1, 1, "100;border=y", "Segmentation type: 'segmentation_name$';"
2390
+ ypos += 4*ystep + ystep
2391
+
2392
+ @draw_table: 0, 60, ypos, ypos+ystep, 1, 1, "100;", "Pitch Range (after stylization);"
2393
+ ypos += ystep
2394
+
2395
+ selectObject: profileID
2396
+ .t$ = " ;ST;Hz;"
2397
+ .v = Get value: .row_speaker, j_pitch_range
2398
+ .t$ += "range (span);'.v:1'; ;"
2399
+ .vH = Get value: .row_speaker, j_pitch_top_Hz
2400
+ .v = Get value: .row_speaker, j_pitch_top_ST
2401
+ .t$ += "top;'.v:1';'.vH:0';"
2402
+ .vH = Get value: .row_speaker, j_pitch_mean_Hz
2403
+ .f0_mean = .vH
2404
+ .v = Get value: .row_speaker, j_pitch_mean_of_ST
2405
+ .t$ += "mean;'.v:1';'.vH:0';"
2406
+ .vH = Get value: .row_speaker, j_pitch_median_Hz
2407
+ .v = Get value: .row_speaker, j_pitch_median_ST
2408
+ .t$ += "median;'.v:1';'.vH:0';"
2409
+ .vH = Get value: .row_speaker, j_pitch_bottom_Hz
2410
+ .v = Get value: .row_speaker, j_pitch_bottom_ST
2411
+ .t$ += "bottom;'.v:1';'.vH:0';"
2412
+ .vH = Get value: .row_speaker, j_pitch_stdev_of_Hz
2413
+ .v = Get value: .row_speaker, j_pitch_stdev_of_ST
2414
+ .t$ += "stddev;'.v:2';'.vH:2';"
2415
+ @draw_table: 5, 30, ypos, ypos+7*ystep, 7, 3, "50;R25;R25;border=y header=y", .t$
2416
+ ypos += 8*ystep
2417
+
2418
+ ; Histograms of pitch variability
2419
+ @draw_table: 0, 60, ypos, ypos+ystep, 1, 1, "100;", "Pitch variability;"
2420
+ ypos += ystep
2421
+
2422
+ .hr0 = ypos
2423
+ .hr1 = .hr0 + 2*ystep
2424
+ .hr2 = .hr1 + 11*ystep
2425
+ .hr3 = .hr2 + 11*ystep
2426
+ .hr4 = .hr3 + ystep
2427
+ .hr5 = .hr4 + 3*ystep
2428
+ .hr6 = .hr5 + ystep
2429
+ .hr7 = .hr6 + ystep
2430
+ .hr8 = .hr7 + 2*ystep
2431
+ .hr9 = .hr8 + 11*ystep
2432
+ Draw line: 30, .hr0, 30, .hr5
2433
+ Draw line: 60, .hr0, 60, .hr5
2434
+ Draw line: 30, .hr7, 30, .hr9
2435
+ Draw line: 60, .hr7, 60, .hr9
2436
+
2437
+ ;@msg: "SPEAKER '.j'"
2438
+ ; Create temporary table with values for current speaker
2439
+ ; @create_subtable_where: nucldatID, j_speaker_id, "==", .j, "table1"
2440
+ selectObject: nucldatID
2441
+ .table1 = Extract rows where column: j_speaker_id, "equal to", .j
2442
+ ; Create temporary table with values for current speaker, without f0 discontinuity
2443
+ ; @create_subtable_where: table1, j_f0_discont, "==", 0, "table2"
2444
+ selectObject: .table1
2445
+ .table2 = Extract rows where column: j_f0_discont, "equal to", 0
2446
+
2447
+
2448
+ ;@msg: "Histogram: Row 1 left: mean F0 raw Hz"
2449
+ ; Histogram: mean F0 raw in Hz in nuclei
2450
+ selectObject: .table2
2451
+ .n = Get number of rows
2452
+ .nrbins = 40
2453
+ ; draw_histogram: .table, col, x1, x2, y1, y2, x1_histo, x2_histo, nrbins, xstep (marks), xcenter, legend1, legend2
2454
+ @draw_histogram: .table2, j_f0_mean, 0, 30, .hr1, .hr2, 50, 450, .nrbins, 100, .f0_mean, "mean F0 (raw) in nuclei", "Hz (N='.n')"
2455
+
2456
+
2457
+ ;@msg: "Histogram: Row 2 left: mean F0 raw ST"
2458
+ ; Histogram: mean F0 in ST in nuclei
2459
+ selectObject: .table2
2460
+ .n = Get number of rows
2461
+ .mid = Get column mean (index): j_f0_meanST
2462
+ .nrbins = 40
2463
+ @draw_histogram: .table2, j_f0_meanST, 0, 30, .hr2, .hr3, .mid-20, .mid+20, .nrbins, 6, .mid, "mean F0 (raw) in nuclei", "ST (N='.n')"
2464
+
2465
+
2466
+ ;@msg: "Histogram: Row 1 mid: low&high F0 raw Hz"
2467
+ ; Histogram: low and high F0 in nuclei
2468
+ ; Combine lopitch and hipitch values; for selected speaker
2469
+ selectObject: .table2
2470
+ .nrows = Get number of rows
2471
+ .table3 = Create TableOfReal: "tmp3", .nrows*2, 1
2472
+ .i = 0
2473
+ for .row to .nrows
2474
+ selectObject: .table2
2475
+ .vlo = Get value: .row, lopitch
2476
+ .vhi = Get value: .row, hipitch
2477
+ selectObject: .table3
2478
+ .i += 1
2479
+ Set value: .i, 1, .vlo
2480
+ .i += 1
2481
+ Set value: .i, 1, .vhi
2482
+ endfor
2483
+ selectObject: .table3
2484
+ .n = Get number of rows
2485
+ .nrbins = 40
2486
+ @draw_histogram: .table3, 1, 30, 60, .hr1, .hr2, 50, 450, .nrbins, 100, .f0_mean, "low & high F0 (stylized) in nuclei", "Hz (N='.n')"
2487
+
2488
+
2489
+ ;@msg: "Histogram: Row 2 mid: low&high F0 raw ST"
2490
+ ; Histogram: low and high F0 in ST in nuclei
2491
+ selectObject: .table3
2492
+ .n = Get number of rows
2493
+ for .i to .n
2494
+ .v = Get value: .i, 1
2495
+ Set value: .i, 1, hertzToSemitones (.v) - hertzToSemitones(1)
2496
+ endfor
2497
+ .mid = Get column mean (index): 1
2498
+ .nrbins = 40
2499
+ @draw_histogram: .table3, 1, 30, 60, .hr2, .hr3, .mid-20, .mid+20, .nrbins, 6, .mid, "low & high F0 (stylized) in nuclei", "ST (N='.n')"
2500
+ removeObject: .table3
2501
+
2502
+
2503
+ selectObject: profileID
2504
+ .v = Get value: .row_speaker, j_prop_level
2505
+ @draw_table: 60, 90, .hr0, .hr0+2*ystep, 2, 1, "C100;", "stylized pitch contour;'.v:1'\% nuclei without glissando;"
2506
+
2507
+
2508
+ ;@msg: "Histogram: Row 1 right: intrasyllabic up and down glissando"
2509
+ ; Histogram: intrasyllabic glissandos
2510
+ ; Combine intrasyllab up and down values; for selected speaker
2511
+ selectObject: .table2
2512
+ .table4 = Create TableOfReal: "table4", .nrows*2, 1
2513
+ .i = 0 ; nrof used rows in destination table
2514
+ .ns = 0 ; nrof syllables with glissando
2515
+ .ns_up = 0 ; nrof syllables with upward glissando
2516
+ .ns_down = 0 ; nrof syllables with downward glissando
2517
+ for .row from 1 to .nrows
2518
+ selectObject: .table2
2519
+ .dyn = Get value: .row, j_dynamic
2520
+ if (.dyn <> 0)
2521
+ .v1 = Get value: .row, j_intrasylup
2522
+ .v2 = Get value: .row, j_intrasyldown
2523
+ selectObject: .table4
2524
+ if (.v1 <> 0)
2525
+ .i += 1
2526
+ Set value: .i, 1, .v1
2527
+ .ns_up += 1
2528
+ endif
2529
+ if (.v2 <> 0)
2530
+ .i += 1
2531
+ Set value: .i, 1, .v2
2532
+ .ns_down += 1
2533
+ endif
2534
+ if (.v1 <> 0 or .v2 <> 0)
2535
+ .ns += 1
2536
+ endif
2537
+ endif
2538
+ endfor
2539
+ ;@msg: "selection of rows done, i='.i'"
2540
+ selectObject: .table4
2541
+ .nrows = Get number of rows
2542
+ .row = .nrows
2543
+ if (.i > 0)
2544
+ repeat
2545
+ if (.row > .i)
2546
+ Remove row (index): .row
2547
+ endif
2548
+ .row -= 1
2549
+ until (.row <= .i)
2550
+ .n = Get number of rows
2551
+ .ref = .table4
2552
+ else
2553
+ .ref = undefined
2554
+ endif
2555
+ ;@msg: "removal of rows done"
2556
+ .nrbins = 24
2557
+ @draw_histogram: .ref, 1, 60, 90, .hr1, .hr2, -12, 12, .nrbins, 2, undefined, "nuclei with glissando", "ST (|\^||='.ns_up' & |\_||='.ns_down' in '.ns' nucl)"
2558
+ removeObject: .table4
2559
+
2560
+
2561
+ ;@msg: "Histogram: Row 2 right: intersyllabic"
2562
+ ; Histogram: intersyllabic
2563
+ ; Combine intersyllab values; for selected speaker
2564
+ selectObject: nucldatID
2565
+ .nrows = Get number of rows
2566
+ selectObject: .table2
2567
+ .n = Get number of rows
2568
+ .zeros = 0
2569
+ for .i to .n
2570
+ .v = Get value: .i, j_intersyl
2571
+ if (.v == 0)
2572
+ .zeros += 1
2573
+ endif
2574
+ endfor
2575
+ ;@msg: "prosodic_profile_new: histogram of intersyllabic pitch intervals, n='.n' zeros='.zeros'"
2576
+ .nrbins = 32
2577
+ @draw_histogram: .table2, j_intersyl, 60, 90, .hr2, .hr3, -8, 8, .nrbins, 2, undefined, "intervals between nuclei", "ST (N='.n')"
2578
+ ; |0|='.zeros' or '.prop_zeros:1'\% "
2579
+
2580
+ selectObject: profileID
2581
+ .v = Get value: .row_speaker, j_traj_phon
2582
+ .t$ = "Total trajectory (ST/s);'.v:1';"
2583
+ .v = Get value: .row_speaker, j_traj_intra
2584
+ .t$ += "Intrasyllabic traj. (ST/s);'.v:1';"
2585
+ .v = Get value: .row_speaker, j_traj_inter
2586
+ .t$ += "Intersyllabic traj. (ST/s);'.v:1';"
2587
+ @draw_table: 63, 87, .hr4, .hr4+3*ystep, 3, 2, "70;R30;border=n", .t$
2588
+
2589
+
2590
+ @draw_table: 0, 60, .hr6, .hr6+ystep, 1, 1, "100;", "Temporal organisation;"
2591
+
2592
+ selectObject: profileID
2593
+ .v = Get value: .row_speaker, j_speech_rate
2594
+ .t$ = "Speech rate (syll/s);'.v:1';"
2595
+ .v = Get value: .row_speaker, j_speech_time
2596
+ .t$ += "Speech time (s);'.v:2';"
2597
+ @draw_table: 5, 27, .hr7, .hr7+2*ystep, 2, 2, "70;R30;border=n", .t$
2598
+
2599
+ selectObject: profileID
2600
+ .v = Get value: .row_speaker, j_nucldur_mean
2601
+ .t$ = "Nucleus dur. mean (s);'.v:3';"
2602
+ .v = Get value: .row_speaker, j_nucldur_stdev
2603
+ .t$ += "Nucleus dur. stdev;'.v:3';"
2604
+ @draw_table: 33, 57, .hr7, .hr7+2*ystep, 2, 2, "70;R30;border=n", .t$
2605
+
2606
+ selectObject: profileID
2607
+ .v = Get value: .row_speaker, j_propphon
2608
+ .t$ = "Proportion phonation (\% );'.v:1' ;"
2609
+ .v = Get value: .row_speaker, j_proppause
2610
+ .t$ += "Proportion pauses (\% );'.v:1' ;"
2611
+ @draw_table: 63, 87, .hr7, .hr7+2*ystep, 2, 2, "70;R30;border=n", .t$
2612
+
2613
+
2614
+ ;@msg: "Histogram: Row 3 mid: nucleus duration"
2615
+ ; Histogram: nucleus duration
2616
+ selectObject: profileID
2617
+ .mean = Get value: .row_speaker, j_nucldur_mean
2618
+ .median = Get value: .row_speaker, j_nucldur_median
2619
+ selectObject: .table2
2620
+ .nrbins = 40
2621
+ @draw_histogram: .table2, j_nucldur, 30, 60, .hr8, .hr8+11*ystep, 0, 0.4, .nrbins, 0.1, .mean, "nucleus duration", "s (N='.n')"
2622
+ @draw_xline: 30, 60, .hr8, .hr8+11*ystep, 0, 0.4, .median, "Green"
2623
+
2624
+
2625
+ ; Histogram: pause duration
2626
+ selectObject: .table1
2627
+ ;@msg: "prosodic_profile_new: pause duration: speaker='.j', nucldat rows='.nrows', valid='.n'"
2628
+ .n = Get number of rows
2629
+ .i = 1
2630
+ .zeros = 0
2631
+ while (.i <= .n)
2632
+ .v = Get value: .i, j_pause_dur
2633
+ if (.v < mindur_pause_gap)
2634
+ .v = 0
2635
+ Set value: .i, j_pause_dur, 0
2636
+ endif
2637
+ if (.v > 0)
2638
+ .i += 1
2639
+ else
2640
+ .zeros += 1
2641
+ if (.n > 1) ; cannot remove row when it is the only row
2642
+ Remove row (index): .i
2643
+ endif
2644
+ .n -= 1
2645
+ endif
2646
+ endwhile
2647
+ .mean = Get column mean (index): j_pause_dur
2648
+ .mean = max (.mean, mindur_pause_gap)
2649
+ ;@msg: "prosodic_profile_new: pause duration: speaker='.j', nucldat rows='.nrows', zeros='.zeros' valid='.n'"
2650
+ .nrbins = 40
2651
+ @draw_histogram: .table1, j_pause_dur, 60, 90, .hr8, .hr8+11*ystep, 0.3, 1, .nrbins, 0.2, .mean, "pause duration (gap \>_ 'mindur_pause_gap:2')", "s (N='.n')"
2652
+
2653
+
2654
+ removeObject: .table1, .table2
2655
+
2656
+
2657
+ Select inner viewport: ppvp_x1, ppvp_x2, ppvp_y1, ppvp_y2
2658
+ ; Build filename for prosodic profile output file
2659
+ .fname$ = replace_regex$(statsfile$, "^(.*)(\.txt)", "\1", 1)
2660
+ .fname$ += "_speaker_'.j'"
2661
+ if (index(output_format$,"EPS"))
2662
+ @msg: "Writing prosodic profile for speaker '.j' to: '.fname$'.eps"
2663
+ Save as EPS file: .fname$+".eps"
2664
+ elsif (index(output_format$,"PNG 600 dpi"))
2665
+ @msg: "Writing prosodic profile for speaker '.j' to: '.fname$'.png"
2666
+ Save as 600-dpi PNG file: .fname$+".png"
2667
+ else
2668
+ @msg: "Writing prosodic profile for speaker '.j' to: '.fname$'.png"
2669
+ Save as 300-dpi PNG file: .fname$+".png"
2670
+ endif
2671
+ endfor
2672
+ endproc
2673
+
2674
+
2675
+ procedure table_copy: .src, .var_name$
2676
+ ; Replacement for Praat's TableOfReal: Copy, which is broken in Praat 6.0.50
2677
+ selectObject: .src
2678
+ .nrows = Get number of rows
2679
+ .ncols = Get number of columns
2680
+ .dst = Create TableOfReal: "tmp_copy", .nrows, .ncols
2681
+ for .col to .ncols
2682
+ selectObject: .src
2683
+ .label$ = Get column label: .col
2684
+ selectObject: .dst
2685
+ Set column label (index): .col, .label$
2686
+ endfor
2687
+ for .row to .nrows
2688
+ selectObject: .src
2689
+ .label$ = Get row label: .row
2690
+ selectObject: .dst
2691
+ Set row label (index): .row, .label$
2692
+ for .col to .ncols
2693
+ selectObject: .src
2694
+ .v = Get value: .row, .col
2695
+ selectObject: .dst
2696
+ Set value: .row, .col, .v
2697
+ endfor
2698
+ endfor
2699
+ '.var_name$' = .dst
2700
+ endproc
2701
+
2702
+
2703
+ procedure table_column_by_label: .table, .column_label$, .varname$
2704
+ selectObject: .table
2705
+ .ncols = Get number of columns
2706
+ .col = 1
2707
+ .ok = 0
2708
+ repeat
2709
+ .label$ = Get column label: .col
2710
+ if (.label$ == .column_label$)
2711
+ .ok = .col
2712
+ endif
2713
+ .col += 1
2714
+ until (.ok or .col > .ncols)
2715
+ '.varname$' = .ok
2716
+ endproc
2717
+
2718
+
2719
+ procedure table_row_where_col_value_gt: .table, .col, .value, .varname$
2720
+ selectObject: .table
2721
+ .nrows = Get number of rows
2722
+ .ncols = Get number of columns
2723
+ .row = 1
2724
+ .ok = 0
2725
+ repeat
2726
+ .y = Get value: .row, .col
2727
+ if (.y > .value)
2728
+ .ok = .row
2729
+ endif
2730
+ .row += 1
2731
+ until (.ok or .row > .nrows)
2732
+ '.varname$' = .ok
2733
+ endproc
2734
+
2735
+
2736
+
2737
+ procedure calc_isodur: .table
2738
+ ; Calculate duration of inter syllable onset
2739
+ ; Requires nucleus, speaker and pause information
2740
+ @debug_msg: "calc_isodur: entry"
2741
+ selectObject: .table
2742
+ .rows = Get number of rows
2743
+ for .row to .rows ; for each nucleus/syllable in the signal
2744
+ .t1 = Get value: .row, j_nucl_t1
2745
+ .t2 = Get value: .row, j_nucl_t2
2746
+ .sp = Get value: .row, j_speaker_id
2747
+ .pause = Get value: .row, j_before_pause
2748
+ .isodur = .t2-.t1 ; default when followed by speaker turn or end-of-signal
2749
+ if (.row < .rows)
2750
+ .t = Get value: .row+1, j_nucl_t1
2751
+ .sp2 = Get value: .row+1, j_speaker_id
2752
+ if (.sp2 == .sp)
2753
+ if (.pause)
2754
+ .isodur = (.t2-.t1) ; + 0.3
2755
+ else
2756
+ .isodur = .t - .t1
2757
+ endif
2758
+ endif
2759
+ elsif (.row == .rows)
2760
+ .isodur = (.t2 - .t1) ; + 0.3
2761
+ endif
2762
+ Set value: .row, j_isodur, .isodur
2763
+ endfor
2764
+ @debug_msg: "calc_isodur: exit"
2765
+ endproc
2766
+