phoneticspraat

Look for multiple words in praat script


I am writing a praat script that will search through multiple files for a list of words. This is what I have so far. It only zooms in to the first word in the procedure, and doesn't loop through the rest. I think this has something to do with what is selected. For the For i through n, only the text grid is selected, but then in the annotator, both are selected. I need the script to continue searching through each interval so that the other words in the procedure can be found as well.

directory$ = "directory"
listfile$ = "test.txt"

Read Strings from raw text file... 'directory$'/'listfile$'
last = Get number of strings

# loop through each file
for a from 1 to last
    listfile2$ = listfile$ - ".txt"
    select Strings 'listfile2$'
    textgrid$ = Get string... 'a'
    Read from file... 'directory$'/'textgrid$'
    object_name$ = selected$("TextGrid")

    Read from file... 'directory$'/'object_name$'.wav

    # rearrange tiers 
    select TextGrid 'object_name$'
    Duplicate tier: 3, 1, "MAU"
    Remove tier: 4
    Insert interval tier: 1, "subphone"

    # find target word
    n = Get number of intervals: 3  
    for i to n

@instance: "strikes"
@instance: "raindrops"
@instance: "and"
@instance: "rainbow"
@instance: "into"
@instance: "round"
@instance: "its"
@instance: "its"

procedure instance: .target_word$

    label$ = Get label of interval: 3, i
        if label$ == .target_word$
        index = i
        i += n

# get the start and end point of the word
startpoint = Get starting point... 3 index
endpoint = Get end point... 3 index

        select TextGrid 'object_name$'
        plus Sound 'object_name$'
        View & Edit
        editor TextGrid 'object_name$'

# annotation
Select... startpoint endpoint
Zoom to selection
pause Annotate stops then continue
Close
endeditor

        endif # if the label = target word
    endfor # for number of intervals



select TextGrid 'object_name$'
Write to text file: directory$ + "/" + object_name$ + "_editedtext.TextGrid"

select all
minus Strings 'listfile2$'
Remove

endproc

#writeInfoLine: "done!"
#select Strings 'listfile2$'
endfor # for each of the files
clearinfo
print That's it!

Edit: Here's the revised script, based on the answer.

directory$ = "/Users/directorypath"
listfile$ = "test.txt"

Read Strings from raw text file... 'directory$'/'listfile$'
last = Get number of strings
listfile2$ = listfile$ - ".txt"

# loop through each file
for a from 1 to last
    select Strings 'listfile2$'
    textgrid$ = Get string... 'a'
    Read from file... 'directory$'/'textgrid$'
    object_name$ = selected$("TextGrid")
    Read from file... 'directory$'/'object_name$'.wav

    # rearrange tiers
    select TextGrid 'object_name$'
    Duplicate tier: 3, 1, "MAU"
    Remove tier: 4
    Insert interval tier: 1, "subphone"

    n = Get number of intervals: 3

    for i to n
        @instance: "strikes"
        @instance: "raindrops"
        @instance: "and"
        @instance: "rainbow"
        @instance: "into"
        @instance: "round"
        @instance: "its"
        @instance: "its"

    endfor
endfor

procedure instance: .target_word$

label$ = Get label of interval: 3, i
if label$ == .target_word$
    index = i
    i += n

    # get the start and end point of the word
    startpoint = Get starting point... 3 index
    endpoint = Get end point... 3 index

    select TextGrid 'object_name$'
    plus Sound 'object_name$'
    View & Edit
    editor TextGrid 'object_name$'

    # annotation
    Select... startpoint endpoint
    Zoom to selection
    pause Annotate stops then continue
    Close
    endeditor

    endif

endproc

Solution

  • The script you wrote was not very carefully indented, so I tried formatting it so that it would be easier to understand what was going on. And in a sense, it did. But what surfaced took some effort to understand nevertheless.

    Here's a step-by-step follow-through on what's going on, as Praat sees it:

    1. In line 8 you start a for loop: for a from 1 to last

    2. Inside that loop, in line 25, you start a second one: for i to n

    3. Inside that second loop, in line 27, you call a procedure named instance.

      At this point, Praat skips to the last line that defines that procedure (so if you define it a number of times, you only get the last one). Since there is only one, Praat jumps to line 36: procedure instance: .target_word$

    4. Inside that procedure (which, by the way, is defined inside a for loop, which is ... unusual) you have an if block: if label$ == .target_word$

    5. At the end of that block, the endfor increments the control variable (in this case, i) and closes the for loop. But which one?

      You might expect it to close the last for loop that we entered (that's what I did). But actually, Praat seems to keep track of opening for and closing endfor statements, and maps them vertically.

      I haven't looked at the interpreter in enough detail to figure out exactly what happens, but in a case like this, the results are the same as mapping the lowest endfor (= the one closest to the bottom of your script) to the highest for, and so on.

      (This is likely not what really happens (otherwise multiple non-overlapping loops wouldn't work), but this is not really important: what is important is that an endfor only closes a single for, regardless of where in the script it is or when Praat sees it. As an aside, this is not what happens with endproc.)

      Regardless of the precise rules this endfor gets mapped to the second for, which we entered in point 2 (that was line 25). So we go back to the first line of that loop (line 26).

    6. Now we get to line 27 for the second time (this time for the second interval) and we again call @instance: "strikes". We still haven't got to @instance: "raindrops"!

    7. This repeats for all the intervals, each time incrementing i by one (whenever we hit the endfor), until i becomes n. This time, when we call @instance, we go through the if block and we once again get to the endfor from point 5.

      Praat obediently increments the control variable (so now i = n + 1), and checks the end condition set at the start of the for loop. In this case, Praat knows the for loop ends when i == n, and since i = n + 1, instead of jumping back to the top, it keeps going.

      Only now, after having gone through all the intervals of the first file, do we actually get to the end of the procedure!

    8. The procedure finally ends. Praat remembers that we entered this procedure way back in point 3, and back then we were reading line 27. So, dutifully, it goes and reads line 28, which is another call to the same procedure: @instance: "raindrops".

    9. And here's where (finally!) it dies.

      It dies because the control variable i is now n + 1 (it became that in point 7). This would not normally happen, since you would not normally hit an endfor statement for a loop that has already finished. But in this case we do. So when Praat tries to read the label of interval i in a TextGrid with i-1 intervals... it complains saying that the interval number is too large. Because it is.

      Your initial problem (it doing only part of the work, and not actually dying) I cannot reproduce because in the if block you actually manually change the value of i (which is risky), and that if block only gets executed if the label matches early enough in your TextGrid (early enough so that it happens before the script explodes).

    You can see this whole mess in action with this simplified version of the structure of your script:

    last = 10
    for a from 1 to last
      appendInfoLine: "First line of main loop; a = ", a
    
      n = 5 
      for i to n
        appendInfoLine: "First line of second loop; i = ", i
    
        @instance: "strikes"
        @instance: "raindrops"
        @instance: "and"
    
        procedure instance: .target_word$
          appendInfoLine: "Called @instance: " + .target_word$
          appendInfoLine: "a=", a, " i=", i
    
      endfor # for number of intervals
    
          appendInfoLine: "End of @instance"
        endproc
    
      appendInfoLine: "Script claims we are done!"
    endfor # for each of the files
    

    To fix this, you should probably re-structure your code so that it follows more or less this pattern:

    for a to last
      for i to n
        @instance: "word"
      endfor
    endfor
    
    procedure instance: .word$
      # do things
    endproc
    

    This has been highly educational. :)