为什么我的呈现音频刺激的脚本具有不可预测的行为?
why my script to present audio stimuli has an unpredictable behaviour?
我正在尝试 运行 使用 python 进行一个简单的实验。我想展示两种不同类型的音频刺激,一种是高音,一种是低音。较高的音调具有固定的持续时间 200ms
,而较低的音调成对出现,第一个具有固定的持续时间 250ms
,第二个具有可变的持续时间,可以采用以下值 [.4, .6, .8, 1, 1.2]
.我需要知道刺激开始和结束的时间(机器),以及它们的持续时间(精度不是最重要的问题,我有 ~ 10ms 的容忍度),因此我记录了这些信息
我正在使用库 audiomath
来创建和呈现刺激,并且我已经创建了几个自定义函数来管理任务的其他方面。我有 3 个脚本:一个在其中定义函数,一个在其中为每个主题设置实验的特定参数(source),一个带有 main()
我的问题是 main()
工作不稳定:有时它会工作,有时它似乎进入了无限循环并且出现了某种声音并且从不停止播放。关键是这种行为似乎真的是随机的,即使使用完全相同的参数,问题也会在不同的试验中出现,或者根本不会出现。
这是我的代码:
源文件
#%%imports
from exp_funcs import tone440Hz, tone880Hz
import numpy as np
#%%global var
n_long = 10
n_short = 10
short_duration = .2
long_durations = [.4, .6, .8, 1, 1.2]
#%%calculations
n_tot = n_long + n_long
trial_types = ['short_blink'] * n_short + ['long_blink'] * n_long
sounds = [tone880Hz] * n_short + [tone440Hz] * n_long
np.random.seed(10)
durations = [short_duration] * n_short + [el for el in np.random.choice(long_durations, n_long)]
durations = [.5 if el < .2 else el for el in durations]
cue_duration = [.25] * n_tot
spacing = [1.25] * n_tot
np.random.seed(10)
iti = [el for el in (3 + np.random.normal(0, .25, n_tot))]
函数
import numpy as np
import audiomath as am
import time
import pandas as pd
TWO_PI = 2.0 * np.pi
@am.Synth(fs=22050)
def tone880Hz(fs, sampleIndices, channelIndices):
timeInSeconds = sampleIndices / fs
return np.sin(TWO_PI * 880 * timeInSeconds)
@am.Synth(fs=22050)
def tone440Hz(fs, sampleIndices, channelIndices):
timeInSeconds = sampleIndices / fs
return np.sin(TWO_PI * 440 * timeInSeconds)
def short_blink(sound, duration):
p = am.Player(sound)
init = time.time()
while time.time() < init + duration:
p.Play()
end = time.time()
p.Stop()
print(f'start {init} end {end} duration {end - init}')
return(init, end, end - init)
def long_blink(sound, duration, cue_duration, spacing):
p = am.Player(sound)
i_ = time.time()
while time.time() < i_ + cue_duration:
p.Play()
p.Stop()
time.sleep(spacing)
init = time.time()
while time.time() < init + duration:
p.Play()
end = time.time()
p.Stop()
print(f'start {init} end {end} duration {end - init}')
return(init, end, end - init)
def run_trial(ttype, sound, duration, cue_duration, spacing):
if ttype == 'short_blink':
init, end, effective_duration = short_blink(sound, duration)
else:
init, end, effective_duration = long_blink(sound, duration,
cue_duration, spacing)
otp_df = pd.DataFrame([[ttype, init, end, effective_duration]],
columns = ['trial type', 'start', 'stop',
'effective duration'])
return(otp_df)
主要
import pandas as pd
import sys
import getopt
import os
import time
import random
from exp_funcs import run_trial
from pathlib import PurePath
def main(argv):
try:
opts, args = getopt.getopt(argv,'hs:o:',['help', 'source_file=', 'output_directory='])
except getopt.GetoptError:
print ('experiment.py -s source file -o output directory')
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print ('experiment.py -s source file')
sys.exit()
elif opt in ("-s", "--source_file"):
source_file = arg
elif opt in ("-o", "--output_directory"):
output_dir = arg
os.chdir(os.getcwd())
if not os.path.isfile(f'{source_file}.py'):
raise FileNotFoundError('{source_file} does not exist')
else:
source = __import__('source')
complete_param = list(zip(source.trial_types,
source.sounds,
source.durations,
source.cue_duration,
source.spacing,
source.iti))
# shuffle_param = random.sample(complete_param, len(complete_param))
shuffle_param = complete_param
dfs = []
for ttype, sound, duration, cue_duration, spacing, iti in shuffle_param:
time.sleep(iti)
df = run_trial(ttype, sound, duration, cue_duration, spacing)
dfs.append(df)
dfs = pd.concat(dfs)
dfs.to_csv(PurePath(f'{output_dir}/{source_file}.csv'), index = False)
if __name__ == "__main__":
main(sys.argv[1:])
3个文件在同一个目录下,我在目录下用终端浏览,运行主要如下python experiment.py -s source -o /whatever/output/directory
。
任何帮助将不胜感激
这也是一个 big/complex 程序,希望在 Whosebug 上获得有关非特定“不稳定”行为的帮助。您需要将其归结为一个行为异常的小型可重现示例。如果它有时有效而不是其他,则系统地关注导致它失败的条件。我确实尝试 运行 整个事情,但是在修复了一些丢失的导入之后,仍然存在未指定的“源文件”内容的问题。
所以我不知道你的具体问题是什么。但是,从 audiomath
和一般实时性能的角度来看,我当然可以确定一些您不应该做的事情:
虽然 Player
个实例被设计为在时间关键时刻播放、停止或操作,但它们(默认情况下)并非设计为在时间关键时刻创建和销毁.如果你想快速 create/destroy 它们,预先初始化一个持久的 Stream()
实例,并在创建 Player
时将其作为 stream
参数传递,如 https://audiomath.readthedocs.io/en/release/auto/Examples.html#play-sounds
如果您正在使用 Synth
实例,您可以利用它们的 .duration
属性,而不是在 while
循环中显式检查时钟。例如可以设置tone880Hz.duration = 0.5
,然后与p.Play(wait=True)
同步播放声音。你的时钟观察 while
循环的一个大问题是它们目前是“忙等待”循环,这会扰乱 CPU,可能会导致你的声音偶尔中断(Python' s 多线程远非完美)。但是,在解决此问题之前,您应该知道...
策略“Play()
,等待,睡眠,Play()
”永远不会实现一种刺激相对于另一种刺激的精确时间安排。首先,无论何时您在任何软件中发出播放声音的命令,在命令和声音的物理开始之间都不可避免地存在非零(并且随机变化!)延迟。其次,sleep()
不太可能像您认为的那样精确。这既适用于您一直用来创建间隙的 sleep()
,也适用于 Play(wait=True)
将在内部使用的 sleep()
。睡眠实现暂停操作“至少”指定的时间量,但它们不保证上限。这是非常依赖于硬件和 OS 的;在某些 Windows 系统上,您甚至可能会发现粒度 永远不会 比 10 毫秒更好。
如果你真的想使用 Synth
方法,我想你可以在程序上将间隙编程到 tone440Hz()
和 tone880Hz()
的函数定义中,访问 cue_duration
, duration
和 spacing
作为全局变量(事实上,当你这样做的时候,为什么不把频率也做成一个全局变量,并且只写一个函数)。但是我看不出这有什么很大的优势,无论是在性能上还是在代码可维护性上。
我要做的是预初始化以下内容(一次,在程序开始时):
max_duration = 1 # length, in seconds, of the longest continuous tone we'll need
tone440Hz = am.Sound(fs=22050).GenerateWaveform(freq_hz=440, duration_msec=max_duration*1000)
tone880Hz = am.Sound(fs=22050).GenerateWaveform(freq_hz=880, duration_msec=max_duration*1000)
m = am.Stream()
然后使用您想要的参数将每个“长时间眨眼”刺激组合成静态 Sound
。
这将确保音调和间隙持续时间精确:
s = tone440Hz[:cue_duration] % spacing % tone440Hz[:duration]
为了获得最佳的实时性能,您可以使用不同的参数预先计算出一整套这些刺激。或者,如果事实证明这些组合操作 (slicing and splicing) 发生得足够快,您可能会决定可以在试用时在 long_blink()
函数中不这样做。
无论哪种方式,在试用时播放刺激:
p = am.Player(s, stream=m) # to make Player() initialization fast, use a pre-initialized Stream() instance
p.Play(wait=True)
最后:在实现这个过程中,从头开始——从简单开始,在复合之前测试几个简单案例的性能。
我正在尝试 运行 使用 python 进行一个简单的实验。我想展示两种不同类型的音频刺激,一种是高音,一种是低音。较高的音调具有固定的持续时间 200ms
,而较低的音调成对出现,第一个具有固定的持续时间 250ms
,第二个具有可变的持续时间,可以采用以下值 [.4, .6, .8, 1, 1.2]
.我需要知道刺激开始和结束的时间(机器),以及它们的持续时间(精度不是最重要的问题,我有 ~ 10ms 的容忍度),因此我记录了这些信息
我正在使用库 audiomath
来创建和呈现刺激,并且我已经创建了几个自定义函数来管理任务的其他方面。我有 3 个脚本:一个在其中定义函数,一个在其中为每个主题设置实验的特定参数(source),一个带有 main()
我的问题是 main()
工作不稳定:有时它会工作,有时它似乎进入了无限循环并且出现了某种声音并且从不停止播放。关键是这种行为似乎真的是随机的,即使使用完全相同的参数,问题也会在不同的试验中出现,或者根本不会出现。
这是我的代码:
源文件
#%%imports
from exp_funcs import tone440Hz, tone880Hz
import numpy as np
#%%global var
n_long = 10
n_short = 10
short_duration = .2
long_durations = [.4, .6, .8, 1, 1.2]
#%%calculations
n_tot = n_long + n_long
trial_types = ['short_blink'] * n_short + ['long_blink'] * n_long
sounds = [tone880Hz] * n_short + [tone440Hz] * n_long
np.random.seed(10)
durations = [short_duration] * n_short + [el for el in np.random.choice(long_durations, n_long)]
durations = [.5 if el < .2 else el for el in durations]
cue_duration = [.25] * n_tot
spacing = [1.25] * n_tot
np.random.seed(10)
iti = [el for el in (3 + np.random.normal(0, .25, n_tot))]
函数
import numpy as np
import audiomath as am
import time
import pandas as pd
TWO_PI = 2.0 * np.pi
@am.Synth(fs=22050)
def tone880Hz(fs, sampleIndices, channelIndices):
timeInSeconds = sampleIndices / fs
return np.sin(TWO_PI * 880 * timeInSeconds)
@am.Synth(fs=22050)
def tone440Hz(fs, sampleIndices, channelIndices):
timeInSeconds = sampleIndices / fs
return np.sin(TWO_PI * 440 * timeInSeconds)
def short_blink(sound, duration):
p = am.Player(sound)
init = time.time()
while time.time() < init + duration:
p.Play()
end = time.time()
p.Stop()
print(f'start {init} end {end} duration {end - init}')
return(init, end, end - init)
def long_blink(sound, duration, cue_duration, spacing):
p = am.Player(sound)
i_ = time.time()
while time.time() < i_ + cue_duration:
p.Play()
p.Stop()
time.sleep(spacing)
init = time.time()
while time.time() < init + duration:
p.Play()
end = time.time()
p.Stop()
print(f'start {init} end {end} duration {end - init}')
return(init, end, end - init)
def run_trial(ttype, sound, duration, cue_duration, spacing):
if ttype == 'short_blink':
init, end, effective_duration = short_blink(sound, duration)
else:
init, end, effective_duration = long_blink(sound, duration,
cue_duration, spacing)
otp_df = pd.DataFrame([[ttype, init, end, effective_duration]],
columns = ['trial type', 'start', 'stop',
'effective duration'])
return(otp_df)
主要
import pandas as pd
import sys
import getopt
import os
import time
import random
from exp_funcs import run_trial
from pathlib import PurePath
def main(argv):
try:
opts, args = getopt.getopt(argv,'hs:o:',['help', 'source_file=', 'output_directory='])
except getopt.GetoptError:
print ('experiment.py -s source file -o output directory')
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print ('experiment.py -s source file')
sys.exit()
elif opt in ("-s", "--source_file"):
source_file = arg
elif opt in ("-o", "--output_directory"):
output_dir = arg
os.chdir(os.getcwd())
if not os.path.isfile(f'{source_file}.py'):
raise FileNotFoundError('{source_file} does not exist')
else:
source = __import__('source')
complete_param = list(zip(source.trial_types,
source.sounds,
source.durations,
source.cue_duration,
source.spacing,
source.iti))
# shuffle_param = random.sample(complete_param, len(complete_param))
shuffle_param = complete_param
dfs = []
for ttype, sound, duration, cue_duration, spacing, iti in shuffle_param:
time.sleep(iti)
df = run_trial(ttype, sound, duration, cue_duration, spacing)
dfs.append(df)
dfs = pd.concat(dfs)
dfs.to_csv(PurePath(f'{output_dir}/{source_file}.csv'), index = False)
if __name__ == "__main__":
main(sys.argv[1:])
3个文件在同一个目录下,我在目录下用终端浏览,运行主要如下python experiment.py -s source -o /whatever/output/directory
。
任何帮助将不胜感激
这也是一个 big/complex 程序,希望在 Whosebug 上获得有关非特定“不稳定”行为的帮助。您需要将其归结为一个行为异常的小型可重现示例。如果它有时有效而不是其他,则系统地关注导致它失败的条件。我确实尝试 运行 整个事情,但是在修复了一些丢失的导入之后,仍然存在未指定的“源文件”内容的问题。
所以我不知道你的具体问题是什么。但是,从 audiomath
和一般实时性能的角度来看,我当然可以确定一些您不应该做的事情:
虽然
Player
个实例被设计为在时间关键时刻播放、停止或操作,但它们(默认情况下)并非设计为在时间关键时刻创建和销毁.如果你想快速 create/destroy 它们,预先初始化一个持久的Stream()
实例,并在创建Player
时将其作为stream
参数传递,如 https://audiomath.readthedocs.io/en/release/auto/Examples.html#play-sounds如果您正在使用
Synth
实例,您可以利用它们的.duration
属性,而不是在while
循环中显式检查时钟。例如可以设置tone880Hz.duration = 0.5
,然后与p.Play(wait=True)
同步播放声音。你的时钟观察while
循环的一个大问题是它们目前是“忙等待”循环,这会扰乱 CPU,可能会导致你的声音偶尔中断(Python' s 多线程远非完美)。但是,在解决此问题之前,您应该知道...策略“
Play()
,等待,睡眠,Play()
”永远不会实现一种刺激相对于另一种刺激的精确时间安排。首先,无论何时您在任何软件中发出播放声音的命令,在命令和声音的物理开始之间都不可避免地存在非零(并且随机变化!)延迟。其次,sleep()
不太可能像您认为的那样精确。这既适用于您一直用来创建间隙的sleep()
,也适用于Play(wait=True)
将在内部使用的sleep()
。睡眠实现暂停操作“至少”指定的时间量,但它们不保证上限。这是非常依赖于硬件和 OS 的;在某些 Windows 系统上,您甚至可能会发现粒度 永远不会 比 10 毫秒更好。
如果你真的想使用 Synth
方法,我想你可以在程序上将间隙编程到 tone440Hz()
和 tone880Hz()
的函数定义中,访问 cue_duration
, duration
和 spacing
作为全局变量(事实上,当你这样做的时候,为什么不把频率也做成一个全局变量,并且只写一个函数)。但是我看不出这有什么很大的优势,无论是在性能上还是在代码可维护性上。
我要做的是预初始化以下内容(一次,在程序开始时):
max_duration = 1 # length, in seconds, of the longest continuous tone we'll need
tone440Hz = am.Sound(fs=22050).GenerateWaveform(freq_hz=440, duration_msec=max_duration*1000)
tone880Hz = am.Sound(fs=22050).GenerateWaveform(freq_hz=880, duration_msec=max_duration*1000)
m = am.Stream()
然后使用您想要的参数将每个“长时间眨眼”刺激组合成静态 Sound
。
这将确保音调和间隙持续时间精确:
s = tone440Hz[:cue_duration] % spacing % tone440Hz[:duration]
为了获得最佳的实时性能,您可以使用不同的参数预先计算出一整套这些刺激。或者,如果事实证明这些组合操作 (slicing and splicing) 发生得足够快,您可能会决定可以在试用时在 long_blink()
函数中不这样做。
无论哪种方式,在试用时播放刺激:
p = am.Player(s, stream=m) # to make Player() initialization fast, use a pre-initialized Stream() instance
p.Play(wait=True)
最后:在实现这个过程中,从头开始——从简单开始,在复合之前测试几个简单案例的性能。