Session 6
Object Selection
Querying Selections
Working in the GUI, when you do something in Praat Objects, most of the time you proceed according to this pattern:
- Select one or more object(s)
- Click a button or a menu item to do something with the selected object(s)
Most of the scripts you'll write yourself or download from the internet are going to behave like those buttons/menu items: They expect that one ore more object is selected when they are launched, then they do something with the selected object(s). If it is necessary for the script to know the attributes of the selected objects (this is necessary more often than not), it must enquire the attributes—preferably at the beginning of the script, because initially selected objects may possibly become deselected during runtime.
Fortunately, Praat provides a function dedicated to the enquiry of object's attributes: selected ()
, which returns the ID of a selected object (remember: the ID is a number). selected ()
has a sibling that returns type and name (both are strings) of a selected object—see below.
selected ()
is pretty flexible concerning the number of arguments: It occurs without argument, with one string argument specifying an object type (e.g. "Sound", "TextGrid" etc.), with one numeric argument specifying the index of the target object, or with two comma-separated arguments, string and numeric, specifying type and index.
Specifying an index (don't confuse the index with an ID!) is only of interest if the script expects more than one object to be selected. Imagine the set of selected objects as a numbered list with the first (topmost) object designated number 1. So if you want to query the third selected object, you would write selected (3)
. To make things even more complicated, an index may be negative, thus counting from the bottom of the list. To query the last selected object you would write selected (-1)
, to query the second last selected (-2)
, accordingly. It follows a hopefully exhaustive list of selection scenarios using the selected ()
function. Note that arguments of built-in functions are enclosed in round brackets.
- If invoked without arguments and
- only one object is selected, the ID of the object is returned
- two or more objects are selected, the ID of the topmost selected object is returned (i.e. the object with the smallest ID)
- example:
selected ()
- If invoked with a string argument (always specifying an object type) and
- only one object is selected and the object is of the specified type, the ID of the object is returned
- only one object is selected and the object is not of the specified type, the script terminates with an error message
- multiple objects are selected, the ID of the topmost selected object of the specified type is returned (if no object of the specified type is part of the selection, the script terminates with an error message)
- example:
selected ("TextGrid")
(returns ID of topmost selected TextGrid object)
- If invoked with a numeric argument (an index) and
- the index is positive, the ID of the object identified by the index, counting from the top of the list, is returned
- the index is negative, the ID of the object identified by the index, counting from the bottom of the list, is returned
- the the absolute value of the index is greater than the number of selected objects, the script terminates with an error message
- example:
selected (2)
(returns ID of second selected object)
- If invoked with a string and a numeric argument
- the ID of the object identified by type and index is returned (if no object fits the requirement, the script terminates with an error message)
- example:
selected ("TextGrid", 2)
(returns ID of second selected TextGrid object)
Confusing? Yes, I agree. I assure you, though, that in reality it's pretty simple. Let's have a look at some examples. Suppose you want your students to use a standardized TextGrid for annotation exercise. The TextGrid shall consist of 3 interval tiers, consistently called words, syllables, and segments. The students are instructed to load a sound file, launch your script and start annotating. That means, when the script is launched one Sound object is selected (the loaded sound file). The first obvious task of the script is to create the TextGrid:
To TextGrid: "words syllables segments", ""
The next task is to start the TextGrid editor. To do this, the Sound and the TextGrid object must both be selected. After the first statement only the newly created TextGrid is selected, so we need a plusObject
statement:
To TextGrid: "words syllables segments", ""
plusObject: ???
Edit
But how do we address the initially selected Sound object with the plusObject
statement? Clearly, we need its ID and the only option to grab it, is at the beginning of the script while it's still selected:
sound = selected ()
To TextGrid: "words syllables segments", ""
plusObject: sound
Edit
When the script is launched only one object is selected, therefore selected ()
without arguments will suffice to grab its ID.
Practical example
Next, suppose you want a macro that concatenates two sounds with 1 second of silence between the sounds. You want to select two sounds, run the script, and listen to the result. The
# grab ID of second selected sound
second_sound = selected ("Sound", 2)
# create silence
sil = Create Sound from formula: "silence", 1, 0, 1, 44100, "0"
Next, the second sound is selected and copied in order to 'move' it to the required position (the name of the copy is irrelevant but some name must be given, e.g. tmp):
second_sound = selected ("Sound", 2)
sil = Create Sound from formula: "silence", 1, 0, 1, 44100, "0"
# copy second sound
selectObject: second_sound
Copy: "tmp"
Now that everything is in the required order (first sound before silence, second sound after silence—check Praat Objects to confirm) we are ready to concatenate. So, let's select the three objects. The (copy of the) second sound is already selected so we add the silence and the first sound to the selection (the order of selection is irrelevant, only the order of the objects in the list counts):
second_sound = selected ("Sound", 2)
sil = Create Sound from formula: "silence", 1, 0, 1, 44100, "0"
selectObject: second_sound
Copy: "tmp"
# add objects to the selection for concatenation
plusObject: sil
plusObject: ???
Ups, we forgot to grab the first sound's ID—no way to select it without ID. After correcting that mistake, we are ready to concatenate and listen to the result (which is already selected because Concatenate
creates a new object):
# grab IDs of both selected sounds
first_sound = selected ("Sound", 1)
second_sound = selected ("Sound", 2)
sil = Create Sound from formula: "silence", 1, 0, 1, 44100, "0"
selectObject: second_sound
Copy: "tmp"
# add objects to the selection for concatenation
plusObject: sil
plusObject: first_sound
# concatenate and play result
Concatenate
Play
This works as long as both target sounds are sampled with 44100 Hz (Concatenate
accepts only sounds with consistent sampling frequency). To enhance the script and make it more flexible the sampling frequency of the silence should be adapted to that of the sounds. Since we can't query both sounds at once we query just one of them by reducing the number of selected objects to one (after grabbing their IDs, of course). Then the literal value 44100 is replaced by the enquired sampling frequency:
first_sound = selected ("Sound", 1)
second_sound = selected ("Sound", 2)
# reduce selection to one sound object
selectObject: second_sound
# query sampling frequency
samp_freq = Get sampling frequency
# create silence with approriate sampling frequency
sil = Create Sound from formula: "silence", 1, 0, 1, samp_freq, "0"
selectObject: second_sound
Copy: "tmp"
plusObject: sil
plusObject: first_sound
Concatenate
Play
There's one last thing we should implement, namely remove the sound copy called tmp and the silence at the end of the script. The script user might find it confusing or annoying if the script creates unexpected and unexplained objects. And since we don't need neither copy nor silence any more, it's perfectly safe to remove them. We'll apply the familiar pattern:
- grab the copy's ID (we already have the silence' ID)
- select them
- do something with them (remove)
Voilà:
first_sound = selected ("Sound", 1)
second_sound = selected ("Sound", 2)
selectObject: second_sound
samp_freq = Get sampling frequency
sil = Create Sound from formula: "silence", 1, 0, 1, samp_freq, "0"
selectObject: second_sound
# grab ID of copy
copy = Copy: "tmp"
plusObject: sil
plusObject: first_sound
Concatenate
Play
# remove auxiliary objects
selectObject: copy, sil
Remove
Other possible enhancements to the script:
- Enquire the sampling frequency of both sounds and compare them so the script can react if they differ. As of now, the script just terminates with an error message in that case. A more helpful reaction would be to offer a solution to the problem, like for instance resampling one of the sounds so that sampling frequencies match and the concatenation can be accomplished. We'll need conditionals to implement this.
- Processing of more than two sounds, i.e. concatenate all selected sounds independent of their number—can be done with a loop.
- Ask the script user to specify the silence duration when the script is launched—see Input Forms.
- …
Querying name & type
At the beginning of this section, I mentioned a sibling of the selected ()
function which returns type and/or name of selected objects instead of IDs. Since type and name are strings, consequently the sibling is called selected$ ()
. (Remember: string values must be assigned to string variables, i.e. variables ending in $
.) Regarding arguments, the possibilities with selected$ ()
are as manifold as with selected ()
. To be honest, there's even one more complication. Without argument or with only a numeric argument (index) selected$ ()
returns type and name (separated by space) of the identified object, following the same principles as selected ()
. If you specify a type (with a string argument), however, selected$ ()
returns only the name! The table below illustrates this behavior:
Suppose there's one Sound object selected, called recording.
statement | return value |
---|---|
selected$ () |
Sound recording |
selected$ (1) |
Sound recording |
selected$ ("Sound") |
recording |
selected$ ("Sound", 1) |
recording |
Unfortunately, there's no variant of selected$ ()
returning only the type of a selected object. If you are interested in the type, you must first enquire type and name and then extract the type part with a general purpose string function provided by Praat (cf. String functions in the Praat manual):
type_name$ = selected$ ()
type$ = extractWord$ (type_name$, "")
The extractWord ()
function extracts a word from an input string. 'Word' is defined as sequence of characters until the first space. The function requires two arguments: the input string and a search string. The search string determines where to start extraction (namely after the search string). If the search string is empty like above, extraction starts at the beginning of the input string. In our case, the input string is something like Sound recording. So if the function starts extracting at the beginning of the string and and stops extracting at the first space, the result is Sound—Bingo!
Easy? Ok, so how would you extract the name instead of the type? I'll show you the solution in a minute to give you some time for musing over the question. First, I'll show you how to cut a corner and save some typing: It is possible to insert the query function into the string processing function, thus saving the complete variable assignment statement (the first line of the original example):
type$ = extractWord$ (selected$ (), "")
First, the query function is evaluated and replaced by its result, then the string processing function kicks in and does its magic. The outcome is exactly the same as above. And here's the solution to the name extracting problem:
type_name$ = selected$ ()
name$ = extractWord$ (type_name$, " ")
or the short version:
name$ = extractWord$ (selected$ (), " ")
With the search string being a space instead of being empty you tell the function to start extracting after the first space in the input string. Since the first space is the separator between type and name, extraction starts with the name and continues until the end of the input string.
Number of selected objects
There's one last function to introduce in this section. Remember the list of possible enhancements to the concatenation script above. One idea was, to modify the script so that it would be possible to process any number of sounds, not just two. To implement this functionality, we would construct a loop to repeat the preparing steps (create silence/copy sound) as often as needed (we'll do that in session 9). And how often is that? That depends, obviously, on the number of selected sounds—if 10 sounds are selected, we need to repeat the necessary steps 10 times. Hence, to tell the loop the number of repetitions the number of selected objects must be established. That's exactly the purpose of this function: numberOfSelected ()
. It returns a numeric value equal to the number of selected objects. It is possible to specify the type of object(s) you're interested in with a string argument: numberOfSelected ("Sound")
returns the number of selected sounds. So if, for instance, 10 objects are selected, 5 of which are sounds, numberOfSelected ("Sound")
would return 5, while numberOfSelected ()
would return 10.