Praatスクリプト入門(6):実験画面を作る

概要
Praatスクリプトの入門講座その6。ポーズ機能を使ってユーザの入力を待つ画面を作る方法を扱う。

被験者に聞こえたかどうかを質問する

本入門講座の目次は「Praatスクリプト入門:目次」というページをご覧いただきたい。また、前回の講座を見たい人は「Praatスクリプト入門(5):入力用ウィンドウを作る」というページをご覧いただきたい。

この入門講座で行うべき実験の手順を確認しよう。手順は大まかに言えば、次の通りになる。

  1. ある周波数の音を合成する(周波数の値はだんだん高くする)
  2. 合成した音を流す
  3. その音が聞こえたか被験者に質問する
    • 被験者が「聞こえた」と答えた場合、最初に戻る
    • 被験者が「聞こえなかった」と答えた場合、そこで中断する

1番目の「ある周波数の音を合成する」は問題なくできるはずだ。適切な設定を行った上で、今までと同様に do と Create Sound from formula… を組み合わせるだけだ。

2番目の「合成した音を流す」も簡単である。例によって、do と Play を使う。

問題は3番目の「その音が聞こえたか被験者に質問する」だ。

前回扱ったフォームを使えばよいだろうと考えた人がいるかもしれない。しかし、フォームはこの状況には適していない。実は1つのスクリプトにつき、フォームは1回しか使えない。この実験では、音が聞こえたかどうかを何度も質問しなくてはならないから、1回しか使えないのでは困るのだ。しかも、フォームを出せるのはスクリプトが動き出した直後に限られる。

こういう時に便利なのが、Praatスクリプトのポーズ(pause)機能である。ポーズ機能は、基本的に言えば、ユーザからの返事を待つための機能だ。フォームと違って、1つのスクリプトの中で何度も使うことができる。

フォームはスクリプトの中で1回しか使えない。ポーズは複数回使うことができる。

ポーズ機能を働かせるための最も簡単な方法は、pause と打ち込んで、その後に表示したいメッセージを入力することだ。次のスクリプトを入力して、実行してみてほしい。

writeInfoLine("Start")
pause Are you ready?
writeInfoLine("Done")

このスクリプトを実行すると、まず1行目の記述により Praat Info ウィンドウにStartというメッセージが表示される。その後、2行目の記述により、即座に Pause: stop or continue というタイトルのウィンドウが出てくる。このウィンドウにはAre you ready?というメッセージが表示されているはずだ。先ほど、スクリプトの2行目に pause Are you ready? と書いたが、pause の後に書いた Are you ready? というメッセージが、ウィンドウにそのまま表示されるわけである。また、ウィンドウの下部には、Stop と Continue というボタンが配置されている。このウィンドウが表示されている間は、Praatはユーザの返事を待っている状態になる。待っている間は、Praatは何もしない状態になる。ユーザが返事をするには、ウィンドウ下部のボタンのいずれか、ここでは Stop か Continue のいずれかを押す。

とりあえず、Continue というボタンを押そう。Continueとは英語で「続ける」という意味だ。すると、スクリプトの3行目の記述により writeInfoLine が実行され、Praat Info ウィンドウに Done という文字列が表示される。ここでのPraatの動きを確認しよう。まず、2行目の pause で、ユーザからの返事を受け取るための Pause: stop or continue というウィンドウを提示し、ユーザからの返事が来るまでPraatは待機している状態になる。2行目で待機しているので、3行目の命令は実行されなかった。ここで、ユーザが Continue というボタンを押すと、Praatは「続けてもよい」と認識し、スクリプトの続き、ここでは3行目の writeInfoLine を実行する。つまり、Continue を押すことで、文字通り、スクリプトの「続き」を実行したわけである。

さて、先ほどの Pause: stop or continue というタイトルのウィンドウで、Continue というボタンを押す代わりに、Stop というボタンを押すとどうなるのだろうか。Stop とは英語で「止める」という意味だ。このボタンを押すと、そこでスクリプトが中断される。先ほどのスクリプトをもう1度実行してみてほしい。すると、前と同じようにウィンドウが出てくるので、Stop ボタンを押そう。

Stop ボタンを押すと、次のようなエラーメッセージが表示される。

You interruputed the script.
Script line 2 not performed or completed:
≪ pause Are you ready? ≫
Menu command “Run” not completed.

