Praat Script基礎文法最速マスター2022

# はじめに

Praatは、音声分析や編集を行うことができるソフトウェアです。音声学でよく使用されます。

https://www.fon.hum.uva.nl/praat/

そして、PraatのUI上の操作を自動化するための言語が表題の「**Praat Script**」です。
（乱暴なたとえですがExcelとVBAのような関係です）
使いこなせば、**音声ファイルが何億個あろうが、for文でぶん回して分析完了！GUIを徹夜でカチカチする必要はもうありません！**

Praat Scriptは言語としても独特な機能を持っているので、**マイナープログラミング言語ハンターの方々にもぜひ読んでいただければと思います** [^syntax]。

そこで、Praat Scriptの言語機能、使い方を4回にわたって紹介したいと思います。

1. Praat Script基礎文法最速マスター2022 (当記事)
2. [Effective Praat Script](https://qiita.com/Syuparn/items/f2e108f370836c4567cb)
3. [悪用厳禁？Praat Scriptの黒魔術、謎文法](https://qiita.com/Syuparn/items/252e121ca7a20502a871)
4. [Praat Scriptでゲームを作ってみよう](https://qiita.com/Syuparn/items/06e9136a715290fb34a3)

# 書いた動機

Praat Scriptの解説記事といえば「**Praatスクリプト基礎文法最速マスター**」(＋その続編)が有名ですが（学生時代本当にお世話になりました :bow:）、その後9年間でPraatのメジャーバージョンが上がり、**Praat Scriptの文法もさらに進化を遂げました**。

https://id.fnshr.info/2013/01/26/praatmaster/

https://id.fnshr.info/2013/11/10/praat-script-renewal/

そこで、本家様をリスペクトし、最新(v6.2系)の文法についても触れた、題して「Praat script基礎文法最速マスター2022」をまとめました。

# バージョン

```praat
appendInfoLine: praatVersion$
; 6.2.11
```

# 言語の特徴

- インタープリター型
- 手続き型
- **ユーザー定義関数が存在しない**
    - 代わりにProcedure(計算用の名前空間)を使用
- 強い型付け
- **suffixによる変数の型の明示** (`num`, `str$`, `vec#`, `mat##`, `strvec#$`)
- UIも作れる
- [1997年誕生](http://www.fon.hum.uva.nl/praat/manual/What_was_new_in_3_6_.html)（今年で**25周年** :tada:）

バージョンごとの新機能はこちらで確認できます。

https://www.fon.hum.uva.nl/praat/manual/What_s_new_.html

# インストール

まずはPraatをインストールしましょう。[公式ページ](https://www.fon.hum.uva.nl/praat/)の左上「Download Praat」から最新版のzipをダウンロードします。解凍したら、パス等設定しなくてもそのまま起動できます。

![praat_open.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/5b09680a-2e08-eb9a-eab5-b8e2284913e1.png)

# 実行の仕方

左上「Praat→New Script」でPraat Scriptのエディタが開きます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/8ee14240-bbe6-40e5-7b0d-f7b9004ff23b.png" width="50%">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/c6772706-c0d4-4f40-3b62-90814965f50d.png" width="50%">

コードを書いたら「Run」を押す(または Ctrl+R)と実行します。文字列を出力した場合「Praat Info」のダイアログに表示されます。
もちろん、事前に書いたソースコードを開いて実行することも可能です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/15d08289-0328-1791-40a5-460d1d9d4455.png" width="75%">


また、「Edit→Paste History」で今までのUI操作をPraat Scriptに変換してくれます。最初は手作業でやって、それをもとに自動化コードを書くといった使い方も可能です。

今はPraat Scriptのエディタを開いただけなので、Paste Historyで `New Praat Script` が貼り付けられました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/31f30607-7f31-713a-4a12-7db7292cb9a6.png" width="75%">

# Hello, world!

`appendInfoLine` でダイアログに文字列を表示します。

```praat:hello.praat
clearinfo
appendInfoLine: "Hello, world!"
```

前に出力した文字列が残ってしまうので、 最初に`clearinfo` でダイアログを真っさらにしています。以下のコードでは省略するので、先頭に `clearinfo` を適宜おぎなって読んでください。

# コメント

行頭コメントは `#`, 行中コメントは `;` で書けます。

```praat
# 行頭コメント
appendInfoLine: "Hello, world!" ; 行中コメント
```

# 変数と代入

一般的なスクリプト系言語のように、代入時に初期化できます。ただし、変数名は小文字で始める必要があります。

```praat
n = 10 ; 宣言&初期化
# 上書きも可能
n = 20

# 以下は文法エラー
; N = 100
```

# 改行

`...` で途中改行できます。

```praat
appendInfoLine: "very very
... long string" ; very very long string
sum = 1 + 2 + 3
...+ 4 + 5 + 
...6 ; 演算子の前でも後でも改行可能
```

注意点として、 **`...` の前の行には行中コメントを入れることはできません**。

# 変数の型

Praat Scriptのユニークな点1つ目。
Praat Scriptでは、**変数の型を末尾の記号で表します** [^type]。Perlを使ったことがある方なら、シジルをイメージしてしていただくと分かりやすいです [^sigil] 。

```praat
# number
num = 100

# string
str$ = "abc"

# vector
vec# = {1, 2, 3}

# matrix
mat## = {{1, 2}, {3, 4}}

# string vector
strVec$# = {"foo", "bar", "baz"}
```

「スクリプト」と言いながら、頑張れば静的解析が可能です。~~そのようなlinterはまだありませんが...~~

また、強い型付けなので、型が合わないと実行時エラーになります。うっかり文字列が `0` になったりしないので安全ですね。

```praat
# エラー！
; appendInfoLine: 1+"2" ; cannot add a string to a number

# 明示的に変換が必要
appendInfoLine: string$(1)+"2" ; "12"
```

## vector等の要素参照/更新

 `[i]` で参照できます。**1始まりなので要注意！**
 mutableなので要素を書き換えることも可能です。

```praat
nums# = {2, 4, 6}
appendInfoLine: nums# ; 2 4 6
appendInfoLine: nums#[3] ; 6
# 上書き
nums#[2] = 5
appendInfoLine: nums# ; 2 5 6

mat## = {{1, 2}, {3, 4}}
# 行列の要素は[i][j]ではなく[i,j]なので注意！
appendInfoLine: mat##[1, 2] ; 2
```

## (疑似)配列/辞書

連想配列のような見た目の変数を作れます。ただしこちらはベクトルのように全要素を一括で操作することはできません[^pseudoarray]。

```praat
pseudoArr[1] = 100
pseudoArr["foo"] = 5
pseudoArr$[10] = "bar"
```

# 演算子

基本は一般的な言語と同様です。

## 四則演算等

```praat
# number
appendInfoLine: 7 + 2 ; 9
appendInfoLine: 7 - 2 ; 5
appendInfoLine: 7 * 2 ; 14
appendInfoLine: 7 / 2 ; 3.5
appendInfoLine: 7 ^ 2 ; 49
appendInfoLine: 7 div 2 ; 3
appendInfoLine: 7 mod 2 ; 1

# string
appendInfoLine: "foo" + "bar" ; foobar
# - で末尾を取り除ける
appendInfoLine: "Praat" - "at" ; Pra
```

表現できないnumberは `undefined` (別言語でいう `NaN` )になります。

```praat
appendInfoLine: 6 / 0 ; --undefined--
appendInfoLine: (-4) ^ 0.5 ; --undefined--
```

## 比較演算子

bool型は無いため、trueは `1`、falseは `0` と表現されます。

```praat
appendInfoLine: 5 == 5 ; 1
appendInfoLine: 5 != 5 ; 0
appendInfoLine: "a" == "b" ; 0
appendInfoLine: {1, 2} == {1, 1} ; 0

# 両辺の型が違うとエラー
; appendInfoLine: 1 = "a" ; cannot compare a number to a string
```

## 論理演算子

```praat
# and, &&, & どれでも可
appendInfoLine: 5 == 5 && 1 == 1 ; 1

# or, ||, | どれでも可
appendInfoLine: 5 == 5 || 0 == 2 ; 1

# not, !どれでも可
appendInfoLine: !(5 == 5) ; 0
# 0以外はすべてtruthy
appendInfoLine: !5 ; 0

# number以外は使えない
; appendInfoLine: !"a" ; cannot negate a string
```

## パーセント

numberをパーセント表記できます。割合を指定する際には分かりやすいです。

```praat
appendInfoLine: 30% ; 0.3
```

# 組み込み関数
数値計算や文字列処理に使いたい関数は大抵そろっています。多いので一部だけ。

## 数値計算

```praat
appendInfoLine: abs(-3) ; 3
appendInfoLine: max(1, 5, 2) ; 5
appendInfoLine: log10(2) ; 0.3010...
```

## 文字列処理

**関数名の後ろには、戻り値型の記号がつきます**。

```praat
# 長さ
appendInfoLine: length("Hello") ; 5
# number -> string
appendInfoLine: string$(3) ; "3"
# string -> number
appendInfoLine: number("3") ; 3
# 置換
appendInfoLine: replace$("string", "i", "o", -1) ; "strong"
# split
sv$# = splitByWhitespace$#("foo bar")
appendInfoLine: sv$#[1] ; "foo"
```

リファレンスに関数の一覧が載っています。

https://www.fon.hum.uva.nl/praat/manual/Formulas_5__Mathematical_functions.html

https://www.fon.hum.uva.nl/praat/manual/Formulas_6__String_functions.html

# 制御構造
## if

特に変わったところはありません。
条件式にはnumberしか入れることができません (`0` 以外はtruthy)。

```praat
n = 6

if n mod 15 == 0
	appendInfoLine: "fizzbuzz"
elsif n mod 3 == 0
	appendInfoLine: "fizz"
elsif n mod 5 == 0
	appendInfoLine: "buzz"
else
	appendInfoLine: n
endif
```

## while

```praat
n = 5
while n > 0
    appendInfoLine: n
    n -= 1
endwhile
```

## repeat

こちらは他言語のdo whileに相当します。

```praat
n = 5
repeat
    appendInfoLine: n
    n -= 1
until n <= 0
```

## for

stepは1固定です。残念ながら `break` と `continue` はありません。

```praat
for i from 1 to 5
	appendInfoLine: i
endfor
```

# procedure

**Praatではユーザー定義関数を作ることができません。代わりに、procedureを使用します。**
**Praatで最も個性的、かつ初見殺しな言語機能**です（※個人の感想です）。

procedureは関数のように使用できますが、**戻り値を返すことはできません**！

```praat
# procedure定義。ローカル変数は先頭に `.` を付ける
procedure hello: .name$
  appendInfoLine: "hello, ", .name$
endproc

# `@` でprocedure呼び出し
@hello: "Tom" ; "hello, Tom"
```

計算結果を受け取りたい場合は、**procedureのスコープ内を参照します**。

```praat
procedure myProc:
  .val = 1
endproc

@myProc:
# myProcのローカル変数は外側からも参照可能！
appendInfoLine: myProc.val ; 1
```

正直「:thinking: :question:」かと思います。実は、procedureの実態は関数よりも「**名前空間変更を伴うgoto**」に近いです。

procedureの呼び出しによって `myProc` 内部にジャンプします。以後変数は `myProc` 名前空間内に宣言されるので、 `.val = 1` は `myProc.val = 1` と同等です。
そして、**`myProc` のスコープを抜けた後も変数は死にません**。`myProc.val` は外部からも参照できるので、実質的な戻り値として使えるというわけです。

procedureについてはまだまだ語りつくせないので、詳細については「Effective Praat Script」の方に書きたいと思います。

# ユーザー入力

ユーザーからの入力は `form` で受け取れます。

```praat
form Input your profile!
	# 型名 変数名 デフォルト値の順に記載
	word firstName Taro
	word lastName Yamada
endform

# 変数としてコードで使用する際には型( `$` )の指定が必要
appendInfoLine: "Hello, 'firstName$' 'lastName$'!"
```

実行するとフォーム画面が出てくるので、入力して「OK」を押します。
（ちなみに、キャンセルするとプログラムは終了します）

![praat_form.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/c804a63e-e292-1322-8050-783c2f1589fe.png)


入力内容でinfoに表示されました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239887/598c6731-fb9e-094c-5ec7-090b00d5efe3.png" width="50%">

**フォームで指定できる型は変数のデータ型と異なる分類**なので注意が必要です。

https://www.fon.hum.uva.nl/praat/manual/Scripting_6_1__Arguments_to_the_script.html

# ファイル入出力
## 入力

`readFile$` でファイルの文字列を読み込めます。

```:~/foo.txt
this is my file
```

```praat
s$ = readFile$("~/foo.txt")
appendInfoLine: s$ ; this is my file
```

実際のコードでは、読み込みエラーが起きないよう事前に存在確認しておきましょう。

```praat
fileName$ = "~/foo.txt"
if fileReadable(fileName$)
	s$ = readFile$("~/foo.txt")
	appendInfoLine: s$
endif
```

行ごとに分割したい場合は `readLinesFromFile$#` を使います。

```:~/bar.txt
line1
line2
line3

```

```praat
fileName$ = "~/bar.txt"
if fileReadable(fileName$)
	lines$# = readLinesFromFile$#(fileName$)
	appendInfoLine: lines$#[2] ; line2

    # 末尾の空行は無視される
	appendInfoLine: size(lines$#) ; 3
endif
```

組み込み関数を使用することで、音声ファイル等文字列以外のファイルも読み込めます。（音声分析の際にはこちらの方がよく使うと思います）

参考

https://github.com/Syuparn/praat_batch_analysis/blob/master/f0statistics.praat#L57

## 出力

文字列のファイル出力は、標準出力と似たような書き方で行えます。

```praat
fileName$ = "~/hello.txt"
writeFileLine: fileName$, "Hello, world!"
```

```:~/hello.txt
Hello, world!
```

追記の場合は `appendFileLine` を使います。

```praat
fileName$ = "~/hello.txt"
appendFileLine: fileName$, "See you again"
```

```:~/hello.txt
Hello, world!
See you again
```

# おわりに

以上、Praat Scriptの文法の紹介でした。駆け足になってしまいましたが、「こんな文法の言語もあるのか」、「音声分析に使ってみようかな」等思っていただけたら幸いです。

次回の記事「[Effective Praat Script](https://qiita.com/Syuparn/items/f2e108f370836c4567cb)」ではより実践的なテクニックをまとめていきたいと思います！

[^syntax]: 記事はまだ少ないですが、Qiitaでシンタックスハイライト対応しています！もっと有名にしよう！
[^type]: 最近追加された文法なので、Qiita(Rouge)のシンタックスハイライトがまだ効いていません :sob:
[^sigil]: ただし、シジルと違って型変換(`a` で `a#` の要素数が取れる等)はできません。型が別であれば同名でも全く別の変数として扱われます。
[^pseudoarray]: ベクトル型ができるよりずっと前からあった機能なので、歴史的経緯だと思います。