はじめに
Praatは、音声分析や編集を行うことができるソフトウェアです。音声学でよく使用されます。
そして、PraatのUI上の操作を自動化するための言語が表題の「Praat Script」です。
(乱暴なたとえですがExcelとVBAのような関係です)
使いこなせば、音声ファイルが何億個あろうが、for文でぶん回して分析完了!GUIを徹夜でカチカチする必要はもうありません!
Praat Scriptは言語としても独特な機能を持っているので、マイナープログラミング言語ハンターの方々にもぜひ読んでいただければと思います 1。
そこで、Praat Scriptの言語機能、使い方を4回にわたって紹介したいと思います。
- Praat Script基礎文法最速マスター2022 (当記事)
- Effective Praat Script
- 悪用厳禁?Praat Scriptの黒魔術、謎文法
- Praat Scriptでゲームを作ってみよう
書いた動機
Praat Scriptの解説記事といえば「Praatスクリプト基礎文法最速マスター」(+その続編)が有名ですが(学生時代本当にお世話になりました )、その後9年間でPraatのメジャーバージョンが上がり、Praat Scriptの文法もさらに進化を遂げました。
そこで、本家様をリスペクトし、最新(v6.2系)の文法についても触れた、題して「Praat script基礎文法最速マスター2022」をまとめました。
バージョン
appendInfoLine: praatVersion$
; 6.2.11
言語の特徴
- インタープリター型
- 手続き型
-
ユーザー定義関数が存在しない
- 代わりにProcedure(計算用の名前空間)を使用
- 強い型付け
-
suffixによる変数の型の明示 (
num
,str$
,vec#
,mat##
,strvec#$
) - UIも作れる
- 1997年誕生(今年で25周年 )
バージョンごとの新機能はこちらで確認できます。
インストール
まずはPraatをインストールしましょう。公式ページの左上「Download Praat」から最新版のzipをダウンロードします。解凍したら、パス等設定しなくてもそのまま起動できます。
実行の仕方
左上「Praat→New Script」でPraat Scriptのエディタが開きます。
コードを書いたら「Run」を押す(または Ctrl+R)と実行します。文字列を出力した場合「Praat Info」のダイアログに表示されます。
もちろん、事前に書いたソースコードを開いて実行することも可能です。
また、「Edit→Paste History」で今までのUI操作をPraat Scriptに変換してくれます。最初は手作業でやって、それをもとに自動化コードを書くといった使い方も可能です。
今はPraat Scriptのエディタを開いただけなので、Paste Historyで New Praat Script
が貼り付けられました。
Hello, world!
appendInfoLine
でダイアログに文字列を表示します。
clearinfo
appendInfoLine: "Hello, world!"
前に出力した文字列が残ってしまうので、 最初にclearinfo
でダイアログを真っさらにしています。以下のコードでは省略するので、先頭に clearinfo
を適宜おぎなって読んでください。
コメント
行頭コメントは #
, 行中コメントは ;
で書けます。
# 行頭コメント
appendInfoLine: "Hello, world!" ; 行中コメント
変数と代入
一般的なスクリプト系言語のように、代入時に初期化できます。ただし、変数名は小文字で始める必要があります。
n = 10 ; 宣言&初期化
# 上書きも可能
n = 20
# 以下は文法エラー
; N = 100
改行
...
で途中改行できます。
appendInfoLine: "very very
... long string" ; very very long string
sum = 1 + 2 + 3
...+ 4 + 5 +
...6 ; 演算子の前でも後でも改行可能
注意点として、 ...
の前の行には行中コメントを入れることはできません。
変数の型
Praat Scriptのユニークな点1つ目。
Praat Scriptでは、変数の型を末尾の記号で表します 2。Perlを使ったことがある方なら、シジルをイメージしてしていただくと分かりやすいです 3 。
# 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
になったりしないので安全ですね。
# エラー!
; appendInfoLine: 1+"2" ; cannot add a string to a number
# 明示的に変換が必要
appendInfoLine: string$(1)+"2" ; "12"
vector等の要素参照/更新
[i]
で参照できます。1始まりなので要注意!
mutableなので要素を書き換えることも可能です。
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
(疑似)配列/辞書
連想配列のような見た目の変数を作れます。ただしこちらはベクトルのように全要素を一括で操作することはできません4。
pseudoArr[1] = 100
pseudoArr["foo"] = 5
pseudoArr$[10] = "bar"
演算子
基本は一般的な言語と同様です。
四則演算等
# 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
)になります。
appendInfoLine: 6 / 0 ; --undefined--
appendInfoLine: (-4) ^ 0.5 ; --undefined--
比較演算子
bool型は無いため、trueは 1
、falseは 0
と表現されます。
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
論理演算子
# 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をパーセント表記できます。割合を指定する際には分かりやすいです。
appendInfoLine: 30% ; 0.3
組み込み関数
数値計算や文字列処理に使いたい関数は大抵そろっています。多いので一部だけ。
数値計算
appendInfoLine: abs(-3) ; 3
appendInfoLine: max(1, 5, 2) ; 5
appendInfoLine: log10(2) ; 0.3010...
文字列処理
関数名の後ろには、戻り値型の記号がつきます。
# 長さ
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"
リファレンスに関数の一覧が載っています。
制御構造
if
特に変わったところはありません。
条件式にはnumberしか入れることができません (0
以外はtruthy)。
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
n = 5
while n > 0
appendInfoLine: n
n -= 1
endwhile
repeat
こちらは他言語のdo whileに相当します。
n = 5
repeat
appendInfoLine: n
n -= 1
until n <= 0
for
stepは1固定です。残念ながら break
と continue
はありません。
for i from 1 to 5
appendInfoLine: i
endfor
procedure
Praatではユーザー定義関数を作ることができません。代わりに、procedureを使用します。
Praatで最も個性的、かつ初見殺しな言語機能です(※個人の感想です)。
procedureは関数のように使用できますが、戻り値を返すことはできません!
# procedure定義。ローカル変数は先頭に `.` を付ける
procedure hello: .name$
appendInfoLine: "hello, ", .name$
endproc
# `@` でprocedure呼び出し
@hello: "Tom" ; "hello, Tom"
計算結果を受け取りたい場合は、procedureのスコープ内を参照します。
procedure myProc:
.val = 1
endproc
@myProc:
# myProcのローカル変数は外側からも参照可能!
appendInfoLine: myProc.val ; 1
正直「 」かと思います。実は、procedureの実態は関数よりも「名前空間変更を伴うgoto」に近いです。
procedureの呼び出しによって myProc
内部にジャンプします。以後変数は myProc
名前空間内に宣言されるので、 .val = 1
は myProc.val = 1
と同等です。
そして、myProc
のスコープを抜けた後も変数は死にません。myProc.val
は外部からも参照できるので、実質的な戻り値として使えるというわけです。
procedureについてはまだまだ語りつくせないので、詳細については「Effective Praat Script」の方に書きたいと思います。
ユーザー入力
ユーザーからの入力は form
で受け取れます。
form Input your profile!
# 型名 変数名 デフォルト値の順に記載
word firstName Taro
word lastName Yamada
endform
# 変数としてコードで使用する際には型( `$` )の指定が必要
appendInfoLine: "Hello, 'firstName$' 'lastName$'!"
実行するとフォーム画面が出てくるので、入力して「OK」を押します。
(ちなみに、キャンセルするとプログラムは終了します)
入力内容でinfoに表示されました。
フォームで指定できる型は変数のデータ型と異なる分類なので注意が必要です。
ファイル入出力
入力
readFile$
でファイルの文字列を読み込めます。
this is my file
s$ = readFile$("~/foo.txt")
appendInfoLine: s$ ; this is my file
実際のコードでは、読み込みエラーが起きないよう事前に存在確認しておきましょう。
fileName$ = "~/foo.txt"
if fileReadable(fileName$)
s$ = readFile$("~/foo.txt")
appendInfoLine: s$
endif
行ごとに分割したい場合は readLinesFromFile$#
を使います。
line1
line2
line3
fileName$ = "~/bar.txt"
if fileReadable(fileName$)
lines$# = readLinesFromFile$#(fileName$)
appendInfoLine: lines$#[2] ; line2
# 末尾の空行は無視される
appendInfoLine: size(lines$#) ; 3
endif
組み込み関数を使用することで、音声ファイル等文字列以外のファイルも読み込めます。(音声分析の際にはこちらの方がよく使うと思います)
参考
出力
文字列のファイル出力は、標準出力と似たような書き方で行えます。
fileName$ = "~/hello.txt"
writeFileLine: fileName$, "Hello, world!"
Hello, world!
追記の場合は appendFileLine
を使います。
fileName$ = "~/hello.txt"
appendFileLine: fileName$, "See you again"
Hello, world!
See you again
おわりに
以上、Praat Scriptの文法の紹介でした。駆け足になってしまいましたが、「こんな文法の言語もあるのか」、「音声分析に使ってみようかな」等思っていただけたら幸いです。
次回の記事「Effective Praat Script」ではより実践的なテクニックをまとめていきたいと思います!