このエラーメッセージは、「あなたはスクリプトを中断しました。スクリプトの2行目が終了しませんでした。」ということを意味している。Stop ボタンを押すと、その場でスクリプトの実行が中断される。ここでは、2行目に pause を書いたので、2行目で中断する。中断されたのだから、それ以降の記述は実行されない。ここでは、3行目の writeInfoLine が実行されず、Praat Info ウィンドウに Done という文字列が表示されることはない。このため、Praat Info ウィンドウには1行目の writeInfoLine で表示された Start という文字列が残ったままになっているはずである。

このように pause を使うと、続けるか中断するかをユーザに選択させることができる。このしくみを使えば、音が聞こえたかどうかを被験者に質問し、聞こえたら続け、聞こえなかったら中断するということができる。

pause Did you hear any sounds?

例えば、このようにスクリプトを書けば、被験者にDid you hear any sounds?(何か音が聞こえましたか?)というメッセージを表示して、Continue か Stop かをユーザに選ばせることができる。

しかし、このままでは被験者にとって使いづらい面がある。「音が聞こえたか」と質問されたのに、答えとしては Continue (続ける)と Stop (中断する)の2つしかないというのは変だ。ここはもっと分かりやすく、Yes (はい)と No (いいえ)の2つの選択肢を挙げたいところだ。

実は、うまく設定することで、ポーズ機能をこういった場合でも使いやすくすることができる。単にポーズ機能を動かしたければ、pause で済むが、ポーズ機能を細かく設定したければ、pause の代わりに beginPause と endPause を使う必要がある。beginPause と endPause を使ってポーズ機能を記述するのは、form と endform を使ってフォームを記述するのとよく似ている。しかし、後で述べるようにポーズとフォームでは記述方法にいくつかの違いがある。

簡単な例を見てみよう。次のスクリプトを入力して実行してほしい。beginPause と endPause は、P が大文字になっていることに気をつけよう。

writeInfoLine("Start")
beginPause ("Question")
  comment ("Did you hear any sounds?")
endPause ("Yes", "No", 1)
writeInfoLine("Done")

このスクリプトを実行すると、Praat Info ウィンドウにStartと表示された後、Pause: Question というタイトルのウィンドウが出てくる。そして、Did you hear any sounds?というメッセージと、Stop , Yes , No という3つのボタンが表示されるはずだ。Stop ボタンを押せばそこで中断され、5行目の writeInfoLine(“Done”) は実行されない。また、Yes あるいは No を押した場合は、スクリプトの続きが実行され、5行目の記述に基づき、Praat Info ウィンドウにDoneと表示される。

このスクリプトでのポーズの記述について簡単に説明する。まず、2行目の beginPause の直後に、(“Question”) と書いてあるのがウィンドウのタイトルになる。これは、フォームを記述する際に、form の後にフォームのタイトルを記述したことと似ている。ただし、form では form Question のようにタイトルとして表示したいものをそのまま書けばよかったのだが、beginPause では表示したいものをダブルクォーテーションで囲った後 [1] 、さらに丸括弧で囲む必要がある。書き方がまぎらわしいので注意しよう。

3行目は、comment を使って、ウィンドウに表示するメッセージを設定している。これは、基本的にフォームの中の comment と同じである。ただし、記述の方法が少し異なっている。フォームの中では comment Did you hear any sounds? のように表示したいメッセージをそのまま書けばよかったの が、ポーズ機能の記述の際には comment (“Did you hear any sounds?”) のようにダブルクォーテーションと丸括弧で囲む必要がある。

4行目では、endPause と書いて、ポーズ機能の記述を終えている。また、endPause の後の、(“Yes”, “No”, 1) は、ウィンドウに表示するボタンの記述である。最後に書いてある 1 という数字 [2] はとりあえず無視して、残りの部分に着目してほしい。”Yes” と “No” という記述があるので、Yes と No というボタンが表示される。また、特に何もしなければ、Stop ボタンは自動的に表示される。Stop を表示させない方法は後で扱う。

ボタンはいくつ並べてもかまわない。例えば、次のスクリプトでは、Stop ボタンの他に、Black, Blue, Green, Pink, Purple, Red, White, Yellow というボタンが表示される。ボタンの数がいくつになっても、Stop ボタンを押せば中断し、それ以外のボタンを押せば継続する。

beginPause ("Question")
  comment ("What is your favorite color?")
endPause ("Black", "Blue", "Green", "Pink", "Purple", "Red", "White", "Yellow", 1)

