|
Sometimes in a Praat script, you will want to perform the same thing more than once. In §5.4 we saw how loops can help there. In this section we will see how procedures (also called subroutines) can help us.
Imagine that you want to play a musical note with a frequency of 440 Hz (an “A”) followed by a note that is one octave higher, i.e. has a frequency of 880 Hz (an “a”). You could achieve this with the following script:
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 440, 0.2, 0.01, 0.01
Play
Remove
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 880, 0.2, 0.01, 0.01
Play
Remove
This script creates a sound with a sine wave with an amplitude of 0.4 and a frequency of 440 Hz, then plays this sound, then changes the sound into a sine wave with a frequency of 880 Hz, then plays this changed sound, and then removes the Sound object from the object list.
This script is perfect if all you want to do is to play those two notes and nothing more. But now imagine that you want to play such an octave jump not only for a note of 440 Hz, but also for a note of 400 Hz and for a note of 500 Hz. You could use the following script:
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 440, 0.2, 0.01, 0.01
Play
Remove
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 880, 0.2, 0.01, 0.01
Play
Remove
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 400, 0.2, 0.01, 0.01
Play
Remove
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 800, 0.2, 0.01, 0.01
Play
Remove
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 500, 0.2, 0.01, 0.01
Play
Remove
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, 1000, 0.2, 0.01, 0.01
Play
Remove
This script works but is no longer perfect. It contains many similar lines, and is difficult to read.
Here is where procedures come in handy. With procedures, you can re-use similar pieces of code. To make the three parts of the above script more similar, I’ll rewrite it using two variables (frequency
and octaveHigher
):
frequency = 440
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01
Play
Remove
octaveHigher = 2 * frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01
Play
Remove
frequency = 400
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01
Play
Remove
octaveHigher = 2 * frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01
Play
Remove
frequency = 500
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01
Play
Remove
octaveHigher = 2 * frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01
Play
Remove
You can now see that seven lines of the script appear identically three times. I'll put those seven lines into a procedure that I name playOctave
:
procedure playOctave
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01
Play
Remove
octaveHigher = 2 * frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01
Play
Remove
endproc
As you see, a procedure definition in Praat consists of three parts:
procedure
, followed by the name of the procedure, followed by a pair of parentheses;endproc
.You can put a procedure definition anywhere in your script; the beginning or end of the script are common places.
The bodies of procedures are executed only if you call the procedure explicitly, which you can do anywhere in the rest of your script:
frequency = 440
@playOctave
frequency = 400
@playOctave
frequency = 500
@playOctave
procedure playOctave
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01
Play
Remove
octaveHigher = 2 * frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01
Play
Remove
endproc
This script works as follows. First, the number 440 is assigned to the variable frequency
in line 1. Then, execution of the script arrives at the @
(“call”) statement of line 2. Praat then knows that it has to jump to the procedure called playOctave
, which is found on line 7. The execution of the script then proceeds with the first line of the procedure body, where a Sound is created. Then, the other lines of the procedure body are also executed, ending with the removal of the Sound. Then, the execution of the script arrives at the endproc
statement. Here, Praat knows that it has to jump back to the line after the line that the procedure was called from; since the procedure was called from line 2, the execution proceeds at line 3 of the script. There, the number 400 is assigned to the variable frequency
. In line 4, execution will jump to the procedure again, and with the next endproc
the execution will jump back to line 5. There, 500 is assigned to frequency
, followed by the third jump to the procedure. the third endproc
jumps back to the line after the third @
, i.e. to line 7. Here the execution of the script will stop, because there are no more executable commands (the procedure definition at the end is not executed again).
The above example contains something awkward. The procedure playOctave
requires that the variable frequency
is set to an appropriate value, so before calling playOctave
you always have to insert a line like
frequency = 440
This can be improved upon. In the following version of the script, the procedure playOctave
requires an explicit argument:
@playOctave: 440
@playOctave: 400
@playOctave: 500
procedure playOctave: frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01
Play
Remove
octaveHigher = 2 * frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01
Play
Remove
endproc
This works as follows. The first line of the procedure now not only contains the name (playOctave
), but also a list of variables (here only one: frequency
). In the first line of the script, the procedure playOctave
is called with the argument 440. Execution then jumps to the procedure, where the argument 440 is assigned to the variable frequency
, which is then used in the body of the procedure.
Although the size of the script has now been reduced to 12 lines, which cannot be further improved upon, there is still something wrong with it. Imagine the following script:
frequency = 300
@playOctave: 440
@playOctave: 400
@playOctave: 500
writeInfoLine: frequency
procedure playOctave: frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, frequency, 0.2, 0.01, 0.01
Play
Remove
octaveHigher = 2 * frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, octaveHigher, 0.2, 0.01, 0.01
Play
Remove
endproc
You might have thought that this script will write “300” to the Info window, because that is what you expect if you look at the first five lines. However, the procedure will assign the values 440, 400, and 500 to the variable frequency
, so that the script will actually write “500” to the Info window, because 500 is the last (fourth!) value that was assigned to the variable frequency
.
What you would want is that variables that are used inside procedures, such as frequency
and octaveHigher
here, could somehow be made not to “clash” with variable names used outside the procedure. A trick that works would be to include the procedure name into the names of these variables:
frequency = 300
@playOctave: 440
@playOctave: 400
@playOctave: 500
writeInfoLine: frequency
procedure playOctave: playOctave.frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, playOctave.frequency, 0.2, 0.01, 0.01
Play
Remove
playOctave.octaveHigher = 2 * playOctave.frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, playOctave.octaveHigher, 0.2, 0.01, 0.01
Play
Remove
endproc
This works. The six tones will be played, and 00"will be written to the Info window. But the formulation is a bit wordy, isn't it?
Fortunately, Praat allows an abbreviated version of these long names: just leave “playOctave” off from the names of the variables, but keep the period (.
):
frequency = 300
@playOctave: 440
@playOctave: 400
@playOctave: 500
writeInfoLine: frequency
procedure playOctave: .frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, .frequency, 0.2, 0.01, 0.01
Play
Remove
.octaveHigher = 2 * .frequency
Create Sound as pure tone: “note”, 1, 0, 0.3, 44100, .octaveHigher, 0.2, 0.01, 0.01
Play
Remove
endproc
This is the final version of the script. It works because Praat knows that you are using the variable names .frequency
and .octaveHigher
in the context of the procedure playOctave
, so that Praat knows that by these variable names you actually mean to refer to playOctave.frequency
and playOctave.octaveHigher
.
It is advisable that you use such “local” variable names for all parameters of a procedure, i.e. for the variables listed after the procedure
word (e.g. .frequency
), as well as for all variables that you create in the procedure body (e.g. .octaveHigher
). In this way, you make sure that you don't inadvertently use a variable name that is also used outside the procedure and thereby perhaps inadvertently change the value of a variable that you expect to be constant across a procedure call.
You can use multiple arguments, separated by commas, and string arguments (with a dollar sign in the variable name):
@listSpeaker: “Bart”, 38
@listSpeaker: “Katja“, 24
procedure listSpeaker: .name$, .age
appendInfoLine: “Speaker ”, .name$, “ is ”, .age, “ years old.”
endproc
or
@conjugateVerb: “be”, “I am”, “you are”, “she is”
procedure conjugateVerb: .verb$, .first$, .second$, .third$
writeInfoLine: “Conjugation of 'to ”, .verb$, “':”
appendInfoLine: “1sg ”, .first$
appendInfoLine: “2sg ”, .second$
appendInfoLine: “3sg ”, .third$
endproc
For the arguments you can use expressions:
@playOctave: 400 + 100
As with all string literals, the double quotes in literal string arguments should be doubled:
procedure texts: .top$, .bottom$
Text top: “yes”, .top$
Text bottom: “yes”, .bottom$
endproc")
@texts: """ hello"" at the top", """goodbye"" at the bottom"
unless you use curly quotes:
@texts: “" hello" at the top”, “"goodbye" at the bottom”
or even
@texts: ““ hello” at the top”, ““goodbye” at the bottom”
The Praat scripting language does not have the concept of a “function” like some other scripting languages do. A function is a procedure that returns a number, a string, a vector, a matrix, or a string array. For instance, you can imagine the function squareNumber
(), which takes a number (e.g. 5) as an argument and returns the square of that number (e.g. 25). Here is an example of how you can do that, using the global availability of local variables:
@squareNumber: 5
writeInfoLine: “The square of 5 is ”, squareNumber.result, “.”
procedure squareNumber: .number
.result = .number ^ 2
endproc
Another way to emulate functions is to use a variable name as an argument:
@squareNumber: 5, “square5”
writeInfoLine: “The square of 5 is ”, square5, “.”
procedure squareNumber: .number, .squareVariableName$
'.squareVariableName$' = .number ^ 2
endproc
However, this uses variable substitution, a trick better avoided.
© Paul Boersma 2017-09-04