By Golan Levin, July 2026
This project makes the In2Games Gametrak PlayStation controller usable as a six-axis HID (human interface device) via OSC, MIDI, WebMIDI, WebSockets, stdout, and other workflows. Working examples are provided for popular creative coding environments including Processing (Java), p5.js, and Python.
This work builds on the libgametrak C-language library by Géry Casiez (@casiez) — especially his discovery of the Gametrak's special USB initialization message. The Gametrak controller appears on macOS as a standard USB HID joystick-class device. However, the GameTrak does not begin to stream useful data until it receives the initialization message used by libgametrak's PS2 mode. More information about third-party code redistributed in this project can be found here; some references to creative research with the Gametrak can be found here.
This work has been tested in macOS 15.6 using Python 3.10 and 3.14, Processing 4.5.5 and 4.3, and p5.js 1.11.13 and 2.3.0. The specific unit tested here is physically labeled GameTrak V2.0 on its underside, while USB reports Game-Trak V1.3 from In2Games Ltd; it uses VID 0x14B7 and PID 0x0982.
Contents:
- About the Gametrak Controller
- Supported Workflows
- Quick Start
- Python Commands
- Processing And p5.js
- Device And Protocol Facts
- Calibration Boundary
- Development
- Troubleshooting
- Acknowledgments
The Gametrak was originally made as a PlayStation 2 controller for the golf
game Real World Golf by In2Games Ltd. Its unusual interface has two joystick assemblies, each with X/Y motion plus a retractable tether, yielding six 12-bit position streams in the 0..4095 range at 75 Hz. Because used units can be quite inexpensive on eBay (about
USD $20-40), and because the tethered joysticks are expressive and physically
legible 3D position-trackers, the device has become a popular platform for experimental interfaces, especially in electronic music and machine-learning workflows such as
Wekinator. Some examples of creative arts-research projects that use the Gametrak can be found listed here.