ところで、先ほど endPause (“Yes”, “No”, 1) と書いたときに、最後の 1 についてちゃんと説明しなかった。この数値は、デフォルトのボタンの番号を指定するものである。ここでは1番目のボタン、すなわち Yes ボタンがデフォルトのボタンとして扱われる。ポーズのウィンドウが表示されたときに、キーボードでエンターキーを押すと、このデフォルトのボタンを押したと見なされる。なお、デフォルトのボタンは、そうでないボタンと、影の描き方が異なって表示される。先ほどのスクリプトを実行したとき、Yes のボタンについている影は、他のボタンの影と違っていたはずだ。デフォルトとするボタンを変えたければ、この数値を書き換えることになる。例えば、先ほどのスクリプトで、デフォルトを No のボタンにしたければ、No は2番目なので、endPause (“Yes”, “No”, 2) と書く。

どのボタンもデフォルトのボタンにしたくなければ、endPause (“Yes”, “No”, 0) のように 0 と書く。要するに、0番目という存在しないボタンをデフォルトにするので、実質的にはデフォルトのボタンが存在しなくなる。この入門講座で行う実験からすると、デフォルトのボタンを設定しない方が無難なので、これからは endPause (“Yes”, “No”, 0) としておこう。被験者にはマウスを使って答えてもらうが、その際うっかりキーボードに触れてしまうかもしれない。触れたときにエンターキーを押してしまったら、デフォルトのボタンを設定していれば、そのボタンを押したと見なされてしまう。

次に、ユーザが押したボタンの情報を得る方法について説明しよう。

beginPause ("Question")
  comment ("Did you hear any sounds?")
clicked = endPause ("Yes", "No", 0)

writeInfoLine("You clicked Button ", clicked, ".")

このスクリプトの3行目では、イコール(=)を使って代入している。具体的に言うと、endPause の結果を clicked に代入している。endPause の結果というのは、どのボタンを押したのかという情報に他ならない。とりあえず、このスクリプトを実行し、Yes というボタンを押してほしい。すると、You clicked Button 1.と表示されるはずだ。Yes というボタンは1番目のボタンなので、このボタンを押すことで、endPause の結果が1になり、その結果が clicked に代入されるわけである。ここでは clicked という名前の数値変数を使っているが、他の名前にしてもかまわない。Stop ボタンを押した場合は、そこで処理が中断するので、代入が行われない。

ポーズ機能の結果

先ほどのスクリプトで、No というボタンを押すと、どんなメッセージが表示されるか。

さて、先ほどのスクリプトでは、Stop というボタンが出てきてしまう。Yes と No さえあれば問題ないので、Stop ボタンは消したいところである。Stop ボタンを消すには次のスクリプトの3行目のようにする。

beginPause ("Question")
  comment ("Did you hear any sounds?")
clicked = endPause ("Yes", "No", 0, 2)

writeInfoLine("You clicked Button ", clicked, ".")

このスクリプトを実行して、Stop ボタンが消えていることを確認してほしい。なお、endPause (“Yes”, “No”, 0, 2) の 0 は今までと同じようにデフォルトのボタンが何番目かを示している。

ポーズ機能の挙動
  1. 先ほどのスクリプトを実行し、Yes ボタンも No ボタンも押さずに、Pause: Question ウィンドウを閉じると、どうなるか。
  2. 先ほどのスクリプトの3行目の数字の 2 を 1 に改めて、clicked = endPause (“Yes”, “No”, 0, 1) とせよ。その後、スクリプトを実行し、Yes ボタンも No ボタンも押さずに、Pause: Question ウィンドウを閉じよ。Windowsならばウィンドウ右上の×印のボタンを押すことでウィンドウを閉じることができる。閉じるとどうなるか。
  3. endPause の最後の数、すなわち、endPause (“Yes”, “No”, 0, 2) でいうところの 2 の場所に来る数はどんな働きをしていると考えられるか。

実験用のスクリプトを完成させる

今までやってきたことを踏まえれば、実験用のスクリプトの完成まであと一息だ。

具体的にどれぐらいの周波数の音を聞かせるかは今までちゃんと定めていなかった。ここでしっかり決めておこうと思う。最初は10000 Hzの正弦波を聞かせることにする。11000 Hz, 12000 Hz, 13000 Hzと1000 Hzずつ高くしていき、20000 Hzの正弦波でやめることにしよう。

10000 Hzからはじめて、1000 Hzずつ高くしていき、20000 Hzまで再生するスクリプトは次の通りになる。

multiplier = 1000

for i from 10 to 20
  frequency = multiplier * i
  formula$ = "sin(2*pi*" + string$(frequency) + "*x)"
  do("Create Sound from formula...", "sound", 1, 0.0, 1.0, 44100, formula$)
  do("Play")
  do("Remove")
