0

I am trying to use the DirectIN Rotary Controller on Mac OS X (yosemite) with PsychoPy2 (v. 1.82.01). I would like to conduct a two-alternative forced choice experiment and use the buttons on the button box in order to respond; however, I cannot get psychopy to recognize the device.

Somebody with a similar problem was able to make the button box work in coder (see here), and there is a similar question using a different game controller here. So far I have gathered the following information:

  1. Psychopy will recognize the button box as a joystick.
  2. I need to use a code component in the trial routine.

The post from the emprisoft forum provides the following code:

import pyglet
joysticks = pyglet.input.get_joysticks()


for joy in joysticks:
    if joy.device.name == 'Analog Scale Device':
        joy.open()
        break

def present_pair_joystick(trial,isi,curdata): #trial is a sound object, isis is the time to wait after response/end of sound, and curdata is a dictionary used to store response data
    event.clearEvents()
    while joy.buttons[0] or joy.buttons[1]:
        continue
    curdata['trial_start']=time.time()
    trial.play()
    dur = trial.getDuration()
    while True:
        if not (joy.buttons[0] and joy.buttons[1]):
            if joy.buttons[0]:
                curdata['rt'] = time.time() - curdata['trial_start']
                curdata['resp'] = 'Word'
                break
            elif joy.buttons[1]:
                curdata['rt'] = time.time() - curdata['trial_start']
                curdata['resp'] = 'Nonword'
                break
            if 'escape' in event.getKeys():
                core.quit()
    if time.time() - curdata['trial_start'] > dur:
        core.wait(isi)
    else:
        core.wait((dur - (time.time() - curdata['trial_start'])) + isi)
    curdata['dur'] = dur
    return

So I believe I can get the button box to work if I incorporate this code into a code component in builder, but I have not had any success with this (I am able to run an experiment without error, but the key responses are not recorded). Any help would be appreciated.

Community
  • 1
  • 1
babylinguist
  • 392
  • 1
  • 13

1 Answers1

1

You say that "I cannot get PsychoPy to recognize the device" but don't actually say what you did, so it is hard to diagnose the problem.

Simply pasting in the code above wouldn't do anything visible for you, as it creates a function (present_pair_joystick()) which you aren't otherwise explicitly calling.

The first bit of code accesses the joystick but doesn't give you any visible feedback as to whether it succeeded, so let's address that. I'm not familiar with joysticks, so will just modify the code you found above and assume that it is appropriate. e.g. Put this in the "Begin Experiment" tab of a code component:

import pyglet
joysticks = pyglet.input.get_joysticks()

joystickFound = False

for joy in joysticks:
    if joy.device.name == 'Analog Scale Device':
        joy.open()
        joystickFound = True
        break

if joystickFound:
    print('Joystick found!')
else:
    print('Joystick NOT found.')
    core.quit() # no point continuing

Assuming the connection is successful, then we can start dealing with the responses, at the beginning of each trial and on every screen refresh.

It seems that joystick button presses can keep registering even when they haven't been released, and hence could carry over from one trial to the next. hence, we need to ensure that at least for a moment, the button is NOT pressed at the beginning of a trial before we test for the next actual button press.

So put something like this in the "Begin Routine" tab of a code component:

buttonReleased = False

And something like this in the "Every frame" tab:

# check that the buttons go through a period of NOT being pressed 
# before we check if they HAVE been pressed:
if not (joy.buttons[0] or joy.buttons[1]):
    buttonReleased = True # the previous trial's press has been released

if buttonReleased:
    response = ''
    if joy.buttons[0]:
        response = '0'
    elif joy.buttons[1]:
        response = response + '1' # allows you to capture if both buttons pressed simultaneously

    if response != '':
        thisExp.addData('buttonResponse', response) # save the response in the data
        thisExp.addData('buttonRT', t) # store current time since trial start
        continueRoutine = False # end trial once response received

event.clearEvents(eventType='joystick')

