pelican-nlp 0.3.8__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.
- pelican_nlp/_version.py +1 -1
- pelican_nlp/praat/eps_conv.praat +193 -0
- pelican_nlp/praat/polytonia.praat +773 -0
- pelican_nlp/praat/prosogram.praat +102 -0
- pelican_nlp/praat/prosomain.praat +3787 -0
- pelican_nlp/praat/prosoplot.praat +1546 -0
- pelican_nlp/praat/segment.praat +1224 -0
- pelican_nlp/praat/setup.praat +1 -0
- pelican_nlp/praat/stylize.praat +2766 -0
- pelican_nlp/praat/util.praat +632 -0
- {pelican_nlp-0.3.8.dist-info → pelican_nlp-0.3.9.dist-info}/METADATA +1 -1
- {pelican_nlp-0.3.8.dist-info → pelican_nlp-0.3.9.dist-info}/RECORD +16 -7
- {pelican_nlp-0.3.8.dist-info → pelican_nlp-0.3.9.dist-info}/WHEEL +0 -0
- {pelican_nlp-0.3.8.dist-info → pelican_nlp-0.3.9.dist-info}/entry_points.txt +0 -0
- {pelican_nlp-0.3.8.dist-info → pelican_nlp-0.3.9.dist-info}/licenses/LICENSE +0 -0
- {pelican_nlp-0.3.8.dist-info → pelican_nlp-0.3.9.dist-info}/top_level.txt +0 -0
@@ -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
|
+
|