Using keys to move stimulus: quick and slow movement while keeping record of the pressed keys

Hi All,

I have a feature in my design (for a rating slider) where the user moves the slider marker by pressing keys. Moving the marker by an increment for each key press was too slow, moving it smoothly while the key is pressed was not precise enough, so I combined the two methods and assigned one key for smooth movement and one for gradual movement. So in the code snippet below ‘a’ moves the marker smoothly to the left and ‘f’ to the right, (keysA) and ‘s’ moves the marker to the left and ‘d’ to the right by one (keysB). ‘Enter’ confirms the selection and breaks the while loop.

At the same time, I would like to keep a record of all the keys pressed (not just the assigned ones). Unfortunately, when I add another getKeys() ‘s’ and ‘d’ are not being recorded, I assume because they are assigned in keysB and getting cleared from the buffer? Enter (‘return’) is also not being recorded for some reason. Does anybody know how I could remedy this?

Extra credit question :wink:
Ideally, I would like to achieve different movement speeds with the same keys, so one press of ‘s’ would move the marker by one increment, but if it’s pressed down it would move the marker quickly. Like if you press left and right arrows in a word document. Can anyone think of a way to code this?

I would be madly appreciative if anyone has any suggestions :slight_smile:

from psychopy import visual, monitors
from psychopy.hardware import keyboard

kb = keyboard.Keyboard()
mon = monitors.Monitor('myMonitor', width=35.56, distance=60) 
win = visual.Window(monitor=mon, fullscr=False, size=[500,500], pos=[0,0])

markerStim = visual.Rect(win=win, size=(0.01, 0.2), colorSpace = 'rgb255', fillColor = [147,0,0], pos = [0,0])

kb.clearEvents()

while True:
    markerStim.draw()
    win.flip()
    
    allKeys = kb.getKeys(None, waitRelease=True, clear = False)
    keysA = kb.getKeys(['a', 'f', 'return'], waitRelease=False, clear = False)
    keysB = kb.getKeys(['s', 'd'], waitRelease=False, clear = True)

    if keysA and not keysA[-1].duration: 
        keyA = keysA[-1].name
        if keyA == 'a':
            markerStim.pos -= [0.01, 0]
        elif keyA == 'f':
            markerStim.pos += [0.01, 0]
        elif keyA == 'return': 
            break
    if keysB:
        for k in keysB:
            keyB = keysB[-1].name
            if keyB == 's':
                markerStim.pos -= [0.01, 0]
            elif keyB == 'd':
                markerStim.pos += [0.01, 0]

win.close()

Yep, but just set clear=False for keysB as well and only set it to true for the final getKeys that catches all of the key-presses.

Basically what you’re looking for is adding a counter for consecutive frames on which ‘s’ or ‘d’ has been detected as pressed, and then modifying the speed of the position marker accordingly. Something like this might work:

    if keysB:
        for k in keysB:
            keyB = keysB[-1].name
            if keyB == 's':
                dcounter = 0 # Resetting the counter for D if it is not pressed
                speed = .01
                scounter += 1
                if scounter >= 40:
                    speed = .2
                elif scounter >= 20:
                    speed = .1
                elif scounter >= 10:
                    speed = .05
                markerStim.pos -= [speed, 0]

And the same for D, and just have it that if keysB is false or is not S, the counter resets to 0.

It might be tricky to do with getKeys because of the buffer-clear issue. The solution offered here might be a reasonable alternative if you have trouble getting it to work with getKeys:

1 Like

Thank you for your suggestions, I have managed to get the log of the keys. Just changing clear to False in keysB didn’t get me what I needed, because the marker still jumped a few positions without keys being cleared, so I changed waitRelease to True as well, which got me what I needed.

Thanks to your other suggestion I also managed to get the marker to move with the same keys at different speeds! My issue here was that I also needed to save the marker position (it’s a rating) and display it. I got it to work, it’s not perfect, the single key press sometimes skips a value, but it’s gonna have to do :smiley:

Here are both scripts in case they may be of use to somebody else.
Thank you so much for your help Jonathan!!!

Different keys for slow and quick movement (with key log)

from psychopy import event, visual, monitors
from psychopy.hardware import keyboard

kb = keyboard.Keyboard()
mon = monitors.Monitor('myMonitor', width=35.56, distance=60) 

