Scripting 5.5. Procedures

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:

  1. a line with the word `procedure`, followed by the name of the procedure, followed by a pair of parentheses;
  2. the body of the procedure (here: seven lines);
  3. a line with the word `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).

## Arguments

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.

## Encapsulation and local variables

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.

## A list of numeric and string arguments

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
` `=>`
`Speaker Bart is 38 years old.
` `Speaker Katja is 24 years old.
`

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
` `=>`
`Conjugation of 'to be':
` `1sg I am
` `2sg you are
` `3sg she is
`

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”
`

## Restrictions

You can have any number of procedures in your script. The name of a procedure can have any number of characters (at least 1, of course). A procedure name can consist of letters (“word characters”), underscores (“”), periods (“.”) and digits, but it cannot start with a digit; this is the same restriction as for variable names.

## Functions

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
` `=>`
`The square of 5 is 25.
`

Another way to emulate functions is to use a variable name as an argument:

`@squareNumber2: 5, “square5”
` `writeInfoLine: “The square of 5 is ”, square5, “.”
` `procedure squareNumber2: .number, .squareVariableName$
` `'.squareVariableName$' = .number ^ 2
` `endproc
` `=>`
`The square of 5 is 25.
`

However, this uses variable substitution, a trick better avoided.

### Links to this page

- `endproc`
- `procedure`
- Scripting
- Scripting 5. Language elements reference