This handout is a supplement to the third workshop in the 2019 Praat Scripting Workshop Series, offered at the University of Georgia DigiLab. It contains some extra information that just didn’t fit in the time we had available. It is currently under construction and will likely change many times in the future. For more information about the series and for links to the other handouts, visit joeystanley.com/praat.
So far, we’ve only been able to extract formants from one file. That’s certainly a lot of work being done automatically, but we can take it a step further and run this script on multiple files all at once. To do this we’ll basically put this whole thing into another for
loop that will cycle through all the sound files in a directory. Adding loops within loops adds quite a bit of complexity to your script, so you’re more error prone and the script gets both longer and harder to read. But the benefit is that you can work with any number of files as easily as you can with just one.
The first step is to use code to locate all the files you want to extract formants from. For now, the simplest way to do this requires that the files be pretty systematic in their names. They should be like how I’ve named in them in the sample file: .wav and .TextGrid files should have the same name (e.g. “19-Kathleen”) and all the files you want to work with should be in the same folder on your computer. There are ways to have a different system than this (like having all files each in their own folder, with .wav and .TextGrid files with different names), but that’s project-dependent and out of the scope of this workshop. I’ll leave it up to you to figure out how to do that.
Anyway, what we’ll do is create a new variable, I like to call mine dir$
that contains the path to the directory where all these files live. Here’s what that might look like on my computer:
dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/"
Now comes the key function. We’re going to let Praat look through and save the filename to each file in that directory. We can do that with Create Strings as file list:
. That takes two arguments. First, is the name that we want to call that list. I’ll call it speakers
. The other argument is the directory where these files live, which in our case is dir$
.
dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$
If you run just those two lines, you should see a new Strings object called “speakers”:
And if you click “View & Edit”, you should see a list of all the files in that folder.
The way this works is that we can set up a for
loop that will cycle through each element of that list and perform some action.
The problem is that this list contains both .wav and .TextGrid files. For simplicity, it’s easier that if that list contains only the .wav files because otherwise every other iteration of the loop would end up doing a different thing (one Sound iteration and then one TextGrid iteration and back and forth). It would make more sense to just work on the Sound files and then open their accompanying TextGrids with them rather than treat them separately.
Fortunately, we can filter out the list as we read it in by adding + "*.wav"
to the dir$
string. What this does is it only reads in the files in dir$
that end in .wav
. The *
is a wildcard, meaning that anything can come before the .wav
. So by adding this, it will only read in the .wav files.
dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav"
The last thing we need to do, now that we’ve read in these files is count how long the list is. This will tell us how many times we need to run a loop. Similar to other commands, we’ll use Get number of strings
to accomplish this, and save that value into numberOfSpeakers
.
The very top of your script should now look like this
writeInfoLine: "Extracting formants...", newline$ dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav" numberOfSpeakers = Get number of strings ... appendInfoLine: newline$, "Whoo-hoo! It didn't crash!"
Try what happens with this line of code instead:
Create Strings as file list: "speakers", dir$ + "W*.wav"
Which files are read in, which are not, and why?
When you added the “W” just before the star, it made it so that only the files that start with “W” and end with “.wav” were read in. In this case, it only read in the last seven files since they all start with “WS”. This is handy if your filenames contain some metadata like speaker sex or ethnicity. In this case, the WS stands for “Western States” and comes from a different project than the other files.
So at this point, our script informs Praat of the existance of some files. But right now it doesn’t even do anything with them. What we’ll need to do is read them in. Up until this point, your script has assumed your files have been open and selected. We’re now making it so that you can start your script with an empty slate.
To accomplish this, we’ll need to wrap basically everything we’ve written so far into a for
loop. Now, for simplicity, I’m going to ignore everything we did with formant extraction and just focus on this outer loop because until we’re done, it’ll crash and it’ll be frustrating. So for now, let’s start with an empty for
loop that cycles through each element of our “speakers” list.
writeInfoLine: "Extracting formants...", newline$ dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav" numberOfSpeakers = Get number of strings # Loop through each speaker for thisSpeaker from 1 to numberOfSpeakers # The rest of the script will eventually go here # End looping through speakers endfor appendInfoLine: newline$, "Whoo-hoo! It didn't crash!"
Okay, so we’ve set up a loop that will happen exactly 13 times, once for each sound file. The first thing we should do is read in that sound file. Unfortunately, it’s not quite as easy as you might think right now. It’ll take a couple lines of code still. Why? Well, let’s look at what the thisSpeaker
variable actually contains.
writeInfoLine: "Extracting formants...", newline$ dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav" numberOfSpeakers = Get number of strings # Loop through each speaker for thisSpeaker from 1 to numberOfSpeakers appendInfoLine: thisSpeaker # End looping through speakers endfor appendInfoLine: newline$, "Whoo-hoo! It didn't crash!"
If you run this (output omitted), it’ll show a list from 1 to 13. We saw this exact same behavior when we were looping through intervals in a TextGrid. So, we’ll need to take that number and use it to access its corresponding element in the list speakers
. We’ll use the Get string:
function for that:
writeInfoLine: "Extracting formants...", newline$ dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav" numberOfSpeakers = Get number of strings # Loop through each speaker for thisSpeaker from 1 to numberOfSpeakers speakerName$ = Get string: thisSpeaker appendInfoLine: speakerName$ # End looping through speakers endfor appendInfoLine: newline$, "Whoo-hoo! It didn't crash!"
When you run this, you should now see a list 13 elements long that has the filename of each of the sound files in your directory. This is the important information we need to read those files in.
Now that we have this information, let’s finally go ahead and read them in with Read from file:
and appending speakerName$
to dir$
to get the full path to that sound file. Once you read something in in Praat, it’ll be selected, so let’s go ahead and save the name of this now-Praat object as thisSound$
.
writeInfoLine: "Extracting formants...", newline$ dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav" numberOfSpeakers = Get number of strings # Loop through each speaker for thisSpeaker from 1 to numberOfSpeakers speakerName$ = Get string: thisSpeaker appendInfoLine: speakerName$ # Read in the Sound file Read from file: dir$ + speakerName$ thisSound$ = selected$("Sound") # End looping through speakers endfor
However, try running this. Did you get an error?
What does that mean? Well it says the error is on line 10. It’s not immediately obvious which line this is, but if you hit control/command + L, you can take yourself to line 10. Why did it have a problem with getting the filename? In fact, you can see that it read in the first file just fine (in my data, 02-Carol). So why did it fail the second time through?
As it turns out, the hint is in the error message. The Get string
function is not allowed on the current selection. What is currently selected? Is the Sound file because we read it in. But Get string
only works on a list of strings. So, to remedy the situation, we’ll need to re-select the strings object. We’ll add this in as the first thing we do with each iteration of the loop. That way, regardless of what object we end the loop on, we can start fresh again.
writeInfoLine: "Extracting formants...", newline$ dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav" numberOfSpeakers = Get number of strings # Loop through each speaker for thisSpeaker from 1 to numberOfSpeakers select Strings speakers speakerName$ = Get string: thisSpeaker appendInfoLine: speakerName$ # Read in the Sound file Read from file: dir$ + speakerName$ thisSound$ = selected$("Sound") # End looping through speakers endfor
This should resolve the error and you should now have all the sound objects read into Praat.
We can now do a similar process with the TextGrids. However, the speakerName$
variable contains the filenames for the .wav files. We want to read in the .TextGrid files. Since the filenames match, we can just a little bit of string manipulation.
Let’s take a string, say “Praat scripting is hard.” Let’s make a really simple script that prints that out:
message$ = "Praat scripting is hard" writeInfoLine: message$
We’ve already seen how you can add strings together. It’s simply with the plus sign +
. We can add an adverb like right now to the end of our string. Notice that I had to put in an extra space in there.
message$ = "Praat scripting is hard" writeInfoLine: message$ appendInfoLine: message$ + " right now"
Analogously, you can actually subtract material from the end of the Praat script using the minus sign -
:
message$ = "Praat scripting is hard" writeInfoLine: message$ appendInfoLine: message$ + " right now" appendInfoLine: message$ - " is hard"
We can even do multiple additions and substractions all at once:
message$ = "Praat scripting is hard" writeInfoLine: message$ appendInfoLine: message$ + " right now" appendInfoLine: message$ - " is hard" appendInfoLine: message$ - "hard" + "easy!"
Using the same logic, we can strip away the “.wav” from the end of the filename in speakerName$
and replace it with .TextGrid
:
writeInfoLine: "Extracting formants...", newline$ dir$ = "/Users/joeystanley/Desktop/Praat Workshop/sample_recordings/" Create Strings as file list: "speakers", dir$ + "*.wav" numberOfSpeakers = Get number of strings # Loop through each speaker for thisSpeaker from 1 to numberOfSpeakers select Strings speakers speakerName$ = Get string: thisSpeaker appendInfoLine: speakerName$ # Read in the Sound file Read from file: dir$ + speakerName$ thisSound$ = selected$("Sound") # Read in the TextGrid file Read from file: dir$ + speakerName$ - ".wav" + ".TextGrid" thisTextGrid$ = selected$("TextGrid") # End looping through speakers endfor
We’ve now gotten Praat to read in 13 Sound files and their corresponding TextGrids!