【MATLAB】Psychtoolbox中的视听刺激的呈现及精确计时的方法

1 听觉刺激的呈现

PsychPortAudio是一个高时间精度的音频驱动器 (High precision sound driver),在Psychtoolbox-3中(以下简称为PTB-3),听觉刺激的呈现主要由PsyPortAudio相关函数来完成。

PsychPortAudio的一些特点:

  • 可以在非常低滞后 (latency) 的情况下,立刻开始音频的播放

  • 可以在未来某个时间点 (例如视觉刺激开始呈现的时候) 开始音频的播放,并在此之前继续运行你的代码。在某些系统上,该操作可以精确到亚毫秒 (sub-millisecond) 级别

  • 暂停你的代码以便等待音频开始,或着在这个过程中立刻继续执行你的代码

  • 支持异步操作 (Asynchronous operation),即,音频在后台播放的同时,你的代码可以继续去做其它事情

  • 无限重复或重复n次音频

  • 获取关键事件的时间戳和状态

  • 支持多通道设备

  • 支持多通道声音录制,在某些系统上支持同时播放和录制

  • 可以同时打开和使用多个声卡

  • 可靠 (与Matlab的声音设备相比)

  • 高效 (因为cpu占用很低)

1.1 呈现纯音刺激

先从最简单的来,让我们试着呈现一个“哔~”的声音,并在这个过程中了解一下PsychPortAudio的工作过程。

1.1.1 PsychPortAudio的初始化

首先是清除工作区的变量、清除命令行窗口并关闭所有打开的窗口,然后使用InitializePsychSound命令来初始化PsychPortAudio。

clear;
clc;
sca;

InitializePsychSound;

在初始化之后,对于MacOS/X、GNU/Linux系统,PsychPortAudio能够提供低滞后 (low latency)、高时间精度的听觉刺激控制,对于Widowns系统,只有当音频子系统 ( Audio subsystem) 属于WDM/KS、WASAPI时,timing才能达到研究的要求 (误差低于500ms)。Windows Vista及之后的Windowns版本采用的是WASAPI。

输入代码PsychPortAudio('GetDevices'),然后打开工作区的变量ans,便可查看电脑上的音频设备的信息。

1.1.2 设置pahandle

先定义一个变量freq,作为音频的采样频率,这里设置的采样频率为48000hz。

freq = 48000;

为什么是48000hz呢?百度百科是这么介绍的:

在当今的主流采集卡上,采样频率一般共分为11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五个等级……

然后我自己测试了一下,在我的电脑上,只有设置为48000hz,程序才可以正常运行。所以采样率必须是特定的数值,和音频设备有关。注意,这里的采样频率指的是每秒采样的音频的量,并不是刺激的音高 (其单位也是hz)。

接下来,我们用以下代码来“打开”PsychPortAudio。

pahandle = PsychPortAudio('Open', [], 1, 1, freq, 2);

这里有五个参数,含义如下:

  • deviceid:设备id。此处我们将其设置为[],也就是默认值。
  • mode:默认值为1,即仅播放音频,2为录制音频,3为同时播放和录制音频。此处我们将其设置为1。
  • reqlatencyclass:涉及到PsychPortAudio控制音频滞后、提高时间精度的程度,从0到4。0是完全不管,1(默认值)是尽量获取低滞后,2到4的控制程度较高但可能会影响到系统内存。我的理解是,没有滞后 (latency,或称为延迟) 应该就是“哔~”,高滞后可能就是“哔哔哔噗噗叭叭”。此处我们将其设为1。
  • freq:采样频率,上文已经介绍过了。
  • channels:1是单声道,2是立体声。不过我还不太了解在单声道的情况下,将音频指定输出到左耳/右耳的方法。如果同时播放和录制音频时,可以为channels设置多个数值,例如,[2, 1]意味着播放立体声音频、录制单声道音频。

此外还有其他参数,就不一一介绍了。设置完毕后,我们将得到名为pahandle的对象,handle指句柄,pa大概指的是PsychPortAudio。我们可以将PsychPortAudio视为一个音频播放器,而pahandle则是遥控器。