From "Capturing Movement in Sound" (2024) by Richard McReynolds, performed by Jodi Ann Nicholson. See more creative projects that use the GameTrak.
This repository presents several ways to acquire, translate, and use data from the Gametrak controller:
| Path | What It Is, When To Use It |
|---|---|
gametrak-stdout |
HID-to-stdout. This is a command-line program, built in Python, which emits Gametrak controller data to stdout in the Terminal. It is good for quick diagnostics, logging, piping data to other programs, and other Unix-style scripts. |
gametrak-record |
HID-to-JSONL. This is a command-line program, built in Python, which records full raw HID reports as JSONL files. It is intended for protocol research, official sample captures, bug reports, and reproducible test data. |
gametrak-playback |
JSONL-to-stdout/OSC/MIDI/WebSocket. This is an offline command-line program, built in Python, which replays gametrak-record captures through one selected output protocol. It is useful for receiver development without the physical controller. |
gametrak-osc |
HID-to-OSC. This is a command-line program, built in Python, which transmits Gametrak controller data over OSC to other software (such as Processing, TouchDesigner, Max/MSP, etc.). A Processing (Java) OSC receiver is also provided. |
gametrak-midi |
HID-to-MIDI. This is a command-line program, built in Python, which transmits Gametrak controller data as MIDI pitch-bend signals. It is usable with any MIDI software (Max/MSP, Ableton Live, Logic, VCV Rack, MIDI Monitor) that can read virtual MIDI ports, as well as web applications in browsers that support WebMIDI. A p5.js receiver is also provided. |
gametrak-ws |
HID-to-WebSocket JSON. This is a command-line program, built in Python, which streams Gametrak data to browser sketches over native WebSockets. It is useful for p5.js and other JavaScript projects and does not require Node.js or WebMIDI. A p5.js receiver is also provided. |
| gametrak_midi_receiver_p5v2 | MIDI-to-p5. This is a p5.js (v.2.3.0) sketch which receives and visualizes MIDI data from gametrak-midi via WebMIDI. The six 12-bit data streams are sent as pitch-bend signals on MIDI channels 1-6. Both v1 and v2 of p5.js are supported. Uses webmidi.js. |
| gametrak_ws_receiver_p5v2 | WebSocket-to-p5. This is a p5.js (v.2.3.0) sketch which receives and visualizes JSON messages from gametrak-ws over browser-native WebSockets. Both v1 and v2 of p5.js are supported. |
| gametrak_osc_receiver | OSC-to-Processing. This is a Processing (v.4.5.5) app which receives and visualizes OSC data from gametrak-osc. By default, OSC receivers should listen on UDP port 2434. |
| gametrak_standalone | HID-to-Processing. This is a standalone Processing (v.4.5.5) sketch that connects to the Gametrak directly as an HID device. No other software is required. Uses hid4java. |
| gametrak_osc_transmitter | HID-to-OSC. This is a standalone Processing (v.4.5.5) sketch that connects to the Gametrak directly as an HID device and transmits /gametrak/raw packets over OSC. No other software is required, but it can be used with the gametrak_osc_receiver. |
All paths expose the same semantic channel order:
left_x left_y left_r right_x right_y right_r
- Raw values are
0..4095. - Joystick X/Y values are centered controls.
- Tether
Rvalues are inverted relative to its “extension” (a short/retracted tether reads high, while pulling it out makes the raw value go down). - The Gametrak footswitch was not available for testing and is not yet supported.
Install the Python commands from a source checkout with pipx:
cd python
pipx install --python python3.10 .If you already installed an earlier checkout, force a reinstall so new commands and dependencies are picked up:
cd python
pipx install --force --python python3.10 .If pipx install . reports that Python 3.9 is too old, keep using the explicit
--python python3.10 form above, or point pipx at another installed Python
3.10+ interpreter.
Run one of the Python "bridge" apps:
gametrak-stdout— prints data to the Terminalgametrak-record— records a JSONL filegametrak-playback— replays a JSONL file without the devicegametrak-osc— transmits OSCgametrak-midi— transmits MIDIgametrak-ws— transmits WebSocket JSON for browser sketches
Do not run multiple HID-owning bridges at the same time. For example, you
should quit gametrak-osc before launching gametrak-midi, gametrak-ws,
gametrak-record, or the standalone Processing HID sketch. gametrak-playback
does not open the Gametrak device, so it can be used when the controller is not
attached.
(Image from Charles Lootd's GameTrak review video)
Hardware-owning Python commands support:
--diagnoseDiagnostic mode prints a technical HID/USB report and exits without streaming.
It includes expected VID/PID, matching hidapi devices, manufacturer/product
strings, usage page/usage, interface number, hidapi path, a short open/close
probe, and macOS USB topology when system_profiler exposes it.
gametrak-stdout prints exactly six values per line:
left_x left_y left_r right_x right_y right_r
Examples:
gametrak-stdout
gametrak-stdout --rate 30
gametrak-stdout --normalized --precision 4
gametrak-stdout --hex
gametrak-stdout --0dec
gametrak-stdout --diagnoseModes:
| Option | Output | Example value |
|---|---|---|
default / --raw |
raw decimal 0..4095 |
26 |
--0dec |
raw decimal, 4-digit zero-padded | 0026 |
--hex |
raw hex, 3-digit uppercase zero-padded | 01A |
--normalized |
descriptor-scale convenience floats | -0.125 |
Because stdout contains only data lines and diagnostics go to stderr, normal Unix pipelines work:
gametrak-stdout --rate 30 > capture.txt
gametrak-stdout --rate 30 | awk '{ print $1, $2 }'
gametrak-stdout --rate 30 | ./my-gametrak-consumergametrak-record writes a JSONL capture of full HID input reports. Each line is
one JSON object. The first line is session metadata, each report line preserves
the original report bytes plus decoded axes, and the final line is a summary.
gametrak-record --seconds 10 --out sample_reports/official_001.jsonl
gametrak-record --seconds 10 --label official --note "short movement sample" --out sample_reports/official_001.jsonl
gametrak-record --seconds 5 --print
gametrak-record --diagnoseJSONL is used instead of one large JSON file because GameTrak data is a stream:
partial captures remain useful if recording is interrupted, files can be
inspected with tail while recording, and the format stays friendly to Unix
pipelines.
gametrak-playback replays a JSONL capture made by gametrak-record. It does not open the Gametrak device. Instead, it reconstructs decoded reports from the
stored raw_bytes_hex rows and emits them through one selected protocol. The official sample recording is here.
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --stdout
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --stdout --rate 30
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --osc --loop
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --ws --loop
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --midi --port-name "GameTrak Playback"Choose exactly one output protocol:
| Option | Output |
|---|---|
--stdout |
Terminal lines matching gametrak-stdout |
--osc |
OSC messages matching gametrak-osc |
--midi |
MIDI pitch bend matching gametrak-midi |
--ws |
WebSocket JSON matching gametrak-ws |
By default, playback follows the recorded elapsed_ns timing. Use --rate to
force a fixed output rate, --speed to scale recorded timing, and --loop to
repeat the capture indefinitely while developing receivers.
Output mode flags match the live tools where they apply:
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --stdout --hex
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --stdout --normalized
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --osc --normalized
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --ws --normalized
gametrak-playback sample_reports/gametrak_sample_10_second_recording.jsonl --osc --wekinator--hex and --0dec are stdout-only. MIDI playback uses raw Gametrak values,
just like gametrak-midi.
gametrak-osc reads the GameTrak over HID, sends the required init/keepalive
sequence, decodes reports, and broadcasts OSC. You can test it with the provided Processing program, gametrak_osc_receiver.
gametrak-osc
gametrak-osc --host 127.0.0.1 --port 2434
gametrak-osc --raw --normalized --rate 60 --print
gametrak-osc --diagnose
gametrak-osc --wekinatorDefault output is raw (i.e., 12-bit values in the range 0...4095):
/gametrak/raw
int left_x left_y left_r right_x right_y right_r buttons
Optional normalized output (joystick X/Y values in -1..1; tether extension values in 0..1):
/gametrak/normalized
float left_x left_y left_r right_x right_y right_r
For normalized values, joystick X/Y axes are descriptor-scale -1..1; tether
R axes are descriptor-scale 0..1, where 0 means retracted/short and 1
means extended. These values are convenience values, not per-device calibrated
values.
"Wekinator" mode sends:
/wekinator/control/inputs
float left_x left_y left_r right_x right_y right_r
The OSC bridge also listens for control messages by default:
127.0.0.1:2435
/gametrak/init
Resend the init sequence on the current HID handle.
/gametrak/reconnect
Close/reopen the HID handle, then resend init.
Use --rate only to throttle output below the device's natural report rate.
Omitting --rate sends every valid HID report.
gametrak-midi creates a virtual MIDI output named GameTrak MIDI.
Chrome sees that virtual output as a WebMIDI input. You can test this with the provided p5 WebMIDI sketch, gametrak_midi_receiver_p5v2.
gametrak-midi
gametrak-midi --rate 60
gametrak-midi --print
gametrak-midi --port-name "GameTrak MIDI"
gametrak-midi --diagnoseThe bridge sends raw GameTrak values directly as signed MIDI pitch bend values
0..4095 on channels 1-6:
| MIDI channel | GameTrak value |
|---|---|
| 1 | left_x |
| 2 | left_y |
| 3 | left_r |
| 4 | right_x |
| 5 | right_y |
| 6 | right_r |
WebMIDI exposes pitch bend's unsigned wire value as event.rawValue, so a
browser receiver subtracts 8192 to recover the signed value:
const rawGameTrakValue = event.rawValue - 8192;gametrak-ws opens the Gametrak over HID and serves browser-native WebSocket
JSON on ws://127.0.0.1:2436. It is intended for p5.js and other browser
apps that cannot receive UDP OSC and do not need WebMIDI.
gametrak-ws
gametrak-ws --host 127.0.0.1 --port 2436
gametrak-ws --normalized
gametrak-ws --rate 60
gametrak-ws --print
gametrak-ws --diagnoseEach WebSocket text message is compact JSON:
{"address":"/gametrak/raw","args":[123,2048,4095,500,900,3900,0]}The first six args values are:
left_x left_y left_r right_x right_y right_r
The seventh value is the decoded button bitfield.
With --normalized, gametrak-ws sends /gametrak/normalized with six
descriptor-scale normalized floats and no button bitfield:
{"address":"/gametrak/normalized","args":[0.0,0.0,0.0,1.0,-1.0,1.0]}Path:
processing/gametrak_standalone/
This Processing sketch talks to the Gametrak directly through hid4java. It does not
require Python, OSC, WebMIDI, or a local server. It has been tested in
Processing 4.3 and Processing 4.5.5 on macOS 15.6.
Bundled Java dependencies:
processing/gametrak_standalone/code/hid4java-0.8.0.jar
processing/gametrak_standalone/code/jna-5.14.0.jar
Source layout:
processing/gametrak_standalone/gametrak_standalone.pde
processing/gametrak_standalone/GameTrakDirectHid.java
The .pde file owns the Processing UI. The .java helper owns all direct HID
work so Processing's PDE preprocessor does not have to parse external
hid4java types.
Quit any Gametrak Python bridge before running this sketch so both programs do not compete for the same HID handle.
Path:
processing/gametrak_osc_transmitter/
This Processing sketch talks to the Gametrak directly through hid4java and sends each
valid HID report as OSC /gametrak/raw to 127.0.0.1:2434. It does not
require gametrak-osc or any other Python tool. It also listens for OSC
control messages on 127.0.0.1:2435.
OSC payload:
/gametrak/raw
int left_x left_y left_r right_x right_y right_r buttons
Quit any other Gametrak HID client before running this sketch. It owns the HID handle directly.
Control messages:
/gametrak/init
/gametrak/reconnect
Path:
processing/gametrak_osc_receiver/
This Processing sketch receives /gametrak/raw OSC data on UDP port 2434
using a small dependency-free OSC parser built on java.net.DatagramSocket.
It visualizes the six raw values as bars and shows left/right X/Y timelines.
It can work with either gametrak-osc or the Processing-based gametrak_osc_transmitter. For example, run:
gametrak-oscThen open the Processing sketch.
The sketch includes a reset button that sends /gametrak/reconnect to the
Python OSC bridge's control port, UDP 2435. The data port 2434 was chosen
because it is the Gametrak's HID Product ID; the control port uses the next
port number.
This p5.js sketch uses WebMIDI to receive MIDI signals from the gametrak-midi Python bridge.
Path:
p5js/gametrak_midi_receiver_p5v2/
Run the MIDI bridge:
gametrak-midiServe the repo over localhost, i.e. on a local server:
cd path/to/GameTrak-Liberation
python3 -m http.server 8000Open in Chrome:
http://127.0.0.1:8000/p5js/gametrak_midi_receiver_p5v2/
Click Enable MIDI and allow Chrome's MIDI permission prompt. The sketch
prefers the input named GameTrak MIDI. Versions of the p5 sketch are available for both v1 and v2 of p5.js:
- gametrak_midi_receiver_p5v1 (v.1.11.13)
- gametrak_midi_receiver_p5v2 (v.2.3.0)
This p5.js sketch receives JSON messages from gametrak-ws using the browser's native WebSocket API.
Path:
p5js/gametrak_ws_receiver_p5v2/
Run the WebSocket bridge:
gametrak-wsOpen the sketch directly in Chrome:
file:///path/to/GameTrak-Liberation/p5js/gametrak_ws_receiver_p5v2/index.html
The sketch connects to gametrak-ws at:
ws://127.0.0.1:2436
No local HTTP server is required for this WebSocket receiver. If you prefer to
serve the static files with python3 -m http.server, that is also fine, but (e.g.)
port 8000 is only the page server; GameTrak data still arrives over WebSocket
port 2436.
Versions of the WebSocket sketch are available for both v1 and v2 of p5.js:
- gametrak_ws_receiver_p5v1 (v.1.11.13)
- gametrak_ws_receiver_p5v2 (v.2.3.0)
- Additional information about the Gametrak communication protocol can be found here.
- Sample recordings of the Gametrak's data can be found here.
Verified USB identity:
| Property | Value |
|---|---|
| Physical underside label | GameTrak V2.0 golfing interface device for PlayStation |
| USB product string | Game-Trak V1.3 |
| USB manufacturer string | In2Games Ltd. |
| Vendor ID | 0x14B7 decimal 5303 |
| Product ID | 0x0982 decimal 2434 |
| USB speed | Full-speed USB, 12 Mbps |
| HID usage page | 0x01, Generic Desktop |
| HID primary usage | 0x04, Joystick |
| Max input report size | 16 bytes |
| Max output report size | 4 bytes |
The HID descriptor exposes six absolute axes:
X, Y, Z, Rx, Ry, Rz
Each axis is a 16-bit little-endian field with logical range 0..4095.
Empirical semantic mapping:
| HID field | Semantic value | Direction note |
|---|---|---|
X |
left_x |
low = left, high = right |
Y |
left_y |
low = toward user, high = away |
Z |
left_r |
high = retracted/short, lower = extended |
Rx |
right_x |
low = left, high = right |
Ry |
right_y |
low = toward user, high = away |
Rz |
right_r |
high = retracted/short, lower = extended |
Known report notes:
bytes 0-11: six uint16_le axes
byte 12 bits 0-3: hat switch per descriptor
byte 12 bits 4-7 plus byte 13: 12 buttons per descriptor
bytes 14-15: descriptor padding, but live captures show right-tether-related movement
The available hardware used for testing did not include the external footswitch. The descriptor exposes button bits, but footswitch bit index and polarity remain untested.
Full protocol notes live in:
docs/protocol.md
The Python tools and Processing sketches do not perform per-device calibration. They are responsible for:
- opening the HID device
- sending init/keepalive writes
- decoding reports
- applying the fixed semantic channel order
- emitting or displaying raw values
Application code should handle:
- per-device min/max calibration
- deadband
- smoothing/filtering
- coordinate transforms
- gesture interpretation
- application-specific scaling
Descriptor-scale normalized values exist only as convenience output when explicitly requested.
For Python development:
cd python
python -m pip install -e ".[dev]"
python -m pytestRepository layout:
python/ Python package, CLIs, tools, tests
processing/ Processing OSC and standalone HID sketches
p5js/ Browser MIDI sketches
docs/ Protocol and prior-art documentation
sample_reports/ Captured sample reports
Useful source docs:
docs/protocol.md
docs/prior_art.md
THIRD_PARTY.md
sample_reports/README.md
If no reports arrive, make sure the device is connected directly or through a reliable powered path. On macOS, moving from a hub to a direct adapter caused the OS to ask for USB accessory permission for the tested unit.
If a bridge cannot open the HID device, quit other HID clients first. Chrome WebMIDI, the standalone Processing HID sketch, and Python bridges can compete for the same device.
If pipx install . uses Python 3.9, reinstall with:
pipx install --force --python python3.10 .If Chrome does not show the WebMIDI input, keep gametrak-midi running and
reload the p5 page from http://127.0.0.1, not file://.
Use diagnostics for a quick device report:
gametrak-osc --diagnose
gametrak-stdout --diagnose
gametrak-record --diagnose
gametrak-midi --diagnose- The Gametrak was invented in 2000 by Elliott Myers.
- Thanks to Rebecca Fiebrink for introducing me to the Gametrak during a Wekinator workshop at the STUDIO. Rebecca has cut the path for demonstrating how expressive controllers like the Gametrak, coupled with machine learning, can serve musical inclusion and exploration.
- Thanks to Géry Casiez, whose
libgametrakreverse-engineering project did the heavy lifting to make this possible, more than 14 years ago. - An index of redistributed third-party code can be found here.
- This repository was developed with Codex.