win = visual.Window(monitor=mon, fullscr=False, size=[500,500], pos=[0,0])

markerStim = visual.Rect(win=win, size=(0.01, 0.2), colorSpace = 'rgb255', fillColor = [147,0,0], pos = [0,0])

kb.clearEvents()
keysLog = []
while True:
    markerStim.draw()
    win.flip()
    
    keysA = kb.getKeys(['a', 'f'], waitRelease=False, clear = False)
    keysB = kb.getKeys(['s', 'd'], waitRelease=True, clear = False)
    allKeys = kb.getKeys(None, waitRelease=True, clear = True)
    if keysA and not keysA[-1].duration: 
        keyA = keysA[-1].name
        if keyA == 'a':
            markerStim.pos -= [0.01, 0]
        elif keyA == 'f':
            markerStim.pos += [0.01, 0]
    if keysB:
        for k in keysB:
            keyB = keysB[-1].name
            if keyB == 's':
                markerStim.pos -= [0.01, 0]
            elif keyB == 'd':
                markerStim.pos += [0.01, 0]
                break
    if allKeys: 
        for k in allKeys:
            keysLog.append([k.name, k.rt, k.duration])
        if allKeys[-1].name == 'return':
            break

print(keysLog)
win.close()

Same keys, different speeds depending on the length of press (displaying and logging the rating value)

import os
import math

from psychopy import event, visual, monitors
from psychopy.hardware import keyboard

kb = keyboard.Keyboard()
mon = monitors.Monitor('myMonitor', width=35.56, distance=60) 
mon.setSizePix([2560,1600])

win = visual.Window(monitor=mon, fullscr=False, size=[500,500], pos=[0,0])
sliderCol = 255-win.color

start_point = 48
rating = start_point

# Kill switch
event.globalKeys.clear()
event.globalKeys.add(key='q', func=os._exit, func_args=[1], func_kwargs=None) 

outlineStim = visual.Rect(win=win, size=(1, 0.2), colorSpace = 'rgb255', color = sliderCol)
leftEdge = -0.5*outlineStim.size[0]
rightEdge = 0.5*outlineStim.size[0]
increment = outlineStim.size[0]/100 # Move marker by a 100th of the width of the rating bar

markerStim = visual.Rect(win=win, size=(0.01, 0.2), colorSpace = 'rgb255', fillColor = [147,0,0], pos = [0,0])
if not (start_point == 50): 
    m = start_point - 50
    markerStim.pos += [increment*m, 0]

ratingStim = visual.TextBox2(win=win, text="", alignment = 'center', letterHeight=0.1, colorSpace = 'rgb255', color=(147,0,0), pos=[0, -0.2], font = "Avenir", bold = False)

kb.clearEvents()
keysLog = []
scounter = 0
dcounter = 0 
while True:
    rating = markerStim.pos[0]*100+50
    ratingStim.text = str(math.ceil(rating))
    ratingStim.pos = [markerStim.pos[0], -0.2]
    ratingStim.draw()
    outlineStim.draw()
    markerStim.draw()
    win.flip()
    
    keysB = kb.getKeys(['s', 'd'], waitRelease=False, clear = False)
    if keysB:
        for k in keysB:
            keyB = keysB[-1].name
            if keyB == 's':
                dcounter = 0 # Resetting the counter for D if it is not pressed
                speed = increment/6
                scounter += 1
                if scounter >= 30:
                    speed *= 7
                elif scounter >= 15:
                    speed *= 5
                elif scounter >= 10:
                    speed *= 3
                if outlineStim.contains(markerStim.pos - [speed, 0]):
                    markerStim.pos -= [speed, 0]
                else:
                    markerStim.pos = [leftEdge, 0]

            if keyB == 'd':
                scounter = 0
                speed = increment/6
                dcounter += 1
                if dcounter >= 30:
                    speed *= 7
                elif dcounter >= 15:
                    speed *= 5
                elif dcounter >= 10:
                    speed *= 3
                if outlineStim.contains(markerStim.pos + [speed, 0]):
                    markerStim.pos += [speed, 0]
                else:
                    markerStim.pos = [rightEdge, 0]

            if keysB[-1].duration:
                scounter = 0
                dcounter = 0
 
win.close()
2 Likes