diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 949748178..56c165336 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -135,6 +135,8 @@ add_library(core STATIC
     frontend/emu_window.h
     frontend/framebuffer_layout.cpp
     frontend/framebuffer_layout.h
+    frontend/input_interpreter.cpp
+    frontend/input_interpreter.h
     frontend/input.h
     hardware_interrupt_manager.cpp
     hardware_interrupt_manager.h
diff --git a/src/core/frontend/input_interpreter.cpp b/src/core/frontend/input_interpreter.cpp
new file mode 100644
index 000000000..66ae506cd
--- /dev/null
+++ b/src/core/frontend/input_interpreter.cpp
@@ -0,0 +1,45 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/frontend/input_interpreter.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+
+InputInterpreter::InputInterpreter(Core::System& system)
+    : npad{system.ServiceManager()
+               .GetService<Service::HID::Hid>("hid")
+               ->GetAppletResource()
+               ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {}
+
+InputInterpreter::~InputInterpreter() = default;
+
+void InputInterpreter::PollInput() {
+    const u32 button_state = npad.GetAndResetPressState();
+
+    previous_index = current_index;
+    current_index = (current_index + 1) % button_states.size();
+
+    button_states[current_index] = button_state;
+}
+
+bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const {
+    const bool current_press =
+        (button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
+    const bool previous_press =
+        (button_states[previous_index] & (1U << static_cast<u8>(button))) != 0;
+
+    return current_press && !previous_press;
+}
+
+bool InputInterpreter::IsButtonHeld(HIDButton button) const {
+    u32 held_buttons{button_states[0]};
+
+    for (std::size_t i = 1; i < button_states.size(); ++i) {
+        held_buttons &= button_states[i];
+    }
+
+    return (held_buttons & (1U << static_cast<u8>(button))) != 0;
+}
diff --git a/src/core/frontend/input_interpreter.h b/src/core/frontend/input_interpreter.h
new file mode 100644
index 000000000..fea9aebe6
--- /dev/null
+++ b/src/core/frontend/input_interpreter.h
@@ -0,0 +1,120 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::HID {
+class Controller_NPad;
+}
+
+enum class HIDButton : u8 {
+    A,
+    B,
+    X,
+    Y,
+    LStick,
+    RStick,
+    L,
+    R,
+    ZL,
+    ZR,
+    Plus,
+    Minus,
+
+    DLeft,
+    DUp,
+    DRight,
+    DDown,
+
+    LStickLeft,
+    LStickUp,
+    LStickRight,
+    LStickDown,
+
+    RStickLeft,
+    RStickUp,
+    RStickRight,
+    RStickDown,
+
+    LeftSL,
+    LeftSR,
+
+    RightSL,
+    RightSR,
+};
+
+/**
+ * The InputInterpreter class interfaces with HID to retrieve button press states.
+ * Input is intended to be polled every 50ms so that a button is considered to be
+ * held down after 400ms has elapsed since the initial button press and subsequent
+ * repeated presses occur every 50ms.
+ */
+class InputInterpreter {
+public:
+    explicit InputInterpreter(Core::System& system);
+    virtual ~InputInterpreter();
+
+    /// Gets a button state from HID and inserts it into the array of button states.
+    void PollInput();
+
+    /**
+     * The specified button is considered to be pressed once
+     * if it is currently pressed and not pressed previously.
+     *
+     * @param button The button to check.
+     *
+     * @returns True when the button is pressed once.
+     */
+    [[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const;
+
+    /**
+     * Checks whether any of the buttons in the parameter list is pressed once.
+     *
+     * @tparam HIDButton The buttons to check.
+     *
+     * @returns True when at least one of the buttons is pressed once.
+     */
+    template <HIDButton... T>
+    [[nodiscard]] bool IsAnyButtonPressedOnce() {
+        return (IsButtonPressedOnce(T) || ...);
+    }
+
+    /**
+     * The specified button is considered to be held down if it is pressed in all 9 button states.
+     *
+     * @param button The button to check.
+     *
+     * @returns True when the button is held down.
+     */
+    [[nodiscard]] bool IsButtonHeld(HIDButton button) const;
+
+    /**
+     * Checks whether any of the buttons in the parameter list is held down.
+     *
+     * @tparam HIDButton The buttons to check.
+     *
+     * @returns True when at least one of the buttons is held down.
+     */
+    template <HIDButton... T>
+    [[nodiscard]] bool IsAnyButtonHeld() {
+        return (IsButtonHeld(T) || ...);
+    }
+
+private:
+    Service::HID::Controller_NPad& npad;
+
+    /// Stores 9 consecutive button states polled from HID.
+    std::array<u32, 9> button_states{};
+
+    std::size_t previous_index{};
+    std::size_t current_index{};
+};