Caveat: I'm not entirely sure about the placement of the clearEvents call, and have no joystick to test this button press/release handling.

Michael MacAskill
  • 2,411
  • 1
  • 16
  • 28
  • Thank you very much for your response. I have followed your advice and the experiment fails after entering the participant information. This is the output: nameOfSomeTextComponent.text = 'Waiting for a response...' NameError: name 'nameOfSomeTextComponent' is not defined – babylinguist May 22 '15 at 03:59
  • Also, to be more specific, in response to button presses, I would like to record which key was pressed and end the trial, as is typically the case in a two-alternative forced choice paradigm. – babylinguist May 22 '15 at 06:22
  • You need to read the code rather than just copying and pasting it :-) `nameOfSomeTextComponent` clearly needs to be replaced by the actual name of a text component that you have created. – Michael MacAskill May 22 '15 at 11:25
  • I did read the code. I tried replacing nameOfSomeTextComponent with the name of the component, with a newly defined variable (as in the first example above) and always get the same error. If I take out the 3rd line of the code you instructed me to include in the "every frame" tab the experiment runs but then fails once I actually press a button. It doesn't make much sense to me to place a text component there, I don't want text to show up in the trial. I want a number (0 or 1) to show up in the data file. I know all of this is trivial to you, but I have never coded in python. – babylinguist May 22 '15 at 14:47
  • I should also be more clear and state that the above code (that came from somebody else's experiment), is indeed what I want to happen... it is essentially the same experiment as mine. Thus, it calculates reaction time and places it, as well as the participants response, in the data file. – babylinguist May 22 '15 at 15:30
  • Excellent! Thank you. I did not initially see the edit to your answer. This is now working. The only remaining issue is that the button press does not end the trial. In other words, I press the button and it shows up on the screen and goes away once I release it. It is also output to the data file. I assume I need the equivalent of "Force end of routine" on every button press. – babylinguist May 22 '15 at 16:20
  • 1
    The text stuff was only there as a placeholder, as I didn't know what you actually wanted to do. The code above is now edited to simply record the response and reaction time, and end the trial as required once a response is given. It should also capture simultaneous button presses (as '01'), which would probably need to be discarded in the analysis. – Michael MacAskill May 24 '15 at 06:05
  • this is great. My mistake was placing ```countineRoutine = False``` in too many places. I have accepted your answer and consider my initial question answered. That said, another strange issue has come up. My experiment is set to present 13 stimuli in a loop that repeats 10 times. Now that the button box is working in conjunction with the code component, the loop quits after the first button press is recorded and the experiment ends. The data file shows that all trials occurred (all 130) and whichever button I press once is recorded as the answer in every observation. Any ideas? – babylinguist May 24 '15 at 16:05
  • 1
    Maybe we need to clear the event queue? i.e what you describe sounds like the first button press "sticking" and then almost instantly causing all 129 subsequent trials to trigger and end almost immediately. Could you try adding `event.clearEvents()` before the `continueRoutine = False` line and let me know if that resolves the issue? If so, I'll amend the answer accordingly. – Michael MacAskill May 24 '15 at 21:01
  • I have added ```event.clearEvents()``` before ```continueRoutine = False```. Now I am able to push a single button (either one) three consecutive times before the loops ends, or 0 and then 1 (two total presses). In both cases the loop ends early and the data output file shows 128 or 129 presses of whichever button is selected first. – babylinguist May 25 '15 at 00:05
  • 1
    I think joystick buttons must behave differently to the keyboard, in that the button can still register as pressed even though we've cleared the event queue and the button hasn't been released. I can see that the original code you pasted might be dealing with this but that is harder to implement in Builder, where we can't create loops outside of the screen refresh cycle. I'm going to edit the answer *(again )* to try to test for button releases as well as button presses but this is tricky within Builder and I don't have a joystick to debug with, so let me know if it works. – Michael MacAskill May 25 '15 at 03:02
  • That did the trick. It is not working as expected. Thank you once again for all of your help. – babylinguist May 25 '15 at 05:29