Praatスクリプト入門(3):周波数を変更してみる その1

概要
Praatスクリプトの入門講座その3。変数の用い方、コメントの書き方、if による条件分け。

周波数の違いで音がどう変わるか確かめよう

本入門講座の目次は「Praatスクリプト入門:目次」というページをご覧いただきたい。また、前回の講座を見たい人は「Praatスクリプト入門(2):音を合成してみる」というページをご覧いただきたい。

前回は、以下のようにして、1000 Hzの正弦波を作成した。
do("Create Sound from formula...", "sineWave1000", 1, 0.0, 1.0, 44100, "sin(2*pi*1000*x)")
writeInfoLine("Created sineWave1000")
do("Play")
appendInfoLine("Played sineWave1000")
do("Remove")

このスクリプトの1行目の sin(2*pi*1000*x) というのが、この正弦波の形を示した数式だ。sin(2*pi*1000*x) の 1000 が、この正弦波が1000 Hzであるということを示している。正弦波の周波数を変えたい場合、sin(2*pi*1000*x) の 1000 を別の数字に変える必要がある。

一点イ(A4)の基本周波数は440 Hzである。
一点イ(A4)の基本周波数は440 Hzである。

440 Hzの正弦波を作ってみよう。実は、440 Hzというのは、音楽で言うと、一点イ(A4)の音の基本周波数に当たる。この正弦波の数式は sin(2*pi*440*x) なので、スクリプトは次のようになる。1000 Hzの正弦波を作るスクリプトを少し書き換えるだけだ。どこが書き換えられたのか確認しながら、以下のスクリプトを見てほしい。

do("Create Sound from formula...", "sineWave440", 1, 0.0, 1.0, 44100, "sin(2*pi*440*x)")
writeInfoLine("Created sineWave440")
do("Play")
appendInfoLine("Played sineWave440")
do("Remove")

1000 Hzの正弦波を作るスクリプトからの変更点は以下の4つである。1つずつ直そう。

作成する音の周波数を変えてみよう

1000 Hzや440 Hz以外の周波数の正弦波を上の例にならって作成してみよ。例えば、262 Hzにするとどういう音ができるか確かめてみよう。

変数を使って楽をしよう

1000 Hzの正弦波を作るスクリプトから440 Hzの正弦波を作るスクリプトに変更した際に、同じ変更をした場所があった。sineWave1000 から、sineWave440 に変更するということを3回も繰り返しているのだ。3回ぐらいならば大したことはないと思うかもしれないが、もっと複雑なスクリプトを書く場合は、何十回も書き換える必要が出てくるので、大変な作業になる。また、ある場所は変更したのに、別の場所は変更するのを忘れたということも出てくるだろう。

こういった問題が起きないように、プログラムを作るときは、同じ作業はまとめて処理できるように組んでいく。

プログラムを作る場合、同じ作業はまとめられないか常に考える。

さて、変更する量を減らすには、sineWave1000 や sineWave440 と書く場所を1つに限定してしまうのが一番楽である。このようなときに便利なのが変数だ。

Praatのスクリプトの変数には、2種類ある。数値変数と、文字列変数だ。数値変数は文字通り数値を格納する変数だ。これに対して、文字列変数は文字列を格納する。

Praatのスクリプトでは、英語の大文字・小文字、それに数字を使って、変数の名前を自由につけることができる。ただし、変数の名前の最初の1文字は英小文字でなくてはならない。このため、userA や user1 というのは問題ないのだが、Auser や 1user というのは変数の名前として許されない。

文字列変数は、末尾にドル記号を付けなくてはならない。数値変数の末尾にはドル記号を付けてはならない。例えば、ant, student1 は数値変数となるし、elephant$, teacher1$ は文字列変数になる。

変数の名前で、大文字と小文字は区別される。例えば、dog , dOg , dOG , doG はそれぞれ別の変数と見なされる。

まず、数値変数から見てみよう。以下のスクリプトを見てほしい。

a = 50
b = 24
c = a+b
writeInfoLine(c)
d = a-b
appendInfoLine(d)