1.1.3 设置音量

PsychPortAudio('Volume', pahandle, 0.5);

有两个参数,pahandle就是刚刚提到的遥控器啦,此处我们将Volume设置为0.5,也就是将音量的强度设置为50%。如果是1的话,就是100%音量,1.25就是125%音量,-1则会将信号反转 (invert the signal)。

1.1.4 创建纯音刺激

接下来,用MakeBeep函数来创建一段纯音,作为听觉刺激。

[myBeep, samplingRate] = MakeBeep(500, 1, freq);

第一个参数是音频的音高,我们将其设置为500,从而生成500hz的纯音。第二个参数是音频的时长 (单位:秒),我们将其设置为1秒。第三个参数是采样率。返回的值有两个,一个是可以用于生成音频的数组MyBeep,另一个是samplingRate,其并非是必须的 (所以这行代码也可以写为myBeep = MakeBeep(500, 1, freq)),其是采样率的值 (也就是和freq的值一致)。

同时,使用以下代码,将音频转换为立体声。

PsychPortAudio('FillBuffer', pahandle, [myBeep; myBeep]);

1.1.5 呈现音频刺激

接下来就可以呈现音频啦。很简单,只需要通过两行代码来“开始”和“结束”音频即可,相当于遥控器上的“播放”和“结束”按钮。

首先是“开始”。

PsychPortAudio('Start', pahandle, 1, 0, 1);

后三个参数含义如下:

  • repetitions:音频重复的次数,默认值为1,也就是仅播放一次。
  • when:何时开始播放。默认值为0,也就是立刻开始。
  • waitForStart:如果值为非0,则会返回一个精确的音频开始的时间,可以通过添加一个返回值来查看这个时间,例如startTime = PsychPortAudio('Start', pahandle, 1, 0, 1),“结束”的代码也可以输出startTime,所以这里就不输出了。

然后是“结束”。

[startTime, endPositionSecs, xruns, estStopTime] = PsychPortAudio('Stop', pahandle, 1, 1);

这里用到了两个参数,含义如下。

  • waitForEndOfPlayback:0会在音频尚未播放完成的情况下结束播放/录制,1为默认值,其会等待音频播放结束,2会强制结束音频的播放/录制 (可能会导致一些错误的声音),3则不会尝试结束音频。
  • blockUntilStopped:子函数是否需要等待声音处理完全停止,默认值为1。

再来看看返回值,中间两个暂且不需要管,第一个和最后一个返回值是音频开始和结束的时间戳,两者的差值 (即,estStopTime - startTime) 便是音频呈现的实际时间。

1.1.6 PsychPortAudio的关闭

使用以下代码关闭PsychPortAudio即可。

PsychPortAudio('Close', pahandle);

1.1.7 尝试运行

大功告成了,运行一下试试看。可以发现确实呈现了一声短暂的“哔”,同时程序在命令行窗口输出了一些信息。

PTB-INFO: New audio device -1 with handle 8 opened as PortAudio stream:
PTB-INFO: For 2 channels Playback: Audio subsystem is Windows WASAPI, Audio device name is 鎵0鍣�/鍚瓛 (Realtek Audio)
PTB-INFO: Real samplerate 48000.000000 Hz. Input latency 0.000000 msecs, Output latency 22.000000 msecs.

例如,我们能够了解到设备的代码 (也就是-1)、音频的格式为8bit (应该是这个意思?),该电脑采用的音频子系统是WASAPI,还告诉了我们音频设备的名称 (中文乱码了,对应的英文名称为Realtek Audio)、真实的采样率 (48000hz)、输入的滞后 (0ms,因为我们没有录制音频)、输出的滞后 (为22ms,低于500ms,根据官方的说法可以满足科研实验的要求)。

1.1部分的完整代码如下。注:该部分的代码参考了官方的例子beepdemo。

clear;
clc;
sca;

InitializePsychSound;

% Open Psych-Audio port
freq = 48000;
pahandle = PsychPortAudio('Open', [], 1, 1, freq, 2);

