10. 音声と動画を刺激に用いよう

PsychoPy Builderでは音声ファイルや動画ファイルを再生することもできます。動画再生機能についてはいろいろと技術的な難しさがあって、どのような動画ならば問題なく再生できるとは簡単には判断できないのですが、試してみることはそんなに難しくないのでぜひみなさん自身のPCで動かして確認してみてください。前章に引き続き、まとまった実験を作成するのではなくデモと解説を中心に進めます。

10.1. Soundコンポーネント

SoundコンポーネントはBuilderで音声刺激を扱うためのコンポーネントです。 図10.1 にSoundコンポーネントのアイコン及びプロパティ設定ダイアログを示します。Soundコンポーネントのプロパティの内、これまでに紹介済みのコンポーネントと共通ではないのは [音][ボリューム $] です。 [音] には無圧縮WAV形式の音声ファイルを指定できるほか、AやBfl (B♭)、Csh (C#)のようにキーコードで音を指定することも出来ます。また、2000という具合に正の数値を入力すると、その周波数の音がなります。実行環境によってはWAV以外にOGGなどの音声ファイルを再生できますが、無圧縮WAVならほとんどの環境で再生できるので無難です。 [ボリューム $] は0.0から1.0の範囲でボリュームを指定します。再生環境や音声ファイル形式によってはうまく機能しませんので、可能なら音声データ作成の時点でボリュームを調整していた方が良いでしょう。 [開始] および [終了] で定められた時間が音声ファイルの時間より短い場合は、音声ファイルの再生が途中で終了します。

_images/sound-properties.png

図10.1 Soundコンポーネントのアイコン及びプロパティ設定ダイアログ

音声ファイルを用いた実験を行う時にしばしば困るのが、「音声ファイルが再生されている間文字列が表示され、再生終了と共に消える」といった処理や、「音声ファイルの再生が終わったら文字列が表示されるようにしたいが、ルーチンは継続したいので [Routineを終了] は使いたくない」という場合です。使用する音声ファイルの再生時間がすべて同じであれば [開始][終了] の値を再生時間に合わせて設定すればいいのですが、ファイルによって再生時間が異なる場合は工夫が必要です。具体的には、Soundコンポーネントに対応するPsychoPyクラスが持っているstatusというデータ属性を利用します。音声または動画ファイルが再生されていなければ、statusはNOT_STARTEDという値が設定されています。再生中であればPLAYING (またはSTARTED)、再生が終了していればSTOPPED (またはFINISHED)です。これを利用すると、Codeコンポーネントを用いて以下のようにstim_Soundの再生終了時にルーチンを強制終了させることが出来ます。

if stim_Sound.status == FINISHED:
    continueRoutine = False

ルーチン全体を終了させるのではなく、特定のコンポーネントの描画を開始したり終了したりしたい場合は、そのコンポーネントの [開始] および [終了] で「条件式」 を使用すると便利です。 図10.2 に音声ファイルの再生開始、終了に合わせてコンポーネントの開始、終了する例を示します。

_images/start-stop-by-condition.png

図10.2 [開始] および [終了] に 「条件式」 を指定すると、条件式によってコンポーネントの開始、終了を制御できます

最後にふたつ注意点を挙げておきます。まず、Soundコンポーネントによる音声の再生タイミングはかなり「いいかげん」です。例えば「ぴぴっ」と音を短い音を2回鳴らしたいとします。再生時間0.1秒のSoundコンポーネントを2個配置して、それぞれの [開始] を0.5秒ずらしてやると「ぴぴっ」となるはずですが、実行するPCによっては1回しか音がならなかったり、全く音がならなかったりします。元々、PCのオーディオ機能はエラー音などを鳴らしたり、ひとつの音声ファイルを鳴らしたりするためのもので、短時間に複数の音声を正確に再生する機能は保証されていません。このような場合は、2つの音を1つの音声ファイルにまとめるべきです。視覚-聴覚の相互作用の研究を考えておられる方は刺激を動画として作成するのもひとつの対策でしょう。

もうひとつの注意点は、異なるサンプリングレートで作成された音声ファイルをひとつの実験で使用しない方がよいということです。例えば、実験で音声ファイルを10個使用しているうちの8個が44.1kHz、2個が48kHzでサンプリングされているといった状況です。実験の実行環境によっては音声ファイル読み込みの時点でエラーが起こって実験が強制終了されてしまうことがあります。

チェックリスト
  • 無圧縮WAV形式の音声ファイルを再生できる。

  • 指定された周波数の音を鳴らすことが出来る。

  • 指定されたキーコードの音を鳴らすことが出来る。

  • 音声のボリュームを指定できる。

  • 音声ファイルの再生を指定された時刻に途中終了できる。

  • 様々な再生時間の音声ファイルの再生開始、終了に合わせて他のコンポーネントを開始または終了させることが出来る。

  • 短時間に複数のSoundコンポーネントを鳴らそうとした時に期待した結果が得られない理由を説明できる。

  • 異なるサンプリングレートの音声ファイルをひとつの実験で混ぜて使用してはいけない理由を説明できる。

10.2. Movieコンポーネント

Builderで動画を再生するにはMovieコンポーネントを使用します(図10.3)。Movieコンポーネントのプロパティの内、これまでに紹介済みのコンポーネントと共通ではないのは [バックエンド][動画ファイル][音声無し] です。

_images/movie-properties.png

図10.3 Movieコンポーネントのアイコンとプロパティ設定ウィンドウ

[バックエンド] は、これはPsychoPyが動画データを再生するときに使用するライブラリの指定です。PsyhcoPy Builderのユーザーから見ると「Movieコンポーネント」が操作画面に見えていて実際に操作する対象であり、これを「フロントエンド」と呼びます。それに対して、Movieコンポーネントが動画再生のために内部で利用しているライブラリが「バックエンド」です。PsychoPy 3.0.5の時点でmoviepy、opencv、avbinの3つが選択できます。これらの違いを 表10.1 にまとめます。PsychoPy 3.0.5の時点では [バックエンド] の初期値はmoviepyに設定されています。これから新たに作成する実験はmoviepyにしておくのが無難でしょう。

表10.1 Movieコンポーネントのバックエンド

avbin

PsychoPyが最初に動画再生をサポートした時に使われていたもので、今では主に過去に作成された実験との互換性のためだけに残っていると思ってください。新たに作成する実験でこのバックエンドを選ぶ利点はありません。

opencv

avbinの後継として導入されたバックエンドで、OpenCVというライブラリを通じてlibvlcというライブラリを使用します。そこそこ動作が安定していて対応している動画も多いですが、libvlcのファイルが(おそらくライセンスの都合上)PsychoPyに含まれていませんので、別途libvlcを入手してインストールする必要があります。libvlcを入手するにはVLC MediaPlayerという動画再生ソフトをインストールするのが一番簡単です。

moviepy

PsychoPy 3.0.5の時点で最も新しい動画再生バックエンドで、moviepyというPythonのパッケージを通じてFFmpegというライブラリを使用します。moviepyにFFmpegをダウンロードする機能が備わっているので、FFmpegを持っていなくても自動的にダウンロードされます。ダウンロードにはもちろんインターネット接続が必要ですが、1回ダウンロードすれば後はインターネット接続は必要ありません。FFmpegを使用しているため、多くの動画形式に対応しています。

[動画ファイル] には、再生する動画ファイル名を指定します。再生できる動画ファイルの形式はバックエンドによって決まりますが、moviepy(FFmpeg)なら一般的な形式はほとんど再生できると思います。動画ファイルのフォーマットはお勧めできる定番がないのですが、筆者はMP4形式をよく使用しています。

[サイズ [w, h] $] を動画ファイルと異なる値に設定することによって、動画を縦横に拡大縮小して再生することが出来ます。動画ファイルの元の解像度のまま再生する場合は [サイズ [w, h] $] は空白にします。ただ、このようにPsychoPy上で拡大縮小できるからといって、実際に描画するサイズより解像度が高い動画ファイルを縮小表示するべきではありません。具体的にいうと、実験用に撮影した動画の解像度が1920×1080で、実験に使用する時の表示サイズが480×270であるならば、実験に使用する前に動画編集ソフトを用いて480×270に縮小すべきです。といいますのも、第一に解像度の高い動画ファイルは(よほど画質を落としていない限り)ファイルサイズが大きいので、その分PCのメモリを消費します。そのため実験時にメモリが不足するかもしれません。第二に、動画のフレーム毎に縮小処理を行う必要があるため、余計な負荷がかかります。使用しているPCの性能にもよりますが、動画再生は非常に負荷が高い処理なので、余計な負荷は出来る限り減らした方が良いです。

最後に、[音声無し] は文字通り音声なしで再生します。moviepyバックエンドでは音声データの処理が効率的ではなくて、音声を再生せずに済むならその分負荷を軽減できます。実験の目的上音声が必要ないならチェックしておくとよいでしょう。

チェックリスト
  • 動画ファイルを拡大縮小して再生することが出来る。

  • 動画ファイルを音声なしで再生することが出来る。

10.3. Movieコンポーネントを使ってみよう

それでは実際にMovieコンポーネントを使ってみましょう。なにか手ごろな動画ファイルを用意してください。動画ファイルはそのフォーマットと動画データの符号化方法が一対一対応していないので非常にややこしいのですが、mp4形式の動画ファイルなら多くの場合moviepyバックエンドで再生できるはずです。動画ファイルが用意できたら作業を始めましょう。

  • 実験設定ダイアログ

    • [単位] をpixにする。

  • trialルーチン

    • Codeコンポーネントをひとつ配置する。

    • Movieコンポーネントをひとつ配置して以下の通り設定する。

      • [名前] をmovieにする(初期値)。

      • [終了] を空欄にする。

      • [動画ファイル] に使用する動画ファイルを指定する。相対パスが使える点などはImageコンポーネントと同様である。

      • [単位] を pix にする (本来不要だが反映されないことがあるので指定すること)。

    • Textコンポーネントをひとつ配置して以下の通りに設定する。

      • [終了] を「条件式」にして movie.status == FINISHED と入力する。

      • [文字の高さ] を48にする。

      • [文字列] に $delay と入力し、「フレーム毎に更新」にする。

作業が終了したら、Codeコンポーネントの [フレーム毎] に以下のコードを入力する。

delay = t - movie.getCurrentFrameTime()

完成したら実行してみよう。動画が非対応でなければ、画面中央に動画が再生されてその上に数値が表示されます。この数値はルーチンの時計tと動画の再生位置の時刻の差なのですから、動画が遅延なく再生できていればほとんど変動しないはずです。本来ならばこの値が0になるのが理想ですが、どうしてもある程度の差は生じます。他のコンポーネントと連携させたいときに、この程度の時間のズレはあるというつもりで実験を作成するようにしてください。

数値の更新が速すぎて読めないという方は「 8.11:軌跡データを間引きしよう 」の方法で 変数frameN を使って間引きをするといいでしょう。以下にヒント(というかほとんど答え)を示しますが、よい練習になるので自分で考えてみてください。

if frameN % 10 == 0:
    delay = t - movie.getCurrentFrameTime()

再生の遅延や時間のズレは、同一のPCでも再生する動画の負荷によって変動します。いろいろなサイズの動画を用意して、 [動画ファイル] の項目を書き換えていろいろ実行してみると良いでしょう。また、 [サイズ [w, h] $] に動画の解像度と異なる値を(例えば(480,270)のように)指定してみて、どの程度の影響が出るかを試してみてください。

このデモのうち、TextコンポーネントとCodeコンポーネントは時刻などの情報を出力するために配置しているものであり、動画を再生するだけなら不要です。Codeコンポーネントに記入したコードについては解説が必要ですね。バックエンドがmoviepyのMovieコンポーネントを配置すると、その [単位] に指定した変数にpsyhcopy.visual.MovieStim3オブジェクトが作成されます(avbinならMovieStim、opencvならMovieStim2)。getCurrentFrameTime()メソッドは、現在の動画フレームの時刻を返します。1行目で変数mtにgetCurrentFrameTime()の値を代入しておいて、2行目で文字列に埋め込んでいます。tはこれまでの章で使ってきた、現在のルーチンが開始してからの時刻を保持している内部変数です。

tとgetCurrentFrameTime()の差がどの程度だったか実験のたびに保存しておかないと心配だという方は、第7章 で解説した方法を使って差を変数に保持し、trial-by-trial記録ファイルに出力すると良いでしょう。

まず、trialルーチンを繰り返すようにループを作成してください。 [名前] はtrails、 [繰り返し回数] は1でいいでしょう。ループを作成したら、のCodeコンポーネントの [Routine開始時] に以下のコードを追加します。

delay_list = []

続いて [フレーム毎] の最後に以下のコードを追加しましょう。間引きをした人は字下げに注意してください。

delay_list.append(delay)

最後に [Routine終了時] に以下のコードを追加します。average()とstd()は 表5.2 で出てきた平均値と標準偏差を計算する関数です。

trials.addData('delay_mean', average(delay_list))
trials.addData('delay_std', std(delay_list))

これでtrial-by-trial記録ファイルにtとgetCurrentFrameTime()の差の平均値と標準偏差が出力されるようになりました。なお、このコードを実際の実験で使用するときは、動画再生終了後直ちにルーチンを終了するようにしてください。そうしないと、もうすでに再生していない動画の時刻との差をappendし続けてしまいます。

動画再生終了後もルーチンを継続する必要がある場合は、動画再生中のみ差をappendするようにすればいいでしょう。Codeコンポーネントに慣れていないとちょっと難しいかもしれませんので、これも例を出しておきましょう。動画再生終了後もルーチンを5秒間継続して、計算した平均値と標準偏差を画面上に表示することにします。以下の通り作業してください。 [文字列] のところでは「 6.10.2:改行文字を使った複数行の文字列の表現(上級) 」で解説した方法を使用しています。

  • trialルーチン

    • Textコンポーネントをひとつ配置して、以下の通り設定する。

      • [開始] を「条件式」にして movie.status == FINISHED と入力する。

      • [終了] を「実行時間(秒)」にして5と入力する。

      • [文字の高さ] を48にする。

      • [文字列] に $'平均: ' + str(average(delay_list)) + 'n標準偏差:' + str(std(delay_list)) と入力し、「フレーム毎に更新」にする。

そして、Codeコンポーネントの [フレーム毎] に入力しているコードにif文を追加しましょう。2行目と3行目が既に入力済みの部分です。

if movie.status == PLAYING:
    delay = t - movie.getCurrentFrameTime()
    delay_list.append(delay)
else:
    delay = 0

動画が再生されている時にはデータ属性statusの値がPLAYINGになっているので、if文を使ってその時のみ差を計算してappendするようにしました。elseの後の部分は動画が再生されていないときにもdelayという変数が存在するのを保証するために設けています。else以下を削除してしまうと実験開始直後に「delayという変数がない」というエラーメッセージが表示されて実験が止まります。 [Routine開始時] に delay=0 と書いておくことでも回避できます。このあたりの小細工がピンと来るようならCodeコンポーネントにかなり慣れてきたと思ってもよいのではないでしょうか。

チェックリスト

  • Codeコンポーネントを使って動画の再生中のフレーム時刻を得ることが出来る。

  • Codeコンポーネントを使って動画の再生中のみ実行する処理を記述することが出来る。

10.4. 動画の再生位置を変更してみよう

実験に使用する動画は、出来る限り実験の準備段階で実際に提示するとおりの状態にしておくことが理想ですが、実験によっては条件に応じて動画の特定の時点から再生したいということがあるかも知れません。そのような時に便利なのが動画のシークです。Builderで動画のシークを行うにはCodeコンポーネントを通じて動画オブジェクトのseek()メソッドを用います。これもデモを作成してみましょう。 5秒以上の長さがある動画 を用意してください。

  • 実験設定ダイアログ

    • [単位] をheightにする。

  • trialルーチン

    • Codeコンポーネントをひとつ配置する。

    • Movieコンポーネントをひとつ配置して以下の通り設定する。

      • [名前] をmovieにする(初期値)。

      • [終了] を空欄にする。

      • [動画ファイル] に使用する動画ファイルを指定する。5秒以上の長さがあるものを使用してください。

そして、Codeコンポーネントの [Routine開始時] に以下のコードを入力してください。

movie.seek(5.0)

完成したら実行してみましょう。動画の冒頭を5秒飛ばしたところから再生が始まったはずです。ここで用いたseek()というメソッドは、動画の再生位置を引数で指定した値に変更します。引数の単位は秒です。

「動画の終了の5秒前」のように終了時刻を基準に指定したい場合は、動画オブジェクトのdurationというデータ属性を利用します。durationには動画の長さが保持されているので(単位は秒)、以下のように指定すればよいでしょう。

movie.seek(movie.duration - 5.0)

というわけで動画の頭出しが出来るようになりましたが、実験作成の時点で何秒飛ばすかがすでに決まっている場合はその分をカットした動画を作成した方が良いのは間違いありません。そうすると、実際にこのテクニックを使う状況は、実験中の参加者の反応によって飛ばす時間が変化する場合くらいかもしれません。

チェックリスト

  • 動画を途中から再生開始することが出来る。

  • 動画の再生開始位置を先頭から、または末尾からの秒数で指定することが出来る。

10.5. マウスで一時停止やスキップを行えるようにしよう(上級)

シークの話題があっさり終わってしまったので、ここで少し遊んでみましょう。画面中央下に 図10.4 のようなボタンを表示し、に「5秒戻る」、「5秒進む」、「一時停止/再開」、「終了」の機能を割り振ってマウスで操作できるようにしてみます。「遊び」というのは実験としての実用性があまりないからですが、Builderでマウスベースのユーザーインターフェースを作る際の参考になるのではないかと思います。新たに実験を作成し、以下の通り作業してください。

_images/movie-control.png

図10.4 マウスでボタンをクリックして動画再生をコントールしてみます。

  • 実験設定ダイアログ

    • [単位] をheightにする。

  • trialルーチン

    • Movieコンポーネントをひとつ配置して以下の通り設定する。

      • [名前] をmovieにする(初期値)。

      • [終了] を空欄にする。

      • [動画ファイル] に使用する動画ファイルを指定する。数十秒以上の長さがあるものが望ましい。

    • Polygonコンポーネントをひとつ配置して以下の通り設定する。

      • [名前] をrewindにする。

      • [終了] を空欄にする。

      • [形状] を「三角形」にする。

      • [サイズ [w, h] $] を(0.05, 0.05)にする。

      • [回転角度 $] を-90にする。

      • [位置 [x, y] $] を(-0.2, -0.4)にする。

    • rewindをコピーして、forwardという名前で貼り付けて以下の通り設定する。

      • [回転角度 $] を90にする。

      • [位置 [x, y] $] を(0.0, -0.4)にする。

    • Polygonコンポーネントをひとつ配置して以下の通り設定する。

      • [名前] をpauseにする。

      • [終了] を空欄にする。

      • [形状] を「長方形」にする。

      • [サイズ [w, h] $] を(0.05, 0.05)にする。

      • [位置 [x, y] $] を(-0.1, -0.4)にする。

    • Polygonコンポーネントをひとつ配置して以下の通り設定する。

      • [名前] をcloseにする。

      • [終了] を空欄にする。

      • [形状] を「十字」にする。

      • [回転角度 $] を45にする。

      • [サイズ [w, h] $] を(0.07, 0.07)にする。

      • [位置 [x, y] $] を(0.2, -0.4)にする。

    • Mouseコンポーネントをひとつ配置して以下の通り設定する。

      • [名前] をmouseにする(初期値)。

      • [終了] を空欄にする。

      • [ボタン押しでRoutineを終了] を「有効なクリック」にする。

      • [マウスの状態を保存] を「なし」にする。

      • [クリック可能な視覚刺激] にcloseと入力する。

    • Codeコンポーネントをひとつ配置する。

ここまで作業が終わったら、Codeコンポーネントにコードを入力します。

最後に本章で使用したメソッドとデータ属性を表にまとめておきます。まず [Routine開始時] に以下のコードを入力します。この変数stepはボタンを押したときに進む/戻る時間を保持しています。この値を変更すると進む/戻る時間が変わります。

step = 5.0

続いて [フレーム毎] に以下のコードを入力します。ここでpause()とplay()というメソッドが出てきますが、これはそれぞれ動画再生の一時停止、再開を行うものです。

ct = movie.getCurrentFrameTime()

if mouse.isPressedIn(rewind):
    movie.pause()
    movie.seek(max(0,ct-step))
    movie.play()
elif mouse.isPressedIn(forward):
    movie.pause()
    movie.seek(min(movie.duration,ct+step))
    movie.play()
elif mouse.isPressedIn(pause):
    if movie.status == PLAYING:
        movie.pause()
    elif movie.status == PAUSED:
        movie.play()

簡単に処理内容を解説しておくと、まずgetCurrentFrameTime()で現在の再生位置を得て変数ctに代入しておきます。続いて 第8章 で少し触れたマウスオブジェクトのメソッドであるgetPressedInを使ってrewind、forward、pauseの各Polygonオブジェクト上でマウスボタンが押されたかを判定していきます。isPressedInの引数に指定されたオブジェクト内にマウスカーソルがあってボタンが押されていたらTrueが返されるので、if文で処理を分岐します。

rewindとforwardでマウスのボタンが押されていた時の処理では、先ほどの変数ctにstepを加算、もしくは減算してseek()を実行しています。これで「現在再生中の位置からstep秒戻る、または進む」が実現できます。ただ、再生中にseek()を行うと音声は進んでいるのに映像が止まったままになったりすることがあるので、seek()の前にpause()でいったん再生を止め、そしてseek()後にplay()で再開しています。pause()とplay()をコメントアウトして比較してみるとよいでしょう。

pauseでマウスのボタンが押されていた時は、動画の再生状態に応じて処理を分岐します。再生中であればデータ属性statusの値がPLAYING、一時停止中であればPAUSEDとなっているので、if文で分岐してPLAYINGならばpause()、PAUSEDならばplay()を実行します。

ここにはcloseを押したときの終了処理が書かれていませんが、それはMouseコンポーネントの [クリック可能な視覚刺激] にcloseを設定することで実現されているので、Codeコンポーネントを使う必要がありません。

ここまで作業が出来たら、一度保存して実行してみましょう。画面中央に動画が再生され、画面中央下に 図10.4 のように表示されますので、クリックして動作を確認してください。PsychoPyにとって動画の一時停止、再生再開は重い処理なので、一般的なスペックのPCだと各ボタンをクリックしてから効果が表れるまで一呼吸待たされますのでそのつもりでいてください。なお、動画の再生が終了してもデモは終了しませんので、closeを押して終了してください。

いかがだったでしょうか。「戻る」と「進む」、「終了」は(やや待たされるかもしれませんが)特に問題なく動作したと思います。でも、「一時停止」はうまくいくこともあれば、一瞬だけ止まってすぐ動きだしたりしなかったでしょうか? なぜそうなるかというと、isPressedIn()は「現在マウスのボタンが押されているか」を返すメソッドだからです。PCの画面が60fpsで描かれている場合、 [フレーム毎] の処理は1/60秒に1回実行されます。今回のデモではpause()やplay()が少々重い処理なので1/60秒内で終わらないこともありますが、それでも人がマウスのボタンを「カチッ」とクリックした間に数フレームは過ぎてしまいます。そうすると、現在のコードでは1回「カチッ」とボタンを押しただけでpause()とplay()が繰り返し実行されてしまいます。なので、クリックした後にうまく一時停止する場合と再生されて続けてしまう場合があるのです。

_images/mouse-click-frame.png

図10.5 クリックが数フレームにわたった場合に対応する必要がある

ではどうすればいいかというと、いくつか方法があります。ここではmouse.isPressedIn(pause)がTrueになったときの時刻を保持しておいて、一定時間が経過するまではmouse.isPressedIn(pause)がTrueであっても無視するという方法を取り上げましょう。まずCodeコンポーネントの [Routine開始時] に以下の行を追加します。

wait = 0

続いて [フレーム毎] に以下のように追加します。追加した行の最後に目印としてコメントを入れています。皆さんが試してみるときはこれらのコメントを入力する必要はありません。

ct = movie.getCurrentFrameTime()
if wait > 0:    # (1)
    wait -= 1   # (1)

if mouse.isPressedIn(rewind):
    movie.pause()
    movie.seek(max(0,ct-step))
    movie.play()
elif mouse.isPressedIn(forward):
    movie.pause()
    movie.seek(min(movie.duration,ct+step))
    movie.play()
elif mouse.isPressedIn(pause) and wait <= 0:  # (2)
    wait = 30                                 # (3)
    if movie.status == PLAYING:
        movie.pause()
    elif movie.status == PAUSED:
        movie.play()

この例では、waitという変数を用意して、一時停止と再開の処理を行う前に wait=30 をセットしています(コメント(3))。その後、フレーム毎に waitの値が 0 より大きければ1を減算していきます(コメント(1))。そしてコメント(2)のところでボタン押しを判定する際に wait<=0 を条件として追加することで、waitの値が 0より大きい間はボタンを押しても一時停止/再開が行われないようにしています。このwaitという変数はルーチン開始時に存在していないといけないので、 [Routine開始時] にwait=0としているわけです。実行してみて、一時停止が機能することを確認してください。

なお、この方法ではボタンが押されてからの時間をフレーム数でカウントしていることになります。コメント(3)で wait=30 としているので30フレーム、60fpsで刺激提示しているのなら待ち時間は0.5秒です。長すぎる場合は数値を減らしてみましょう。また、この例では戻る、進む処理について対策していませんが、これらについても対策を追加するにはいくつか書き方があるので、考えてみると良い練習になるでしょう。

他の方法としては、直前のフレームのボタンの状態をmouse.getPressed()で保持しておいて、「直前のフレームでボタンが押されていなくて、今のフレームでは押されている」という時だけ処理するという方法が考えられます。この場合、もうボタンが押されていることは確実なのでisGetPressedIn()ではなくcontains()でクリックされたPolygonオブジェクトを判別できます。

フレーム数を数える方法は、操作している人がボタンをずっと長押しすれば処理が繰り返されます。それに対して、直前のフレームの状態を保持する方法では、長押ししても処理されるのは1回のみです。どちらがの方が望ましいかは状況によるでしょうから、使い分けるのが理想です。

以上、MovieコンポーネントとMouseコンポーネントで「遊んで」みましたがいかがだったでしょうか。最後に、この章で使用したMovieオブジェクトのデータ属性とメソッドを 表10.2 にまとめておきます。

表10.2 Movieオブジェクトの主なデータ属性とメソッド

status

現在の状態をあらわす。再生前ならNOT_STARTED、再生中ならPLAYING (STARTED)、一時停止中ならPAUSED、再生終了ならSTOPPED (FINISHED)。

duration

動画の長さ。単位は秒。

getCurrentFrameTime()

動画の現在の再生位置を返す。単位は秒。

play()

動画の再生を開始する。一時停止している場合は再生を再開する。

pause()

動画の再生を一時停止する。

seek()

動画の再生位置を変更する。単位は秒。

チェックリスト

  • 動画の再生を一時停止、再開できる。

  • Codeコンポーネントで動画が一時停止中であることを条件に処理を分岐できる。

  • マウスでオブジェクトを「クリック」した際にボタンが押されている期間が複数フレームにわたる場合を考慮したコードを記述できる。

10.6. この章のトピックス

10.6.1. Staticコンポーネントを用いた動画の読み込み

動画ファイルは一般的にファイルサイズが大きく、読み込みに時間がかかります。ループで繰り返しのたびに異なる動画を読み込むと、ルーチン開始前に読み込みを行いますので、ここで時間がかかると繰り返しのたびに意図しない空白画面が表示され続けることになります。ただ時間がかかるだけならまだしも、動画ファイルによって読み込みの時間が異なると試行間の間隔がばらばらになってしまって実験によっては望ましくありません。こういう時に便利なのがStaticコンポーネントです。

Staticコンポーネントはコンポーネントペインの「カスタム」の中に含まれる 図10.6 左のアイコンです。クリックすると 図10.6 右のようなダイアログが表示されます。他のコンポーネントとは異なり、 [名前] の初期値がコンポーネント名と同じではなく ISI となっているので注意してください。 [開始][終了] は他のコンポーネントと同様、Staticコンポーネントが有効な期間を指定ます。 最後の [カスタムコード] については後述します。

_images/static-properties.png

図10.6 Staicコンポーネントのアイコンとプロパティ設定ウィンドウ

プロパティ設定ダイアログのOKをクリックしてダイアログを閉じると、ルーチンペインに 図10.7 上のように赤色の領域が現れます。これがStaticコンポーネントです。削除するときはこの赤い領域内のどこかで右クリックしてメニューから「削除」を選んでください。

Staticコンポーネントを配置した後に、他のコンポーネントのプロパティ設定ダイアログを開いた際に、各プロパティの更新方法として「更新方法: trial.ISI」のような項目が追加されます( 図10.7 下)。ここでtrialはStaticコンポーネントを置いているルーチン名、ISIはスタティックコンポーネントの名前です。testというルーチンにload_stimという名前でStaticコンポーネントを配置したならtest.load_stimとなります。この項目を選択すると、プロパティの更新が指定したStaticコンポーネントの期間中に行われます。例えば実験で使用する動画が最も読み込みに時間がかかるものでも0.4秒で完了するなら、Staticコンポーネントの長さを(少し余裕をもって)0.5秒にしておけば、どの動画も0.5間で読み込むことが出来てばらつきが生じません。Staticコンポーネントで設定した期間内に終わらない処理を行わせると元も子もないので、あらかじめ動作確認して余裕を持たせておくことが重要です。

_images/static-update.png

図10.7 Staticコンポーネントを設置すると他のコンポーネントの更新方法のところに項目が追加されます

「Staticコンポーネント」の名前の通り、この期間には刺激を描画したりキー押しを検出したりするべきではありません。やって出来ない事はないのですが、時間的な精度が保障されなくなります。Staticコンポーネントの期間中(あるいは期間開始と同時に)に静止した刺激を描画しておくことには何の問題もありません。

ファイルの読み込みタイミングとして他のルーチンに配置したStaticコンポーネントを選択することも可能なので、実験期間中で都合がよいタイミングにファイルを読み込んでくことが可能です。Staticコンポーネントはファイルサイズが大きくなりがちな動画ファイルの読み込みに特に力を発揮しますが、音声ファイルを読み込む時や、画像ファイルを十数枚一気に読み込む必要がある時などにも役に立ちます。

[カスタムコード] は、他のコンポーネントのパラメータの更新以外の作業を行わせたいときに使用します。入力欄が狭いので、複雑な処理を行わせる場合はCodeコンポーネントで関数を定義してその関数を呼び出すなどの工夫が必要かもしれません。上級者向けの機能だと思います。