Hi everyone,
I’m experiencing a consistent timing issue in my Psychtoolbox experiment. The very first stimulus in each trial (e.g., the fixation cross or the feedback after response window) is always about ~30 ms shorter than the intended duration (e.g., 0.5 s).
However, all subsequent stimuli within the same trial are perfectly precise when I chain flips using:
vbl = Screen('Flip', win, vbl + (waitframes - 0.5)*ifi);
- The first stimulus segment in each tria or the part after response windowl (e.g., fixation) is ~20–30 ms shorter than expected.
*The video stimuli segments that use
vbl = Screen('Flip', win, vbl + (waitframes - 0.5)*ifi)
are perfectly frame-accurate. - IFI is ~0.0167 s at 60 Hz(59.95Hz)
For example:
Expected fixation: 0.500 s
Measured fixation: ~0.470 s
Psychtoolbox 3.0.19
MATLAB R2024b
Windows
I was wondering what is the recommended way to make the first segment or the segement after break in a trial frame-accurate like subsequent chained segments?
% Preallocate a structure to store data
results = struct();
for trial = 1:2
response = -1;
rt = -1;
response_key = -1;
distance = trialTable_s.distance(trial);
cat_relate = trialTable_s.cat_relate{trial};
if strcmp(cat_relate, 'same')
correct_response_key = keySame;
else
correct_response_key = keyDiff;
end
%% -------------------- Trial Preparation -------------------------
% Extract video names from design table
v1_raw = trialTable_s.v1{trial};
v2_raw = trialTable_s.v2{trial};
% Remove file extension
v1_id = char(erase(string(v1_raw), ".mp4"));
v2_id = char(erase(string(v2_raw), ".mp4"));
% Load frame file paths
framesL = make_frame_list(stimDir, v1_id, stimFrames, 'png');
framesR = make_frame_list(stimDir, v2_id, stimFrames, 'png');
% Preallocate texture arrays (GPU memory)
texL_all = zeros(1, stimFrames);
texR_all = zeros(1, stimFrames);
% Convert images into PTB textures (improves presentation timing)
for frame = 1:stimFrames
texL_all(frame) = Screen('MakeTexture', win, framesL{frame});
texR_all(frame) = Screen('MakeTexture', win, framesR{frame});
end
%% -------------------- Fixation Period ---------------------------
Screen('DrawDots', win, [xCenter, yCenter], 20, white, [0,0], 2);
vbl = Screen('Flip', win); % Fixation onset timestamp
fixdot_t = vbl;
% Maintain fixation for remaining frames
for frame = 2:fixFrames
Screen('DrawDots', win, [xCenter, yCenter], 20, white, [0,0], 2);
vbl = Screen('Flip', win, vbl + (waitframe - 0.5) * ifi);
end
% the time of fixation
fprintf('Fixation: %.4f s\n', (vbl - fixdot_t));
%% -------------------- Stimulus Presentation ---------------------
flipTimes = zeros(1, stimFrames);
% --- Present first frame ---
Screen('DrawTexture', win, texL_all(1), [], leftRect);
Screen('DrawTexture', win, texR_all(1), [], rightRect);
KbQueueFlush; % Clear previous keypresses
ispress = 0;
vbl = Screen('Flip', win, vbl + (waitframe - 0.5) * ifi);
stimOnset = vbl; % True physical stimulus onset time
flipTimes(1) = stimOnset;
% --- Stimuli frame Loop ---
for frame = 2:stimFrames
% 1. Check for response
[pressed, firstPress] = KbQueueCheck;
if pressed
if firstPress(escapeKey), error('User quit');
end
% Use the variables you defined in Section 5 (keySame, keyDiff)
if firstPress(keySame) || firstPress(keyDiff)
if firstPress(keySame)
response = 1;
response_key = keySame;
rt = firstPress(keySame) - stimOnset;
else
response = 2;
response_key = keyDiff;
rt = firstPress(keyDiff) - stimOnset;
end
stimOffset = vbl;
ispress = 1;
break; % Exit the frame loop early
else
ispress = 0;
end
end
% Draw current frame
Screen('DrawTexture', win, texL_all(frame), [], leftRect);
Screen('DrawTexture', win, texR_all(frame), [], rightRect);
% Draw fixation dot on top
Screen('DrawDots', win, [xCenter, yCenter], 20, white, [0,0], 2);
% Flip to screen at precise time
[vbl, ~, ~, missed] = Screen('Flip', win, vbl + (waitframe - 0.5) * ifi);
flipTimes(frame) = vbl;
% Check if flip missed the deadline
if missed > 0
fprintf('Warning: Missed deadline at frame %d!\n', frame);
end
% Record last frame time
if frame == stimFrames
stimOffset = vbl;
end
end
%% -------------------- Timing Analysis ---------------------
% find the flip large than 0
actualFlips = flipTimes(flipTimes > 0);
if length(actualFlips) > 1
validIntervals = diff(actualFlips);
fprintf('\n--- Trial %d Timing Analysis ---\n', trial);
fprintf('Real stim duration (until response): %.4f s\n', stimOffset - stimOnset + ifi);
fprintf('Mean frame interval: %.6f s (Expected: %.6f s)\n', mean(validIntervals), ifi);
% drop frame?
dropped = sum(validIntervals > 1.5 * ifi);
fprintf('Dropped frames count: %d\n', dropped);
else
fprintf('\n--- Trial %d Timing Analysis ---\n', trial);
fprintf('Response on first frame, no intervals to analyze.\n');
end
%% -------------------- Blank / Response Window -------------------
%if not ispress
if ispress == 0
% Show blank screen (just fixation)
Screen('DrawDots', win, [xCenter, yCenter], 20, white, [0,0], 2);
Screen('Flip', win);
start_time = GetSecs;
% Wait for remaining time until maxRespTime is reached
% (Note: maxRespTime should be relative to stimOnset)
while (GetSecs - start_time) < maxRespTime
[pressed, firstPress] = KbQueueCheck;
if pressed
if firstPress(escapeKey), error('User quit'); end
if firstPress(keySame)
response = 1;
response_key = keySame;
rt = firstPress(keySame) - stimOnset;
ispress = 1;
break;
elseif firstPress(keyDiff)
response = 2;
response_key = keyDiff;
rt = firstPress(keyDiff) - stimOnset;
ispress = 1;
break;
end
end
%WaitSecs(0.001);
end
end_response_win = GetSecs;
Screen('FillRect', win, grey);
vbl = Screen('Flip', win);
end
if ispress && response_key == correct_response_key
correct = 1;
else
correct = 0;
end
% Feedback & Accuracy logic
if ispress == 1
if response_key == correct_response_key
correct = 1; % Success
fbText = 'Correct!';
fbColor = [0 1 0]; % Green
else
correct = 0; % Chose the wrong button
fbText = 'Wrong!';
fbColor = [1 0 0]; % Red
end
else
correct = -1; % Failed to respond (Missed)
fbText = 'Too Slow!';
fbColor = [1 1 0]; % Yellow/Orange
end
% Show feedback only if in practice mode
if isprac == 1
% 1. Prepare the text
Screen('TextSize', win, 40)
Screen('TextStyle', win, 1)
DrawFormattedText(win, fbText, 'center', 'center', fbColor);
% 2. Flip to show the feedback (getting the onset time)
vbl = Screen('Flip', win, vbl + (waitframe - 0.5) * ifi);
vbl_feedbackOnset = vbl;
% 3. Hold for feedbackFrames (defined in Section 7)
for frame = 2:feedbackFrames
% Keep drawing the text so it doesn't flicker/disappear
DrawFormattedText(win, fbText, 'center', 'center', fbColor);
% Flip based on the previous VBL to maintain frame-lock
vbl = Screen('Flip', win, vbl + (waitframe - 0.5) * ifi);
end
f_duration = vbl-vbl_feedbackOnset;
fprintf('Trial %d: Feedback duration: %.6f s\n', trial, f_duration);
Screen('TextStyle', win, 0);
end
``