Force keypress release

I am creating a self-paced reading task to be run online. Participants see one word at the time on their screen and press spacebar to replace the current word by the next word of the sentence.

If I run the study offline using PsychoPy, participants are not able to rush through the experiment by keeping the spacebar pressed. One needs to release the spacebar and press it again to continue to the third word. But if I keep the spacebar pressed while running the task on Pavlovia, the replacement of the words will continue.

This is my JS code:

// Begin routine
sentenceList = sentence.split(" ");
wordNumber = -1;
timeofLastResponse = 0;
word_text.text = '#';

// Each frame
let theseKeys = psychoJS.eventManager.getKeys();
if (theseKeys.length > 0) {  
 if (theseKeys.includes('space')){
     thisResponseTime = t;
     wordNumber++;
     if (wordNumber < sentenceList.length) {
         psychoJS.experiment.addData("word" + (wordNumber+1), thisResponseTime - timeofLastResponse);
         timeofLastResponse = thisResponseTime;
         word_text.text = sentenceList[wordNumber];
     }
     else {
         continueRoutine = false;}
 } else if (theseKeys.includes('esc')){
     core.quit();}} 

I tried clearKeys(), but this didn’t change anything. I then tried to solve this issue by using the Keyboard class with the waitRelease function.

// Begin routine
...
keyboard = new core.Keyboard({PsychoJS});

// Each frame
let theseKeys = keyboard.getKeys(waitRelease = true);
...

Unfortunately, this doesn’t work: the experiment will not proceed to the first word of the sentence and stays on # (the first screen).

Is there a way to prevent participants from keeping the spacebar pressed?

Hey @PsyLing! Did you find a solution to this? I’m having the exact same issue and it’s really frustrating!

If you check whether the key is pressed every frame then you can spot when it isn’t pressed and require that to happen before it can be pressed again.

I might have code like this in my interactive slider online demo. event.getKeys() works differently offline and online in this regard.

Thanks @wakecarter. I’ve had a look at your interactive slider demo and its code. All trials in that demo also allow you to press and hold the arrow keys, so I’m not quite sure how to go about recycling the code you have for the keyboard polling etc. in that demo. Do you have any advice?

This is proving more difficult than I expected.

I discovered that my own self-paced reading code suffered from the same issue so I tried to fix it with the following code:

keys=event.getKeys()
if 'space' in keys:
    if keyup > 4:
        thisExp.addData('Word'+str(wordNum),myClock.getTime())
        wordNum+=1
        if wordNum==nWords:
            continueRoutine=False
    keyup = 0
else:
    keyup += 1

I discovered that holding down the space bar doesn’t catch a space every frame so I decided to require space not to be sent for 5 consecutive frames before the space bar can be registered again. However, for a reason that escapes me, when I hold down the spacebar I get two words and then it waits for a key release. It’s as if wordNum is increased by two before the reset of keyup is noticed.

Edit – it seems that there is a forced gap after the first keypress before the spacebar is registered again. When the spacebar is held down the second response is at around 28 frames, and then there is a response every frame or two. Waiting for a minimum of 28 frames between responses is far too long so at the moment I’m stuck with allowing a double press from holding down (but at a speed that is probably slower than two separate quick presses).

Very difficult. I’ve tried various options, including ensuring that a key duration is logged (since duration is only logged when there’s a key up and key down). Do you have any other suggestions, or do we think that this is just insurmountable?

My next plan (but I should answer some emails from dissertation students before trying it) is to switch to a keyboard component with new keypresses only storing all presses and then using the length of key_resp.keys to determine the word to show.

I’d appreciate it if you could let me know if you manage to find a solution. FYI, I posted a thread about this here: Self Paced Reading: Participants can currently hold down spacebar to reveal words!, which shows the trials loop setup and the code that works but allows the spacebar to be held.

Hi! I found some workaround. I added these lines to the code:

