Week 12: Procedures
1 Intro
In previous weeks we have seen how for-loops can help us perform operations on a series of Objects, variables, etc.: they are one of the most important tools for scripting.
However, there is a second tool that is even more powerful than loops. They are called procedures, and have the virtue of (a) in some cases, make the scripts shorter; and (b) add a boost to the power of your script by being able to stack variables for one single process.
2 A minimal example
Basically, a procedure is a set of actions that can be defined somewhere in the script and then be "called" by a variable or set of them. Let's create an example with string variables (yes, procedures can be run over string and numeric variables). Let's say that we have two long words (hippopotamus and dodecahedron), and we want to count the number of letters in them. We want to use the length (mystring$) function to calculate the length, of each word, and then we want to print the result to the info window.
@myProc: "hippopotamus"
@myProc: "dodecahedron"
procedure myProc: .longword$
.number= length (.longword$)
appendInfoLine: .longword$, tab$, .number
endproc
What is this? Let's see:
- Let's take a look at the procedure lines first (lines 4-7):
- Every procedure starts with the word procedure and ends with endproc.
- Take a look at line 4. Immediately after you write the word procedure,you give your procedure a name (we called it here myproc), followed by a colon. What comes after is the name you will give to the variable storing the values on which the procedure will operate. So .longword$ here refers to the words hippopotamus and dodecahedron. IMPORTANT: Note that variables within procedures are preceded by a full-stop. This way, if you have a variable called longword$ elsewhere in your script, the procedure will not look at that variable.
- Line 5 creates a new numeric variable called .number, which will store the number obtained by applying the length (mystring$) function to the words in question.
- Line 6 is just our good ol' appendInfoLine, which will print both the word stored into the variable .longword$ and the number stored as the variable .number.
- Finally, line 7 closes the procedure with endproc.
- Now let's look at the lines before the procedure (lines 1 and 2). Each of the words to which we want to apply the procedure is preceded by@myProc. Recall that we called the procedure myProc on line 4. So what we are doing here is to use the @ to call a procedure named myProc, which can be anywhere in your script. Bear in mind that procedures will jump and do their work whenever there is an @ followed by their name. For instance, if later on in your script you would like to execute this procedure again on different values (let's say, that you have the word triskaidekaphobia) you can simply add @myProc: "triskaidekaphobia" wherever you want in your script and the procedure will execute.
3 More examples
3.1 Get pitch values at different times
Let's pretend that we have a vowel from a speaker of Mandarin and we would like to get pitch values throughout the vowel at three points: start, mid, and end. In this case, we have just visually inspected them and know that these times are 0.17, 0.3, and 0.58. We want to print the values to the Info window.
appendInfoLine: "Time", tab$, "Pitch"
#upon visual inspection, we enter start, mid, and end times of the vowel. We write down the following:
@getValue: 0.17
@getValue: 0.3
@getValue: 0.58
Where getValue is the name of the procedure, followed by the times from which we want to get the pitch values. Remember that the @ is the way we say "Praat, call the procedure on these values". Now we create the procedure itself, which can go anywhere in your script.
procedure getValue: .time
selectObject: "Sound ma2"
p=To Pitch: 0, 75, 600
ps=Smooth: 10
.val= Get value at time: .time, "Hertz", "linear"
appendInfoLine: .time, tab$, .val
endproc
Line 6 defines the procedure as getValue, which is the name we used in lines 3-5 when calling it. Then we have a assigned an internal name to the variables above for future reference: we called it .time . Then we select the Sound and create the Pitch Object and smoothen it (lines 7-9). Afterwards we create a new variable called .val, which stores the pitch value at the time stated on line 3 of this script. Finally, we print the information to the Info window. The last line closes the procedure. After this, the script will move on to the following @getValue (line 4), repeat, then apply to the next @getValue (line 5). You can call the same procedure as many times as you like throughout your script.
You can add as many actions inside your procedure. Here we added the creation of a Formant object (line 7), in case we wanted to get the F1 values at the same times stated above:
procedure getValue: .time
selectObject: "Sound ma2"
p=To Pitch: 0, 75, 600
ps=Smooth: 10
.val= Get value at time: .time, "Hertz", "linear"
selectObject: "Sound ma2"
form= To Formant (burg): 0, 5, 5500, 0.025, 50
.meanf1= Get value at time: 1, .time, "hertz", "linear"
appendInfoLine: .time, tab$, .val, tab$, .meanf1
endproc
3.2 Create several continua
3.2.1 Without procedure
Let's assume that we are creating a continuum between /i/ and /u/, as we did as a homework last week. We will need to create a few variables, and then feed them to a KlattGrid:
#We define f1, the f2 for the frontmost vowel, and the f2 for the backmost one
f1=300
f2_front=2800
f2_back=1200
After opening the for-loop (line 5), we create a KlattGrid named "vowel", which will be 0.05 s long, will have a maximum of 6 formants, etc. (line 6). We will add two pitch points in order to give a more natural, descending pitch contour (lines 7-8); then we add an amplitude point (line 9), and one formant frequency point and bandwidth point for each formant (lines 10-13). Finally, we synthesize the sounds (line 14) and save them in a folder called continuum on our Desktop (line 15). Line 16 closes the for-loop.
for i from 1 to 5
Create KlattGrid: "vowel", 0, 0.5, 6, 1, 1, 6, 1, 1, 1
Add pitch point: 0.05, 180
Add pitch point: 0.45, 170
Add voicing amplitude point: 0.25, 90
Add oral formant frequency point: 1, 0.25, f1
Add oral formant bandwidth point: 1, 0.25, 50
Add oral formant frequency point: 2, 0.25, f2_front -(((f2_front - f2_back)/4)*i-1)
Add oral formant bandwidth point: 2, 0.25, 50
To Sound
Save as WAV file: "/home/fernanda/Desktop/continuum/" + "vowel_"+string$(i)+".wav"
endfor
However, this is not a good approach, as we will need to repeat the same for-loop for the other continuum. A procedure will be useful in this case beacuse it would allow us to create one for-loop for both continua.
3.2.1 With procedure
Let's look at the following script:
@createCont: 300, "high", 2800, 1100
@createCont: 800, "low", 1800, 1200
What did we just do? Here we are going to call a procedure named createCont, which will apply to a series of variables. The first instance has the parameters for creating a continuum between high vowels /i/ and /u/. In order to create this continuum, we need the values for the F1 (300 Hz for both vowels), and then the F2 values for /i/ (the frontest vowel: 2800 Hz) and /u/ (the backest vowel: 1100 Hz). The procedure will calculate the steps.
The second instance contains the parameters for creating the continuum between low vowels /æ/ and ɑ/. Note that we have listed the parameters in the same order as above; this is crucial.
Now let's move on to the procedure chunk:
procedure createCont: .f1, .height$, .f2_front, .f2_back
for i from 1 to 5
Create KlattGrid: .height$+string$(i), 0, 0.5, 6, 1, 1, 6, 1, 1, 1
Add pitch point: 0.05, 180
Add pitch point: 0.45, 170
Add voicing amplitude point: 0.25, 90
Add oral formant frequency point: 1, 0.25, .f1
Add oral formant bandwidth point: 1, 0.25, 50
Add oral formant frequency point: 2, 0.25, .f2_front -(((.f2_front - .f2_back)/4)*i-1)
Add oral formant bandwidth point: 2, 0.25, 50
To Sound
Save as WAV file: "/home/fernanda/Desktop/continua/" + .height$+string$(i)+".wav"
endfor
endproc
Line 3 gives names to each one of the parameters stated in the lines above, so that the procedure can use them. So the first variable was called .f1, the second one was called .height$, the third one is .f2_front, and the fourth one is .f2_back. Then, the procedure refers to these variables and operates on them. So we turned the for-loop in section 3.2.1. into a procedure that can be applied to different sets of values, with very little changes.
4 Homework
Download the following sound from here (you can also find it on ILIAS). We will create four Sound objects, each one with different Mandarin tones. For this:
- Look at the following table with the start, mid, and end values for each tone of Mandarin Chinese.
- Create a procedure that (a) Creates a Manipulation object for the Sound object that you downloaded; (b) Creates an empty PitchTier; (c) Adds the three values above; (d) Takes the Manipulation object and replaces the PitchTier with the one you just created; and (e) Resynthesizes the sound (the command for this is Get resynthesis (overlap-add), which has to be applied to the Manipulation object).
- Upload your Praat script on ILIAS as a .praat file.