Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding game controlers as input method #86

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 106 additions & 0 deletions src/RenderLoop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,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_QUIT:
_wantsToQuit = true;
break;
Expand Down Expand Up @@ -324,6 +346,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;
Expand Down
12 changes: 12 additions & 0 deletions src/RenderLoop.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
78 changes: 76 additions & 2 deletions src/SDLRenderingWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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)};
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Copy link
Member

@kblaschke kblaschke Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, you could also add a setting and an option in the settings dialog to let the user select a specific controller. Then select the stored one automatically on startup, e.g. via its name or device path (which should map to the port it is connected to).

If you're not into Dear ImGui, I can also add this stuff at a later time.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't knew about Dear ImGui before I had a look at this project. Adding features to the GUI is a bit over my head at the moment (my available time is scrace). Actually I'm not even into C++ but this doesn't hinder me to tinker around :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem! I've got the same time issue, but I'll try my best to spend a bit of it on projectM every week 😀
I'll review the changes this weekend more thoroughly, but at a glance it all looks fine, so expect it to be merged soon!

Thanks again for the contribution!

//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;
}
25 changes: 25 additions & 0 deletions src/SDLRenderingWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:

/**
Expand Down Expand Up @@ -156,6 +180,7 @@ class SDLRenderingWindow : public Poco::Util::Subsystem

bool _fullscreen{ false };

SDL_GameController *_controller;
};


11 changes: 5 additions & 6 deletions src/resources/projectMSDL.properties.in
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down