Session 14
Example
The Script
The complete script has 117 lines, but don't be scared! Below you'll find a detailed explanation of every step. If you want, you can download the script, load it into an editor (preferably one that can display line numbers), and have it open beside your browser.
sound = selected ("Sound")
sound$ = selected$ ("Sound")
grid = selected ("TextGrid")
### input form
selectObject: grid
n_tiers = Get number of tiers
duration = 0
beginPause: "Isochronous Segments"
comment: "Select interval tier:"
optionMenu: "Tier number", 1
# present only interval tiers;
# remember if no interval tier was found
found_itier = 0
for i to n_tiers
is_itier = Is interval tier: i
if is_itier
found_itier = 1
option: string$ (i)
endif
endfor
# present dummy option if no interval tier was found
# (to enable canceling)
if found_itier = 0
option: "-"
comment: "No interval tier found. Aborting."
endif
comment: "Target segments are labeled with: (no spaces)"
word: "Label", "s"
comment: "Specify desired duration in seconds; if the desired duration"
comment: "corresponds to the mean duration of all target segments specify 0,"
comment: "otherwise specify a positive real number:"
real: "Duration (s)", duration
clicked = endPause: "Cancel", "OK", 2, 1
if clicked = 1 or found_itier = 0 or duration < 0
exitScript ()
endif
tier_number = number (tier_number$)
### create manipulation object
selectObject: sound
start_time = Get start time
end_time = Get end time
m_object = To Manipulation: 0.01, 75, 600
### calculate mean duration of targets or apply input duration
selectObject: grid
n_intervals = Get number of intervals: tier_number
t_intervals = Count intervals where: tier_number, "is equal to", label$
if duration = 0
sum_dur = Get total duration of intervals where: tier_number, "is equal to", label$
target_dur = sum_dur / t_intervals
else
target_dur = duration
endif
### create vectors to collect duration ratios (default=1) and boundaries
tmp# = zero# (n_intervals)
dur_ratios# = tmp# + 1
boundaries# = zero# (n_intervals + 1)
boundaries# [1] = start_time
### create duration tier and add default start and end point
dur_tier = Create DurationTier: "durtier", start_time, end_time
Add point: start_time, 1
Add point: end_time, 1
### loop through intervals and add points to duration tier
### at start and end of interval
for i to n_intervals
selectObject: grid
i_label$ = Get label of interval: tier_number, i
i_start = Get start time of interval: tier_number, i
i_end = Get end time of interval: tier_number, i
i_dur = i_end - i_start
if i_label$ = label$
# calculate and set duration ratio for target segments
dur_ratios# [i] = target_dur / i_dur
@setPoints: i, dur_ratios# [i]
else
# set duration ratio to 1 for non-targets
@setPoints: i, 1
endif
endfor
### apply duration tier and create resynthesis
selectObject: dur_tier, m_object
Replace duration tier
removeObject: dur_tier
selectObject: m_object
m_sound = Get resynthesis (overlap-add)
Rename: sound$ + "_modified"
### create and populate Textgrid for manipulated sound
m_grid = To TextGrid: "duration-ratio", ""
for i from 2 to size (boundaries#) - 1
Insert boundary: 1, boundaries# [i]
endfor
for i to n_intervals
Set interval text: 1, i, fixed$ (dur_ratios# [i], 3)
endfor
removeObject: m_object
selectObject: m_sound, m_grid
### write new boundary to vector and add duration points
### at the start and end of interval
procedure setPoints: .i, .ratio
if .i < n_intervals
boundaries# [.i+1] = boundaries# [.i] + i_dur * .ratio
endif
selectObject: dur_tier
# set start point slightly to the right of the left interval bounday
# and end point slightly to the left of the right interval boundary
# (we can't add two points at the exact same point of time)
Add point: i_start + 0.000001, .ratio
Add point: i_end - 0.000001, .ratio
endproc
Lines 1-3
sound = selected ("Sound")
sound$ = selected$ ("Sound")
grid = selected ("TextGrid")
The script requires a Sound object and a TextGrid object to be selected in Praat objects. If the requirement is not satisfied the script terminates with an error message. The IDs of both objects are assigned to numeric variables and the name of the Sound object is assigned to a string variable.
→ variable assignment
→ querying selections
Lines 6-8
selectObject: grid
n_tiers = Get number of tiers
duration = 0
The number of tiers in the selected TextGrid is assigned to a variable because we'll need it to build a menu in the input form (see below). The variable duration gets assigned a default value; any real number ≥ 0 will do.
→ object selection
→ assign query result to a variable
Lines 9-38
These lines create and evaluate the input form:
beginPause: "Isochronous Segments"
comment: "Select interval tier:"
optionMenu: "Tier number", 1
# present only interval tiers;
# remember if no interval tier was found
found_itier = 0
for i to n_tiers
is_itier = Is interval tier: i
if is_itier
found_itier = 1
option: string$ (i)
endif
endfor
# present dummy option if no interval tier was found
# (to enable canceling)
if found_itier = 0
option: "-"
comment: "No interval tier found. Aborting."
endif
comment: "Target segments are labeled with: (no spaces)"
word: "Label", "s"
comment: "Specify desired duration in seconds; if the desired duration"
comment: "corresponds to the mean duration of all target segments specify 0,"
comment: "otherwise specify a positive real number:"
real: "Duration (s)", duration
clicked = endPause: "Cancel", "OK", 2, 1
if clicked = 1 or found_itier = 0 or duration < 0
exitScript ()
endif
tier_number = number (tier_number$)
The form definition starts in line 9 with beginPause
and the form title. It is concluded in line 34 with endPause
and a button specification.
There is no magic to lines starting with comment:
(e.g. line 10), they just display some explanatory text.
Lines 11-27 are used to construct a menu from which the user can select a target tier. To limit choices to interval tiers (excluding point tiers), all tiers in the selected TextGrid are tested to see if they are interval tiers; if they are they become an option. First, we assume there are no interval tiers by assigning 0 (false) to the variable found_itier (line 14). Then we loop through all tiers (for-loop, lines 15-21; luckily we know the number of tiers, see line 7) and ask each tier if it is an interval tier (line 16). If it is (lines 17-20), we first assign 1 (true) to found_itier to remember that at least one interval tier is present (line 18). Second, we define a menu option, converting the tier number to a string (line 19).
If at least one interval tier is present, lines 24-27 are ignored (because found_itier would be 1, see line 18). If the selected TextGrid doesn't include an interval tier, found_itier is still 0 (see line 14) and the user is informed that the script will abort (line 26). A dummy option is inserted (line 25) because Praat wouldn't allow the user to close the form without choosing an option (and without an interval tier there are no options to choose from, obviously).
The rest of the form definition is straightforward: Line 29 defines a word field to specify the label of the target segments. Line 33 defines a real field to specify the target duration (a field of the type positive wouldn't allow 0).
Line 34 concludes the form definition and creates two buttons, labeled "Cancel" and "OK". The variable clicked contains the index of the pressed button. If the user pressed "Cancel", clicked takes the value 1, if the user pressed "OK", clicked takes the value 2.
Evaluation of the user input is done in lines 35-37. Execution of the script is aborted (line 36) if the user pressed "Cancel" (clicked = 1) or if no interval tier was found (found_itier = 0) or if the user specified a negative target duration (duration < 0).
If all is well, lines 35-37 are ignored and the script continues with line 38. The field title "Tier number" (line 11) becomes the variable tier_number and we are interested in the content of the chosen option (i.e. the tier number), not in the index of the chosen option (whether it's the first, the second or the nth option). Since the content of an option is always available in a string variable, namely tier_number$ (while the index is available in the numeric variable tier_number), we convert the string in tier_number$ to a number with the built-in function number ()
and replace the content of the numeric variable tier_number with the result.
Conclusion: Only 13 lines are responsible for the actual form definition: lines 9-11, 19, 25, 26, and 28-34. The remaining lines just add some logic to restrict user input to valid options.
→ flexible input forms
→ for-loops
→ conditionals
→ built-in functions
Lines 41-44
selectObject: sound
start_time = Get start time
end_time = Get end time
m_object = To Manipulation: 0.01, 75, 600
Duration manipulation in Praat is done with the help of a Manipulation object which is associated with a Sound object. Hence, we select the Sound object (de-selecting the TextGrid object, line 41) using it's ID (see line 1) and create an associated Manipulation object; the ID of the Manipulation object is assigned to m_object (line 44). But first, we inquire the start and end time of the original sound for later use (lines 42-43).
Lines 47-55
selectObject: grid
n_intervals = Get number of intervals: tier_number
t_intervals = Count intervals where: tier_number, "is equal to", label$
if duration = 0
sum_dur = Get total duration of intervals where: tier_number, "is equal to", label$
target_dur = sum_dur / t_intervals
else
target_dur = duration
endif
Next, we take care of the target duration. If the user specified a number other than 0 that's easy: Just assign the input value to the new variable target_dur (line 54; the field title "Duration (s)" corresponds to the variable duration).
If the user specified 0 (line 50) we need to calculate the mean duration of all target segments and assign the result to target_dur. To accomplish this, we select the TextGrid object (de-selecting the Sound object, line 47) and inquire the total number of intervals in the chosen tier for later use (line 48) as well as the number of target segments (line 49). Then we implement a convenient query command to sum up the duration of all target segments (line 51). Both query commands (lines 49 and 51) use variables from the input form: The chosen tier number tier_number (see lines 11ff and 38) and the label identifying target segments label$ (see line 29). The summed duration divided by the number of target segments gives the mean duration (line 52).
→ conversion of field titles to variables (simple forms)
→ conversion of field titles to variables (flexible forms)
→ formulas and arithmetic operators
Lines 58-61
tmp# = zero# (n_intervals)
dur_ratios# = tmp# + 1
boundaries# = zero# (n_intervals + 1)
boundaries# [1] = start_time
To create the new TextGrid with new boundaries and duration ratios (see lines 95-101), we'll use two vectors to collect this data while it is calculated (see lines 70-84). Duration ratios will be displayed per interval, so we need a vector with the same number of dimensions as total number of intervals. First we create a temporal vector with the correct number of zeros (line 58), then, since the default duration ratio for non-target segments is 1, we add 1 to each value (line 59), creating the final vector dur_ratios#.
Interval tiers always have one more boundary than intervals, hence we add one dimension when we create the boundaries vector (line 60). In line 61 we set the first boundary to the start time of the sound.
→ vectors
Lines 64-66
dur_tier = Create DurationTier: "durtier", start_time, end_time
Add point: start_time, 1
Add point: end_time, 1
Before we start calculating duration ratios and boundaries, we create an object that handles the ratios for manipulation (line 64): a DurationTier object that starts and ends at the same time points as the original sound. A DurationTier consists of points with a time point (x axis, first argument of Add point:
) and a duration ratio (y axis, second argument of Add point:
). To initialize the DurationTier with a default ratio of 1 at the beginning and at the end, we add two points (lines 65 and 66).
Lines 70-84 and 107-117
for i to n_intervals
selectObject: grid
i_label$ = Get label of interval: tier_number, i
i_start = Get start time of interval: tier_number, i
i_end = Get end time of interval: tier_number, i
i_dur = i_end - i_start
if i_label$ = label$
# calculate and set duration ratio for target segments
dur_ratios# [i] = target_dur / i_dur
@setPoints: i, dur_ratios# [i]
else
# set duration ratio to 1 for non-targets
@setPoints: i, 1
endif
endfor
This is the core of the script. We loop through all intervals of the target tier and query the label, start time, and end time of each interval (lines 72-74). After calculation of the interval duration (line 75), a distinction is made between two cases: (1) The label of the interval that is currently considered by the loop (i_label$) matches the specified label (label$, see line 29). Or (2) the current label doesn't match.
In both cases, the script needs to execute the same sequence of commands, only with slightly different parameters: calculate new boundaries and adding points to the DurationTier. This is a typical use case for procedures. So in both cases, the same procedure setPoints is called (lines 79 and 82), but with different arguments. In the first case, a duration ratio is calculated and inserted into the duration ratio vector (line 78), then the procedure is called with this ratio as second argument. In the second case, we use the default ratio 1 as second argument (i.e. no duration manipulation). In both cases, the first argument is the index of the current interval.
The procedure setPoints:
procedure setPoints: .i, .ratio
if .i < n_intervals
boundaries# [.i+1] = boundaries# [.i] + i_dur * .ratio
endif
selectObject: dur_tier
# set start point slightly to the right of the left interval bounday
# and end point slightly to the left of the right interval boundary
# (we can't add two points at the exact same point of time)
Add point: i_start + 0.000001, .ratio
Add point: i_end - 0.000001, .ratio
endproc
setPoints expects two arguments: The index of the interval (.i) and the duration ratio (.ratio). Lines 108-110 insert a boundary into the boundary vector, namely the end point of the current interval (which corresponds to the starting point of the next interval). Since the very first interval of an interval tier always starts with the first boundary, the end point of the first interval is the second boundary. The second interval starts with the second boundary end ends with the third and so on. Therefore, we add 1 to the interval index: boundaries# [.i+1]. The time point of the end point of an interval is calculated as
It is important not to use the original starting point (i_start) but the one collected in the boundary vector (boundaries# [.i]) since previous boundaries could have been moved due to shortening or lengthening of intervals.
To make this clear: For un-modified intervals, the duration added to the starting point is equal to the original interval duration because
But if the duration ratio is ≠ 1, the added duration deviates from the original duration.
Next, the DurationTier is selected (line 111) and two points are added—near the original starting point of the current interval (115) and near the end point (116), both with the same duration ratio. We can't add them exactly at starting and end points because we also need to add points to the adjacent intervals and Praat doesn't allow more then one point at the same time point. Example of a DurationTier:
Points that seem to be on top of each other at interval boundaries are in fact 0.002 milliseconds apart, as you can validate if you zoom in:
When the loop is finished, we have a proper DurationTier for manipulation and resynthesis and we have vectors containing all the information necessary to build a new TextGrid.
Lines 87-92
selectObject: dur_tier, m_object
Replace duration tier
removeObject: dur_tier
selectObject: m_object
m_sound = Get resynthesis (overlap-add)
Rename: sound$ + "_modified"
There is nothing special happening here. In line 87 the DurationTier and the Manipulation object is selected to replace the duration tier of the Manipulation object with our own DurationTier (88). Then we remove our DurationTier (89), resynthesize the manipulated sound (90-91), and rename it by adding the string _modified to the original name (92).
Lines 95-103
m_grid = To TextGrid: "duration-ratio", ""
for i from 2 to size (boundaries#) - 1
Insert boundary: 1, boundaries# [i]
endfor
for i to n_intervals
Set interval text: 1, i, fixed$ (dur_ratios# [i], 3)
endfor
removeObject: m_object
selectObject: m_sound, m_grid
The last step: Assemble a TextGrid for the manipulated Sound with fitting boundaries and informative labels. We start by creating a new TextGrid object with one interval tier (line 95). As soon as an (apparently empty) interval tier is created it automatically contains one interval with a starting point at the start time of the associated sound and an end point corresponding to the end time of the associated sound.
Next, we insert the boundaries that we collected earlier (for-loop, lines 96-98). Since we collected end points of intervals and since the starting point of the first interval is already set at the start time, the for-loop starts with the second boundary: for i from 2
. And since the end point is also already set, the for-loop stops at the penultimate boundary: to size (boundaries#) - 1
.
The second for-loop (lines 99-101) is used to insert labels into the intervals that we created above by inserting boundaries (of course it would be possible to combine both loops, which would be more efficient, but also more complex...). We visit each interval and insert the respective duration ratio from our vector, rounded to 3 decimal places, as a string.
Because it is good practice to remove unnecessary temporary objects generated by a script at the end of the script, we dump the Manipulation object in line 102 (the DurationTier was already dumped in line 89). With the last command in line 103 the two new objects are selected: the manipulated Sound and the Textgrid.
Next: Epilogue