まず1行目で、a という数値変数に50という数値を代入している。Praatでは、変数に代入を行うときに、イコール(=)を用いる。イコールの右側にあるものを、イコールの左側にあるものに格納すると考えればよい。つまり、ここでは a の中身を50にしているわけである。同様に2行目では、b という数値変数に24という数値を代入している。

こうやって代入すれば、色々なところの計算で使える。例えば、3行目では a と b の和を別の数値変数 c に代入している。a には50が、b には24が格納されているわけだから、a+b は 50 + 24 = 74 となる。つまり、c には74という数値が代入される。a や b に代入する数値が変われば、a+b の結果も変わるので、c の中身も変わる。同様に5行目では a と b の差を数値変数 d に代入している。

4行目や6行目のように、writeInfoLine や appendInfoLine を使うと、数値変数に格納されている数値を表示することができる。前回、writeInfoLine や appendInfoLine を使ったときは、writeInfoLine(“Created sineWave1000”) のように、表示したい内容をダブルクォーテーションで囲んだ。これに対して、変数の中身を表示する場合は、変数をダブルクォーテーションで囲む必要はない。ダブルクォーテーションで囲んで writeInfoLine(“c”) としてしまうと、変数の中身ではなく、cという文字そのものが出力されてしまう。

Praat の数値変数の扱いに慣れる
  1. 上のスクリプトで、4行目が writeInfoLine で、6行目が appendInfoLine が用いられているのはなぜか。
  2. 上のスクリプトで、1行目で a に代入する数値を変更して、結果がどう変わるか確かめよ。また、2行目で b に代入する数値を変更してみよ。
  3. 上のスクリプトで、4行目と5行目の間に a = 30 という行を挿入するとどうなるか。結果を確かめよ。

さて、正弦波を合成するスクリプトの話に戻ろう。今度は文字列変数を使う。以下のスクリプトでは、soundName$ という文字列変数に、sineWave1000という文字列を代入する。先ほど述べたように、Praatでは、変数に代入を行うときに、イコール(=)を使う。イコールの右側にあるものをイコールの左側にあるものに格納するというのは、数値変数でも文字列変数でも同じである。ここでsineWave1000が文字列であるということをはっきりさせるために、ダブルクォーテーションで囲っている。Praatで文字列を文字列変数に代入する際は、このようにダブルクォーテーションで囲う必要がある。スクリプトの2行目では、writeInfoLine を使って、soundName$ に格納されているものを表示している。ここでは、sineWave1000と表示されるはずだ。

soundName$ = "sineWave1000"
writeInfoLine(soundName$)

writeInfoLine を使って変数の中身を表示させるときは、変数をダブルクォーテーションで囲んでしまわないようにしよう。ダブルクォーテーションで囲んでしまったら、soundName$という文字列そのものが表示されてしまうのだ。

Praat の文字列変数の扱いに慣れる
  1. 上のスクリプトで、代入する内容をsineWave1000からsineWave440にすると、結果はどうなるか。どうなるか想像した上で、実際に試してみよ。
  2. 上で soundName$ = “sineWave1000” のダブルクォーテーションを外して、soundName$ = sineWave1000 とするとどうなるか実際に試してみよ。
  3. 上で writeInfoLine(soundName$) でダブルクォーテーションを加えて、writeInfoLine(“soundName$”) とするとどうなるか。どうなるか想像した上で、実際に試してみよ。

文字列変数を使って、正弦波を合成するスクリプトを作り直してみよう。作り直した後のスクリプトは以下のようになる。作り直したスクリプトがちゃんと動くか確認してみよう。

soundName$ = "sineWave1000"
do("Create Sound from formula...", soundName$, 1, 0.0, 1.0, 44100, "sin(2*pi*1000*x)")
writeInfoLine("Created ", soundName$)
do("Play")
appendInfoLine("Played ", soundName$)
do("Remove")