% Set the volume
PsychPortAudio('Volume', pahandle, 0.5);

% Make a beep which we will play back to the user
[myBeep, samplingRate] = MakeBeep(500, 1, freq);
PsychPortAudio('FillBuffer', pahandle, [myBeep; myBeep]);

% Show audio playback
PsychPortAudio('Start', pahandle, 1, 0, 1);
[startTime, endPositionSecs, xruns, estStopTime] = PsychPortAudio('Stop', pahandle, 1, 1);

% Close the audio device
PsychPortAudio('Close', pahandle);

1.2 呈现音频文件中的音频

PsychPortAudio的基本功能已经讲完了,所以这部分就不打算讲了,基本的框架和上一部分是一样的,区别在于需要从音频文件中提取audio data,具体可以参考PsychDemos文件夹中的例子“BasicSoundOutputDemo.m”。

1.3 听觉刺激的timing

当我们需要记录音频的实际呈现时长 (例如,先呈现一定时长的纯音,然后让被试按键复现出来),或者其他一些类似的情境,就会涉及到timing的问题。此时应该如何准确地获取音频的时间信息呢?

我们首先想到的可能是GetSecs函数,但是根据网上的讨论,该方法可能并不合适 (无论是在视觉还是听觉刺激中……)

GetSecs is almost always the wrong approach when visual or auditory stimulation is involved.

事实上,PsychPortAudio已经内置了相关的计时功能。呈现音频时,PsychPortAudio('Start')PsychPortAudio('Stop')能够提供onset和offset的时间戳,录制音频时,PsychPortAudio('GetAudioData')也能够提供类似的时间戳。

例如,对于上文的代码:

[startTime, endPositionSecs, xruns, estStopTime] = PsychPortAudio('Stop', pahandle, 1, 1);

此时,estStopTime - startTime便是刺激实际呈现的时长。

现在让我们来对比一下estStopTime - startTimeGetSecs() - t0的时间精度。上文写的代码不变,在PsychPortAudio('Close', pahandle)前添加如下代码。

timing = zeros(30, 2);
for i=1:30
    t0 = GetSecs();
    PsychPortAudio('Start', pahandle, 1, 0, 1);
    [startTime, endPositionSecs, xruns, estStopTime] = PsychPortAudio('Stop', pahandle, 1, 1);
    timing(i, 2) = GetSecs() - t0;  % Getsecs() timing
    timing(i, 1) = estStopTime - startTime;  % PsychPortAudio timing
    % WaitSecs(0.5);
end

大概的意思就是,呈现30次500hz的1秒纯音,同时用两种方式记录其实际的时长,将结果保存在数组timing中。连续呈现时,我们听到的并不是“哔、哔、哔、哔”,而是连续不断的“哔~~~~~~~~~”,如果想要区分出来,可以添加代码WaitSecs(0.5),使每两次呈现之间间隔0.5秒。为了省时间,我这里就不添加了。

运行一遍,喝口水等30秒,跑完啦,现在让我们来看看结果。首先输入以下函数查看原始数据。

disp(table(timing(:,1), timing(:,2), 'VariableNames', {'PsychPortAudio_timing'; 'GetSecs'}));

如下,可以发现,精度确实不太一样,PsychPortAudio自带的时间戳明显更好一些。

    PsychPortAudio_timing    GetSecs
    _____________________    _______

            1.0009           1.0123 
             1.003           1.0138 
            1.0006           1.0119 
           0.99157           1.0024 
            1.0009           1.0119 
            1.0008           1.0114 
            1.0017           1.0133 
            1.0011           1.0118 
            0.9948           1.0056 
            1.0033           1.0149 
           0.99187           1.0027 
            1.0016           1.0123 
            1.0053           1.0159 
            1.0027           1.0031 
            1.0001           1.0112 
           0.99558           1.0075 
           0.99662           1.0081 
            1.0051            1.016 
            1.0032            1.014 
            1.0027           1.0129 
           0.99696           1.0071 
            1.0028           1.0139 
             1.002           1.0133 
            0.9923           1.0031 
            1.0082           1.0192 
            1.0001           1.0108 
            1.0044           1.0154 
           0.99711           1.0078 
             1.004           1.0152 
            1.0073           1.0091 