endfor
周波数の変更
  1. 8000 Hzからはじめて、500 Hzずつ大きくしていき、18000 Hzまで再生したい場合、このスクリプトをどう変更すればよいか。
  2. 逆に18000 Hzからはじめて、500 Hzずつ小さくしていき、8000 Hzまで再生したい場合、このスクリプトをどう変更すればよいか。
  3. 先ほどのスクリプトでは、1つの周波数の音が1回しか流れない。複数回流したい場合はどうすればよいか。

先ほどのスクリプトに、被験者への質問をするためのポーズに関する記述を加えたのが次のスクリプトである。音が1回再生されるごとに、ポーズのウィンドウが出現し、Yes か No のボタンを押す必要が出てくる。なお、このスクリプトでは、Yes を押した場合と No を押した場合とで違いはない。

multiplier = 1000

for i from 10 to 20
  frequency = multiplier * i
  formula$ = "sin(2*pi*" + string$(frequency) + "*x)"
  do("Create Sound from formula...", "sound", 1, 0.0, 1.0, 44100, formula$)
  do("Play")
  do("Remove")
  beginPause ("Question")
    comment ("Did you hear any sounds?")
  clicked = endPause ("Yes", "No", 0, 2)
endfor

さて、Yes を押した場合と No を押した場合とで違いがないと困るだろう。実験の際には、Yes が押されたらそのまま継続し、for によるループを繰り返することが望まれる。また、No が押されたら中断して for によるループを繰り返さないようにしたいところである。先ほどのスクリプトでは、clicked にどのボタンを押したかという情報が格納されている。これを使って、動作を振り分けよう。if を使えば簡単に振り分けることができる。

Yes が押されたら clicked に1が、No が押されたら clicked に2が格納される。だから、clicked の中身が1なら継続、2なら中断すればよい。次のスクリプトを見てほしい。13行目から15行目で if を使って「clicked の中身が2なら中断する」という記述がなされている。実際に試してうまく動くか見てほしい。

multiplier = 1000

for i from 10 to 20
  frequency = multiplier * i
  formula$ = "sin(2*pi*" + string$(frequency) + "*x)"
  do("Create Sound from formula...", "sound", 1, 0.0, 1.0, 44100, formula$)
  do("Play")
  do("Remove")
  beginPause ("Question")
    comment ("Did you hear any sounds?")
  clicked = endPause ("Yes", "No", 0, 2)

  if clicked = 2
    exit Clicked No button.
  endif

endfor
スクリプトの説明
  1. このスクリプトの8から10行目が何をしているのか説明せよ。また、10行目の endPause の後の括弧の中にあるものについても説明せよ。
  2. このスクリプトでは、「clicked の中身が1なら継続」ということを書いた if がない。なぜそれでもうまく動くのか。

先ほどのスクリプトでは、全部の音が聞こえた場合、何も表示されないままに終わってしまう。また、聞こえない音があって No を押した場合、その音の周波数がいくつだったのかを表示しないまま終了した。これでは不都合なので、周波数がどれだけだったか出力するようにしよう。周波数が Praat Info ウィンドウに出力するようにしたのが次のスクリプトである。

multiplier = 1000

for i from 10 to 20
  frequency = multiplier * i
  formula$ = "sin(2*pi*" + string$(frequency) + "*x)"
  do("Create Sound from formula...", "sound", 1, 0.0, 1.0, 44100, formula$)
  do("Play")
  do("Remove")
  beginPause ("Question")
    comment ("Did you hear any sounds?")
  clicked = endPause ("Yes", "No", 0, 2)

  if clicked = 2
    previousFrequency = frequency - multiplier
    writeInfoLine("Maximum audible frequency is in the range from ", string$(previousFrequency), " to " + string$(frequency) + ".")
    exit Clicked No button.
  endif

endfor

writeInfoLine("Maximum audible frequency is more than " + string$(frequency) + ".")
スクリプトの説明

このスクリプトの13から17行目が何をしているのか説明せよ。特に previousFrequency が何を表しているのか注意して説明せよ。

先ほどのスクリプトでは、No を押すと、16行目の記述により exit が発動して、エラーメッセージとともに終了する。仕方がないと言えば仕方がないのだが、エラーという訳でもないのに、エラーメッセージが出るのは何だか変である。exit を使わずに終了させる方法はないのだろうか。