基本的に言えば、今まで sineWave1000 と書いていたところを soundName$ に置き換えているだけである。ただ、3行目と5行目の書き方は今まで出てこなかったものなので、ここで説明したい。3行目では、Created という文字列と soundName$ という変数の中身とを表示する必要がある。文字列そのものを出力する場合は writeInfoLine(“hoge”) のようにダブルクォーテーションで囲めばよかった。これに対して、変数の中身を出力する場合は、ダブルクォーテーションで囲まずに、writeInfoLine(soundName$) のようにしなくてはならなかった。ここではダブルクォーテーションで囲むものと囲まないものを合わせて表示する必要がある。合わせるためには、コンマで区切る。だから、writeInfoLine(“Created “, soundName$) のようにコンマで2つの部分に分けたのだ。5行目も同様だ。

さて、先ほど1000 Hzの正弦波を作るスクリプトからの440 Hzのスクリプトにしたときは変更点が4つもあった。しかし、今のスクリプトでは2つ直すだけで済む。

しかし、もっと楽にできるはずだ。上記の2つの変更点は、いずれも 1000 を 440 に変えるだけのものだ。1000 を 440 に変えるという点で共通しているので、これらをまとめて処理することができるはずだ。

ここでも変数を使うことで楽になる。スクリプトを以下のようにすると、1行目の 1000 という数値を書き換えるだけで、音の名前も数式も一気に変更される。

frequency = 1000
soundType$ = "sineWave"
soundName$ = soundType$ + string$(frequency)
do("Create Sound from formula...", soundName$, 1, 0.0, 1.0, 44100, "sin(2*pi*frequency*x)")
writeInfoLine("Created ", soundName$)
do("Play")
appendInfoLine("Played ", soundName$)
do("Remove")

ここで一番ややこしいのは3行目の記述なので、これについて説明したい。3行目では、sineWave1000をくっつけて、sineWave1000という文字列を作っている。sineWaveは soundName$ という文字列変数に格納されている。また、1000 は frequency という数値変数に格納されている。文字列と数値をくっつけて、sineWave1000という文字列を作るのが目標である。しかし、Praatでは、文字列と数値を厳密に区別する。このため、そのままでは、文字列と数値をくっつけることができない。よって、まず数値を文字列に置き換える必要が出てくる。ここで役に立つのが、string$ という関数だ。これには数値を文字列に変換する働きがある。例えば、string$(42) とすると、数値の 42 を文字列の42に変換する。人間からすると見た目は42のままで変わらないのだが、Praatからすると数値から文字列へと大きく変わったことになる。何はともあれ、string$ を使うことで、文字列扱いされる1000が出てくる。これで、文字列であるsineWaveと対等の立場となり、くっつけることができるようになる。文字列と文字列をくっつけるには、プラス(+)を使う。プラスの左側の文字列の後に、プラスの右側の文字列をつなげるのだ。ここでのプラス(+)は数値の足し算をしているわけではない。

コメントを書いて分かりやすくしよう

さて、スクリプトを修正していくうちに、内容がどんどん長くなってしまった。今この入門講座の文章を読んでいる間は、内容をちゃんと理解していても、後で見直したときには内容が分からなくなってしまう可能性がある。このため、後から見直す際の覚え書きがあると良いだろう。

