From d5662c4fb1c8f1df5dac3c5cd4d2e2d88ec3a278 Mon Sep 17 00:00:00 2001 From: Jibb Smart Date: Mon, 31 May 2021 17:56:36 +0800 Subject: [PATCH 1/3] Added GYRO_SPACE option. While it's default value (LOCAL) behaves as normal, you can set it to WORLD_TURN to try a more advanced algorithm, taking gravity into account and combining gyro axes to more faithfully convert the player's movement to an appropriate mouse movement. If the player turns their controller in only two local axes, it'll behave the same as LOCAL. If the player is turning their controller in world space, this will give a very accurate result. Realistically, players unintentionally do something between local and world, but whatever it is this will tend to give a good result. Alternatively, for players who prefer to lean their controller side to side to turn the camera, they can choose WORLD_LEAN. --- CHANGELOG.md | 8 ++ JoyShockMapper/include/JoyShockMapper.h | 8 ++ JoyShockMapper/src/main.cpp | 142 ++++++++++++++++++++---- README.md | 3 +- 4 files changed, 136 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ffe2e..69bcf18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ Most recent updates will appear first. This is a summary of new features and bugfixes. Read the README to learn how to use the features mentioned here. +## 3.2.0 + +Jibb added the new GYRO_SPACE setting for more one-size-fits-all gyro aiming. Default behaviour is unchanged, but set to WORLD_TURN to try the new algorithm (or WORLD_LEAN if you prefer to lean your controller side to side to turn the camera). + +### Features + +* GYRO_SPACE can be set to LOCAL (default), WORLD_TURN (recommended), or WORLD_LEAN. + ## 3.1.1 Fix linux build diff --git a/JoyShockMapper/include/JoyShockMapper.h b/JoyShockMapper/include/JoyShockMapper.h index c0682c4..de77979 100644 --- a/JoyShockMapper/include/JoyShockMapper.h +++ b/JoyShockMapper/include/JoyShockMapper.h @@ -235,6 +235,7 @@ enum class SettingID FLICK_DEADZONE_ANGLE, FLICK_TIME_EXPONENT, CONTROLLER_ORIENTATION, + GYRO_SPACE, TRACKBALL_DECAY, TRIGGER_SKIP_DELAY, TURBO_PERIOD, @@ -274,6 +275,13 @@ constexpr float MAGIC_INSTANT_DURATION = 40.0f; // in milliseconds constexpr float MAGIC_EXTENDED_TAP_DURATION = 500.0f; // in milliseconds constexpr int MAGIC_TRIGGER_SMOOTHING = 5; // in samples +enum class GyroSpace +{ + LOCAL, + WORLD_TURN, + WORLD_LEAN, + INVALID +}; enum class ControllerOrientation { FORWARD, diff --git a/JoyShockMapper/src/main.cpp b/JoyShockMapper/src/main.cpp index f4c3fe2..f19cb8a 100644 --- a/JoyShockMapper/src/main.cpp +++ b/JoyShockMapper/src/main.cpp @@ -85,6 +85,7 @@ JSMSetting motion_deadzone_inner = JSMSetting(SettingID::MOTION_DE JSMSetting motion_deadzone_outer = JSMSetting(SettingID::MOTION_DEADZONE_OUTER, 135.f); JSMSetting lean_threshold = JSMSetting(SettingID::LEAN_THRESHOLD, 15.f); JSMSetting controller_orientation = JSMSetting(SettingID::CONTROLLER_ORIENTATION, ControllerOrientation::FORWARD); +JSMSetting gyro_space = JSMSetting(SettingID::GYRO_SPACE, GyroSpace::LOCAL); JSMSetting trackball_decay = JSMSetting(SettingID::TRACKBALL_DECAY, 1.0f); JSMSetting mouse_ring_radius = JSMSetting(SettingID::MOUSE_RING_RADIUS, 128.0f); JSMSetting screen_resolution_x = JSMSetting(SettingID::SCREEN_RESOLUTION_X, 1920.0f); @@ -573,6 +574,9 @@ class JoyShock } } break; + case SettingID::GYRO_SPACE: + opt = GetOptionalSetting(gyro_space, *activeChord); + break; case SettingID::ZR_MODE: opt = GetOptionalSetting(zrMode, *activeChord); break; @@ -2333,9 +2337,6 @@ void joyShockPollCallback(int jcHandle, JOY_SHOCK_STATE state, JOY_SHOCK_STATE l float inGravX, inGravY, inGravZ; motion.GetGravity(inGravX, inGravY, inGravZ); - inGravX *= 1.f / 9.8f; // to Gs - inGravY *= 1.f / 9.8f; - inGravZ *= 1.f / 9.8f; float inQuatW, inQuatX, inQuatY, inQuatZ; motion.GetOrientation(inQuatW, inQuatX, inQuatY, inQuatZ); @@ -2365,32 +2366,122 @@ void joyShockPollCallback(int jcHandle, JOY_SHOCK_STATE state, JOY_SHOCK_STATE l float gyroX = 0.0; float gyroY = 0.0; - int mouse_x_flag = (int)jc->getSetting(SettingID::MOUSE_X_FROM_GYRO_AXIS); - if ((mouse_x_flag & (int)GyroAxisMask::X) > 0) - { - gyroX += inGyroX; - } - if ((mouse_x_flag & (int)GyroAxisMask::Y) > 0) + GyroSpace gyroSpace = jc->getSetting(SettingID::GYRO_SPACE); + if (gyroSpace == GyroSpace::LOCAL) { - gyroX -= inGyroY; - } - if ((mouse_x_flag & (int)GyroAxisMask::Z) > 0) - { - gyroX -= inGyroZ; + int mouse_x_flag = (int)jc->getSetting(SettingID::MOUSE_X_FROM_GYRO_AXIS); + if ((mouse_x_flag & (int)GyroAxisMask::X) > 0) + { + gyroX += inGyroX; + } + if ((mouse_x_flag & (int)GyroAxisMask::Y) > 0) + { + gyroX -= inGyroY; + } + if ((mouse_x_flag & (int)GyroAxisMask::Z) > 0) + { + gyroX -= inGyroZ; + } + int mouse_y_flag = (int)jc->getSetting(SettingID::MOUSE_Y_FROM_GYRO_AXIS); + if ((mouse_y_flag & (int)GyroAxisMask::X) > 0) + { + gyroY -= inGyroX; + } + if ((mouse_y_flag & (int)GyroAxisMask::Y) > 0) + { + gyroY += inGyroY; + } + if ((mouse_y_flag & (int)GyroAxisMask::Z) > 0) + { + gyroY += inGyroZ; + } } - int mouse_y_flag = (int)jc->getSetting(SettingID::MOUSE_Y_FROM_GYRO_AXIS); - if ((mouse_y_flag & (int)GyroAxisMask::X) > 0) + else if (gyroSpace == GyroSpace::WORLD_TURN || gyroSpace == GyroSpace::WORLD_LEAN) { + float gravLength = sqrtf(inGravX * inGravX + inGravY * inGravY + inGravZ * inGravZ); + float normGravX = 0.f; + float normGravY = 0.f; + float normGravZ = 0.f; + if (gravLength > 0.f) + { + float gravNormalizer = 1.f / gravLength; + normGravX = inGravX * gravNormalizer; + normGravY = inGravY * gravNormalizer; + normGravZ = inGravZ * gravNormalizer; + } + + float flatness = abs(normGravY); + if (flatness > 1.f) + { + flatness = 1.f; + } + float upness = sqrtf(1.f - flatness); + float flatFactor = min(flatness / 0.5f, 1.f); + float upFactor = min(upness / 0.5f, 1.f); + bool flatsideDown = normGravY < 0.f; + bool upsideDown = normGravZ < 0.f; + + if (gyroSpace == GyroSpace::WORLD_TURN) + { + if (flatsideDown) inGyroY = -inGyroY; + if (upsideDown) inGyroZ = -inGyroZ; + if (flatness > upness) + { + float gyroSign = inGyroZ < 0.f ? -1.f : 1.f; + float reducedGyro = gyroSign * abs(inGyroY) * min(upness / flatness, abs(inGyroZ / inGyroY)); + inGyroZ = reducedGyro + (inGyroZ - reducedGyro) * upFactor; + } + else + { + float gyroSign = inGyroY < 0.f ? -1.f : 1.f; + float reducedGyro = gyroSign * abs(inGyroZ) * min(flatness / upness, abs(inGyroY / inGyroZ)); + inGyroY = reducedGyro + (inGyroY - reducedGyro) * flatFactor; + } + } + else // WORLD_LEAN + { + if (!upsideDown) inGyroY = -inGyroY; + if (flatsideDown) inGyroZ = -inGyroZ; + if (flatness > upness) + { + float gyroSign = inGyroZ < 0.f ? -1.f : 1.f; + float reducedGyro = gyroSign * abs(inGyroZ) * min(upness / flatness, abs(inGyroY / inGyroZ)); + inGyroY = reducedGyro + (inGyroY - reducedGyro) * flatFactor; + } + else + { + float gyroSign = inGyroY < 0.f ? -1.f : 1.f; + float reducedGyro = gyroSign * abs(inGyroY) * min(flatness / upness, abs(inGyroZ / inGyroY)); + inGyroZ = reducedGyro + (inGyroZ - reducedGyro) * upFactor; + } + } + float bigger; + float smaller; + if (abs(inGyroY) > abs(inGyroZ)) + { + bigger = inGyroY; + smaller = inGyroZ; + } + else + { + bigger = inGyroZ; + smaller = inGyroY; + } + if (inGyroZ * inGyroY >= 0.f) + { + // same sign (ish) + const float gyroSign = bigger > 0.f ? 1.f : -1.f; + gyroX += gyroSign * sqrtf(bigger * bigger + smaller * smaller); + } + else + { + // opposite sign + const float gyroSign = bigger > 0.f ? 1.f : -1.f; + gyroX += gyroSign * sqrtf(bigger * bigger - smaller * smaller); + } + gyroY -= inGyroX; } - if ((mouse_y_flag & (int)GyroAxisMask::Y) > 0) - { - gyroY += inGyroY; - } - if ((mouse_y_flag & (int)GyroAxisMask::Z) > 0) - { - gyroY += inGyroZ; - } float gyroLength = sqrt(gyroX * gyroX + gyroY * gyroY); // do gyro smoothing // convert gyro smooth time to number of samples @@ -3337,6 +3428,7 @@ int main(int argc, char *argv[]) joycon_gyro_mask.SetFilter(&filterInvalidValue); joycon_motion_mask.SetFilter(&filterInvalidValue); controller_orientation.SetFilter(&filterInvalidValue); + gyro_space.SetFilter(&filterInvalidValue); zlMode.SetFilter(&filterTriggerMode); zrMode.SetFilter(&filterTriggerMode); flick_snap_mode.SetFilter(&filterInvalidValue); @@ -3543,6 +3635,8 @@ int main(int argc, char *argv[]) ->SetHelp("Pick a gyro axis to operate on the mouse's Y axis. Valid values are the following: X, Y and Z.")); commandRegistry.Add((new JSMAssignment(controller_orientation)) ->SetHelp("Let the stick modes account for how you're holding the controller:\nFORWARD, LEFT, RIGHT, BACKWARD")); + commandRegistry.Add((new JSMAssignment(gyro_space)) + ->SetHelp("How gyro input is converted to 2D input. With LOCAL, your MOUSE_X_FROM_GYRO_AXIS and MOUSE_Y_FROM_GYRO_AXIS settings decide which local angular axis maps to which 2D mouse axis.\nYour other options are WORLD_TURN and WORLD_LEAN. These both take gravity into account to combine your axes more reliably.\n\tUse WORLD_TURN if you like to turn your camera or move your cursor by turning your controller side to side.\n\tUse WORLD_LEAN if you'd rather lean your controller to turn the camera.")); commandRegistry.Add((new JSMAssignment(zlMode)) ->SetHelp("Controllers with a right analog trigger can use one of the following dual stage trigger modes:\nNO_FULL, NO_SKIP, MAY_SKIP, MUST_SKIP, MAY_SKIP_R, MUST_SKIP_R, NO_SKIP_EXCLUSIVE, X_LT, X_RT, PS_L2, PS_R2")); commandRegistry.Add((new JSMAssignment(zrMode)) diff --git a/README.md b/README.md index 3b3d646..1387634 100644 --- a/README.md +++ b/README.md @@ -645,8 +645,9 @@ JoyShockMapper allows you to say, "When turning slowly, I want this sensitivity. **Finally**, there are a bunch more settings you can tweak if you so desire: +* **GYRO\_SPACE** (default LOCAL) - Simple gyro aiming solutions will map one of your controller's gyro axes to your camera/cursor's horizontal axis and one to the vertical axis. That's the behaviour you'll get with JoyShockMapper when GYRO\_SPACE is set to "LOCAL". This is simple to implement and leaves no room for misinterpretation, but aiming can feel off as you tilt your controller more and more. If you'd prefer a more advanced reading of the gyro combined with the accelerometer to more naturally handle different controller positions, WORLD\_TURN is the way to go. Or, if you prefer to lean your controller side to side to turn your camera, try WORLD\_LEAN. * **GYRO\_AXIS\_X** and **GYRO\_AXIS\_Y** (default STANDARD) - This allows you to invert the gyro directions if you wish. Want a left- gyro turn to translate to a right- in-game turn? Set GYRO\_AXIS\_X to INVERTED. For normal behaviour, set it to STANDARD. -* **MOUSE\_X\_FROM\_GYRO\_AXIS** and **MOUSE\_Y\_FROM\_GYRO\_AXIS** (default Y and X, respectively) - Maybe you want to turn the camera left and right by rolling your controller about its local Z axis instead of turning it about its local Y axis. Or maybe you want to play with a single JoyCon sideways. This is how you do that. Your options are X, Y, Z, and NONE, if you want an axis of mouse movement unaffected by the gyro. +* **MOUSE\_X\_FROM\_GYRO\_AXIS** and **MOUSE\_Y\_FROM\_GYRO\_AXIS** (default Y and X, respectively) - Maybe you want to turn the camera left and right by rolling your controller about its local Z axis instead of turning it about its local Y axis. Or maybe you want to play with a single JoyCon sideways. This is how you do that. Your options are X, Y, Z, and NONE, if you want an axis of mouse movement unaffected by the gyro. These settings only apply when GYRO\_SPACE is set to LOCAL. * **GYRO\_CUTOFF\_SPEED** (default 0.0 degrees per second) - Some games attempt to cover up small unintentional movements by setting a minimum speed below which gyro input will be ignored. This is that setting. It's never good. Don't use it. Some games won't even let you change or disable this "feature". I implemented it to see if it could be made good. I left it in there only so you can see for yourself that it's not good, or for you to perhaps show me how it can be. It might be mostly harmless for interacting with a simple UI with big-ish buttons, but it's useless if the player will *ever* intentionally turn the controller slowly (such as to track a slow-moving target), because they may unintentionally fall below the cutoff speed. Even a very small cutoff speed might be so high that it's impossible to move the aimer at the same speed as a very slow-moving target. One might argue that such a cutoff is too high, and it just needs to be set lower. But if the cutoff speed is small enough that it doesn't make the player's experience worse, it's probably also small enough that it's actually not doing anything. From a5170e29eff7d18e1157d4f888be4ce1e75166d8 Mon Sep 17 00:00:00 2001 From: Nicolas Lessard Date: Tue, 1 Jun 2021 10:53:50 -0400 Subject: [PATCH 2/3] Use future GamepadMotionHelpers --- JoyShockMapper/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JoyShockMapper/CMakeLists.txt b/JoyShockMapper/CMakeLists.txt index 0e05d28..d0f9c19 100644 --- a/JoyShockMapper/CMakeLists.txt +++ b/JoyShockMapper/CMakeLists.txt @@ -221,7 +221,7 @@ target_link_libraries ( CPMAddPackage ( NAME GamepadMotionHelpers GITHUB_REPOSITORY JibbSmart/GamepadMotionHelpers - GIT_TAG main + GIT_TAG wip ) target_link_libraries ( From f086711123e70163511f911b76285b8504b413f9 Mon Sep 17 00:00:00 2001 From: Jibb Smart Date: Wed, 2 Jun 2021 12:09:10 +0800 Subject: [PATCH 3/3] Added AUTO_CALIBRATE_GYRO option. Defaults to OFF, but set to ON for the gyro to automatically calibrate when held very still or left on a steady surface. Thresholds are tuned to be very 'tight' (very difficult to accidentally miscalibrate, but players with unsteady hands will have to put the controller down to trigger calibration). In future we can let players tune their thresholds, and I'm experimenting with automatic thresholds that just detect appropriate values based on the controller's historical movements. Also made it so GYRO_SPACE resets with RESET_MAPPINGS. AUTO_CALIBRATE_GYRO does not reset with RESET_MAPPINGS, because I figure it falls under the category of "calibration state" that's already ignored by the reset? I'm not married to this, though --- CHANGELOG.md | 3 ++- JoyShockMapper/include/MotionIf.h | 1 + JoyShockMapper/src/MotionImpl.cpp | 14 ++++++++++++++ JoyShockMapper/src/main.cpp | 13 +++++++++++++ README.md | 12 ++++++------ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc8acd..d9f83c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ This is a summary of new features and bugfixes. Read the README to learn how to ## 3.2.0 -Jibb added the new GYRO_SPACE setting for more one-size-fits-all gyro aiming. Default behaviour is unchanged, but set to WORLD_TURN to try the new algorithm (or WORLD_LEAN if you prefer to lean your controller side to side to turn the camera). +Jibb added the new GYRO_SPACE setting for more one-size-fits-all gyro aiming. Default behaviour is unchanged, but set to WORLD_TURN to try the new algorithm (or WORLD_LEAN if you prefer to lean your controller side to side to turn the camera). He also added the option for automatic gyro calibration. ### Features * GYRO_SPACE can be set to LOCAL (default), WORLD_TURN (recommended), or WORLD_LEAN. +* AUTO_CALIBRATE_GYRO can be set to ON to activate automatic calibration, which will try and detect when the controller is held still or put down and recalibrate automatically. ## 3.1.1 diff --git a/JoyShockMapper/include/MotionIf.h b/JoyShockMapper/include/MotionIf.h index b68fe18..b9b49a9 100644 --- a/JoyShockMapper/include/MotionIf.h +++ b/JoyShockMapper/include/MotionIf.h @@ -26,6 +26,7 @@ class MotionIf virtual void ResetContinuousCalibration() = 0; virtual void GetCalibrationOffset(float& xOffset, float& yOffset, float& zOffset) = 0; virtual void SetCalibrationOffset(float xOffset, float yOffset, float zOffset, int weight) = 0; + virtual void SetAutoCalibration(bool enabled, float gyroThreshold, float accelThreshold) = 0; void virtual ResetMotion() = 0; }; diff --git a/JoyShockMapper/src/MotionImpl.cpp b/JoyShockMapper/src/MotionImpl.cpp index a6a8ec5..bf3277b 100644 --- a/JoyShockMapper/src/MotionImpl.cpp +++ b/JoyShockMapper/src/MotionImpl.cpp @@ -73,6 +73,20 @@ class MotionImpl : public MotionIf gamepadMotion.SetCalibrationOffset(xOffset, yOffset, zOffset, weight); } + virtual void SetAutoCalibration(bool enabled, float gyroThreshold, float accelThreshold) override + { + if (enabled) + { + gamepadMotion.SetCalibrationMode(GamepadMotionHelpers::CalibrationMode::Stillness | GamepadMotionHelpers::CalibrationMode::SensorFusion); + gamepadMotion.Settings.StillnessGyroDelta = gyroThreshold; + gamepadMotion.Settings.StillnessAccelDelta = accelThreshold; + } + else + { + gamepadMotion.SetCalibrationMode(GamepadMotionHelpers::CalibrationMode::Manual); + } + } + void virtual ResetMotion() override { gamepadMotion.ResetMotion(); diff --git a/JoyShockMapper/src/main.cpp b/JoyShockMapper/src/main.cpp index 9219818..94e1d82 100644 --- a/JoyShockMapper/src/main.cpp +++ b/JoyShockMapper/src/main.cpp @@ -116,6 +116,7 @@ JSMVariable left_trigger_offset = JSMVariable(25); JSMVariable left_trigger_range = JSMVariable(150); JSMVariable right_trigger_offset = JSMVariable(25); JSMVariable right_trigger_range = JSMVariable(150); +JSMVariable auto_calibrate_gyro = JSMVariable(Switch::OFF); JSMVariable currentWorkingDir = JSMVariable(PathString()); vector grid_mappings; // array of virtual buttons on the touchpad grid @@ -1374,6 +1375,7 @@ static void resetAllMappings() aim_x_sign.Reset(); gyro_y_sign.Reset(); gyro_x_sign.Reset(); + gyro_space.Reset(); flick_time.Reset(); flick_time_exponent.Reset(); gyro_smooth_time.Reset(); @@ -2329,6 +2331,14 @@ void joyShockPollCallback(int jcHandle, JOY_SHOCK_STATE state, JOY_SHOCK_STATE l IMU_STATE imu = jsl->GetIMUState(jc->handle); + if (auto_calibrate_gyro.get() == Switch::ON) + { + motion.SetAutoCalibration(true, 1.2f, 0.015f); + } + else + { + motion.SetAutoCalibration(false, 0.f, 0.f); + } motion.ProcessMotion(imu.gyroX, imu.gyroY, imu.gyroZ, imu.accelX, imu.accelY, imu.accelZ, deltaTime); float inGyroX, inGyroY, inGyroZ; @@ -3499,6 +3509,7 @@ int main(int argc, char *argv[]) left_trigger_offset.SetFilter(&filterClampByte); right_trigger_range.SetFilter(&filterClampByte); left_trigger_range.SetFilter(&filterClampByte); + auto_calibrate_gyro.SetFilter(&filterInvalidValue); // light_bar needs no filter or listener. The callback polls and updates the color. for (int i = argc - 1; i >= 0; --i) @@ -3716,6 +3727,8 @@ int main(int argc, char *argv[]) commandRegistry.Add((new JSMAssignment(magic_enum::enum_name(SettingID::RIGHT_TRIGGER_OFFSET).data(), right_trigger_offset))); commandRegistry.Add((new JSMAssignment(magic_enum::enum_name(SettingID::LEFT_TRIGGER_RANGE).data(), left_trigger_range))); commandRegistry.Add((new JSMAssignment(magic_enum::enum_name(SettingID::RIGHT_TRIGGER_RANGE).data(), right_trigger_range))); + commandRegistry.Add((new JSMAssignment("AUTO_CALIBRATE_GYRO", auto_calibrate_gyro)) + ->SetHelp("Gyro calibration happens automatically when this setting is ON. Otherwise you'll need to calibrate the gyro manually when using gyro aiming.")); bool quit = false; commandRegistry.Add((new JSMMacro("QUIT")) diff --git a/README.md b/README.md index 1387634..dc20ce0 100644 --- a/README.md +++ b/README.md @@ -617,11 +617,11 @@ A common use for the motion sensors is to map left and right leans of the contro ### 4. Gyro Mouse Inputs **The first thing you need to know about gyro mouse inputs** is that a controller's gyro will often need calibrating. This just means telling the application where "zero" is. Just like a scale, the gyro needs a point of reference to compare against in order to accurately give a result. This is done by leaving the controller still, or holding it very still in your hands, and finding the average velocity over a short time of staying still. It needs to be averaged over some time because the gyro will pick up a little bit of "noise" -- tiny variations that aren't caused by any real movement -- but this noise is negligible compared to the shakiness of human hands trying to hold a controller still. -When you first connect controllers to JoyShockMapper, they'll all begin "continuous calibration" -- this just means they're collecting the average velocity over a long period of time. This accumulated average is constantly applied to gyro inputs, so if the controller is left still for long enough, you should be able to play without obvious problems. - If you have gyro mouse enabled and the gyro moves across the screen (even slowly) when the controller is lying still on a solid surface, your device needs calibrating. That's okay -- I do it at the beginning of most play sessions, especially with Nintendo devices, which seem to need it more often. -To calibrate your gyro, place your controller on solid surface so that it's not moving at all, and then use the following commands: +If you set **AUTO\_CALIBRATE\_GYRO** to **ON**, JoyShockMapper will try to detect when your controller is being held still or left on a steady surface and calibrate the gyro automatically. This is imperfect, though -- every automatic calibration solution will *sometimes* interpret slow and steady movement as the controller being held still. This can interrupt you making small adjustments to your aim or tracking slow/distant targets. It's also only a new feature, and we try not to change default behaviour. Also, it doesn't yet give you any settings to tweak its thresholds. For all of these reasons this setting is **OFF** by default, and it's recommended that you calibrate your gyro manually instead. + +To manually calibrate your gyro, place your controller on steady surface so that it's not moving at all, and then use the following commands: * **RESTART\_GYRO\_CALIBRATION** - All connected gyro devices will begin collecting gyro data, remembering the average collected so far and treating it as "zero". * **FINISH\_GYRO\_CALIBRATION** - Stop collecting gyro data for calibration. JoyShockMapper will use whatever it has already collected from that controller as the "zero" reference point for input from that controller. @@ -919,7 +919,7 @@ T2,TDOWN = 8 ### 9. Miscellaneous Commands There are a few other useful commands that don't fall under the above categories: -* **RESET\_MAPPINGS** - This will reset all JoyShockMapper's settings to their default values. This way you don't have to manually unset button mappings or other settings when making a big change. It can be useful to always start your configuration files with the RESET\_MAPPINGS command. The only exceptions to this are the calibration state and AUTOLOAD. +* **RESET\_MAPPINGS** - This will reset all JoyShockMapper's settings to their default values. This way you don't have to manually unset button mappings or other settings when making a big change. It can be useful to always start your configuration files with the RESET\_MAPPINGS command. The only exceptions to this are the gyro calibration state / settings and AUTOLOAD. * **RECONNECT\_CONTROLLERS** - Controllers connected after JoyShockMapper starts will be ignored until you tell it to RECONNECT\_CONTROLLERS. When this happens, all gyro calibration will reset on all controllers. You can add MERGE or SPLIT to indicate whether you want all joycons under a single controller or separate controllers. The player LED will help you identify whether they are merged or split. * **\# comments** - Any line or part of a line that begins with '\#' will be ignored. Use this to organise/annotate your configuration files, or to temporarily remove commands that you may want to add later. * **JOYCON\_GYRO\_MASK** (default IGNORE\_LEFT) - Most games that use gyro controls on Switch ignore the left JoyCon's gyro to avoid confusing behaviour when the JoyCons are held separately while playing. This is the default behaviour in JoyShockMapper. But you can also choose to IGNORE\_RIGHT, IGNORE\_BOTH, or USE\_BOTH. @@ -942,11 +942,11 @@ What more? There are some configuration files that can be run automatically to s ### 1. OnStartup.txt -When JoyShockMapper first boots up, it will attempt to load the commands found in the file OnStartup.txt. This file should be in the JSM_DIRECTORY, which is next to your executable by default. This is a great place to automatically calibrate the gyro, and load a default configuration for navigating the OS, and whitelisting JoyShockMapper. +When JoyShockMapper first boots up, it will attempt to load the commands found in the file OnStartup.txt. This file should be in the JSM_DIRECTORY, which is next to your executable by default. This is a great place to automatically calibrate the gyro, load a default configuration for navigating the OS, and/or whitelisting JoyShockMapper. ### 2. OnReset.txt -This configuration is found in the same location as OnStartup.txt explained above. This file is run each time RESET\_MAPPINGS is called, as well as before OnStartup.txt. This file is a good spot to have a CALIBRATE button for your controller, which you will typically always need. +This configuration is found in the same location as OnStartup.txt explained above. This file is run each time RESET\_MAPPINGS is called, as well as before OnStartup.txt. This file is a good spot to set a CALIBRATE button for your controller and/or set your GYRO\_SPACE if you're not using the default value. ### 3. Autoload feature