From my recent exploration of the Windows built-in joystick controller calibration tool, I can confirm the following behaviour on a PS4 joystick (results should be similar for other controllers):
After going through the calibration wizard, the following registries are written with the calibrated data,
HKEY_CURRENT_USER\System\CurrentControlSet\Control\MediaProperties\PrivateProperties\DirectInput\<DEVICE_ID>\Calibration\0\Type\Axes\<NUM>
, where <NUM>
is the axis number.
For a PS4 joystick, Windows reads six axis and therefore six registry key values are created. The numbering of the axes are as follows:
- 0 (x) --> Left analog stick horizontal motion.
- 1 (y) --> Left analog stick vertical motion.
- 2 (z) --> Right analog horizontal motion.
- 3 (Rx) --> L2 trigger.
- 4 (Ry) --> R2 trigger.
- 5 (Rz) --> Right analog vertical motion.
The format of the registry keys is Calibration
mapped to a 12 byte binary value (encoded as <MIN> <MID> <MAX>
). For instance, my Axis 0 (x) has value: <00 00 00 00> <80 00 00 00> <ff 00 00 00> which translates to
- Minimum Axis 0 value = <00 00 00 00> = 0 (decimal)
- Middle Axis 0 value = <80 00 00 00> = 128 (decimal)
- Maximum Axis 0 value <ff 00 00 00> = 255 (decimal)
The calibration that Windows does is convert the physical values into a calibrated range (min, mid, max values from above). For instance, suppose my defective left analog stick reads from 10-100 for horizontal movement (x-axis) when it should be range from 0-255. Then I can set the min/max values to 10 and 100 respectively to calibrate for the defective analog stick.
There doesn't seem to be a specific setting for deadzones, so I assume this is an implementation detail left for the application above (e.g. For a game, defined as part of game logic code).
Since the calibration settings are stored in registry, calibration is not persistent across different machines.
As for your particular case, you might want to consider using the some Windows API to read the values (e.g. XInput, UWP API). As a bonus, here is some of my buggy that reads controller input using Windows XInput API.
#pragma comment(lib,"XInput.lib")
#pragma comment(lib,"Xinput9_1_0.lib")
#include <iostream>
#include <roapi.h>
#include <Xinput.h>
XINPUT_STATE fetchAConnectedJoystick()
{
DWORD dwResult;
XINPUT_STATE state;
for (DWORD i=0; i < XUSER_MAX_COUNT; i++)
{
ZeroMemory(&state, sizeof(XINPUT_STATE));
// Simply get the state of the controller from XInput.
dwResult = XInputGetState(i, &state);
// Controller is connected
if(dwResult == ERROR_SUCCESS)
{
return state;
}
}
std::exit;
}
void printLeftAnalogStickReadings(XINPUT_STATE state)
{
float LX = state.Gamepad.sThumbLX;
float LY = state.Gamepad.sThumbLY;
std::cout << "(X=" << LX << ", Y=" << LY << ")\n";
}
int computeAdjustedMagnitude(XINPUT_STATE state)
{
int INPUT_DEADZONE = 42;
float LX = state.Gamepad.sThumbLX;
float LY = state.Gamepad.sThumbLY;
float magnitude = sqrt(LX*LX + LY*LY); // Determine how far the controller is pushed
// Determine the direction the controller is pushed
float normalizedLX = LX / magnitude;
float normalizedLY = LY / magnitude;
if (magnitude > INPUT_DEADZONE) // Check if the controller is outside a circular dead zone
{
if (magnitude > 32767) magnitude = 32767; // Clip the magnitude at its expected maximum value
magnitude -= INPUT_DEADZONE; // Adjust magnitude relative to the end of the dead zone
}
else // If the controller is in the deadzone zero out the magnitude
{
magnitude = 0.0;
}
return magnitude;
}
int main()
{
XINPUT_STATE state;
int sleepDuration = 100; // Milliseconds
int adjustedMag = 0;
while (true) {
state = fetchAConnectedJoystick();
printLeftAnalogStickReadings(state);
// adjustedMag = computeAdjustedMagnitude(state);
// std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
}
}