はじめに
PsychoPyで実験タスクのバックグラウンドでメトロノームを鳴らす実装でハマったので,Soundコンポーネントの挙動から洗い出して整理しました.
基本的に実装はBuilderで完結させます.
ちなみにM1MacではPsychoPy自体の挙動がおかしい時がある気がしますね...
環境
- macOS Monterey v12.3.1 (M1チップ)
- PsychoPy standalnoe v2021.2.3
概要
ハマりポイントとしては,Builderが生成するコード(hogehoge_lastrun.py
)にdurationに関与するパラメータが複数あるというか,Soundコンポーネントの仕様が以下のようであるという点です.
制御の概要図としてはこんな感じ.
ざっくりいうと,SoundコンポーネントのConstructer引数のsecsとstop()メソッドを呼ぶタイミングの整合性が取れていないと,(おそらく)無音状態が再生され続けるというものです.
手っ取り早く解決するには,secsを十分大きく取ってやれば良いはず.
Builderで生成されるコード
Builder生成コードの挙動確認
以下のような構成で検証します.Soundコンポーネント(mtr)以外は無視してもらって構いません.
このpsyexpファイルからコードを生成.
mtrのduration制御に関係がある箇所は,以下3つ.
#エディタからそのままコピペしたのでインデント崩れはご了承ください.
# 1つ目
mtr = sound.Sound('A', secs=.2, stereo=True, hamming=True,
name='mtr')
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 2つ目
# update component parameters for each repeat
mtr.setSound('A', secs=.1, hamming=True)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 3つ目
if mtr.status == STARTED:
# is it time to stop? (based on global clock, using actual start)
if tThisFlipGlobal > mtr.tStartRefresh + .1-frameTolerance:
# keep track of stop time/frame for later
mtr.tStop = t # not accounting for scr refresh
mtr.frameNStop = frameN # exact frame index
win.timeOnFlip(mtr, 'tStopRefresh') # time at next scr refresh
mtr.stop()
- 1,2つ目のsecs
- 音が鳴り続ける時間を定義するパラメータ
- 後述する
mtr.status==STARTED
のままということから推察するに,secsは実際に音が鳴る時間を指し,その後はstop()が呼ばれるまで無音が再生されるという事のよう
- 3つ目のIf文内の
stop()
- 時間判定し,stopするという処理
-
stop()
が実行されると,mtr.status==FINISHED
つまり終了(停止)状態となる
つまり,secsより後にstop()を呼ぶように実装してしまうと,実装者はstopのタイミングで音が止まることを期待するが,実際のプログラムはsecsの時間経過後に音が止まる(無音状態になり)ので,意図した時間よりも早く音が止まってしまうということになりかねる.
→secとstop判定する時間の大小関係に注意!!
#特に,メトロノームのような音の繰り返し処理をしたいときに,codeコンポーネントをごにょごにょするとハマる!
statusについて
-
NOT_STARTED
:play()
以前の状態 -
STARTED
:play()
実行後の状態 -
FINISHED
:stop()
実行後の状態
という具合に確認できたので,secs経過後=無音状態が再生されているのではと推測した.
同じSoundコンポーネントで繰り返し再生する場合は,FINISHED
後にstatus = NOT_STARTED
と戻してやれば,再度play()を呼ぶと再生される.