diff --git a/CMakeLists.txt b/CMakeLists.txt
index e251ce2..a174997 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,6 +75,11 @@ find_package(projectM4 REQUIRED COMPONENTS Playlist)
 find_package(SDL2 REQUIRED)
 find_package(Poco REQUIRED COMPONENTS JSON XML Util Foundation)
 
+if(Poco_VERSION VERSION_GREATER_EQUAL 1.14.0)
+    # POCO 1.14 requires at least C++17
+    set(CMAKE_CXX_STANDARD 17)
+endif()
+
 if(ENABLE_FREETYPE)
     find_package(Freetype)
 endif()
diff --git a/README.md b/README.md
index 205774f..a4936d1 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,26 @@ It will listen to audio input and produce mesmerizing visuals. Some commands are
 This project is in a bit of a transition state and is in the process of being modernized. There are many rough edges at
 present.
 
+## GUI controls
+
+
+| Command | Keyboard | Mouse | Game Controller |
+| --- | --- | --- | --- |
+| Toggle full screen | `ctrl` + `f` | Right | A |
+| Show/hide GUI | `esc` |  | Start |
+| Random preset | `r` |  | B |
+| Next preset | `n` |  | D-Pad right |
+| Previous preset | `p` |  | D-Pad left |
+| Increase beat sensitivity | `Up` | Wheel up | D-Pad up |
+| Decrease beat sensitivity | `Down` | Wheel down | D-Pad down |
+| Toggle shuffle | `y` |  | Y |
+| Last preset | `Backspace` |  | Guide |
+| TogglePresetLocked | `Space` |  | X |
+| NextAudioDevice | `ctrl` + `i` |  | Shoulder left |
+| NextDisplay | `ctrl` + `m` |  | Shoulder right |
+| Toggle aspect ratio correction | `a` |  |  |
+| Quit | `q` |  | Back |
+
 ## Building from source
 
 ### Build and install libprojectM
diff --git a/src/RenderLoop.cpp b/src/RenderLoop.cpp
index 1fe8fa1..33cdd73 100644
--- a/src/RenderLoop.cpp
+++ b/src/RenderLoop.cpp
@@ -106,6 +106,28 @@ void RenderLoop::PollEvents()
 
                 break;
 
+            case SDL_CONTROLLERDEVICEADDED:
+                poco_debug(_logger, "Controller added event received");
+                _sdlRenderingWindow.ControllerAdd(event.cdevice.which);
+
+                break;
+
+            case SDL_CONTROLLERDEVICEREMOVED:
+                poco_debug(_logger, "Controller remove event received");
+                _sdlRenderingWindow.ControllerRemove(event.cdevice.which);
+
+                break;
+
+            case SDL_CONTROLLERBUTTONDOWN:
+                ControllerDownEvent(event);
+
+                break;
+
+            case SDL_CONTROLLERBUTTONUP:
+                ControllerUpEvent(event);
+
+                break;
+
             case SDL_DROPFILE: {
                 char* droppedFilePath = event.drop.file;
 
@@ -399,11 +421,6 @@ void RenderLoop::MouseDownEvent(const SDL_MouseButtonEvent& event)
         case SDL_BUTTON_RIGHT:
             _sdlRenderingWindow.ToggleFullscreen();
             break;
-
-        case SDL_BUTTON_MIDDLE:
-            projectm_touch_destroy_all(_projectMHandle);
-            poco_debug(_logger, "Cleared all custom waveforms.");
-            break;
     }
 }
 
@@ -415,6 +432,90 @@ void RenderLoop::MouseUpEvent(const SDL_MouseButtonEvent& event)
     }
 }
 