输入以下函数查看描述统计的结果。

disp(table([mean(timing(:,1)); std(timing(:,1))], [mean(timing(:,2)); std(timing(:,2))], 'VariableNames', {'PsychPortAudio_timing'; 'GetSecs'}, 'RowNames', {'mean'; 'std'}));

结果如下。

            PsychPortAudio_timing     GetSecs 
            _____________________    _________

    mean           1.0006            1.0109
    std            0.0043014         0.0043937

再来做一下配对t检验。

[h, p] = ttest(timing(:,1),timing(:,2))

结果如下。

h =

     1


p =

   9.2364e-20

h=1,所以是拒绝零假设,p=9.2364e-20<.001,两种计时方式存在显著差异。

结论:推荐采用PsychPortAudio自带的时间戳进行计时,因为其做到了在刺激开始和结束呈现的那个时刻获取时间戳。不建议使用GetSecs,因为其只能在开始、结束呈现前后进行计时,这意味着计时会存在一定的滞后。

2 视觉刺激的呈现

2.1 绘制视觉刺激

上一篇关于PTB-3的文章已经简单展示了绘制视觉刺激的一些方法,包括呈现图片文件(例如呈现指导语)以及直接绘制视觉刺激(例如绘制注视点)。我们可以选取相应的函数来绘制想要的刺激,在命令行窗口输入Screen即可查看一些基本的函数,例如我们可以通过以下函数来绘制线条、矩形、椭圆等。

% Draw lines and solids like QuickDraw and DirectX (OS 9 and Windows):
currentbuffer = Screen(‘SelectStereoDrawBuffer’, windowPtr [, bufferid] [, param1]);
Screen(‘DrawLine’, windowPtr [,color], fromH, fromV, toH, toV [,penWidth]);
Screen(‘DrawArc‘,windowPtr,[color],[rect],startAngle,arcAngle)
Screen(‘FrameArc‘,windowPtr,[color],[rect],startAngle,arcAngle[,penWidth] [,penHeight] [,penMode])
Screen(‘FillArc‘,windowPtr,[color],[rect],startAngle,arcAngle)
Screen(‘FillRect’, windowPtr [,color] [,rect] );
Screen(‘FrameRect’, windowPtr [,color] [,rect] [,penWidth]);
Screen(‘FillOval’, windowPtr [,color] [,rect] [,perfectUpToMaxDiameter]);
Screen(‘FrameOval’, windowPtr [,color] [,rect] [,penWidth] [,penHeight] [,penMode]);
Screen(‘FramePoly’, windowPtr [,color], pointList [,penWidth]);
Screen(‘FillPoly’, windowPtr [,color], pointList [, isConvex]);

此外还有不少高级的绘制方法,例如画gabor patch什么的,PTB-3的官网上有相应的例子,大家可以根据需要自己进行学习。

2.2 视觉刺激的timing

首先说一下视觉刺激类函数的工作原理。

在PTB-3中,刺激的绘制使用了一种叫做双重缓冲绘制模型(Double buffered drawing model)的方法(见下图)。这意味着,程序并不会在用户看到的屏幕上(即,前缓冲器,Frontbuffer)直接绘制刺激,而是先在用户无法看到的后缓冲器上(Backbuffer)绘制刺激(视觉刺激是通过OpenGL绘制的,与此同时,Matlab和PTB能够做一些其他事情,例如判断按键反应、输出声音等等)。绘制完毕后,通过Screen('Flip')命令对缓冲器进行翻转(flip),使前缓冲器变成后缓冲器、后缓冲器变成前缓冲器,用户便能看见绘制好的刺激了。通过这种方式,PTB-3便实现了复杂刺激的快速呈现。

需要注意的是,在翻转屏幕之后,我们还需要等待显示器刷新屏幕,才能真正显示出绘制好的刺激。

