-
Notifications
You must be signed in to change notification settings - Fork 17
/
SemiAutoFormantExtractor.praat
418 lines (367 loc) · 15.9 KB
/
SemiAutoFormantExtractor.praat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# ########################################################################################################################### #
# PRAAT SCRIPT "SEMI-AUTO FORMANT EXTRACTOR"
# This script semi-automates measuring formants from sound files with labeled TextGrids. It is based on and similar to the
# script "SemiAutoPitchAnalysis" by Daniel McCloy. This script cycles through a directory of TextGrids, finds associated
# sound files, opens them one at a time, displays a table of formant values for the interval at user-specified time points,
# and prompts the user to either (1) accept the formant measurements, (2) adjust the formant settings and recalculate, or
# (3) mark the interval as unmeasurable, before continuing on to the next interval or file.
#
# By default, the script will show a wideband spectrogram (from 0-5000Hz) and a formant track while running, but will
# not show pitch, pulses, intensity, etc. If you want to see additional analyses, change the arguments of the line that starts
# "Show analyses" (lines 165 & 168). If you want to change the underlying spectrogram settings, change lines 205-206.
# VERSION 0.3 (2012.05.05)
#
# CHANGELOG
# VERSION 0.3: Major reorganization of code (several complex control structures moved into procedures). Bug fixed where
# sometimes all time points through the interval were written out with values from the midpoint. Major
# improvements to efficiency by using formant values drawn from the editor window (since it's already open
# anyway) rather than extracting a sound slice and creating a formant object from it.
#
# VERSION 0.2: No significant code changes. Moderate improvements to documentation. License changed from CC to GPL.
#
# AUTHORS: DANIEL MCCLOY: (drmccloy@uw.edu) & AUGUST MCGRATH
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE v3.0 OR HIGHER: http://www.gnu.org/licenses/gpl.html
# DEVELOPMENT OF THIS SCRIPT WAS FUNDED BY THE NATIONAL INSTITUTES OF HEALTH, GRANT # R01DC006014 TO PAMELA SOUZA
# ########################################################################################################################### #
# COLLECT ALL THE USER INPUT
form Select directories for TextGrids and Sound files
sentence Textgrid_directory ~/Desktop/textgrids/
sentence Sound_directory ~/Desktop/sounds/
sentence Sound_extension .wav
comment Which TextGrid tier contains your segment labels?
integer Label_tier 1
comment You can pick up where you left off if you like:
integer Starting_file_number 1
comment How many seconds of the sound file do you want to
comment see during analysis? (enter "0" to view the entire file)
real Zoom_duration 1
comment Full path of the output file:
sentence Output_file /home/dan/Desktop/FormantAnalysisResults.txt
comment Default formant tracker settings (you can adjust "max formant" and
comment "number of formants" later as you step through the intervals):
positive Default_max_formant 5500
integer Default_formant_number 5
real Time_step 0.1
real Preemphasis_from 50
positive Window_length 0.025
positive Dynamic_range 30
positive Dot_size 0.6
optionmenu Interval_measurement_option: 3
option midpoint
option onset, midpoint, offset
option 20%, 50%, 80%
option 25%, 50%, 75%
option 10%, 30%, 50%, 70%, 90%
option 5%, 10%, 20%, 50%, 80%, 90%, 95%
endform
# RUN SOME FUNCTIONS ON THE USER INPUT (TO BE USED LATER)
call pointsPerInterval
# BE FORGIVING IF THE USER FORGOT TRAILING PATH SLASHES OR LEADING FILE EXTENSION DOTS
call cleanPath 'textgrid_directory$'
textgrid_dir$ = "'cleanPath.out$'"
call cleanPath 'sound_directory$'
sound_dir$ = "'cleanPath.out$'"
call cleanExtn 'sound_extension$'
sound_extn$ = "'cleanExtn.out$'"
# INITIATE THE OUTPUT FILE
if fileReadable (output_file$)
beginPause ("The output file already exists!")
comment ("The output file already exists!")
comment ("You can overwrite the existing file, or append new data to the end of it.")
overwrite_setting = endPause ("Append", "Overwrite", 1)
if overwrite_setting = 2
filedelete 'output_file$'
call initializeOutfile
endif
else
# THERE IS NOTHING TO OVERWRITE, SO CREATE THE HEADER ROW FOR THE NEW OUTPUT FILE
call initializeOutfile
endif
# MAKE A LIST OF ALL TEXTGRIDS IN THE FOLDER
Create Strings as file list... list 'textgrid_dir$'*.TextGrid
file_list = selected("Strings")
file_count = Get number of strings
# LOOP THROUGH THE LIST OF FILES...
for current_file from starting_file_number to file_count
# READ IN THE TEXTGRID & CORRESPONDING SOUND...
select Strings list
gridname$ = Get string... current_file
Read from file... 'textgrid_dir$''gridname$'
filename$ = selected$ ("TextGrid", 1)
Open long sound file... 'sound_dir$''filename$''sound_extn$'
total_duration = Get total duration
# BOOLEAN TO PREVENT OPENING MULTIPLE EDITORS FOR THE SAME LONGSOUND
new_file = 1
# FIND THE LABELED INTERVAL...
select TextGrid 'filename$'
num_intervals = Get number of intervals... label_tier
for interval to num_intervals
select TextGrid 'filename$'
label$ = Get label of interval... label_tier interval
# IF THE LABEL IS NON-EMPTY, GET ITS ENDPOINTS
if label$ <> ""
start = Get starting point... label_tier interval
end = Get end point... label_tier interval
midpoint = (start+end)/2
# PREVENT ZOOM DURATION FROM EXTENDING BEYOND THE ENDS OF THE FILE, BUT TRY TO MAINTAIN THE DESIRED WINDOW SIZE
if not zoom_duration = 0
left_edge = midpoint - zoom_duration/2
right_edge = midpoint + zoom_duration/2
right_excess = right_edge - total_duration
if left_edge < 0
zoom_start = 0
if zoom_duration > total_duration
zoom_end = total_duration
else
zoom_end = zoom_duration
endif
elif right_edge > total_duration
zoom_end = total_duration
if left_edge > right_excess
zoom_start = zoom_end - zoom_duration
else
zoom_start = 0
endif
else
zoom_start = left_edge
zoom_end = right_edge
endif
else ; zoom_duration = 0
zoom_start = 0
zoom_end = total_duration
endif ; zoom_duration
if new_file = 1
# IF THIS IS THE FIRST INTERVAL OF THE CURRENT FILE, SHOW THE EDITOR WINDOW
select LongSound 'filename$'
plus TextGrid 'filename$'
View & Edit
# SINCE WE'RE IN THE FIRST LABELED INTERVAL, SET ALL THE SETTINGS
editor TextGrid 'filename$'
# FIRST, HIDE THE SPECTROGRAM ETC TO PREVENT ANNOYING FLICKERING
Show analyses... no no no no no 10
Zoom... zoom_start zoom_end
# NOW SET ALL THE RELEVANT SETTINGS AND DISPLAY WIDEBAND SPECTROGRAM
Spectrogram settings... 0 5000 0.005 50
Advanced spectrogram settings... 1000 250 Fourier Gaussian yes 100 6 0
Formant settings... default_max_formant default_formant_number window_length dynamic_range dot_size
Advanced formant settings... burg preemphasis_from
# SHOW THE FORMANT TRACKS
if not zoom_duration = 0
# MAKE SURE THE "MAX ANALYSIS" SETTING IS LONG ENOUGH SO THE SPECTROGRAM ACTUALLY SHOWS UP
Show analyses... yes no no yes no zoom_duration*2
else
# THE USER SPECIFED "WHOLE FILE" SO WE ASSUME THE FILES ARE SHORT AND 10 SECONDS SHOULD BE ENOUGH
Show analyses... yes yes no yes no 10
endif
endeditor
else
# WE'RE NOT IN THE FIRST LABELED INTERVAL, SO EDITOR IS OPEN & SETTINGS ARE SET, SO JUST MOVE TO THE CURRENT INTERVAL
editor TextGrid 'filename$'
Zoom... zoom_start zoom_end
endeditor
endif
new_file = 0
# INITIALIZE SOME VARIABLES FOR THE PAUSE U.I.
clicked = 0
max_formant = default_max_formant
formant_number = default_formant_number
call getMeasureTimes
call getFormants
call makeFormantTable
current_time_point = getMeasureTimes.time[1]
current_interval_measurement = 1
# PLACE CURSOR AT FIRST MEASUREMENT POINT
editor TextGrid 'filename$'
Move cursor to... current_time_point
endeditor
# SHOW A U.I. WITH FORMANT TRACKER SETTINGS & MEASURED FORMANT VALUES.
# KEEP SHOWING IT UNTIL THE USER ACCEPTS OR CANCELS THE MEASUREMENT FOR THIS INTERVAL.
repeat
beginPause ("Adjust formant tracker settings")
comment ("File 'filename$' (file number 'current_file' of 'file_count')")
comment ("You can change these settings if the formant track doesn't look right.")
integer ("New_max_formant", max_formant)
integer ("New_number_formants", formant_number)
comment ("Clicking PLAY will play the sound in the interval")
comment ("Clicking REDRAW will redraw the formant tracks with the settings above")
comment ("Cicking SKIP will record all formants as zero to mark for manual measurement")
comment (" ")
comment ("Formant measurements:")
# CREATE THE FORMANT TABLE
call getFormants
call makeFormantTable
comment ("'makeFormantTable.header$'")
comment ("'makeFormantTable.f3$'")
comment ("'makeFormantTable.f2$'")
comment ("'makeFormantTable.f1$'")
comment (" ")
sentence ("Notes_or_comments", "")
clicked = endPause ("Play", "Redraw", "Skip", "Accept", 4)
# IF THEY CLICKED "PLAY"
if clicked = 1
editor TextGrid 'filename$'
Play... start end
endeditor
# IF THEY CLICKED "REDRAW"
elif clicked = 2
max_formant = new_max_formant
formant_number = new_number_formants
editor TextGrid 'filename$'
Formant settings... max_formant formant_number window_length dynamic_range dot_size
endeditor
endif
until clicked >2
# END OF THE PAUSE U.I.
# THE USER HAS EITHER ACCEPTED OR SKIPPED, SO WRITE OUT THE VALUES
for i from 1 to pointsPerInterval.pts
time = getMeasureTimes.time[i]
percent = ((time-start)/(end-start))*100
if clicked = 3
# MARK FOR HAND MEASUREMENT
f1 = 0
f2 = 0
f3 = 0
elif clicked = 4
# GET MEASURED VALUES
f1 = getFormants.f1[i]
f2 = getFormants.f2[i]
f3 = getFormants.f3[i]
endif
# WRITE OUT TO FILE
resultline$ = "'current_file''tab$''filename$''tab$''label$''tab$''percent:0''tab$''time''tab$''f1''tab$''f2''tab$''f3''tab$''max_formant''tab$''formant_number''tab$''notes_or_comments$''newline$'"
fileappend "'output_file$'" 'resultline$'
endfor ; EACH POINT IN THE INTERVAL
endif ; LABEL <> ""
endfor ; EACH INTERVAL IN THE FILE
# REMOVE ALL THE OBJECTS FOR THAT FILE AND GO ON TO THE NEXT ONE
select LongSound 'filename$'
plus TextGrid 'filename$'
Remove
select Strings list
endfor ; EACH FILE IN THE FOLDER
# REMOVE THE STRINGS LIST AND GIVE A SUCCESS MESSAGE
select Strings list
Remove
clearinfo
files_read = file_count - starting_file_number + 1
printline Done! 'files_read' files read.'newline$'
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# FUNCTIONS (A.K.A. PROCEDURES) THAT WERE CALLED EARLIER #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
procedure cleanPath .in$
if not right$(.in$, 1) = "/"
.out$ = "'.in$'" + "/"
else
.out$ = "'.in$'"
endif
endproc
procedure cleanExtn .in$
if not left$(.in$, 1) = "."
.out$ = "." + "'.in$'"
else
.out$ = "'.in$'"
endif
endproc
procedure initializeOutfile
headerline$ = "number'tab$'filename'tab$'label'tab$'percent through interval'tab$'measurement time'tab$'F1'tab$'F2'tab$'F3'tab$'max formant'tab$'number of formants'tab$'notes'newline$'"
fileappend "'output_file$'" 'headerline$'
endproc
procedure pointsPerInterval
# CALCULATE HOW MANY FORMANT MEASUREMENTS PER INTERVAL
if interval_measurement_option = 1
.pts = 1
elif interval_measurement_option = 2 or interval_measurement_option = 3 or interval_measurement_option = 4
.pts = 3
elif interval_measurement_option = 5
.pts = 5
elif interval_measurement_option = 6
.pts = 7
endif
endproc
procedure getMeasureTimes
# MIDPOINT ONLY
if interval_measurement_option = 1
.time [1] = midpoint
# ONSET-MIDPOINT-OFFSET
elif interval_measurement_option = 2
.time [1] = start
.time [2] = midpoint
.time [3] = end
# 20-50-80
elif interval_measurement_option = 3
.time [1] = start + 0.2*(end-start)
.time [2] = midpoint
.time [3] = start + 0.8*(end-start)
# 25-50-75
elif interval_measurement_option = 4
.time [1] = start + 0.25*(end-start)
.time [2] = midpoint
.time [3] = start + 0.75*(end-start)
# 10-30-50-70-90
elif interval_measurement_option = 5
.time [1] = start + 0.1*(end-start)
.time [2] = start + 0.3*(end-start)
.time [3] = midpoint
.time [4] = start + 0.7*(end-start)
.time [5] = start + 0.9*(end-start)
# 5-10-20-50-80-90-95
elif interval_measurement_option = 6
.time [1] = start + 0.05*(end-start)
.time [2] = start + 0.1*(end-start)
.time [3] = start + 0.2*(end-start)
.time [4] = midpoint
.time [5] = start + 0.8*(end-start)
.time [6] = start + 0.9*(end-start)
.time [7] = start + 0.95*(end-start)
endif
endproc
procedure getFormants
editor TextGrid 'filename$'
for i from 1 to pointsPerInterval.pts
Move cursor to... getMeasureTimes.time[i]
.f3 [i] = Get third formant
.f2 [i] = Get second formant
.f1 [i] = Get first formant
endfor
endeditor
endproc
procedure makeFormantTable
# NOTE: THE EXTRA SPACES ARE INTENTIONAL, TO GET EVERYTHING TO LINE UP PROPERLY IN COLUMNS IN THE PAUSE WINDOW
# MIDPOINT ONLY
if interval_measurement_option = 1
.header$ = "'tab$''tab$'midpoint"
.f3$ = "'tab$'F3'tab$' 'getFormants.f3[1]:0'"
.f2$ = "'tab$'F2'tab$' 'getFormants.f2[1]:0'"
.f1$ = "'tab$'F1'tab$' 'getFormants.f1[1]:0'"
# ONSET-MIDPOINT-OFFSET
elif interval_measurement_option = 2
.header$ = "'tab$''tab$'onset'tab$' mid'tab$'offset"
.f3$ = "'tab$'F3'tab$' 'getFormants.f3[1]:0''tab$' 'getFormants.f3[2]:0''tab$' 'getFormants.f3[3]:0'"
.f2$ = "'tab$'F2'tab$' 'getFormants.f2[1]:0''tab$' 'getFormants.f2[2]:0''tab$' 'getFormants.f2[3]:0'"
.f1$ = "'tab$'F1'tab$' 'getFormants.f1[1]:0''tab$' 'getFormants.f1[2]:0''tab$' 'getFormants.f1[3]:0'"
# 20-50-80
elif interval_measurement_option = 3
.header$ = "'tab$''tab$' 20%'tab$' 50%'tab$' 80%"
.f3$ = "'tab$'F3'tab$' 'getFormants.f3[1]:0''tab$' 'getFormants.f3[2]:0''tab$' 'getFormants.f3[3]:0'"
.f2$ = "'tab$'F2'tab$' 'getFormants.f2[1]:0''tab$' 'getFormants.f2[2]:0''tab$' 'getFormants.f2[3]:0'"
.f1$ = "'tab$'F1'tab$' 'getFormants.f1[1]:0''tab$' 'getFormants.f1[2]:0''tab$' 'getFormants.f1[3]:0'"
# 25-50-75
elif interval_measurement_option = 4
.header$ = "'tab$''tab$' 25%'tab$' 50%'tab$' 75%"
.f3$ = "'tab$'F3'tab$' 'getFormants.f3[1]:0''tab$' 'getFormants.f3[2]:0''tab$' 'getFormants.f3[3]:0'"
.f2$ = "'tab$'F2'tab$' 'getFormants.f2[1]:0''tab$' 'getFormants.f2[2]:0''tab$' 'getFormants.f2[3]:0'"
.f1$ = "'tab$'F1'tab$' 'getFormants.f1[1]:0''tab$' 'getFormants.f1[2]:0''tab$' 'getFormants.f1[3]:0'"
# 10-30-50-70-90
elif interval_measurement_option = 5
.header$ = "'tab$''tab$' 10%'tab$' 30%'tab$' 50%'tab$' 70%'tab$' 90%"
.f3$ = "'tab$'F3'tab$' 'getFormants.f3[1]:0''tab$' 'getFormants.f3[2]:0''tab$' 'getFormants.f3[3]:0''tab$' 'getFormants.f3[4]:0''tab$' 'getFormants.f3[5]:0'"
.f2$ = "'tab$'F2'tab$' 'getFormants.f2[1]:0''tab$' 'getFormants.f2[2]:0''tab$' 'getFormants.f2[3]:0''tab$' 'getFormants.f2[4]:0''tab$' 'getFormants.f2[5]:0'"
.f1$ = "'tab$'F1'tab$' 'getFormants.f1[1]:0''tab$' 'getFormants.f1[2]:0''tab$' 'getFormants.f1[3]:0''tab$' 'getFormants.f1[4]:0''tab$' 'getFormants.f1[5]:0'"
# 5-10-20-50-80-90-95
elif interval_measurement_option = 6
.header$ = "'tab$''tab$' 5%'tab$''tab$' 10%'tab$' 20%'tab$' 50%'tab$' 80%'tab$' 90%'tab$' 95%"
.f3$ = "'tab$'F3'tab$' 'getFormants.f3[1]:0''tab$' 'getFormants.f3[2]:0''tab$' 'getFormants.f3[3]:0''tab$' 'getFormants.f3[4]:0''tab$' 'getFormants.f3[5]:0''tab$' 'getFormants.f3[6]:0''tab$' 'getFormants.f3[7]:0'"
.f2$ = "'tab$'F2'tab$' 'getFormants.f2[1]:0''tab$' 'getFormants.f2[2]:0''tab$' 'getFormants.f2[3]:0''tab$' 'getFormants.f2[4]:0''tab$' 'getFormants.f2[5]:0''tab$' 'getFormants.f2[6]:0''tab$' 'getFormants.f2[7]:0'"
.f1$ = "'tab$'F1'tab$' 'getFormants.f1[1]:0''tab$' 'getFormants.f1[2]:0''tab$' 'getFormants.f1[3]:0''tab$' 'getFormants.f1[4]:0''tab$' 'getFormants.f1[5]:0''tab$' 'getFormants.f1[6]:0''tab$' 'getFormants.f1[7]:0'"
endif
endproc