+void RenderLoop::ControllerDownEvent(const SDL_Event& event)
+{
+    if (!_sdlRenderingWindow.ControllerIsOurs(event.cdevice.which) )
+    {
+        return;
+    }
+
+    switch (event.cbutton.button)
+    {
+        case SDL_CONTROLLER_BUTTON_A:
+            _sdlRenderingWindow.ToggleFullscreen();
+            poco_debug(_logger, "A pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_B:
+            Poco::NotificationCenter::defaultCenter().postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::RandomPreset, _keyStates._shiftPressed));
+            poco_debug(_logger, "B pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_X:
+            Poco::NotificationCenter::defaultCenter().postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::TogglePresetLocked));
+            poco_debug(_logger, "X pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_Y:
+            Poco::NotificationCenter::defaultCenter().postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::ToggleShuffle));
+            poco_debug(_logger, "Y pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_BACK:
+            _wantsToQuit = true;
+            poco_debug(_logger, "Back pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_GUIDE:
+            Poco::NotificationCenter::defaultCenter().postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::LastPreset, _keyStates._shiftPressed));
+            poco_debug(_logger, "Guide pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_START:
+            _projectMGui.Toggle();
+            _sdlRenderingWindow.ShowCursor(_projectMGui.Visible());
+            poco_debug(_logger, "Start pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
+            _audioCapture.NextAudioDevice();
+            poco_debug(_logger, "Shoulder left pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
+            _sdlRenderingWindow.NextDisplay();
+            poco_debug(_logger, "Shoulder right pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_DPAD_UP:
+            // Increase beat sensitivity
+            _projectMWrapper.ChangeBeatSensitivity(0.05f);
+            poco_debug(_logger, "DPad up pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
+            // Decrease beat sensitivity
+            _projectMWrapper.ChangeBeatSensitivity(-0.05f);
+            poco_debug(_logger, "DPad down pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
+            Poco::NotificationCenter::defaultCenter().postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::PreviousPreset, _keyStates._shiftPressed));
+            poco_debug(_logger, "DPad left pressed!");
+            break;
+
+        case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
+            Poco::NotificationCenter::defaultCenter().postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::NextPreset, _keyStates._shiftPressed));
+            poco_debug(_logger, "DPad right pressed!");
+            break;
+    }
+}
+
+void RenderLoop::ControllerUpEvent(const SDL_Event& event)
+{
+
+}
+
 void RenderLoop::QuitNotificationHandler(const Poco::AutoPtr<QuitNotification>& notification)
 {
     _wantsToQuit = true;
diff --git a/src/RenderLoop.h b/src/RenderLoop.h
index 8a63949..5131f9d 100644
--- a/src/RenderLoop.h
+++ b/src/RenderLoop.h
@@ -61,6 +61,18 @@ class RenderLoop
      */
     void MouseUpEvent(const SDL_MouseButtonEvent& event);
 
+    /**
+     * @brief Handles SDL game controller button down events.
+     * @param event The controller button event
+     */
+    void ControllerDownEvent(const SDL_Event& event);
+
+    /**
+     * @brief Handles SDL game controller button up events.
+     * @param event The controller button event
+     */
+    void ControllerUpEvent(const SDL_Event& event);
+
     /**
      * @brief Handler for quit notifications.
      * @param notification The received notification.
diff --git a/src/SDLRenderingWindow.cpp b/src/SDLRenderingWindow.cpp
index efac35e..6c0ee92 100644
--- a/src/SDLRenderingWindow.cpp
+++ b/src/SDLRenderingWindow.cpp
@@ -35,6 +35,8 @@ void SDLRenderingWindow::initialize(Poco::Util::Application& app)
     // Observe user configuration changes (set via the settings window)
     _userConfig->propertyChanged += Poco::delegate(this, &SDLRenderingWindow::OnConfigurationPropertyChanged);
     _userConfig->propertyRemoved += Poco::delegate(this, &SDLRenderingWindow::OnConfigurationPropertyRemoved);
+
+    _controller = FindController();
 }
 
 void SDLRenderingWindow::uninitialize()
@@ -48,6 +50,12 @@ void SDLRenderingWindow::uninitialize()
         DestroySDLWindow();
         _renderingWindow = nullptr;
     }
+
+    if (_controller)
+    {
+        SDL_GameControllerClose(_controller);
+        _controller = nullptr;
+    }
 }
 
 void SDLRenderingWindow::GetDrawableSize(int& width, int& height) const
@@ -196,7 +204,7 @@ void SDLRenderingWindow::NextDisplay()
 
 void SDLRenderingWindow::CreateSDLWindow()
 {
-    SDL_InitSubSystem(SDL_INIT_VIDEO);
+    SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
 
     int width{_config->getInt("width", 800)};
     int height{_config->getInt("height", 600)};
@@ -307,7 +315,7 @@ void SDLRenderingWindow::DestroySDLWindow()
     SDL_DestroyWindow(_renderingWindow);
     _renderingWindow = nullptr;
 
-    SDL_QuitSubSystem(SDL_INIT_VIDEO);
+    SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
 }
 
 void SDLRenderingWindow::DumpOpenGLInfo()
@@ -461,3 +469,69 @@ void SDLRenderingWindow::OnConfigurationPropertyRemoved(const std::string& key)
         UpdateWindowTitle();
     }
 }
+
+SDL_GameController* SDLRenderingWindow::FindController() {
+    //Check for joysticks
+    if( SDL_NumJoysticks() < 1 )
+    {
+        poco_debug(_logger, "No joysticks connected");
+        return nullptr;
+    }
+
+    //For simplicity, we’ll only be setting up and tracking a single
+    //controller at a time
+    for (int i = 0; i < SDL_NumJoysticks(); i++) {
+        if (SDL_IsGameController(i)) {
+            poco_debug(_logger, "Adding first controller");
+            return SDL_GameControllerOpen(i);
+        }
+        else {
+            poco_debug(_logger, "Connected joystick is not a SDL game controller");
+        }
+    }
+
+    return nullptr;
+}
+
+void SDLRenderingWindow::ControllerAdd(const int id )
+{
+    if (!_controller)
+    {
+        if (SDL_IsGameController(id)) {
+            _controller = SDL_GameControllerOpen(id);
+            poco_debug(_logger, "Controller added!");
+        }
+        else {
+            poco_debug(_logger, "Connected joystick is not a SDL game controller");
+        }
+    }
+}
+
+void SDLRenderingWindow::ControllerRemove(const int id )
+{
+    if (_controller && id == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(_controller)))
+    {
+        SDL_GameControllerClose(_controller);
+        poco_debug(_logger, "Controller removed!");
+        _controller = FindController();
+    }
+}
+
+bool SDLRenderingWindow::ControllerIsOurs(const int id )
+{
+    int instance = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(_controller));
+
+    if (!_controller )
+    {
+        poco_debug(_logger, "No controller initialized");
+        return false;
+    }
+
+    if (id != instance)
+    {
+        poco_debug_f2(_logger, "Use controller %?d instead of %?d. Currently only one controller is supported", instance, id);
+        return false;
+    }
+
+    return true;
+}
\ No newline at end of file
diff --git a/src/SDLRenderingWindow.h b/src/SDLRenderingWindow.h
index 06c3bbe..b3f928f 100644
--- a/src/SDLRenderingWindow.h
+++ b/src/SDLRenderingWindow.h
@@ -96,6 +96,30 @@ class SDLRenderingWindow : public Poco::Util::Subsystem
 
     SDL_GLContext GetGlContext() const;
 
+    /**
+     * @brief Returns the ID of the first game controller found
+     * @return SDL_GameController * Returns a gamecontroller identifier or NULL
+     */
+    SDL_GameController* FindController();
+
+    /**
+     * @brief Handles SDL game controller add events (plugin in a new controller) events.
+     * @param id The added controller id
+     */
+    void ControllerAdd(const int id );
+
+    /**
+     * @brief Handles SDL game controller remove events.
+     * @param id The removed controller id
+     */
+    void ControllerRemove(const int id );
+
+    /**
+     * @brief Returns true if the given controller is initialized and the one we currently use.
+     * @param id The removed controller id
+     */
+    bool ControllerIsOurs(const int id );
+
 protected:
 
     /**
@@ -156,6 +180,7 @@ class SDLRenderingWindow : public Poco::Util::Subsystem
 
     bool _fullscreen{ false };
 
+    SDL_GameController *_controller;
 };
 
 
diff --git a/src/resources/projectMSDL.properties.in b/src/resources/projectMSDL.properties.in
index b6a1dce..cb9e8b7 100644
--- a/src/resources/projectMSDL.properties.in
+++ b/src/resources/projectMSDL.properties.in
@@ -102,11 +102,6 @@ projectM.aspectCorrectionEnabled = true
 # For detailed information on how to configure logging, please refer to the POCO documentation:
 # https://docs.pocoproject.org/current/Poco.Util.LoggingConfigurator.html
 
-# Set log level to debug for all components
-#logging.loggers.root.level = debug
-
-
-
 ### Logging configuration
 
 # Verbose log format, includes process/thread ID, source etc.
@@ -145,7 +140,6 @@ logging.channels.async.class = AsyncChannel
 logging.channels.async.channel = split
 
 # Default logging settings.
-logging.loggers.root.level = information
 logging.loggers.root.channel = async
 
 # You can configure log levels, channels etc. for each message source (logger) individually.