if (rT < 0.1) {
        if (wordNumber > -1) {
            wordNumber--;
            timeofLastResponse = 0
            targetStartwordClock.reset()

If the reaction time is too fast due to holding the spacebar, it subtracts 1 of the wordNumber before adding 1 later on in the code, so the result is that it remains on the same word. In other words, you have to redo the word until the reaction time is high enough.

This does the trick in most of the cases, especially when you hold the spacebar just slightly too long after the previous word. But if you hold the spacebar really long, it sometimes goes back to the previous word in the sentence or even up until the first word of the sentence. So you might want to keep track of when this happens (now, the “real” reaction time overwrites the exceptionally fast ones). But normally, this code just does what it is supposed to do.

So in the loop this would become:

(...)
if (theseKeys.includes('space')){
  thisResponseTime = targetStartwordClock.getTime();
        rT = thisResponseTime - timeofLastResponse
        if (rT < 0.1) {
          if (wordNumber > -1) {
              wordNumber--;
              timeofLastResponse = 0
              targetStartwordClock.reset()
          }}
        else {
            wordNumber++;
(...)

Thanks for this. This won’t work for me unfortunately as I don’t want to hardcode a limit. I just want to be able to check that the key has been released and, only if it has, proceed to the next word.

I haven’t tried it myself, but maybe this post is useful for you: Logging duration of keypress to end routine - #6 by DavidBrocker

(and change continueRoutine = false; into your code in order to proceed to the next word)

My test with the keyboard component has been successful.

if key_resp_2.keys:
    wordNum = len(key_resp_2.keys)
if wordNum==nWords:
    continueRoutine=False
elif wordNum>oldWordNum:
1 Like

Hi @wakecarter. Thanks for the update! I’ve tried to implement this in my own project but the problem is still there. Here’s what I have for the SPR routine:

BEGIN ROUTINE:

context_words = context.split(' ')
contextPos = 0
stimulusDisplay_3.text = context_words[contextPos]

EACH FRAME:

if key_resp_2.keys:
    contextPos = len(key_resp_2.keys)
    stimulusDisplay_3.text = context_words[contextPos]
if contextPos == len(context_words):
    continueRoutine=False

key_resp_2 is set to constant and stimulusDisplay_3 is left blank, as it’s assigned in the code by stimulusDisplay_3.text = context_words[contextPos].

What am I missing??

You haven’t said what’s going wrong. Is it still letting you hold the key down? Have you removed your getKeys references from your code? Are you sure you are running the latest version of the code?

Does my code work on your computer?
https://run.pavlovia.org/vespr/self-paced-reading/

Hi @wakecarter. Sorry for the delayed response. I had to create a new project on Pavlovia as the Python updates weren’t syncing. The issue I’m having now is that only the first word of the sentence is being presented before moving on. I don’t have any getKeys() references in the code component. The code component is as follows:

BEGIN ROUTINE (Python):

context_words = context.split(' ')
contextPos = 0
context_text.text = context_words[contextPos]

EACH FRAME (Python):

if key_resp_2.keys:
    contextPos = len(key_resp_2.keys)
    context_text.text = context_words[contextPos]
if contextPos == len(context_words):
    continueRoutine = False

The Python code is getting converted as follows (Auto → JS):

BEGIN ROUTINE:

context_words = context.split(" ");
contextPos = 0;
context_text.text = context_words[contextPos];

EACH FRAME:

if (key_resp_2.keys) {
    contextPos = key_resp_2.keys.length;
    context_text.text = context_words[contextPos];
}
if ((contextPos === context_words.length)) {
    continueRoutine = false;
}

Do you have any idea what might be going on? Your SPR code works as expected for me. As far as I can tell, the code I have should be doing what yours does, right?

Is you keyboard component key_resp_2 set to force end of routine?

1 Like

The keyboard component key_resp_2 is not set to “force end of routine”. Allowed keys are space. By default it is storing first key.

It needs to store all keys

1 Like

I’ve changed it to store all keys and I’m still only getting the very first word of the sentence for some reason.

EDIT: I’ve added the line psychopy.event.clearEvents() in BEGIN ROUTINE. Now, I get the very first word and the very last word, but nothing in between.

Please could you link to a running version of your experiment?

You shouldn’t need clearEvents if you are using a keyboard component and have selected new key presses only.

1 Like