0.0.6 #5

Merged
Lerking merged 4 commits from 0.0.6 into main 2025-04-13 12:57:30 +02:00
24 changed files with 1162 additions and 79 deletions

2
.gitignore vendored
View File

@@ -168,4 +168,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
testing/ testing/*

View File

@@ -25,5 +25,6 @@ Højre - <kbd>d</kbd> eller <kbd>&#8594;</kbd>
Venstre - <kbd>a</kbd> eller <kbd>&#8592;</kbd> Venstre - <kbd>a</kbd> eller <kbd>&#8592;</kbd>
Op - <kbd>w</kbd> eller <kbd>&#8593;</kbd> Op - <kbd>w</kbd> eller <kbd>&#8593;</kbd>
Ned - <kbd>s</kbd> eller <kbd>&#8595;</kbd> Ned - <kbd>s</kbd> eller <kbd>&#8595;</kbd>
Pause - <kbd>p</kbd>
Alternativt, kan et joystick :joystick: eller gamepad :video_game: bruges. Alternativt, kan et joystick :joystick: eller gamepad :video_game: bruges.

View File

@@ -1,76 +1,27 @@
import pygame import pygame
from controls.controlsbase import ControlsBase from controls.controlsbase import ControlsBase
from controls.dualsense_controller import DualSenseController
from controls.dualsense_edge_controller import DualSenseEdgeController
from controls.logitech_f310_controller import LogitechF310Controller
from controls.logitech_f510_controller import LogitechF510Controller
from controls.logitech_f710_controller import LogitechF710Controller
from controls.xbox_controller import XboxController
from controls.generic_controller import GenericController
class ControllerControls(ControlsBase): CONTROLLERS = {
"DualSense Wireless Controller": DualSenseController,
"DualSense Edge Wireless Controller": DualSenseEdgeController,
"Logitech Gamepad F310": LogitechF310Controller,
"Logitech Gamepad F510": LogitechF510Controller,
"Logitech Gamepad F710": LogitechF710Controller,
"Xbox": XboxController
}
class Controllers:
def __init__(self, joy): def __init__(self, joy):
self.device = joy self.controllers = []
self.instance_id: int = self.device.get_instance_id() if not joy.get_name() in CONTROLLERS:
self.name = self.device.get_name() self.controllers.append(GenericController(joy))
self.numaxis: int = self.device.get_numaxis() else:
self.axis: list = [] self.controllers.append(CONTROLLERS[joy.get_name()](joy))
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.power_level: str = ""
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def power_level(self) -> str:
return self._power_level
@power_level.setter
def power_level(self) -> None:
self._power_level = self.device.get_power_level()

View File

@@ -6,4 +6,28 @@ from abc import ABC, abstractmethod
class ControlsBase(ABC): class ControlsBase(ABC):
@abstractmethod @abstractmethod
def handle_input(self, event): def handle_input(self, event):
pass
@abstractmethod
def left(self):
pass
@abstractmethod
def right(self):
pass
@abstractmethod
def up(self):
pass
@abstractmethod
def down(self):
pass
@abstractmethod
def pause(self):
pass
@abstractmethod
def rumble(self):
pass pass

View File

@@ -0,0 +1,70 @@
import os
import time
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
class DualSenseAudio:
def __init__(self):
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_device = self._detect_dualsense()
def _get_alsa_devices(self):
try:
cards = alsaaudio.cards()
return cards
except Exception as e:
print("ALSA detection failed:", e)
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
sinks = pulse.sink_list()
return sinks
except Exception as e:
print("PulseAudio detection failed:", e)
return []
def _detect_dualsense(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_device:
print("DualSense speaker not found.")
return
print(f"Playing tone on DualSense ({self.dualsense_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
if self.dualsense_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_device['name'])
elif self.dualsense_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
def list_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")

View File

@@ -0,0 +1,199 @@
import time
import threading
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
from pydualsense import *
class DualSenseController:
def __init__(self):
# DualSense input/output interface
self.ds = pydualsense()
self.ds.init()
self._listening = False
self._bindings = {}
# Audio detection
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_audio_device = self._detect_dualsense_audio()
print("DualSense initialized.")
# ---------------------- Device Controls ----------------------
def set_rumble(self, small_motor: int, big_motor: int):
self.ds.setRumble(small_motor, big_motor)
def stop_rumble(self):
self.set_rumble(0, 0)
def set_led_color(self, r: int, g: int, b: int):
self.ds.setLightBarColor(r, g, b)
def set_trigger_effects(self, left_mode='Off', right_mode='Off', force=0):
left = getattr(TriggerModes, left_mode.upper(), TriggerModes.Off)
right = getattr(TriggerModes, right_mode.upper(), TriggerModes.Off)
self.ds.triggerL.setMode(left)
self.ds.triggerR.setMode(right)
if force > 0:
self.ds.triggerL.setForce(force)
self.ds.triggerR.setForce(force)
# ---------------------- Predefined Rumble Patterns ----------------------
def rumble_pattern(self, pattern: str, duration: float = 1.0):
patterns = {
"pulse": self._pulse_rumble,
"heartbeat": self._heartbeat_rumble,
"buzz": self._buzz_rumble,
"wave": self._wave_rumble,
"alarm": self._alarm_rumble,
}
if pattern in patterns:
threading.Thread(target=patterns[pattern], args=(duration,), daemon=True).start()
else:
print(f"Unknown rumble pattern: {pattern}")
def _pulse_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(50, 150)
time.sleep(0.2)
self.stop_rumble()
time.sleep(0.2)
def _heartbeat_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(200, 200)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.1)
self.set_rumble(100, 100)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.4)
def _buzz_rumble(self, duration):
self.set_rumble(80, 255)
time.sleep(duration)
self.stop_rumble()
def _wave_rumble(self, duration):
start = time.time()
while time.time() - start < duration:
for i in range(0, 256, 25):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
for i in reversed(range(0, 256, 25)):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
self.stop_rumble()
def _alarm_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(255, 0)
time.sleep(0.1)
self.set_rumble(0, 255)
time.sleep(0.1)
self.stop_rumble()
# ---------------------- Input Listener + Bindings ----------------------
def bind(self, button: str, action: callable):
"""Bind a button to a callable. Ex: controller.bind('cross', lambda: rumble_pattern('buzz'))"""
self._bindings[button] = action
def start_input_listener(self):
def listen():
while self._listening:
#self.ds.update()
for button, action in self._bindings.items():
if getattr(self.ds, button, False):
action()
self._listening = True
thread = threading.Thread(target=listen, daemon=True)
thread.start()
def stop_input_listener(self):
self._listening = False
# ---------------------- Audio Output ----------------------
def _get_alsa_devices(self):
try:
return alsaaudio.cards()
except Exception:
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
return pulse.sink_list()
except Exception:
return []
def _detect_dualsense_audio(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_audio_device:
print("DualSense speaker not detected.")
return
print(f"Playing tone on DualSense ({self.dualsense_audio_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
try:
if self.dualsense_audio_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_audio_device['name'])
elif self.dualsense_audio_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_audio_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
except Exception as e:
print("Failed to play tone:", e)
def list_audio_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")
# ---------------------- Cleanup ----------------------
def close(self):
self.ds.close()
if __name__ == "__main__":
controller = DualSenseController()
# Bind buttons to patterns
controller.bind("cross", lambda: controller.rumble_pattern("heartbeat", 1.5))
controller.bind("circle", lambda: controller.rumble_pattern("buzz", 0.5))
controller.bind("triangle", lambda: controller.rumble_pattern("pulse", 2))
controller.bind("square", lambda: controller.set_led_color(255, 0, 0))
# Start listening
controller.start_input_listener()

View File

@@ -0,0 +1,199 @@
import time
import threading
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
from pydualsense import *
class DualSenseEdgeController:
def __init__(self):
# DualSense input/output interface
self.ds = pydualsense()
self.ds.init()
self._listening = False
self._bindings = {}
# Audio detection
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_audio_device = self._detect_dualsense_audio()
print("DualSense initialized.")
# ---------------------- Device Controls ----------------------
def set_rumble(self, small_motor: int, big_motor: int):
self.ds.setRumble(small_motor, big_motor)
def stop_rumble(self):
self.set_rumble(0, 0)
def set_led_color(self, r: int, g: int, b: int):
self.ds.setLightBarColor(r, g, b)
def set_trigger_effects(self, left_mode='Off', right_mode='Off', force=0):
left = getattr(TriggerModes, left_mode.upper(), TriggerModes.Off)
right = getattr(TriggerModes, right_mode.upper(), TriggerModes.Off)
self.ds.triggerL.setMode(left)
self.ds.triggerR.setMode(right)
if force > 0:
self.ds.triggerL.setForce(force)
self.ds.triggerR.setForce(force)
# ---------------------- Predefined Rumble Patterns ----------------------
def rumble_pattern(self, pattern: str, duration: float = 1.0):
patterns = {
"pulse": self._pulse_rumble,
"heartbeat": self._heartbeat_rumble,
"buzz": self._buzz_rumble,
"wave": self._wave_rumble,
"alarm": self._alarm_rumble,
}
if pattern in patterns:
threading.Thread(target=patterns[pattern], args=(duration,), daemon=True).start()
else:
print(f"Unknown rumble pattern: {pattern}")
def _pulse_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(50, 150)
time.sleep(0.2)
self.stop_rumble()
time.sleep(0.2)
def _heartbeat_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(200, 200)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.1)
self.set_rumble(100, 100)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.4)
def _buzz_rumble(self, duration):
self.set_rumble(80, 255)
time.sleep(duration)
self.stop_rumble()
def _wave_rumble(self, duration):
start = time.time()
while time.time() - start < duration:
for i in range(0, 256, 25):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
for i in reversed(range(0, 256, 25)):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
self.stop_rumble()
def _alarm_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(255, 0)
time.sleep(0.1)
self.set_rumble(0, 255)
time.sleep(0.1)
self.stop_rumble()
# ---------------------- Input Listener + Bindings ----------------------
def bind(self, button: str, action: callable):
"""Bind a button to a callable. Ex: controller.bind('cross', lambda: rumble_pattern('buzz'))"""
self._bindings[button] = action
def start_input_listener(self):
def listen():
while self._listening:
#self.ds.update()
for button, action in self._bindings.items():
if getattr(self.ds, button, False):
action()
self._listening = True
thread = threading.Thread(target=listen, daemon=True)
thread.start()
def stop_input_listener(self):
self._listening = False
# ---------------------- Audio Output ----------------------
def _get_alsa_devices(self):
try:
return alsaaudio.cards()
except Exception:
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
return pulse.sink_list()
except Exception:
return []
def _detect_dualsense_audio(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_audio_device:
print("DualSense speaker not detected.")
return
print(f"Playing tone on DualSense ({self.dualsense_audio_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
try:
if self.dualsense_audio_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_audio_device['name'])
elif self.dualsense_audio_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_audio_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
except Exception as e:
print("Failed to play tone:", e)
def list_audio_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")
# ---------------------- Cleanup ----------------------
def close(self):
self.ds.close()
if __name__ == "__main__":
controller = DualSenseController()
# Bind buttons to patterns
controller.bind("cross", lambda: controller.rumble_pattern("heartbeat", 1.5))
controller.bind("circle", lambda: controller.rumble_pattern("buzz", 0.5))
controller.bind("triangle", lambda: controller.rumble_pattern("pulse", 2))
controller.bind("square", lambda: controller.set_led_color(255, 0, 0))
# Start listening
controller.start_input_listener()

View File

@@ -0,0 +1,68 @@
import pygame
from controls.controlsbase import ControlsBase
class GenericController(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]

View File

@@ -0,0 +1,108 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from controls.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF310Controller(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

View File

@@ -0,0 +1,108 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from controls.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF510Controller(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

View File

@@ -0,0 +1,108 @@
"""
Logitech F310 Controller class.
This controller is a usb controller, with the following features.
(XInput mode)
6 axis
11 buttons
1 hat
(DirectInput mode)
4 axis
12 buttons
1 hat
"""
import pygame
from controls.controlsbase import ControlsBase
from enum import Enum
class InputMode(Enum):
DirectInput = 1
XInput = 2
class ConnectionType(Enum):
WIRED = 1
WIRELESS = 2
class LogitechF710Controller(ControlsBase):
def __init__(self, joy):
self.device = joy
self.instance_id: int = self.device.get_instance_id()
self.name = self.device.get_name()
self.numaxis: int = self.device.get_numaxis()
self.axis: list = []
self.numhats: int = self.device.get_numhats()
self.hats: list = []
self.numbuttons: int = self.device.get_numbuttons()
self.buttons: list = []
self.input_mode: InputMode.DirectInput
self.input_connection: ConnectionType.WIRED
def handle_input(self, event):
pass
def left(self):
pass
def right(self):
pass
def up(self):
pass
def down(self):
pass
def pause(self):
pass
def rumble(self):
pass
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def axis(self) -> list:
return self._axis
@axis.setter
def axis(self) -> None:
self._axis = [self.device.get_axis(a) for a in range(self.numaxis)]
@property
def hats(self) -> list:
return self._hats
@hats.setter
def hats(self) -> None:
self.hats = [self.device.get_hats(h) for h in range(self.numhats)]
@property
def buttons(self) -> list:
return self._buttons
@buttons.setter
def buttons(self) -> None:
self._buttons = [self.device.get_buttons(b) for b in range(self.numbuttons)]
@property
def input_mode(self) -> int:
return self._inputmode
@input_mode.setter
def input_mode(self, mode: int) -> None:
self._inputmode = mode
@property
def input_connection(self) -> int:
return self._input_connection
@input_connection.setter
def input_connection(self, conn: int) -> None:
self._input_connection = conn

199
controls/xbox_controller.py Normal file
View File

@@ -0,0 +1,199 @@
import time
import threading
import numpy as np
import sounddevice as sd
import alsaaudio
import pulsectl
from pydualsense import *
class XboxController:
def __init__(self):
# DualSense input/output interface
self.ds = pydualsense()
self.ds.init()
self._listening = False
self._bindings = {}
# Audio detection
self.alsa_devices = self._get_alsa_devices()
self.pulse_devices = self._get_pulseaudio_devices()
self.dualsense_audio_device = self._detect_dualsense_audio()
print("DualSense initialized.")
# ---------------------- Device Controls ----------------------
def set_rumble(self, small_motor: int, big_motor: int):
self.ds.setRumble(small_motor, big_motor)
def stop_rumble(self):
self.set_rumble(0, 0)
def set_led_color(self, r: int, g: int, b: int):
self.ds.setLightBarColor(r, g, b)
def set_trigger_effects(self, left_mode='Off', right_mode='Off', force=0):
left = getattr(TriggerModes, left_mode.upper(), TriggerModes.Off)
right = getattr(TriggerModes, right_mode.upper(), TriggerModes.Off)
self.ds.triggerL.setMode(left)
self.ds.triggerR.setMode(right)
if force > 0:
self.ds.triggerL.setForce(force)
self.ds.triggerR.setForce(force)
# ---------------------- Predefined Rumble Patterns ----------------------
def rumble_pattern(self, pattern: str, duration: float = 1.0):
patterns = {
"pulse": self._pulse_rumble,
"heartbeat": self._heartbeat_rumble,
"buzz": self._buzz_rumble,
"wave": self._wave_rumble,
"alarm": self._alarm_rumble,
}
if pattern in patterns:
threading.Thread(target=patterns[pattern], args=(duration,), daemon=True).start()
else:
print(f"Unknown rumble pattern: {pattern}")
def _pulse_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(50, 150)
time.sleep(0.2)
self.stop_rumble()
time.sleep(0.2)
def _heartbeat_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(200, 200)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.1)
self.set_rumble(100, 100)
time.sleep(0.1)
self.stop_rumble()
time.sleep(0.4)
def _buzz_rumble(self, duration):
self.set_rumble(80, 255)
time.sleep(duration)
self.stop_rumble()
def _wave_rumble(self, duration):
start = time.time()
while time.time() - start < duration:
for i in range(0, 256, 25):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
for i in reversed(range(0, 256, 25)):
self.set_rumble(i, 255 - i)
time.sleep(0.05)
self.stop_rumble()
def _alarm_rumble(self, duration):
end = time.time() + duration
while time.time() < end:
self.set_rumble(255, 0)
time.sleep(0.1)
self.set_rumble(0, 255)
time.sleep(0.1)
self.stop_rumble()
# ---------------------- Input Listener + Bindings ----------------------
def bind(self, button: str, action: callable):
"""Bind a button to a callable. Ex: controller.bind('cross', lambda: rumble_pattern('buzz'))"""
self._bindings[button] = action
def start_input_listener(self):
def listen():
while self._listening:
#self.ds.update()
for button, action in self._bindings.items():
if getattr(self.ds, button, False):
action()
self._listening = True
thread = threading.Thread(target=listen, daemon=True)
thread.start()
def stop_input_listener(self):
self._listening = False
# ---------------------- Audio Output ----------------------
def _get_alsa_devices(self):
try:
return alsaaudio.cards()
except Exception:
return []
def _get_pulseaudio_devices(self):
try:
pulse = pulsectl.Pulse("dualsense-audio")
return pulse.sink_list()
except Exception:
return []
def _detect_dualsense_audio(self):
# Check ALSA names
for card in self.alsa_devices:
if "DualSense" in card:
return {'type': 'alsa', 'name': card}
# Check PulseAudio sinks
for sink in self.pulse_devices:
if "dualsense" in sink.description.lower():
return {'type': 'pulse', 'name': sink.name}
return None
def play_tone(self, frequency=440.0, duration=2.0, volume=0.5):
if not self.dualsense_audio_device:
print("DualSense speaker not detected.")
return
print(f"Playing tone on DualSense ({self.dualsense_audio_device['type']})...")
fs = 48000 # Sample rate
t = np.linspace(0, duration, int(fs * duration), False)
tone = np.sin(frequency * 2 * np.pi * t) * volume
audio = tone.astype(np.float32)
try:
if self.dualsense_audio_device['type'] == 'pulse':
sd.play(audio, samplerate=fs, device=self.dualsense_audio_device['name'])
elif self.dualsense_audio_device['type'] == 'alsa':
device_index = self.alsa_devices.index(self.dualsense_audio_device['name'])
sd.play(audio, samplerate=fs, device=device_index)
sd.wait()
except Exception as e:
print("Failed to play tone:", e)
def list_audio_devices(self):
print("ALSA Devices:")
for card in self.alsa_devices:
print(f" - {card}")
print("\nPulseAudio Devices:")
for sink in self.pulse_devices:
print(f" - {sink.name} ({sink.description})")
# ---------------------- Cleanup ----------------------
def close(self):
self.ds.close()
if __name__ == "__main__":
controller = DualSenseController()
# Bind buttons to patterns
controller.bind("cross", lambda: controller.rumble_pattern("heartbeat", 1.5))
controller.bind("circle", lambda: controller.rumble_pattern("buzz", 0.5))
controller.bind("triangle", lambda: controller.rumble_pattern("pulse", 2))
controller.bind("square", lambda: controller.set_led_color(255, 0, 0))
# Start listening
controller.start_input_listener()

27
main.py
View File

@@ -3,6 +3,16 @@ import time
import controls import controls
from screens.startscreen import StartScreen from screens.startscreen import StartScreen
from tilemap.playground import PlayGround from tilemap.playground import PlayGround
from player.player import Player, Players
from enum import Enum
class State(Enum):
STARTING = 1
PLAYING = 2
PAUSING = 3
ENDING = 4
SETTING = 5
HIGHSCORE = 6
class Snake: class Snake:
def __init__(self): def __init__(self):
@@ -15,7 +25,7 @@ class Snake:
self.windowed: bool = True self.windowed: bool = True
self.width: int = 800 self.width: int = 800
self.height: int =600 self.height: int =600
self.startscreen = None self.screen = None
self.icon = pygame.image.load("snake.webp") self.icon = pygame.image.load("snake.webp")
pygame.display.set_icon(self.icon) pygame.display.set_icon(self.icon)
@@ -29,7 +39,20 @@ class Snake:
while self.running: while self.running:
match self.state: match self.state:
case 0: case 0:
self.startscreen = StartScreen(self) self.screen = StartScreen(self)
pygame.display.flip()
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.QUIT:
waiting = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_F1:
print("F1 pressed!")
waiting = False
elif event.key == pygame.K_F2:
print("F2 pressed!")
waiting = False
case 1: case 1:
pass pass
case 2: case 2:

View File

@@ -1,5 +1,27 @@
from enum import Enum
from controls.controller import Controllers
class Players(Enum):
UP1 = 1
UP2 = 2
class Player: class Player:
def __init__(self): def __init__(self):
self.controls = None self.controls = None
@property
def player(self) -> int:
return self._player
@player.setter
def player(self, plr: int) -> None:
self._player = plr
@property
def controls(self) -> Controllers:
return self._controls
@controls.setter
def controls(self, ctl: Controllers) -> None:
self._controls = ctl

View File

Before

Width:  |  Height:  |  Size: 327 KiB

After

Width:  |  Height:  |  Size: 327 KiB

View File

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 245 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 665 KiB

After

Width:  |  Height:  |  Size: 665 KiB

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -8,4 +8,7 @@ class StartScreen:
self.banner = pygame.transform.smoothscale(self.banner, (self.banner_width, int(self.banner_width*0.4))) self.banner = pygame.transform.smoothscale(self.banner, (self.banner_width, int(self.banner_width*0.4)))
self.parent.screen.blit(self.banner, (0, 0)) self.parent.screen.blit(self.banner, (0, 0))

View File

@@ -26,9 +26,9 @@ def load_image(path, max_size):
new_size = (int(image_rect.width * scale_factor), int(image_rect.height * scale_factor)) new_size = (int(image_rect.width * scale_factor), int(image_rect.height * scale_factor))
return pygame.transform.smoothscale(image, new_size) return pygame.transform.smoothscale(image, new_size)
keyboard_image = load_image("keyboard.png", (100, 100)) keyboard_image = load_image("resources/gfx/keyboard.png", (100, 100))
#joystick_image = load_image("ps5-dualsense.png", (100, 100)) #joystick_image = load_image("ps5-dualsense.png", (100, 100))
joystick_image = load_image("gamepad.png", (100, 100)) joystick_image = load_image("resources/gfx/gamepad.png", (100, 100))
# Abstract Control Class # Abstract Control Class
class Control(ABC): class Control(ABC):

View File

@@ -57,7 +57,7 @@ def main():
print("Joystick button pressed.") print("Joystick button pressed.")
if event.button == 0: if event.button == 0:
joystick = joysticks[event.instance_id] joystick = joysticks[event.instance_id]
if joystick.rumble(0, 0.7, 500): if joystick.rumble(0.7, 1.0, 1000):
print(f"Rumble effect played on joystick {event.instance_id}") print(f"Rumble effect played on joystick {event.instance_id}")
if event.type == pygame.JOYBUTTONUP: if event.type == pygame.JOYBUTTONUP: