Supplementary material

Recommended hardware, software, and settings

Recommended hardware: ASF runs on the shoulders of the Psychophysics toolbox. Check http://psychtoolbox.org/wikka.php?wakka=SystemRequirements for recommendations.

Software: ASF requires MATLABand the Psychophysics Toolbox (http://psychtoolbox.org) to be installed. Currently, the Psychophysics Toolbox needs the 32-bit version of MATLAB to run. Please refer to the PTBWebsite for updates on this.

Settings: In order to achieve optimal accuracy of timing, disabling all network adapters during an experiment is recommended; also refer to PsychToolbox’s suggestions (help SyncTrouble).

Listings for example project

Stimulus definition file example.std

Listing 1:

S01_empty.bmp

S02_fix.bmp

S03_prime_left.bmp

S04_prime_right.bmp

S05_mask_left.bmp

S06_mask_right.bmp

Trial definition file example.trd

Listing 2:

2 2

1 0 2 30 3 1 2 2 5 6 1 90 4 5 1

2 0 2 30 4 1 2 2 5 6 1 90 4 5 1

1 0 2 30 4 1 2 2 6 6 1 90 4 5 3

2 0 2 30 3 1 2 2 6 6 1 90 4 53

3 0 2 30 3 1 2 5 5 6 1 90 4 51

4 0 2 30 4 1 2 5 5 6 1 90 4 51

3 0 2 30 4 1 2 5 6 6 1 90 4 53

Configurable MATLAB script, which generates a trd-file

Listing 3:

function makeExampleTrd(strProjectName)

%function makeExampleTrd(strProjectName)

%%Creates a .trd file for a masked priming experiment in ASF

%

%Example call to create 'exampleScripted.trd':

%makeExampleTrd('exampleScripted')

%

%%ASF (C) Jens Schwarzbach

%------

%DESIGN ASPECTS

%------

Design.soaLevels = [3, 6];

Design.nSoaLevels = length(Design.soaLevels);

Design.primeDirectionLevels = [0, 1]; %LEFT, RIGHT

Design.nPrimeDirectionLevels = length(Design.primeDirectionLevels);

Design.maskDirectionLevels = [0, 1]; %LEFT, RIGHT

Design.nMaskDirectionLevels = length(Design.maskDirectionLevels);

Design.primeDuration = 1; %PRIMES WILL BE SHOWN FOR ONE FRAME

Design.maskDuration = 6; %MASK WILL BE SHOWN FOR SIX FRAMES

Design.responseWindow = 90; %PARTICIPANTS HAVE NINETY FRAMES TO RESPOND

Design.factorNames = {'Congruence', 'SOA'};

Design.nFactors = length(Design.factorNames);

Design.factorLevels = [2, Design.nSoaLevels];

Design.levelNames{1, 1} = 'congruent';

Design.levelNames{1, 2} = 'incongruent';

for i = 1:Design.factorLevels(2)

Design.levelNames{2, i} = sprintf('SOA%d', Design.soaLevels(i));

end

Design.nReplicationsPerCondition = 10;

%SIMPLE VARIATION OF TRIAL ONSET ASYNCHRONIES FOR fMRI

%WILL ONLY BECOME EFFECTIVE WHEN CALLING ASF WITH

%Cfg.useTrialOnsetTimes = 1

Design.jitteredTrialOnsetAsynchronies = [2:0.5:7]; %MIN, STEP, MAX

%PICTURE CODES MUST REFLECT ORDER IN STD FILE

PictureCodes.empty = 1;

PictureCodes.fix = 2;

PictureCodes.primeLeft = 3;

PictureCodes.primeRight = 4;

PictureCodes.maskLeft = 5;

PictureCodes.maskRight = 6;

%------

%HARDWARE ASPECTS

%------

ResponseKey.left = 1; %LEFT MOUSE BUTTON

ResponseKey.right = 3; %RIGHT MOUSE BUTTON

%------

%LOOP THROUGH ENTIRE DESIGN TO CREATE A LIST CONTAINING ALL TRIALS

%------

%SAME FOR ALL TRIALS

ThisTrial.tOnset = 0;%UNUSED PARAMETER

ThisTrial.primeDuration = Design.primeDuration;

ThisTrial.maskDuration = Design.maskDuration;

ThisTrial.responseWindow = Design.responseWindow;

ThisTrial.startRTmeasurementOnPage = 3;

ThisTrial.endRTmeasurementOnPage = 4;

ThisTrial.empty = PictureCodes.empty;

ThisTrial.fix = PictureCodes.fix;

counter = 0;

for iPrimeDirection = 1:Design.nPrimeDirectionLevels

for iMaskDirection = 1:Design.nMaskDirectionLevels

for iSoa = 1:Design.nSoaLevels

for iReplication = 1:Design.nReplicationsPerCondition

%FILL STRUCTURE ThisTrial WITH ALL PARAMETERS THAT

%ARE RELEVANT FOR THIS TRIAL

%VARIABLE PRESTIMULUS PERIOD BETWEEN 15 AND 45 FRAMES

ThisTrial.preStimPeriod = round(rand*30)+15;

%PRIME

if iPrimeDirection == 1

ThisTrial.prime = PictureCodes.primeLeft;

else

ThisTrial.prime = PictureCodes.primeRight;

end

%MASK

if iMaskDirection == 1

ThisTrial.mask = PictureCodes.maskLeft;

ThisTrial.correctResponse = ResponseKey.left;

else

ThisTrial.mask = PictureCodes.maskRight;

ThisTrial.correctResponse = ResponseKey.right;

end

%SOA-prime-mask

ThisTrial.Soa = Design.soaLevels( iSoa );

%CONGRUENCE (CONGRUENT = 0, INCONGRUENT = 1)

if iPrimeDirection == iMaskDirection

ThisTrial.congruence = 0; %congruent

else

ThisTrial.congruence = 1; %incongruent

end

%WE CODE CONGRUENCE AND SOA-LEVEL

%MAKE SURE THIS HAS THE SAME ORRDER AS FACTORIAL INFO

ThisTrial.code = ASF_encode(...

[ThisTrial.congruence, iSoa-1],...

[2, Design.nSoaLevels] );

%CREATE A TRIALDEFINITION FOR THIS TRIAL

counter = counter + 1;

TrialVec( counter ) = ThisTrial;

end

end

end

end

Design.nTrials = counter;

%------

%RANDOMIZE TRIALS

%------

TrialVec = TrialVec(randperm(Design.nTrials));

%------

%JITTER TRIAL ONSET TIMES (for fMRI DEMO)

%------

TrialVec(1).tOnset = 12; %LET SCANNER ACQUIRE A COUPLE OF VOLUMES BEFORE

%STARTING

jitterVec = Design.jitteredTrialOnsetAsynchronies;

for iTrial = 2:Design.nTrials

%PERMUTE

jitterVec = jitterVec( randperm( length( jitterVec ) ) );

%PICK FIRST ELEMENT OF PERMUTED JITTERVEC AS THIS TRIAL'S JITTER

jitter = jitterVec( 1 );

%NEW ONSET TIME IS ONSET TIME OF PREVIOUS TRIAL PLUS JITTER

TrialVec(iTrial).tOnset = TrialVec( iTrial-1 ).tOnset + jitter;

end

%YOU MAY WANT TO ADD AN EMPTY TRIAL AT THE VERY END OF YOUR EXPERIMENT

%IN ORDER TO LET THE BOLD RESPONSE RELAX. NOT COVERED HERE.

%------

%WRITE TRD FILE

%------

writeTrdFile( strProjectName, Design, TrialVec)

function writeTrdFile( strProjectName, Design, TrialVec)

%function writeTrdFile( strProjectName, Design, TrialVec)

%PUTS TOGETHER ALL INFORMATION IN TRIALDEFINITION TO GENERATE A TRD FILE

fName = [strProjectName, '.trd'];

fid = fopen( fName, 'w');

if fid

fprintf( 1, 'WRITING %s ...', fName);

else

error( 'Error: %s cannot be created.', fName);

end

%WRITE DESIGN INFO

fprintf(fid, '%3d ', Design.factorLevels);

fprintf(fid, '%s ', Design.factorNames{:});

for i = 1:Design.nFactors

for j = 1:Design.factorLevels(i)

fprintf(fid, '%s ', Design.levelNames{i, j});

end

end

%WRITE TRIAL INFO, ONE LINE PER TRIAL

for i = 1:length( TrialVec )

ThisTrial = TrialVec( i ); %PICK CURRENT TRIAL

fprintf( fid, '\n' ); %NEW LINE

fprintf( fid, '%3d\t', ThisTrial.code );

fprintf( fid, '%8.3f\t', ThisTrial.tOnset );

fprintf( fid, '%3d %3d\t\t', ThisTrial.fix, ThisTrial.preStimPeriod );

fprintf( fid, '%3d %3d\t\t', ThisTrial.prime, ThisTrial.primeDuration);

interStimulusInterval = ThisTrial.Soa - ThisTrial.primeDuration;

fprintf( fid, '%3d %3d\t\t', ThisTrial.fix, interStimulusInterval );

fprintf( fid, '%3d %3d\t\t', ThisTrial.mask, ThisTrial.maskDuration );

fprintf( fid, '%3d %3d\t\t', ThisTrial.fix, ThisTrial.responseWindow );

fprintf( fid, '%3d\t', ThisTrial.startRTmeasurementOnPage );

fprintf( fid, '%3d\t', ThisTrial.endRTmeasurementOnPage );

fprintf( fid, '%3d', ThisTrial.correctResponse );

end

fclose(fid);

Basic analysis script

Listing4:

function analyzeExample(projectName)

%function analyzeExample(projectName)

%

%%EXAMPLE CALL

%analyzeExample('example')

%

%%ASF (C) Jens Schwarzbach

%LOAD STORED LOG FILE

load( projectName )

%res contains the following information extracted from the log

%COL 1: CODE

%COL 2: RT

%COL 3: KEY

%COL 4: CORRECTRESPONSE

res = ASF_readExpInfo( ExpInfo );

dat.code = ASF_decode(res(:, 1), [2, 2]);

congruenceVec = ExpInfo.factorinfo.levelNames(1,:);

soaVec = ExpInfo.factorinfo.levelNames(2,:);

for iCongruence = 1:2

for iSoa = 1:2

%SELECT TRIALS FOR THIS FACTORIAL COMBINATION, IN WHICH

%THE PARTICIPANT RESPONDED CORRECTLY

casesCorrect = find(...

(dat.code(:, 1)+1 == iCongruence) &...

(dat.code(:, 2)+1 == iSoa) &...

(res(:, 3) == res(:, 4)) );

%CALCULATE MEAN REACTION TIME AND STANDARD ERROR OF THE MEAN

%FOR THIS FACTORIAL COMBINATION

meanRT( iCongruence, iSoa ) = mean( res(casesCorrect, 2) );

seRT( iCongruence, iSoa ) =...

std( res(casesCorrect, 2) )/sqrt(length(casesCorrect));

end

end

%CREATE A LINEPLOT WITH ERRORBARS (SEM)

figure

ebh = errorbar(meanRT', seRT');

set(ebh, 'LineWidth', 2)

set(gca, 'xtick', [1, 2], 'xlim', [0.5, 2.5], 'xtickLabel', soaVec)

legend(congruenceVec, 'Location', 'NorthWest')

ylabel('RT [ms]')

xlabel('SOA [frames]')

Example session

%STEP 1: prepare experiment

makeExampleTrd('exampleScripted')

%STEP 2: run experiment

%WITHOUT JITTER

ExpInfo = ASF('example.std', 'exampleScripted.trd', 'example', [])

%WITH JITTER

Cfg.useTrialOnsetTimes = 1;

ExpInfo = ASF('example.std', 'exampleScripted.trd', 'example', Cfg)

%STEP 3: analyze experiment

analyzeExample('example')

Supplementary Fig.1 Output of the analysis script analyzeExample. Reaction times are shorter on congruent than on incongruent trials. The effect is stronger for longer SOAs than for shorter SOAs, indicating that primes produce a covert preparation of motor responses, as predicted by an accumulator model of response priming (Vorberg, Mattler, Heinecke, Schmidt, & Schwarzbach, 2003)

Inside the black box: The logic of the runtime module

General structure of ASF’s runtime engine

ASF’s runtime engine consists of three major parts:(1) calls to initialization routines based on functions of the Psychophysics Toolbox to create an OpenGLwindow and custom routines that initialize hardware, such as response devices and routines that read in the stimulus and trialdefinition files;(2) a loop that calls, for each trial of the experiment, the function ASF_ShowTrial() or a user-written trial presentation code or plugin;(3) shutdown routines that write data to a file and that close files and hardwaredevices.

ASF_ShowTrial

A trial is defined by a trial code, a trial onset time (in seconds with respect to the start of the experiment), a vector of stimulus numbers, a vector of respective stimulus durations (in refresh frames), a page number at which response collection shall start, and a code for the correct response.

This function splits a trial into five phases.

·  Phase 1: Wait for the right time to start with the trial presentation. This may be the defined trial onset time (immediately, if the trial onset time is 0) or a specific event—for example, a synchronization pulse from the MR scanner (this depends on the Cfg settings).

·  Phase 2: Loop through page presentations without response collection.

·  Phase 3: Loop through page presentations while checking for a response

·  Phase 4: After a response has been given, loop through any remaining page presentations without response collection.

·  Phase 5: After all pages have been presented, the program can give a feedback for this trial (depending on Cfg settings).

This separation has been chosen in order to maximize timing accuracy, while keeping flexibility for the programmer. For example, page presentation with and without response collection are conceptually separated because the former can be hooked to the highly accurate refresh signal of a graphics card, while the latter requires polling of the status of response devices, which can introduce unwanted delays. While this separation of phases is suitable for most experiments, the whole sequence of operations and inner structure of this function can be changed to meet the experimenters’ needs and can be saved as a user-supplied trial function or plugin. The listing below is a copy of ASF’s internal function ASF_showTrial() and can be used as a template for creating new plugins.

Listing 5:

function TrialInfo = ASF_showTrialSample(atrial, windowPtr, Stimuli, Cfg)

%function TrialInfo = ASF_showTrialSample(atrial, windowPtr, Stimuli, Cfg)

%

%%SAVE A COPY OF THIS FILE UNDER A DIFFERENT NAME E.G. myPlugin.m

%%TO USE IT AS A STARTING POINT FOR YOUR PLUGIN-DEVELOPMENTS

%% written by: jens schwarzbach

% VBLTimestamp system time (in seconds) when the actual flip has happened

% StimulusOnsetTime An estimate of Stimulus-onset time

% FlipTimestamp is a timestamp taken at the end of Flip's execution

VBLTimestamp = 0; StimulusOnsetTime = 0; FlipTimestamp = 0; Missed = 0;

Bpos = 0;

StartRTMeasurement = 0; EndRTMeasurement = 0;

timing = [0, VBLTimestamp, StimulusOnsetTime, FlipTimestamp, Missed, Bpos];

nPages = length(atrial.pageNumber);

timing(nPages, end) = 0;

this_response = [];

%ON PAGES WITH WITH RESPONSE COLLECTION MAKE SURE THE CODE RETURNS IN TIME

%BEFORE THE NEXT VERTICAL BLANK. FOR EXAMPLE IF THE RESPONSE WINDOW IS 1000

%ms TOLERANCE MAKES THE RESPONSE COLLECTION CODE RETURN AFTER 1000ms-0.3

%FRAMES, I.E. AFTER 995 ms AT 60Hz

toleranceSec = Cfg.Screen.monitorFlipInterval*0.3;

%HOWEVER, THIS MUST NOT BE LONGER THAN ONE FRAME

%DURATION. EXPERIMENTING WITH ONE QUARTER OF A FRAME

responseGiven = 0;

this_response.key = [];

this_response.RT = [];

%------

%TRIAL PRESENTATION HAS SEVERAL PHASES

% 1) WAIT FOR THE RIGHT TIME TO START TRIAL PRESENTATION. THIS MAY BE

% IMMEDIATELY OR USER DEFINED (E.G. IN fMRI EXPERIMENTS)

%

% 2) LOOP THROUGH PAGE PRESENTATIONS WITHOUT RESPONSE COLLECTION

%

% 3) LOOP THROUGH PAGE PRESENTATIONS WHILE CHECKING FOR USER INPUT/RESPONSES

%

% 4) LOOP THROUGH PAGE PRESENTATIONS WITHOUT RESPONSE COLLECTION

% (AFTER RESPONSE HAS BEEN GIVEN)

%

% 5) FEEDBACK

%------

%IF YOU WANT TO DO ANY OFFLINE STIMULUS RENDERING (I.E. BEFORE THE TRIAL

%STARTS), PUT THAT CODE HERE

%LOG DATE AND TIME OF TRIAL

strDate = datestr(now); %store when trial was presented

%------

% PHASE 1) WAIT FOR THE RIGHT TIME TO START TRIAL PRESENTATION. THIS MAY BE

% IMMEDIATELY OR USER DEFINED (E.G. IN fMRI EXPERIMENTS)

%------

%IF EXTERNAL TIMING REQUESTED (e.g. fMRI JITTERING)

if Cfg.useTrialOnsetTimes

wakeupTime = WaitSecs('UntilTime', Cfg.experimentStart + atrial.tOnset);

