Joystick/refresh rate catch-22

For various reasons I won’t get into, I have a program I need to run that uses a USB joystick (actually a USB gamepad but the psychopy joystick functions) on MacOS Mavericks (I don’t think version matters for this). I’ve run into an interesting combination of issues.

  1. The pyglet joystick interface flat doesn’t work on OSX/MacOS, and never has, as far as I can tell. See: https://groups.google.com/forum/#!topic/psychopy-users/GWNE4RvGbRE This was not fixed in the recent updated to pyglet 1.2, as I am using the most recent version of psychopy and get the exact same error if I try to initialize a joystick and a pyglet window and call win.flip(). That error, for reference: AttributeError: ‘PygletDevice’ object has no attribute ‘dispatch_events’

  2. Simple fix, use pygame to control the joystick. Unfortunately you can’t have the window and the joystick control be different (you just don’t get any of the inputs from the joystick if you try this), so it has to be a pygame window as well.

  3. Pygame is doing something ridiculous with refresh rates and I can’t figure out how to force it to run at 60Hz. I’m using a mid-2012 Macbook pro and it’s reporting an ‘actual frame rate’ of 140Hz, and running all of my stimuli at (slightly more than) double speed.

I can make some ugly workarounds that use clock timing rather than frame timing, but this seems like a problem that should be fixable from one of two directions, either by finding a way to get a pyglet window that still works with joystick inputs, or by locking pygame’s refresh rate. Any suggestions?

1 Like

Have you tried with the new 1.84.0 release of PsychoPy? I think it might have been fixed there using pyglet…?

I did, and verified that it was using pyglet 1.2.1 using the benchmarking tool. Same exact error.

I’ll try and get hold of a joystick and see what’s going on (or if anyone that has one already their help would be welcome). No plans to rekindle support for pygame so the aim will be to get the joystick working with pyglet.

I’m having the same problem with El Capitan with Pyglet in psychopy 1.84

Just posting to see if there have been any developments and to report that my latest kludge of trying to display a pygame window on one screen and a pyglet window on another throws a pygame segmentation fault.

No, I’m afraid I haven’t had time to dig, nor a joystick to investigate with

I have a joystick and while I’m not extraordinarily good with python I can at least do some diagnostic work on this if you can tell me where to start, and I am happy to do so. I have already done a little looking at the pyglet class but that doesn’t give me any obvious hints. I am planning to try to get it to pass the events directly without going through the joystick class, the same way you can get a pyglet keyboard handler without going through events but I haven’t had a chance yet.

Any ideas as to where I should begin? Is this likely to be something about the joystick class or pyglet itself?

I’d start by looking at the pyglet documentation and import the pyglet joystick directly (forget about using the psychopy class one until we can confirm the pyglet one works)

To debug the pyglet error message in psychopy I would insert a print statement just before the error occurs (in win.flip()?) to find out what similar-looking methods the pyglet joystick does have (maybe they just renamed dispatch_events to something else?)

So to the best of my ability to tell this is going to require knowing a bit more about event handlers. Basically, the joystick class, or the device class more generally, doesn’t behave like keyboard and mosue events in pyglet. Keyboard and mouse events can be picked up by the window event dispatcher. Joystick apparently runs completely independent of that? It’s a little hard to tell from the docs. The bottom line is that no matter what I do I can’t get it to dispatch even a single event on win.flip even when I’m accessing it directly (not going through the psychopy joystick class), but I can get it to give static output.

My current thought, which is probably wrong, is to try to make my own event dispatcher for the joystick, and pass an instance of that to the array of event dispatchers instead of the ‘device’ object that the psychopy joystick code currently tries to pass, which seems to be where the issue is. My understanding of event dispatchers and handlers is rudimentary at best, but I think I can put something together tomorrow.

OK, I have a theory about what the issue is and a horrible kludge solution that works, barely.

There is a pyglet joystick.py demo available in the bitbucket repository, code copied at the bottom of this post. You can run this from the psychopy python install and it works just fine, so we know pyglet can talk to a joystick in principle.

However, the way this demo seems to work is that it’s not worrying about event dispatchers at all, it’s just checking the joystick’s status every time the window’s on_draw event occurs. That doesn’t work when going through a psychopy window object. I tried making a win.winHandle.event on_draw handler and it never trips, even when win.flip() is called. If there’s another way to establish a handler for that it might work, but I have no idea.

I can find no working example of anyone using the joystick events anywhere on the internet. So, it is possible that the joystick events don’t work, in which case our only real problem is that something about the way the pyglet window is implemented means that it’s not able to properly update and check the current state of the joystick. That requires a much deeper understanding of psychopy’s window class than I have, I fear.

On the other hand, I can create an awful kluge solution that at least gets the psychopy joystick demo to work, albiet without using the psychopy joystick class. Basically, you just do joystick.close and joystick.open on every screen refresh (just in your main presentation loop before win.flip), and get the values of each of the axes/buttons directly from the pyglet object that way. It’s ugly and probably horribly inefficient in some way (though I haven’t seen any obvious signs of memory or processor load), but it does work.

#!/usr/bin/env python

'''
'''

__docformat__ = 'restructuredtext'
__version__ = '$Id: $'

import pyglet
from pyglet.gl import *

joysticks = pyglet.input.get_joysticks()
assert joysticks, 'No joystick device is connected'
joystick = joysticks[0]
joystick.open()

window = pyglet.window.Window()

@window.event
def on_draw():
    x = (0.8*joystick.x + 1) * window.width / 2
    y = (-0.8*joystick.y + 1) * window.height / 2
    z = joystick.z
    angle = joystick.rz * 180

    # Axes

    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(1, 0, 0)
    glLoadIdentity()
    glTranslatef(x, y, 0)
    glScalef(1 + z, 1 + z, 1 + z)
    glRotatef(-angle, 0, 0, 1)
    glBegin(GL_TRIANGLES)
    glVertex2f(-10, 0)
    glVertex2f(0, 13)
    glVertex2f(10, 0)
    glEnd()

    # Buttons

    glLoadIdentity()
    x = 10
    y = 10
    glPointSize(5)
    glBegin(GL_POINTS)
    for button in joystick.buttons:
        if button:
            glVertex2f(x, y)
        x += 20
    glEnd()

    # Hat

    glColor3f(0, 0, 1)
    x = window.width / 2
    y = window.height / 2
    glBegin(GL_POINTS)
    glVertex2f(x + joystick.hat_x * 50, y + joystick.hat_y * 50)
    glEnd()

pyglet.clock.schedule(lambda dt: None)
pyglet.app.run()
1 Like