以上图为例。对于一个一秒刷新60帧的显示器,①是第一帧,②是第二帧,③是第三帧,以此类推,直到第60帧(即,1秒),我们在这60帧的间隔中不停地翻转屏幕从而呈现刺激。因为1秒内屏幕刷新的帧数为60次 (理想情况下,不考虑误差),所以翻转的上限是60次。这是因为屏幕翻转之后必须要等待下一次刷新才能将翻转的内容显示出来。

例如,①是第0秒,即刺激呈现的起始时间点 (onset time),那么,下一次刷新的时间②就是第0.0167秒 (1除60,约等于0.0167)。这意味着,在①和②之间 (即显示器下一次刷新之前) 是不可能改变屏幕上的刺激的,即使我们在这段时间内 (例如第0.0100秒) 调用Screen('Flip')对屏幕进行翻转,翻转后的画面也需要等时间抵达②的时候(即,显示器再一次刷新)才会呈现在屏幕上。

换句话说,我们需要在显示器每次刷新之前翻转屏幕,为此,我们需要将翻转的时间设置在两次刷新之间,例如上图的when这个时间点。为什么不设置在显示器刷新的同时呢(例如②)?这是因为,我们所计算的下一次翻转的时间点是一个浮点数,当对该浮点数进行四舍五入时,会产生些许误差,这一误差可能会导致翻转的时间晚于下一次屏幕刷新的时间 (即,②),此时翻转的内容将要等到下下次屏幕刷新的时候 (即,③) 才能够呈现出来,这就是“丢帧”,丢帧将一直持续,当丢帧持续且大量地存在的时候,刺激就会呈现出一闪一闪的现象。

大概就是这么回事,现在来看一下基本的变量,有了这些变量,我们就能够以帧为单位呈现刺激了。

numSecs = 1;
waitFrames = 1;
ifi=Screen('GetFlipInterval', w);
frame = round(numSecs / ifi);  % frame rate per sec, or round(FrameRate(w))
slack = ifi/2;

含义如下:

  • numSecs:刺激呈现的秒数,此处定义为1
  • waitFrames:等待刷新的帧数,此处定义为1,但在下文的示例中,我们采用waitFrame - 0.5 (即,0.5) 作为等待刷新的帧数,下文会对此进行解释
  • ifi:通过Screen('GetFlipInterval')获取显示器的垂直刷新率,例如我的显示器每秒刷新60帧,所以ifi等于1/60,约为0.0167
  • frame:刺激呈现的帧数,根据numSecs和ifi进行计算
  • slack:目前我没有看到关于slack的明确解释,我自己的理解是和waitFrames的作用差不多。

翻转的语句如下。

vbl = Screen('Flip', w, vbl + (waitFrames - 0.5) * ifi);

其中,等号左边的vblScreen('Flip')函数的一个可选的返回值,其记录了屏幕翻转的时间戳。上一篇关于PTB-3的文章已经初步介绍了Screen('Flip')的作用 (即,翻转屏幕),这里介绍一下其在高时间精度的程序中的用法。在该函数中有一个可选的参数when,其作用是告诉电脑在何时翻转屏幕,例如Screen('Flip', w, when)的含义为,在“when”秒之后刷新名为w的窗口。默认情况下when=0,即在下一个可能的时候(屏幕刷新的时候)刷新窗口。根据上文的解释,我们将when设置为vbl+(waitFrame - 0.5)*ifi,也就是在上一个时间点vbl(例如上图的①)之后的半帧(例如上图的when)进行翻转。如果设置为vbl+ifi的话,就会是在上图的②(即下一次显示器刷新的时候)进行翻转了。翻转时会返回新的时间戳,同样记为vbl,于是我们就可以在新的时间戳的基础上继续翻转。

我们可以想象有几只小仓鼠在电脑里工作,如下图。最左边的仓鼠负责在一个双面黑板的背面画刺激,画完之后,另一只仓鼠会将黑板翻转,从而使绘制好的刺激呈现在黑板的前面。右边的仓鼠每隔一段时间(例如0.0167秒)就对着黑板拍一次照,拍好的照片便会呈现在我们的电脑屏幕上。

这三只仓鼠分别对应刺激绘制类函数、Screen('Flip')函数,以及显示器刷新。