else

wakeupTime = GetSecs;

end

%LOG TIME OF TRIAL ONSET WITH RESPECT TO START OF THE EXPERIMENT

%USEFUL FOR DATA ANALYSIS IN fMRI

tStart = wakeupTime - Cfg.experimentStart;

if Cfg.Eyetracking.doDriftCorrection

EyelinkDoDriftCorrect(Cfg.el);

end

%------

%END OF PHASE 1

%------

%MESSAGE TO EYELINK

Cfg = ASF_sendMessageToEyelink(Cfg, 'TRIALSTART');

%------

% PHASE 2) LOOP THROUGH PAGE PRESENTATIONS WITHOUT RESPONSE COLLECTION

%------

%CYCLE THROUGH PAGES FOR THIS TRIAL

atrial.nPages = length(atrial.pageNumber);

for i = 1:atrial.startRTonPage-1

if (i > atrial.nPages)

break;

else

%PUT THE APPROPRIATE TEXTURE ON THE BACK BUFFER

Screen('DrawTexture', windowPtr, Stimuli.tex(atrial.pageNumber(i)));

%PRESERVE BACK BUFFER IF THIS TEXTURE IS TO BE SHOWN

%AGAIN AT THE NEXT FLIP

bPreserveBackBuffer = atrial.pageDuration(i) > 1;

%FLIP THE CONTENT OF THIS PAGE TO THE DISPLAY AND PRESERVE IT IN

%THE BACKBUFFER IN CASE THE SAME IMAGE IS TO BE FLIPPED AGAIN TO

%SCREEN

[VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Bpos] =...

ASF_xFlip(windowPtr, Stimuli.tex(atrial.pageNumber(i)),...

Cfg, bPreserveBackBuffer);

%SET TRIGGER (PARALLEL PORT AND EYELINK)

ASF_setTrigger(Cfg, atrial.pageNumber(i));

%LOG WHEN THIS PAGE APPEARED

timing(i, 1:6) = [atrial.pageDuration(i), VBLTimestamp,...

StimulusOnsetTime FlipTimestamp Missed Bpos];

%WAIT OUT STIMULUS DURATION IN FRAMES. WE USE PAGE FLIPPING

%RATHER THAN A TIMER WHENEVER POSSIBLE BECAUSE GRAPHICS BOARDS

%PROVIDE EXCELLENT TIMING; THIS IS THE REASON WHY WE MAY WANT TO

%KEEP A STIMULUS IN THE BACKBUFFER (NONDESTRUCTIVE PAGE FLIPPING)

%NOT ALL GRAPHICS CARDS CAN DO THIS. FOR CARDS WITHOUT AUXILIARY

%BACKBUFFERS WE COPY THE TEXTURE EXPLICITLY ON THE BACKBUFFER

%AFTER IT HAS BEEN DESTROYED BY FLIPPING

nFlips = atrial.pageDuration(i) - 1; %WE ALREADY FLIPPED ONCE

for FlipNumber = 1:nFlips

%PRESERVE BACK BUFFER IF THIS TEXTURE IS TO BE SHOWN

%AGAIN AT THE NEXT FLIP

bPreserveBackBuffer = FlipNumber < nFlips;

%FLIP THE CONTENT OF THIS PAGE TO THE DISPLAY AND PRESERVE

%IT IN THE BACKBUFFER IN CASE THE SAME IMAGE IS TO BE

%FLIPPED AGAIN TO THE SCREEN

ASF_xFlip(windowPtr, Stimuli.tex(atrial.pageNumber(i)),...

Cfg, bPreserveBackBuffer);

end

end

end

%------

%END OF PHASE 2

%------

%------

% PHASE 3) LOOP THROUGH PAGE PRESENTATIONS WHILE CHECKING FOR USER

% INPUT/RESPONSES

%------

%SPECIAL TREATMENT FOR THE DISPLAY PAGES ON WHICH WE ALLOW REACTIONS