ややトリッキーな手法になるが、goto と label を使うことで、終了まで持っていくことができる。なお、goto はこれだけで一単語である。go to のように go と to の間に空白を入れないでほしい。

次のスクリプトを実行して、No を押しても、エラーメッセージ無しで終了することを確認しよう。

multiplier = 1000

for i from 10 to 20
  frequency = multiplier * i
  formula$ = "sin(2*pi*" + string$(frequency) + "*x)"
  do("Create Sound from formula...", "sound", 1, 0.0, 1.0, 44100, formula$)
  do("Play")
  do("Remove")
  beginPause ("Question")
    comment ("Did you hear any sounds?")
  clicked = endPause ("Yes", "No", 0, 2)

  if clicked = 2
    previousFrequency = frequency - multiplier
    writeInfoLine("Maximum audible frequency is in the range from ", string$(previousFrequency), " to " + string$(frequency) + ".")
    goto point
  endif

endfor

writeInfoLine("Maximum audible frequency is more than " + string$(frequency) + ".")

label point

このスクリプトの16行目では、goto point と書かれている。これは文字通り、point という場所に行けという命令だ。それでは、point という場所はどこにあるのだろうか。Praatでは場所を定義するのに label というものを使う。スクリプトの最後の行を見てほしい。ここに label point と書いてある。なので、この最後の行が point という場所になる。この場所より先にスクリプトの記述はないわけだから、ここに来てしまえばスクリプトは終わる。ここでは point という名前をつけたが、別の名前でも問題ない。

goto と label の使い方

先ほどの例では、スクリプトの最後の場所に行ったが、goto と label を使ってスクリプトの最初に戻るにはどうすればよいか。

実際に実験する際に

この入門講座で紹介したスクリプトで実験をする際には、次のようなことに注意するとよいだろう。

また、先ほどのスクリプトでは、writeInfoLine を使って、Praat Info ウィンドウに周波数の情報を出力していた。Praat Info ウィンドウに出力されたものをファイルに保存するには、Praat Info ウィンドウのメニューから File を選んでクリックした上で、Save as… を選ぶ。このようにファイルに保存すれば、出力結果をあとで利用しやすくなる。

しかし、手作業で毎回ファイルに保存するのは面倒である。スクリプトからもファイルに保存できるので、その方法を紹介しよう。

次のスクリプトでは、output$ という文字列変数の中身(ここでは、This is an example)を result.txt というテキストファイルに書き出している。result.txt というテキストファイルが存在しなければ、Praatが勝手に result.txt というファイルを作る。また、既に result.txt というファイルが存在していたら、そのファイルを上書きする。つまり、もともと result.txt に書かれていた内容は消されるので、注意しよう。なお、result.txt は、通常、Praatが置かれているフォルダに作られるはずだ。

output$ = "This is an example."
writeFileLine("result.txt", output$)

上書きでなく、既に書かれている内容の末尾に追記したい場合は、次のようにする。

output$ = "This is an example."
appendFileLine("result.txt", output$)

writeFileLine による上書きと appendFileLine による追記の関係は、writeInfoLine と appendInfoLine の関係と同じである。writeFileLine や writeInfoLine では今まで書いてあったものがすべて消される。これに対して、appendFileLine や appendInfoLine は、今まで書いてあったものの下に出力を付け加えり。なお、先ほど扱った周波数の実験スクリプトに、ファイル保存の操作を組み込んだスクリプト例はここでは挙げないので、各自考えてみてほしい。

この回で扱った内容を復習しよう
  1. pause と form の違いは何か。
  2. goto と label でどのようなことができるか。
  3. 先ほどの実験スクリプトでは、最初の周波数の音が聞こえなかった場合、どうなるか。実は問題があるので、どう改めればよいか考えよ。
  4. 先ほどの実験スクリプトでは、聞こえたかどうかの回答はあくまでも自己申告によるものであった。もしかしたら、聞こえていないのに Yes のボタンを押してしまう人もいるかもしれない。こういう人への対策を考えてみよ。

今回のPraatスクリプトの入門講座は、この第6回でひとまず完結となる。ここまで見てくれてありがとうございました。

脚注
  1. ダブルクォーテーションで囲っているのは、ダブルクォーテーションの中身を文字列として認識させるためである。 []
  2. この最後の数値を間違ってダブルクォーテーションで囲まないようにしよう。ダブルクォーテーションで囲って endPause (“Yes”, “No”, “1”) とするとちゃんと動かなくなる。ダブルクォーテーションで囲む必要があるのは文字列であり、数値は囲む必要がない。 []