Writing Praat scripts in a modular way

2019-12-06
3 min read

Praat is widely used in phonetic research. Working with Praat involves inevitably the Praat scripting. There are plenty of resources of Praat scripts as well as great tutorials to Praat scripting (see below). Why bother writing Praat scripts oneself?

Praat scripts written by others can be helpful, but even an script is available which offer the functionality one needs, it is seldom exactly what one wants. It is normal to do some tailoring to the scripts. Users of Praat may be great phoneticians, but rarely are they professional programmers. When I write my own Praat scripts, I try to explicitly adopt some principles and conventions to facilitate the reusability of code. This post is about these principles and conventions.

Drawbacks of existing Praat scripts

In working with various Praat scripts, I find two drawbacks of some scripts:

  1. Functions of some scripts are too specific
  2. Some scripts are designed to perform two or more tasks simultaneously

These two features have some negative effects. Using scripts with specific functions can lead to tailoring when customizing them to one’s own needs. Sometimes, the tinkering even takes more time than if one decides to write his own scripts right away. To perform several tasks at the same time may save some time, but doing research is never an once for all affair. It is usual for one to go back to revise some of the steps he has taken before. Now the scripts with too many functions get on his way. Undesirable functions distract him, and if he wants to get rid of it, once again he falls into the trap of tailoring.

The Unix pholosophy and modular programming

Professional programmers offer their wisdom. The Unix Philosophy and the modular programming[^1] are priceless in this scenario. The Unix philosophy says that Small is beatiful and requires programs to do one thing well. This inspired me to reflect on the workflow of phonetic research using Praat. Phonetic research starts with annotation and segmentation in Praat, and steps to acoustic analysis. Annotation and segmentation are two separate tasks. It is better to do these tasks using separate scripts.

Another useful idea is modular programming. Modular programming is the practice of splitting a large programme into separate modes. It improves the reusability of codes. Modular programming requires you to pay attention to orthogonality. I will illustrate this idea in the next section.

Use procedure

We can abstract common tasks into functions. Praat provide a mechanism called procedure, which can accept parameters and do certain commands. It differs from a function command in other programming languages in that procedure can not return values or objects.

Making illustrations with Praat is tedious manually. Using procedure, it is much easier to make illustrations, and minor revisions are needed to customize the pictures. Suppose I want to draw the waveform of a sound file together with the annotation file. It is OK to select both obeject and draw them together, as follows.

selectObject: "Sound test"
plusObject: "TextGrid test"
Draw: 0, 0, "yes", "yes", "yes"

However, this approach is not modular. Suppose later I want to draw the spectrogram of the sound file together with its annotation, there is no available function as to “draw TextGrid together with a spectrogram”. This leads me to abstract the drawing of TextGrid as a procedure, which can be used together with other procedures.

procedure draw_TextGrid: .baseName$, .start, .end
    selectObject: "TextGrid " + .baseName$
    Select inner viewport: 1, 4, 1, 3
    Draw: .start, .end, "yes", "yes", "no"
    Draw inner box
    Axes: 0, .end - .start, 0, 5000
    Marks bottom: 5, "yes", "yes", "no"
    Text bottom: "yes", "Time (s)"
endproc

At first glance, this procedure contains more lines than the previous block. The advantage of this procedure is that you can use it independently in any occassion you want, together with other procedures you define. To illustrate, I wrote another procedure which draws the waveform of a sound.

procedure draw_waveform: .baseName$, .start, .end
    selectObject: "Sound " + baseName$
    Select inner viewport: 1, 4, 1, 2.15
    Draw: .start, .end, 0, 0, "no", "Curve"
endproc

And another procedure which draws the spectrogram:

procedure draw_spectrogram: .baseName$, .start, .end
    selectObject: "Sound " + .baseName$
    To Spectrogram: 0.005, 5000, 0.002, 20, "Gaussian"
    selectObject: "Spectrogram " + .baseName$
    Select inner viewport: 1, 4, 1, 2.15
    Paint: .start, .end, 0, 5000, 100, "yes", 50, 6, 0, "no"
    Axes: .start, .end, 0, 5000
    Marks left: 6, "yes", "yes", "no"
    Text left: "yes", "Frequency (Hz)"
endproc

The draw_TextGrid function can be used together with draw_sound or draw_spectrogram to draw the waveform or spectrogram together with the annotation. To change the size of the window, I only need to modify two parameters (.start and .end). I can even write another procedure which draws the pitch of the sound:

procedure draw_pitch: .baseName$, .floor, .ceiling, .start, .end
    @smooth_pitch: .baseName$, .floor, .ceiling
    selectObject: "Pitch smooth"
    Select inner viewport: 1, 4, 1, 2.15
    Axes: 0, .end - .start, 0, .ceiling
    Line width: 3
    #Font size: 14
    Colour: "Blue"
    Draw: .start, .end, 0, .ceiling, "no"
    Line width: 1
    Colour: "Black"
    Marks right: 4, "yes", "yes", "no"
    Text right: "yes", "Pitch (Hz)"
endproc

The procedure even calls another procedure within it. It is this modular design that enables me to do complex things with Praat. The main structure remains highly readable. For example, to draw the waveform, spectrogram, pitch and annotation together, I only need to call these precedures in turn.

@draw_spectrogram: baseName$, start, end
@draw_tg: baseName$, start, end
@draw_pitch: baseName$, floor, ceiling, start, end

It is like lego, that we can put simple pieces into amazing things.

The structure of a script

Most of the Praat scripts are written to do batch tasks. Therefore, a general structure of script can be abstracted.

To do a batch job, a script can be decomposed into the following parts:

  • an I/O interface to the user: a form in Praat terms;
  • a series of files/directories to be processed: a String in Praat terms;
  • some thing to do to the files/directories: a procedure in Praat terms.

People usually write a for loop and put all the things to do within it. This approach is not modular and reproducible. As illustrated in the previous section, to decompose the things to do into clearly-defined steps and wrap them using procedure can be useful. The functionality of a procedure can be easily used by other scripts, and it increases the reusability of codes.

I included a script_strcture.praat script in this repo. It serves as a template for scripting. Simply add procedures at the end of the script and put them into the for loop. This saves a lot of typing.

Conventions of writing Praat scripts

To keep code clean easy to understand, it is good to maintain a consistent style. For R, there is the famous style guide by Hadley Wickham, and for Python, there is PEP 8. I apply some simple conventions to Praat scripting.

When scripting in Praat, I keep some these conventions:

  • camel case for variable names: baseName$;
  • snake case for procedure names: draw_waveform.

To keep procedures modular, I use consistent and meaningful variable names across all scripts. Some frequent variables are listed below:

  • baseName$: the name of sound files or TextGrid files without extensions. It’s used to refer to objects in Praat editors;
  • extension$: the file extension. It can also be
  • fileList$: the name of the String of file in Praat.

Of course you can have your own conventions, but make sure to keep them consistent. For other style options, I always refer to the style guides of R and Python.

I put some of the scripts I wrote in this repository, and I will keep updating.

Resources of praat scripts