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..094a93d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,28 @@ 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 | +| --- | --- | --- | --- | +| Add a new waveform | | `ctrl` + Left | | +| Clear all custom waveforms | | Middle | | +| 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..6937850 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; @@ -415,6 +437,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& 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..8f02244 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,9 +140,13 @@ logging.channels.async.class = AsyncChannel logging.channels.async.channel = split # Default logging settings. -logging.loggers.root.level = information logging.loggers.root.channel = async +# Set log level to debug for all components +#logging.loggers.root.level = debug +logging.loggers.root.level = information + + # You can configure log levels, channels etc. for each message source (logger) individually. # See https://docs.pocoproject.org/current/Poco.Util.LoggingConfigurator.html for details. # Example: