Processing keyboard input - qx1147/Psychtoolbox-3 GitHub Wiki

Contents
 At a glance
  Application scenarios
 Keyboard function categories
  Support functions
   KbName()
   ListenChar()
  KbCheck functions
   Example
  KbQueue functions
   Example
  KbEvent functions
   Example
  GetChar functions
 Mouse and keyboard devices
 Timing accuracy considerations

At a glance

This is an overview of PTB's keyboard functions with example code. More detailed information can be found in PTB's function-specific help.

Note that some keyboard functions are only documented within the MATLAB help system, because the online documentation of these functions is erroneously buried under the according PsychHID functions which might use different input/output parameters though. For affected functions, the links here on this page refer to the function's M-files rather than the according PsychHID documentation.

There are basically two overarching groups of keyboard functions, the GetChar functions and the KbXXX functions. The KbXXX functions are based on PTB's PsychHID module, which can be also used directly and might serve as a fallback for solving specific problems. All of the KbXXX functions can be mingled with each other in the same script irrespective of the group they belong to, but the event-based functions (KbQueueXXX and KbEventXXX) must usually not be mixed with the GetChar functions; read more about the interoperability issues of GetChar functions in the help of ListenChar() and what ListenChar() can do about it.

In general, there are two issues to keep in mind when processing keyboard input: how to safely capture all relevant keyboard input and how to get accurate reaction times. The different approaches described below differ in how straightforward the use of the according functions is and how well they perform with respect to these issues.
Moreover, the approaches can be either state-based (KbCheck functions) or event-based (KbQueue, KbEvent, and GetChar functions). State-based means that the script checks (polls) the keys for their current state (pressed or released), which needs to be done frequently enough for not missing any input. Event-based means that Psychtoolbox keeps track of key states while the script can do other things. Psychtoolbox takes care of buffering input events until the script finds time to process them, and event data will then tell the script exactly which event has occurred (that is, which key changed to which state – pressed or released) and at which time the event has occurred.

Regarding the timing of keyboard processing, higher timing accuracies usually come at the price of higher code complexity and/or higher CPU load. When getting down to millisecond or even sub-millisecond accuracies, matters might also depend quite a bit on the operating-system-specific implementation of Psychtoolbox. Moreover, only so much can be done by Psychtoolbox and the script; so trying to improve accuracies by a few microseconds is not worth the trouble, given that inaccuracies coming from elsewhere are much higher anyway. The accuracy values given here refer to what is caused by Psychtoolbox and do not include potential contributions from the operating system and the devices.

Application scenarios

  • At the very time when or while a key is pressed, the script can actually call a keyboard function and is not busy doing other things like, for example, waiting for a scheduled flip to execute or waiting for heavy computations to finish.
    β†’ Use KbCheck functions, as they are the easiest to use. Timing accuracy is usually at least 6ms (Β±3ms), if the key is pressed down while the script is checking for keyboard input. If the script is checking for keyboard input in a tight loop, timing accuracy is usually even in the sub-millisecond range.

  • The script is busy with presenting the stimulus or doing other things, and permanently checking for keyboard input in between is cumbersome if not impossible. Keys might even have been released already before the script can check for keyboard input.
    β†’ Use KbQueue functions, as they collect keyboard events while the script is doing other things. The script can still tell, post-hoc and usually with sub-millisecond accuracy, when key events have occurred.

  • Many key presses can occur in short time, with possibly the same key being pressed several times before the script has time to process the keyboard input. Or the keyboard input is coming from an external device (e.g., an MR scanner), possibly in addition to input coming from the regular keyboard.
    β†’ Use KbEvent functions, possibly in addition to KbQueue or KbCheck functions, as KbEvent functions cannot lose any events and still provide sub-millisecond accurate timestamps.

  • The script needs to collect text input (like complete words), but timing accuracy is of little concern.
    β†’ Possibly use GetChar functions instead of KbEvent functions.

  • The script needs to respond to key presses as quickly as possible. This may concern the stimulus presentation but also trigger output or alike.
    β†’ Possibly use KbCheck functions rather than KbEvent or KbQueue functions, because the latter might come with some runtime overhead. The gain regarding timing, if any, is expected to be small though, so reconsider before sacrificing simplicity of your script.

Keyboard function categories

Support functions

KbName()

KbName() is used to translate back and forth between key names and key indices. These indices address the elements in the vectors of key states or timestamps which are returned by many KbXXX functions. KbName() can be rather slow, so using it within time-critical code sections is not recommended.
The name of a particular key can be found by just running KbName() from the MATLAB's command window and then pressing the key of interest.

ListenChar()

ListenChar() is used to explicitly enable or disable GetChar functionality, but also allows setting different modes of how Psychtoolbox should process keyboard input. You will hopefully never have to use this function though, but you might have to if GetChar functions are mingled with KbXXX functions or if unwanted keyboard input appears in MATLAB's editor or command window while running the experiment.

KbCheck functions

KbCheck(), KbWait(), KbStrokeWait(), KbPressWait(), KbReleaseWait(): These functions are essentially state-based, meaning they capture keyboard states at the very moment the functions are called. They can be used without any further preparation and without any side-effects to other functions. The returned timestamps are basically the GetSecs() times of when the respective function returned to the script (except for KbStrokeWait(), see below). More importantly, any of the functions which is supposed to wait for some key change, does so in a "lazy" loop, meaning that the function takes a break of 5ms between subsequent keyboard polls. This reduces CPU load but also limits timestamp accuracy.
Note that the "keyCode" return parameter, e.g. of KbCheck(), is not a single key code, as the name might suggest, but a vector of key states – one state for each potential key –, each indicating whether the corresponding key is currently pressed or not. The vector is indexed by key indices which can be retrieved from a key name by using KbName().
The timestamp returned by KbStrokeWait() is special in that the function not only waits for a key press and then assigns the current time to the timestamp, but also waits for all keys to be released afterwards, before finally returning the timestamp of the key press.

KbCheck functions can be used whenever the scenario allows the script to actively check for keyboard input often enough so as to not miss any relevant keyboard events and to provide a high enough time resolution. It allows for sub-millisecond accurate timestamps only if called in a tight loop in which the script keeps calling KbCheck() as fast as possible and without doing too much otherwise. Normally, however, such tight loops should be avoided as they keep the CPU unnecessarily busy. Note that all the KbXXXWait functions are basically just convenience functions which implement a lazy loop in which they keep calling KbCheck() until the function-specific condition is met.
One contraindication for using "KbCheck* functions is when Screen('Flip',...) has to be called while keyboard input is expected. This is because a flip can block the script for considerable time, especially if the function's when parameter was used for scheduling the flip. But even if the flip is supposed to be immediate, the Screen function can still block the script for up to one monitor refresh cycle, or even longer if time-consuming graphics commands have been sent before (e.g. text output can be quite expensive in this respect).

Example (KbCheck functions)

(for more example code, see PsychDemos/KbDemo.m)

5 trials are presented. In each trial there is a black square which briefly appears on the white screen. The keyboard response is collected with a response timeout of 5s. The script is sensitive to all keys, but only the cursor keys yield valid responses (see according messages in the command window once the example code has finished). The script can be aborted with the Escape key.

try
  wnd = Screen('OpenWindow',0);
  ifi = 1/Screen('FrameRate',wnd);
  stimDuration = 10*ifi;
  %--- 'responseKeyIdx' is a vector of 4 indices into the "keyCode"-vector
  %    returned by KbWait() and friends.
  responseKeyIdx = KbName({'left','right','up','down'});
  escapeKeyIdx = KbName('esc');
  %--- Trial loop.
  for trial=1:5
    %--- Start the trial only after all keys have been released.
    KbReleaseWait();
    %--- Inter-trial pause.
    pause(1);
    %--- Draw stimulus (here, just a black square).
    Screen('FillRect', wnd, 0, [500,500,600,600]);
    stimStartTi = Screen('Flip', wnd);
    %---Clear stimulus (done automatically here).
    %--- 'stimDuration' is assumed to be small enough for key presses not to occur 
    %    before the final Screen('Flip',…) is executed (any earlier key presses will 
    %    be classified as premature response, see below). 'stimDuration' should be rounded 
    %    to an integer multiple of 'ifi' {inter-frame-interval, basically 1.0/Screen('FrameRate',wnd)}.
    %    "-ifi/2" makes timing errors less likely.
    Screen('Flip', wnd, stimStartTi+stimDuration-ifi/2);
    %--- Wait for a key with a timeout of 5 seconds.
    %    KbWait also returns if a key is already pressed. And once any key is pressed (even if it is 
    %    none of the response keys), it does return immediately, without waiting for any keys to be released.
    [responseTi, keyStateVec] = KbWait([], [], GetSecs()+5);
    %--- It is always a good idea to allow us to escape.
    if keyStateVec(escapeKeyIdx)
      error('Escape key pressed.');
    end
    %--- Here, RT is defined as the response time relative to stimulus onset.
    RT = responseTi - stimStartTi;
    responseIdx = find( keyStateVec(responseKeyIdx) );
    if RT<0.1 || isempty(responseIdx) || length(responseIdx)>1
      %--- Premature, no, or multiple keys response => invalid response.
      fprintf('Invalid response.\n');
    else
      %--- Valid response as far as the timing and the key is concerned.
      fprintf('Valid response (response=%u, RT=%u ms).\n',responseIdx, round(RT*1000));
    end
  end % next trial
  %--- Before closing the window, make sure that all keys are released, so
  %    no key presses "fall through" to the MATLAB's command window 
  %    or editor and mess things up there.
  KbReleaseWait();
  sca();   % Screen close all
catch e
  %--- An error occurred: Emergency cleanup.
  KbReleaseWait();
  sca();   % Screen close all
  rethrow(e);  
end

KbQueue functions

KbQueueCreate(), KbQueueStart(), KbQueueCheck(), KbQueueStop(), KbQueueRelease(), KbQueueWait(), KbQueueFlush(): These functions are essentially event-based, meaning they allow the script to safely capture input events even if they occurred while the script was busy doing other stuff.
After the keyboard event queuing has been initialized and started, events can be retrieved with KbQueueCheck(), which also resets the queue before the function returns to the script. This automatic resetting makes KbQueueCheck() return only events which occurred since the last call to KbQueueCheck(). Moreover, KbQueueCheck() is somewhat forgetful about events in that it only remembers the first and the last event since its last call, while forgetting potential events in between. However, it does so separately for press and release events and for each potential key, which basically allows the script to register still up to 2 press and 2 release events per key and per call.
Note that KbQueueCheck() does not return single key codes. Instead, it returns four vectors of timestamps for all potential keys, where a timestamp of value 0 codes for "no event". These vectors are indexed in the same way as the "keyCode" vectors returned by the KbCheck functions, so KbName() can be used for the index↔name translation.
The returned timestamps are GetSecs-compatible and, importantly, are sub-millisecond accurate, because they have been assigned directly by the PTB background process which is responsible for handling keyboard events.
Along with initializing and starting the keyboard event queuing, also a keyboard event buffer is set up (see KbEvent group below), which has the negative side-effect that the KbEvent buffer runs full after a few thousand events, unless the script reads or flushes this buffer from time to time (see KbEventFlush(), and calls KbQueueStop() or KbQueueRelease() at the end of the script latest. A buffer overrun is harmless though, except that it can cause annoying warning messages in MATLAB's command window, even after the script has long finished.

KbQueue functions are useful whenever accurate timestamps are needed and/or when it is too cumbersome or even impossible to actively check for keyboard input often enough for not risking to lose input events. The latter is often true with animated stimuli where the subject is allowed to respond while the animation is still going on, that is, while the script has to call the Screen('Flip',...) function which potentially prevents the script from checking for keyboard input for too long.

Example (KbQueue functions)

(for more example code, see PsychDemos/KbQueueDemo.m)

5 trials are presented. In each trial, there is a black square which moves from the upper left corner while becoming bigger. The response may already be given while the stimulus is still moving but does not end the stimulation. The response has to be given latest 5s after the stimulus disappeared. The script is sensitive to all keys but only the cursor keys yield valid responses (see according messages in the command window once the example code has finished). The script can be aborted with the Escape key.

try
  wnd = Screen('OpenWindow',0);
  ifi = 1/Screen('FrameRate',wnd);
  stimFrameDuration = 5*ifi;
  nStimFrames = 30;
  %--- 'responseKeyIdx' is a vector of 4 indices into the "keyCode"-vectors.
  responseKeyIdx = KbName({'left','right','up','down'});
  escapeKeyIdx = KbName('esc');
  %--- Get the KbQueue up and running.
  KbQueueCreate();
  KbQueueStart();
  %--- Trial loop.
  for trial=1:5
    KbReleaseWait();
    %--- Inter-trial-pause.
    pause(1);
    %--- Instead of calling KbQueueFlush(), we could also call KbQueueCheck(), 
    %    because KbQueueCheck() also resets the queue, like KbQueueFlush() does.
    KbQueueFlush();
    %--- Prevent the event buffer from overflowing (important for long experiments
    %    with many key events). If we had called KbQueueFlush([],3) above, we 
    %    would not need this call, but for the sake of clarity…
    KbEventFlush();
    %--- Stimulus presentation loop.
    for fi=1:nStimFrames
      %--- Draw frame 'fi' of stimulus animation (here a moving and expanding black square).
      if fi<nStimFrames
        Screen('FillRect', wnd, 0, fi*[2,2,10,10]);
      end
      %--- Make new frame visible.
      if fi==1
        stimStartTi(1) = Screen('Flip', wnd);
      else
        %--- 'stimFrameDuration' refers to an animation frame and should be rounded to an 
        %    integer multiple 'ifi' {inter-frame-interval, basically 1/Screen('FrameRate', wnd)}. 
        %    "-ifi/2" makes timing errors less likely.
        stimStartTi(fi) = Screen('Flip', wnd, stimStartTi(fi-1)+stimFrameDuration-ifi/2);
      end
    end
    %--- Wait until a key is pressed, with a timeout of 5 seconds. Do not
    %    use KbQueueWait() for this, because it does not give access to the vectors
    %    of timestamps but still resets the queue so that the vectors cannot be 
    %    retrieved anymore by calling KbQueueCheck() afterwards.
    timeout = GetSecs()+5;
    while GetSecs()<timeout
      [~,pressedTiVec] = KbQueueCheck();
      if any(pressedTiVec)
        break
      end
      %--- Yield CPU time. This pause can be long, but should be short enough to 
      %    not make the application appear to be sluggish in accepting user input.
      WaitSecs('YieldSecs', 0.040);      
    end
    %--- The timestamps in the vectors returned by KbQueueCheck() can also be abused as key 
    %    states (specifically for the vector with "pressed" timestamps it is: ~0:pressed, 0:released).
    if pressedTiVec(escapeKeyIdx)
      error('Escape key pressed.');
    end  
    responseIdx = find( pressedTiVec(responseKeyIdx) );
    if length(responseIdx)==1
      %--- Here, RT is defined as the response time relative to stimulus animation onset.
      RT = pressedTiVec(responseKeyIdx(responseIdx)) - stimStartTi(1);
    else 
      RT = 0;
    end
    if RT<0.1 
      %--- Premature or no or multiple keys response => invalid response
      fprintf('Invalid response.\n');
    else
      %--- Valid response as far as the timing and the key is concerned.
      fprintf('Valid response (response=%u, RT=%u ms).\n', responseIdx, round(RT*1000));
    end
  end % next trial
  %--- Shut down event queue processing so that the event buffer cannot overflow.
  KbQueueRelease();
  %--- Before closing the window, make sure that all keys are released, so
  %    no key presses "fall through" to the MATLAB's command window 
  %    or editor and mess things up there.
  KbReleaseWait();
  sca();   % Screen close all
catch e
  %--- An error occurred: Emergency cleanup.
  KbQueueRelease();
  KbReleaseWait();
  sca();   % Screen close all
  rethrow(e);
end

KbEvent functions

KbEventGet(), KbEventAvail(), KbEventFlush(): Like the KbQueue functions, KbEvent functions are also event-based but they do not forget events, even if the same key is pressed several times before the script can check for keyboard input. Moreover, keyboard events can be processed one by one without having the event buffer automatically reset.

Side note: Don't be confused by the function naming. Both KbQueueXXX and KbEventXXX functions are event-based, but they are used differently. In PTB's documentation regarding these functions, a distinction is made between "event queue" (associated with KbQueue functions), and "event buffer" (associated with KbEvent functions). This is somewhat misleading in that, normally, "queue" implies kind of "rather many elements lined up in a row", whereas the interpretation of "buffer" can be this or that, going from a few elements possibly living side by side (as, e.g., with frame buffers) to many elements being somehow ordered (as, e.g., in a FIFO buffer). Nevertheless, in this context here, "queue" is associated with only a few events, whereas "buffer" is associated with many events.

In one way, KbEvent functions seem to offer just a different way of accessing keyboard events which are otherwise maintained by the KbQueue functions. But under the hood, each group of functions actually works with its own copy of keyboard events, which makes them work rather independently from each other. This is reason enough for making the clear distinction between the two, at least in this overview.
As far as the KbEvent functions are concerned, events are buffered in the sequence they arrive, and KbEventGet() removes just the oldest event from the buffer (pressed or released) without touching possibly newer events. This keyboard event buffer is automatically initialized, started, and stopped along with the keyboard queue, that is, using respective KbQueue functions. Otherwise, the keyboard event buffer and the keyboard event queue live rather separate lives, and the according functions can be mingled in the same script with virtually no side-effects. Not surprisingly, the timestamps returned by KbEventGet() are as accurate as those returned by KbQueueCheck().

KbEvent functions are most useful when events appear in very rapid succession and possibly for the same key. Having the event queue and the event buffer being implemented rather independently from each other allows using both for different things. For example, KbQueue can be used for handling normal keyboard responses, while KbEvent is used for registering MRI triggers, which are often fed into the system via virtual key presses. Obviously, there will be still the problem of ignoring trigger events in the event queue and ignoring real keyboard input in the event buffer, but this might be still easier to cope with than having to handle both event types with only one mechanism, either with KbQueue or with KbEvent.

Example (KbQueue+KbEvent functions)

(for more example code, see PsychDemos/KbQueueDemo.m)

For one part, this is basically the KbQueue example: 5 trials are presented, where the stimulus is a black square which moves from the upper left corner while becoming bigger. The response may already be given while the stimulus is still moving but does not end the stimulation. The response has to be given latest 5s after the stimulus disappeared. The script is sensitive to only the cursor keys as far as valid response keys are concerned.
In addition, it is assumed that an external device sends triggers in form of virtual key presses of the 't' key. The start of the first trial is deferred until the first trigger has been received. Thereafter, every other trial is only started once three more triggers have been received. So when running the example, you have to hit the 't' key for simulating the trigger input and the cursor keys for the subject's responses. Trigger times are logged (see according messages in the command window once the example code has finished). The script can be aborted with the Escape key.

try
  wnd = Screen('OpenWindow',0);
  ifi = 1/Screen('FrameRate',wnd);
  stimFrameDuration = 5*ifi;
  nStimFrames = 30;
  %--- 'responseKeyIdx' is a vector of 4 indices into the "keyCode"-vectors 
  %    returned by KbQueueCheck().
  responseKeyIdx = KbName({'left','right','up','down'});
  escapeKeyIdx = KbName('esc');
  triggerKey = KbName('t');
  %--- Get the KbQueue up and running.
  KbQueueCreate();
  KbQueueStart();
  %--- Trial loop.
  triggerTi = [];
  for trial=1:5
    %--- KbReleaseWait() will also block if the trigger key is 'pressed', 
    %    which is assumed to be short enough for being ignored here.
    KbReleaseWait();
    %--- Inter-trial-pause.
    pause(1);
    %--- Collect trigger input and sync the trial start of every other trial 
    %    to every 3rd trigger, beginning with the first trial.
    while KbEventAvail() || (mod(trial,2)==1 && length(triggerTi)<1+(trial-1)/2*3)
      ev = KbEventGet([],9999);
      if ev.Pressed
        switch ev.Keycode
          case escapeKeyIdx
            error('Escape key pressed.');
          case triggerKey
            triggerTi(end+1) = ev.Time;
        end
      end
    end
    %--- Instead of calling KbQueueFlush(), we could also call KbQueueCheck(), 
    %    because KbQueueCheck() also resets the queue, like KbQueueFlush() does.
    KbQueueFlush();
    %--- Stimulus presentation loop.
    for fi=1:nStimFrames
      %--- Draw frame 'fi' of stimulus animation (here a moving and expanding black square).
      if fi<nStimFrames
        Screen('FillRect', wnd, 0, fi*[2,2,10,10]);
      end
      %--- Make new frame visible.
      if fi==1
        stimStartTi(1) = Screen('Flip', wnd);
      else
        %--- 'stimFrameDuration' refers to an animation frame and should be rounded to
        %    an integer multiple 'ifi' {inter-frame-interval, basically 1/Screen('FrameRate', wnd)}. 
        %    "-ifi/2" makes timing errors less likely.
        stimStartTi(fi) = Screen('Flip', wnd, stimStartTi(fi-1)+stimFrameDuration-ifi/2);
      end
    end
    %--- Wait until a key is pressed, with a timeout of 5 seconds. Do not
    %    use KbQueueWait() for this, because it does not give access to the 
    %    timestamp vectors but still resets the queue along with the timestamps,
    %    which therefor cannot be retrieved anymore afterwards via KbQueueCheck().
    timeout = GetSecs()+5;
    while GetSecs()<timeout
      [~,pressedTiVec] = KbQueueCheck();
      %--- The timestamps in the vectors returned by KbQueueCheck() can also be abused as key 
      %    states (specifically for the vector with "pressed" timestamps it is: ~0:pressed, 0:released).
      if pressedTiVec(escapeKeyIdx)
        error('Escape key pressed.');
      end  
      %--- We are only looking for response key presses, thereby ignoring trigger key events. 
      responseIdx = find( pressedTiVec(responseKeyIdx) );
      if ~isempty(responseIdx)
        break
      end
      %--- Yield CPU time. This pause can be long, but should be short enough to 
      %    not make the application appear to be sluggish in accepting user input.
      WaitSecs('YieldSecs', 0.040);      
    end
    if length(responseIdx)==1
      %--- Here, RT is defined as the response time relative to the onset of the stimulus animation.
      RT = pressedTiVec(responseKeyIdx(responseIdx)) - stimStartTi(1);
    else 
      RT = 0;
    end
    if RT<0.1 
      %--- Premature or no or multiple keys response => invalid response
      fprintf('Invalid response.\n');
    else
      %--- Valid response as far as the timing and the key is concerned.
      fprintf('Valid response (response=%u, RT=%u ms).\n', responseIdx, round(RT*1000));
    end
  end % next trial
  %--- Get the last bunch of triggers
  while KbEventAvail()
    ev = KbEventGet();
    if ev.Keycode==triggerKey
      triggerTi(end+1) = ev.Time;
    end
  end
  %--- Shut down event queue processing so that the event buffer cannot overflow.
  KbQueueRelease();
  %--- Before closing the window, make sure that all keys are released, so
  %    no key presses "fall through" to the MATLAB's command window 
  %    or editor and mess things up there.
  KbReleaseWait();
  sca();   % Screen close all
  fprintf('Trigger(%u) = %.3f s\n', [1:length(triggerTi);triggerTi-triggerTi(1)]);
catch e
  %--- An error occurred: Emergency cleanup.
  KbQueueRelease();
  KbReleaseWait();
  sca();   % Screen close all
  rethrow(e);
end

GetChar functions

GetChar(), GetCharTest(), CharAvail(), FlushEvents(): Using these functions is actually not recommended, at least not for any remotely timing-relevant stuff. These functions do not rely on PTB's PsychHID module but on the key event processing of the operating system and MATLAB, which makes their behavior rather dependent on the particular runtime environment, especially when it comes to timing accuracy and interoperability with other keyboard functions.
GetChar functions do not require additional initialization functions (expect maybe ListenChar()) and they return characters (letters, digits) instead of abstract key codes or maps of key states. The shift key, for example, is not returned directly (or at all) but determines whether a returned letter would be upper or lower case. Moreover, GetChar functions are essentially event-based, so key events are safely registered but have the potential disadvantage of inaccurate timing and other quirks (see also ListenChar()).

Mouse and keyboard devices

The mouse button states can be checked using GetMouse() which is similar to KbCheck() but, in addition, returns the current mouse position. For checking just the mouse buttons, actually also KbCheck() and all the other KbXXX functions can be used. This is done by passing a mouse device index as the first parameter to these functions. The index can be found using GetMouseIndices(). For a mouse device, only the first entries of the "keycode" maps returned by the KbXXX functions are relevant, and these entries refer to the left, right and middle mouse button respectively.
The deviceIndex parameter of the KbXXX functions can also be useful for receiving input from only a specific device in a device family (mice or keyboards), if there is more than one connected. This is not supported on all operating systems though. See also GetKeyboardIndices() and GetGamepadIndices().

Timing accuracy considerations

Here are a few aspects to keep in mind when looking at the timing accuracy of keyboard input processing.

  1. Sources of timing inaccuracy
    Psychtoolbox can do only so much when it comes to timing accuracy, because there are potential troublemakers further upstream, namely the operating system, the transmission line (USB bus), and the keyboard device with its Β΅-controller. The timing behavior might vary with system load, the number and type of devices connected to the USB bus, and the keyboard or mouse model.

  2. Average latency and latency variance
    Although the time difference, or latency, between the physical event (electrical contact of a key switch being closed or opened) and the time by which a timestamp is assigned or by which the script becomes aware of the input event, might be quite long on average, this is not necessarily true for the variance, which might be considerably smaller. Fortunately, it is often the variance which is more important for an experimental outcome, because reaction times are usually compared across two or more conditions, in which case any common average latency cancels out, no matter how long it might be.
    For example, a button event of a standard USB mouse under Windows has an average latency of around 25ms (25ms from the electrical contact being closed until the application becoming aware of it), whereas the standard deviation is just 2.6ms.

  3. Timestamps and time of reception
    There is usually a distinction to be made between (1) knowing precisely, but possibly post-hoc, when a key event occurred (via timestamps) and (2) finding out about a key event as quickly as possible. One does not necessarily entail the other, so a script might learn about key events rather late and with varying delays but might still get accurate timestamps. This is not only the case for key events but, for example, also for flip events (see Screen('Flip',...)). Sometimes, however, it is more important to find out about an event as quickly as possible, e.g. if a trigger is to be sent, rather than determining, post-hoc, the exact time of the event. In such case, simpler functions (e.g., KbCheck()) might be favorable over more complex functions (e.g., KbCheckQueue()), just because the latter also imply a more complex implementation which usually comes with longer delays. That being said, the effects are assumed to be in the low sub-millisecond range.

  4. Reaction times
    Usually, it is not the absolute response time which is of interest but the time difference between the response and some other event like, for example, the stimulus onset. So in order to get most accurate reaction times, both times have to be measured as accurately as possible, the time of the reference event (e.g., stimulus onset) and the time of the response event (key press). So whenever possible, use the timestamps provided by the respective functions (e.g., the Screen('Flip',...) and KbXXX functions) rather than creating timestamps yourself upon the return of any of these functions, Γ  la timeOfReturn = GetSecs(). Also remember that the time of appearance of a stimulus on the screen usually depends on the Y position of the stimulus, because the screen is updated from top to bottom.

⚠️ **GitHub.com Fallback** ⚠️