以此为基础,目前我了解到的有两种高时间精度的刺激呈现方法。

其一:

for i = 1:frame
    Screen('DrawDots', w, [wrect(3)/2; wrect(4)/2], 10, [255,255,255], [], 1);
    vbl = Screen('Flip', w, vbl + (waitFrames - 0.5) * ifi);
end

这个是每帧都翻转,可以用于绘制动态刺激(例如定义一个变量x作为刺激的横坐标,每次循环时x都加1,这样便能呈现不断移动的刺激)。

其二:

Screen('DrawDots', w, [wrect(3)/2; wrect(4)/2], 10, [255,255,255], [], 1);
onset_t = Screen('Flip', w);
offset_t = Screen('Flip', w, onset_t + numSecs - slack); 

这个是绘制静态刺激的,绘制完毕后翻转一次使刺激呈现并记下起始时间onset_t,呈现numScs秒后再次翻转从而使刺激消失并记下结束时间offset_t,刺激呈现的实际时长就是offset_t - onset_t。注意第二次翻转的时间减去了slack,目的是在显示器刷新的前半帧时间进行翻转。

两种方法的滞后是不一样的。对于方法二,总共只需要翻转两次屏幕。因此后者的滞后相对而言更低一些。例如,方法一记录到的刺激呈现时间可能会是1.0166s,方法二则可能是1.0018s。

对于按键反应,可以采用以下方法进行计时:

for i = 1:frame

    % draw stimulus
    Screen('DrawDots', w, [wrect(3)/2; wrect(4)/2], 10, [255,255,255], [], 1);
    
    vbl = Screen('Flip', w, vbl + (waitframes - 0.5) * ifi);
    if i == 1
        onset_t = vbl;
    end

    % response
    [keyisdown, secs, keycode] = KbCheck;  % secs: Time of keypress as returned by GetSecs
    if keyisdown == 1
        break
    end
end

offset_t = Screen('Flip', w, onset + (waitframes - 0.5) * ifi);

在这个例子中,我们呈现一个圆点作为刺激,呈现时间为1000ms,若出现按键反应则刺激立即消失,否则1000ms后刺激自动消失。

其中,KbCheck返回的secs是发生按键反应的时候采用GetSecs函数记录到的时间。也就是说,KbCheck本身就自带了计时的功能,无需再添加额外的代码 (例如在刺激呈现之前、之后调用GetSecs) 来记录时间。

对于按键反应,可以采用secs - onset_t作为反应时,其反映的是从刺激呈现到按键的时间。此外,以offset_t - onset_t作为反应时也是可行的,其反应的是刺激呈现在屏幕上的时长。两种方法存在略微的差别(例如,未按键的情况下,前者可能是1.0007s,后者可能是1.0164s),但不存在何者更精确之说,因为不同方法反映的是不同的东西,根据具体的实验设计进行选取即可。

此外,判断按键时,PTB-3实际上是将所有按键放置在一个矩阵中,然后循环判断是否按下了某个按键,所以相比鼠标(只有三个键),键盘的滞后是稍微高一些的(大概几十ms)。相对于普通的键盘,鼠标、特制的键盘以及反应盒是更好的选择。

3 视听刺激的呈现

这是一个同时呈现视听刺激的例子,每个trial开始时呈现视听刺激,trial时长过半时同时停止视听刺激的呈现,并全程记录按键反应。

for trials = 1:trial_num
    
    % prepare and start audio playback for this trial
    PsychPortAudio('FillBuffer', pahandle, audio_stim);
    startTime = PsychPortAudio('Start', pahandle, 1, 0, 1);
    
    for i=1:frame_num
        
        % in this example, we only present both audio and visual stimuli in the half of frames
        if i < frame_num*0.5
            Screen('DrawTexture', w, visual_stim, []);
        elseif i == frame_num*0.5
            [startTime, endPositionSecs, xruns, estStopTime] = PsychPortAudio('Stop', pahandle, 1, 1);
        end
        
        % flip
        vbl = Screen('Flip', w, vbl+(1 - 0.5)*ifi);
        
        [keyisdown, secs, keycode] = KbCheck;
        % put some codes about RT, RESP and ACC here...
    end