こういったときに便利なのがコメントである。コメントとして書いた記述は、Praatには無視されるので、覚え書きとして自由に使用できる。Praatでは、行頭にシャープ(#)を入れることで、その行がコメントと見なされる。スクリプトでの他の英数字と同様に、コメントのためのシャープはいわゆる半角文字の方を入力しなくてはならない。

プログラムを書くときはコメントをしっかり書こう。

以下のスクリプトの1行目と4行目がコメントだ。

# Input the frequency
frequency = 1000
soundType$ = "sineWave"
# Make the name of the sound
soundName$ = soundType$ + string$(frequency)
do("Create Sound from formula...", soundName$, 1, 0.0, 1.0, 44100, "sin(2*pi*frequency*x)")
writeInfoLine("Created ", soundName$)
do("Play")
appendInfoLine("Played ", soundName$)
do("Remove")

コメントを書き入れておけば、後で見直すときに助かる。また、コメントをしっかり書いておくと、別の人にプログラムを見てもらうときにも役立つ。

条件分けをしよう

先ほど作ったスクリプトでは、frequency という数値変数に、作りたい音の周波数の値を格納した。ところで、frequency は数値変数であるから、数値なら何でも入れることができる。例えば、-440 のような負数や 620000 のようなとても大きな数を入れることすらできてしまう。

極端な数値の周波数
  1. frequency に 440 と指定した場合と、-440 を指定した場合の音を比べてみよ。
  2. frequency に 620000 のような大きな数値を指定した場合、音はどうなるか。考えた上で、実際に試してみよ。

人間の聞くことができる音の範囲は50 Hzから20000 Hzと言われているから、この範囲に当てはまらない周波数を frequency に入れる意味はない。よって、frequency に50未満の値、あるいは20000を超えた値が代入された場合は音を合成しないように設定しよう。スクリプトではどう処理すれば良いのだろうか。今実行したいことは、frequency に格納された値によって音を合成するかしないかを決めるということである。こういうときに便利なのが、if を使った条件分けだ。

以下の例では、a に格納されている数値が正なら b$ にpositiveを、負なら b$ に negative を、さもなければ、b$ に zero を代入する。そして、最後に、b$ の中身を表示する。

a = -52
if a > 0
  b$ = "positive"
elsif a < 0
  b$ = "negative"
else
  b$ = "zero"
endif
writeInfoLine(b$)

1行目で a に代入する数値を変更した場合にどうなるか、色々試してみてほしい。

if 構造では、if と同じ行に条件を書き、それ以降の行に条件に当てはまる場合の命令を書く。どこまでが if で条件分けしている範囲かを分かりやすくするため、上記のスクリプトでは行頭にスペースを入れて読みやすくしている。このことをインデントをつけると言う。

インデントをつけることで if 構造を見やすくする。

なお、Praatは行頭に空白があってもなくても気にしない。あくまでも人間にとって見やすくするためにインデントをつけるのだ。

if 構造を理解する
  1. 先ほどのスクリプトで1行目で a に代入する値を変えたらどうなるかを確かめよ。色々な数値を入れてみよう。
  2. 先ほどのスクリプトに適切なコメントを加えよ。

さて、今までのスクリプトを修正して、50未満の数値や20000を超えた数値を排除しよう。なお、exit は、後に続く文字列を出力して、その場で終了する命令である [1] 。ここでは、frequency が20000より大きければ、4行目の exit によりToo big!というメッセージを出して終了する。これに対して、frequency が50未満ならば、6行目の exit によりToo small!というメッセージを出して終了する。

# Input the frequency
frequency = 1000
if frequency > 20000
  exit Too big!
elsif frequency < 50
  exit Too small!
else
  soundType$ = "sineWave"
  # Make the name of the sound
  soundName$ = soundType$ + string$(frequency)
  do("Create Sound from formula...", soundName$, 1, 0.0, 1.0, 44100, "sin(2*pi*frequency*x)")
  writeInfoLine("Created ", soundName$)
  do("Play")
  appendInfoLine("Played ", soundName$)
  do("Remove") 
endif
if 構造を理解する
  1. 上記のスクリプトで、frequency の値が30ならどうなるか。180ならどうなるか。
  2. 50 Hzでなく70 Hz未満の音を排除したい場合は、上記のスクリプトをどう変更すればよいか。
この回で扱った内容を復習しよう
  1. 周波数が2倍になると音はどのように変わるか。220 Hz, 440 Hz, 880 Hzの音を作って聞き比べてみよ。
  2. なぜプログラムを書くときは、コメントを書く必要があるのか。
  3. Praatのスクリプトで文字列と文字列をつなぐ場合にはどうすれば良いか。
  4. exit とは何をする命令か。
  5. Praatでは、if 構造をどのように書くか。
  6. なぜ if 構造を書くときには、インデントをつけるのか。

本入門講座の続きは「Praatスクリプト入門(4):周波数を変更してみる その2」というページをご覧いただきたい。

脚注
  1. exit の書き方は将来のPraatのバージョンアップで、writeInfoLine などの書き方に合わせて、exit(“Too big!”) のように変更される可能性が高いと考えられる。 []