From ab88c2f6112edba35bfa91ee8864e760728d16e8 Mon Sep 17 00:00:00 2001
From: german <german@thesoftwareartisans.com>
Date: Fri, 10 Jul 2020 21:20:50 -0500
Subject: [PATCH] First implementation of controller rumble

---
 src/core/frontend/input.h                     |  3 ++
 src/core/hle/service/hid/controllers/npad.cpp | 25 ++++++++----
 src/core/hle/service/hid/controllers/npad.h   |  2 +-
 src/core/hle/service/hid/hid.cpp              |  8 ++--
 src/input_common/sdl/sdl_impl.cpp             | 39 ++++++++++++++++++-
 5 files changed, 63 insertions(+), 14 deletions(-)

diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 9da0d28297..277b70e53b 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -33,6 +33,9 @@ public:
     virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const {
         return {};
     }
+    virtual bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const {
+        return {};
+    }
 };
 
 /// An abstract class template for a factory that can create input devices.
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 620386cd1d..83c3beab67 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -609,20 +609,31 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode)
     }
 }
 
-void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
+void Controller_NPad::VibrateController(const std::vector<u32>& controllers,
                                         const std::vector<Vibration>& vibrations) {
-    LOG_DEBUG(Service_HID, "(STUBBED) called");
+    LOG_TRACE(Service_HID, "called");
 
     if (!Settings::values.vibration_enabled || !can_controllers_vibrate) {
         return;
     }
-    for (std::size_t i = 0; i < controller_ids.size(); i++) {
-        std::size_t controller_pos = NPadIdToIndex(static_cast<u32>(i));
-        if (connected_controllers[controller_pos].is_connected) {
-            // TODO(ogniK): Vibrate the physical controller
+    bool success = true;
+    for (std::size_t i = 0; i < controllers.size(); ++i) {
+        if (!connected_controllers[i].is_connected) {
+            continue;
+        }
+        using namespace Settings::NativeButton;
+        const auto& button_state = buttons[i];
+        if (button_state[A - BUTTON_HID_BEGIN]) {
+            if (button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay(
+                    vibrations[0].amp_high, vibrations[0].amp_low, vibrations[0].freq_high,
+                    vibrations[0].freq_low)) {
+                success = false;
+            }
         }
     }
-    last_processed_vibration = vibrations.back();
+    if (success) {
+        last_processed_vibration = vibrations.back();
+    }
 }
 
 Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 654d97c3fe..0cff6821fc 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -121,7 +121,7 @@ public:
 
     void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
 
-    void VibrateController(const std::vector<u32>& controller_ids,
+    void VibrateController(const std::vector<u32>& controllers,
                            const std::vector<Vibration>& vibrations);
 
     Vibration GetLastVibration() const;
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 395e83b3ff..dc198791da 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -802,18 +802,18 @@ void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
 
 void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
-    const auto controller_id{rp.Pop<u32>()};
+    const auto controller{rp.Pop<u32>()};
     const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()};
     const auto applet_resource_user_id{rp.Pop<u64>()};
 
-    LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id,
+    LOG_DEBUG(Service_HID, "called, controller={}, applet_resource_user_id={}", controller,
               applet_resource_user_id);
 
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(RESULT_SUCCESS);
 
     applet_resource->GetController<Controller_NPad>(HidController::NPad)
-        .VibrateController({controller_id}, {vibration_values});
+        .VibrateController({controller}, {vibration_values});
 }
 
 void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
@@ -831,8 +831,6 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
 
     std::memcpy(controller_list.data(), controllers.data(), controllers.size());
     std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
-    std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
-                   [](u32 controller_id) { return controller_id - 3; });
 
     applet_resource->GetController<Controller_NPad>(HidController::NPad)
         .VibrateController(controller_list, vibration_list);
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index a9e676f4b2..27a96c18b7 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <array>
 #include <atomic>
+#include <chrono>
 #include <cmath>
 #include <functional>
 #include <mutex>
@@ -78,6 +79,33 @@ public:
         return state.axes.at(axis) / (32767.0f * range);
     }
 
+    bool RumblePlay(f32 amp_low, f32 amp_high, int time) {
+        const u16 raw_amp_low = static_cast<u16>(amp_low * 0xFFFF);
+        const u16 raw_amp_high = static_cast<u16>(amp_high * 0xFFFF);
+        // Lower drastically the number of state changes
+        if (raw_amp_low >> 11 == last_state_rumble_low >> 11 &&
+            raw_amp_high >> 11 == last_state_rumble_high >> 11) {
+            if (raw_amp_low + raw_amp_high != 0 ||
+                last_state_rumble_low + last_state_rumble_high == 0) {
+                return false;
+            }
+        }
+        // Don't change state if last vibration was < 20ms
+        const auto now = std::chrono::system_clock::now();
+        if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) <
+            std::chrono::milliseconds(20)) {
+            return raw_amp_low + raw_amp_high == 0;
+        }
+
+        last_vibration = now;
+        last_state_rumble_low = raw_amp_low;
+        last_state_rumble_high = raw_amp_high;
+        if (sdl_joystick) {
+            SDL_JoystickRumble(sdl_joystick.get(), raw_amp_low, raw_amp_high, time);
+        }
+        return false;
+    }
+
     std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
         float x = GetAxis(axis_x, range);
         float y = GetAxis(axis_y, range);
@@ -139,6 +167,9 @@ private:
     } state;
     std::string guid;
     int port;
+    u16 last_state_rumble_high;
+    u16 last_state_rumble_low;
+    std::chrono::time_point<std::chrono::system_clock> last_vibration;
     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
     std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
     mutable std::mutex mutex;
@@ -207,7 +238,7 @@ void SDLState::InitJoystick(int joystick_index) {
         sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
     }
     if (!sdl_joystick) {
-        LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
+        LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
         return;
     }
     const std::string guid = GetGUID(sdl_joystick);
@@ -303,6 +334,12 @@ public:
         return joystick->GetButton(button);
     }
 
+    bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
+        const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f));
+        const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f));
+        return joystick->RumblePlay(new_amp_low, new_amp_high, 250);
+    }
+
 private:
     std::shared_ptr<SDLJoystick> joystick;
     int button;