end
PsychPortAudio('Close', pahandle);  % do not close it until all trials are finish

其中,audio_stim和visual_stim分别是听觉和视觉刺激对象(可以做成一个矩阵什么的,每个trial读取对应的刺激)。我的想法是直接使用听觉刺激的开始和结束时间来计时,不过我也不知道这么写是否正确,如果有人知道请告诉我……

4 扩展

4.1 扩展1 快速了解函数的参数信息

本文提到了各种与视听刺激呈现有关的函数,每个函数又有着多个参数,实际去使用时,我们很可能已经忘记某个参数的含义了。此时一定要善用帮助系统。

属于MATLAB的函数,可以用help来查看帮助信息,例如help MakeBeep会得到以下结果。

>> help MakeBeep

  [beep,samplingRate] = MakeBeep(freq,duration,[samplingRate])
 
  Compute array that can be used by Snd to produce a pure tone of specified
  "freq" (Hz) and "duration" (s). The "samplingRate" defaults to
  Snd('DefaultRate').
  
    beep = MakeBeep(freq,duration);
    Snd('Open');
    .... do some stuff ....
    Snd('Play',beep);
 
  See Snd.

属于Psychtoolbox的函数,可以使用“函数+问号”的方式,例如PsychPortAudio open?返回的是函数PsychPortAudio('Open')的相关信息 (内容比较多,所以下面只展示了其中一部分)。

>> PsychPortAudio open?

Usage:

pahandle = PsychPortAudio('Open' [, deviceid][, mode][, reqlatencyclass][, freq][, channels][, buffersize][, suggestedLatency][, selectchannels][, specialFlags=0]);

Open a PortAudio audio device and initialize it. Returns a 'pahandle' device
handle for the device.
On most operating systems you can open each physical sound device only once per
running session. If you feel the need to call 'Open' multiple times on the same
audio device, read the section about slave devices and the help 'PsychPortAudio
OpenSlave?' instead for a suitable solution.

......

See also: Close GetDeviceSettings 

这里有一个小技巧,例如,我们想要知道PsychPortAudio('Open')中的参数freq的含义,此时可以单击命令行窗口,然后按快捷键Ctrl+F (对于Windows系统而言),输入“freq”,从而快速找出有关该参数的内容 (见下图)。

4.2 扩展2 在网络上寻找答案

如果尝试各种方法 (帮助系统、tutorial等) 都无法解答疑惑,此时可以在网络上找找解决的方法。例如:

Stack Overflow:大家会在上面寻问各种编程方面的问题,包括Psychtoolbox。

Psychtoolbox的官方论坛:提问之后,论坛上的网友甚至Psychtoolbox的开发者(例如Mario Kleiner)会热心地帮你解答。

 

References

  • https://stackoverflow.com/questions/38014908/explaning-a-line-in-code-from-psychtoolbox-tutorial
  • http://www.portaudio.com/docs/v19-doxydocs/api_overview.html
  • https://peterscarfe.com/beepdemo.html
  • https://raw.githubusercontent.com/Psychtoolbox-3/Psychtoolbox-3/beta/Psychtoolbox/PsychDemos/BasicSoundOutputDemo.m
  • https://baike.baidu.com/item/%E9%9F%B3%E9%A2%91%E9%87%87%E6%A0%B7%E7%8E%87/9023551?fr=aladdin
  • http://psychtoolbox.org/docs/PsychPortAudio-Start
  • https://psychtoolbox.discourse.group/t/how-to-get-the-participant-reaction-time-for-audio/3080/11
  • https://pure.mpg.de/rest/items/item_1790332/component/file_3136265/content

 
----------2021.07.02更新----------
增加了“1.3 听觉刺激的timing”部分的内容
----------2021.09.04更新----------
增加了1.2、2.1、2.2和3部分的内容

你可能感兴趣的:(【MATLAB】Psychtoolbox中的视听刺激的呈现及精确计时的方法)