for-loops count the number of repetitions and stop when the specified maximum number is reached. To keep track of repetitions, for-loops make use of a counter, which is an ordinary numeric variable. The counter is initialized in the loop head with the default value 1. If you don't want to start counting at 1 you may initialize the counter with another number. You are free to name the counter variable within the familiar limits of accepted variable names (but it's very common to call loop counters simply i, j, k etc.). The maximum number of repetitions (the stop condition) can be specified with a literal number, a numeric variable, or a formula.
If you agree with the default initialization value, i.e. if you want to start counting at 1, a generic for-loop looks like this:
for counter to maxNumber code block endfor
for, to, and endfor are reserved words and provide the required syntactic frame for the loop; counter is the name of the counter variable, and maxNumber represents the maximum number of repetitions (a number, a variable, or a formula). The counter is incremented by 1 at the end of each pass. As long as maxNumber is greater than the counter, the code block is executed line by line at each pass. If maxNumber is smaller than the counter, the loop terminates and the script continues with the next statement after
Let's look at some examples. Consider you have a TextGrid object with some annotation in tier 1. For each interval in tier 1 you want to write the label to Praat Info. Extracting a label from an interval and writing it to Praat Info is done like this:
label$ = Get label of interval: 1, 1 appendInfoLine: label$
The first statement extracts the label of the first interval (2nd argument) of tier 1 (1st argument) and assigns it to label$. The second statement deals with the output. To do this not only for the first interval but for all intervals, we implement a for-loop. The number of repetitions is the same as the number of intervals, so let's find out the number of intervals of tier 1:
Query > Query interval tier > Get number of intervals.... If the number of intervals is e.g. 6, we can specify the following loop:
clearinfo for i to 6 label$ = Get label of interval: 1, i appendInfoLine: label$ endfor
Using counter variables
The extraction and output statements are repeated 6 times. We use the counter variable i to specify the target interval in the extraction statement. At the first pass, i equals to 1, so we'll extract the label of interval 1. At the next pass, i is incremented by 1, so i = 2 and we'll extract the label of interval 2. After the sixth pass i equals to 7 and because 7 > 6, the loop terminates. (
clearinfo clears Praat Info of any previous content.)
Instead of querying the number of intervals by hand, it is safer and more convenient to delegate this task to the script. We can then use the variable containing the query result as maximum number of repetitions:
# query number of intervals of tier 1 numberOfIntervals = Get number of intervals: 1 clearinfo for i to numberOfIntervals label$ = Get label of interval: 1, i appendInfoLine: label$ endfor
In addition to literal numbers and variables, we may also use formulas to specify the maximal number of repetitions. For instance, if you want to print out the labels of all but the last interval, you can do this:
numberOfIntervals = Get number of intervals: 1 clearinfo for i to numberOfIntervals - 1 label$ = Get label of interval: 1, i appendInfoLine: label$ endfor
By subtracting 1 from numberOfIntervals the loops terminates at the penultimate interval. Of course, much more complex formulas are allowed, even formulas including built-in functions. If you're only interested in the first half of the TextGrid, try this:
numberOfIntervals = Get number of intervals: 1 clearinfo for i to round (numberOfIntervals / 2) label$ = Get label of interval: 1, i appendInfoLine: label$ endfor
The loop terminates after processing the first half of the intervals. If numberOfIntervals is an odd number, e.g. 7,
round () makes sure that 4 intervals are processed (
round (3.5) = 4); without rounding only 3 intervals would be processed (because 4 > 3.5).
Arbitrary start values
And if you're interested in the second half of the TextGrid? In that case you don't want to start counting at 1 but at the interval after numberOfIntervals / 2. To do that, we must tell Praat to abandon the default initialization value and specify our own:
numberOfIntervals = Get number of intervals: 1 clearinfo for i from numberOfIntervals / 2 + 1 to numberOfIntervals label$ = Get label of interval: 1, i appendInfoLine: label$ endfor
from is another reserved word which may optionally be inserted, expanding the syntactic frame of the for-loop. Between from and to the initialization value can be specified like the stop value: with a literal number, a variable, or a formula. Above, I used the formula numberOfIntervals / 2 + 1 to specify the interval after numberOfIntervals / 2 as starting point.
So far, we've extracted labels from tier 1. If you have more than one tier and want to list all labels in all tiers you need two nested loops. The outer loop handles the tiers one by one, while the inner loop handles the intervals of each tier. Let's start with the outer loop by determining the necessary number of repetitions. In the code block of the outer loop we want to extract all labels of a given tier. So, obviously, the number of repetitions for this task depends on the number of tiers in the selected TextGrid. If the TextGrid includes 3 tiers we need to extract labels 3 times in succession; if the TextGrid includes 10 tiers we need our code block to run 10 times and so on. A query for the number of tiers gives us the number of repetitions:
clearinfo # determine number of tiers numberOfTiers = Get number of tiers # loop through all tiers one by one for i to numberOfTiers ... endfor
With the outer loop in place and handling all available tiers, we can implement the inner loop whose job it is to extract labels from the current tier. This is pretty similar to the examples above, only two adaptions are necessary: (1) Since the outer loop already uses i as name for the counter variable, the inner loop must use another name, e.g. j. (2) Where we used a literal value before to specify the target tier, we now need a variable, namely i, to refer to the current tier.
clearinfo numberOfTiers = Get number of tiers for i to numberOfTiers # determine number of intervals of current tier numberOfIntervals = Get number of intervals: i # loop through all intervals of current tier and extract labels one by one for j to numberOfIntervals label$ = Get label of interval: i, j appendInfoLine: label$ endfor endfor
Summary: First, the number of available tiers is determined. Then, each tier is considered successively, querying the number of intervals of the current tier, followed by the label extraction loop.
Counter variables of for-loops are ordinary variables that may be used like other variables in the loop's code block. Usually, you use counter variables read-only, i.e. you leave it to the built-in mechanism to increment the value and just use this value in a formula or as a reference to tiers or intervals or whatever. But Praat allows you to manipulate counter variables as well. This is dangerous because if you don't pay enough attention there's a good chance to end up with an infinite loop. A simple example that will force you to kill Praat if you run it:
for i to 10 i = i - 1 endfor
The built-in mechanism increments i and the loop would terminate after 10 repetitions, except that by subtracting 1 from i during each pass we thwart the mechanism substantially and the loop will run forever. So, be careful when altering counter variable's values within the loop.
A useful application for this capability is the variation of the increment. If you need another increment than the Praat standard increment of 1, you can realize this by altering the counter variable. For instance, if you don't need all labels of a tier but only every other label (first, third, fifth…) you can realize an increment of 2 like this:
numberOfIntervals = Get number of intervals: 1 clearinfo for i to numberOfIntervals label$ = Get label of interval: 1, i appendInfoLine: label$ i = i + 1 endfor
The variable i starts with the value 1, becomes 2 in the code block, and is then incremented by 1 resulting in the value 3 before the next pass. This goes on until incrementing i by 1 exceeds numberOfIntervals.
To conclude the section on for-loops, let's go back to batch processing of files. In the session on File lists we discussed the following algorithm to successively process a bunch of files:
create file list n = determine number of files to process start loop with i = 1 extract filename[i] from file list load file[i] as object statement 3 statement 4 statement 5 ... remove object[i] increment i by 1 end loop if i = n remove file list
Now that we know about for-loops, we are able to implement the algorithm. Let's assume the following set-up: There's a directory called data where the script will be saved. Within data there's a subdirectory called sounds containing the sound files and the sound files are WAV files with the suffix .wav. With this set-up we can generate a list with all sound files and determine the number of files (i.e. the number of strings in the newly created Strings object):
list = Create Strings as file list: "Filelist", "sounds/*.wav" n = Get number of strings
With a for-loop running from 1 to the number of files we're now able to process all files one by one (and remove the file list when we're finished):
list = Create Strings as file list: "Filelist", "sounds/*.wav" n = Get number of strings for i to n # do something with each file endfor removeObject: list
In the loop we set off by making sure that the file list is selected. This is redundant at the first pass, because the file list is already selected after creation, but at the second pass it's mandatory. Then we extract the current filename, starting at the top of the list, and load the corresponding file:
list = Create Strings as file list: "Filelist", "sounds/*.wav" n = Get number of strings for i to n # select FileList selectObject: list # extract name of file (one by one) filename$ = Get string: i # load file fileID = Read from file: "sounds/" + filename$ endfor removeObject: list
With the current file loaded, processing can take place, then the file is removed before the next pass handles the next file. The only 'processing' which is done in this example is to play the current sound file, but you're free to add any other statements.
list = Create Strings as file list: "Filelist", "sounds/*.wav" n = Get number of strings for i to n selectObject: list filename$ = Get string: i fileID = Read from file: "sounds/" + filename$ # process sound object Play # remove sound object removeObject: fileID endfor removeObject: list