From ae07a848bf19f6d54909c2cac7ec3d51ee28198a Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Wed, 31 Dec 2025 23:16:04 +0100 Subject: [PATCH] feat: Microphone detection --- .idea/misc.xml | 2 +- src/main/java/cz/jzitnik/Game.java | 4 + src/main/java/cz/jzitnik/Main.java | 8 +- .../jzitnik/annotations/ThreadRegistry.java | 11 +++ .../events/handlers/ExitEventHandler.java | 12 ++- .../events/handlers/FullRoomDrawHandler.java | 4 +- .../handlers/MouseActionEventHandler.java | 6 +- .../handlers/PlayerMoveEventHandler.java | 6 +- src/main/java/cz/jzitnik/game/GameState.java | 6 +- .../java/cz/jzitnik/states/RenderState.java | 1 + .../java/cz/jzitnik/states/SoundState.java | 10 +++ .../cz/jzitnik/threads/MicrophoneThread.java | 75 +++++++++++++++++++ .../cz/jzitnik/utils/ShutdownableThread.java | 6 ++ .../java/cz/jzitnik/utils/ThreadManager.java | 47 ++++++++++++ 14 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 src/main/java/cz/jzitnik/annotations/ThreadRegistry.java create mode 100644 src/main/java/cz/jzitnik/states/SoundState.java create mode 100644 src/main/java/cz/jzitnik/threads/MicrophoneThread.java create mode 100644 src/main/java/cz/jzitnik/utils/ShutdownableThread.java create mode 100644 src/main/java/cz/jzitnik/utils/ThreadManager.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 039a9d1..65ff655 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/src/main/java/cz/jzitnik/Game.java b/src/main/java/cz/jzitnik/Game.java index cb4934f..9848c14 100644 --- a/src/main/java/cz/jzitnik/Game.java +++ b/src/main/java/cz/jzitnik/Game.java @@ -2,6 +2,7 @@ package cz.jzitnik; import cz.jzitnik.game.setup.GameSetup; import cz.jzitnik.utils.DependencyManager; +import cz.jzitnik.utils.ThreadManager; import org.reflections.Reflections; public class Game { @@ -11,7 +12,10 @@ public class Game { Cli cli = dependencyManager.getDependencyOrThrow(Cli.class); GameSetup gameSetup = dependencyManager.getDependencyOrThrow(GameSetup.class); + ThreadManager threadManager = dependencyManager.getDependencyOrThrow(ThreadManager.class); + gameSetup.setup(); + threadManager.startAll(); cli.run(); } diff --git a/src/main/java/cz/jzitnik/Main.java b/src/main/java/cz/jzitnik/Main.java index 5f7d51f..884a74a 100644 --- a/src/main/java/cz/jzitnik/Main.java +++ b/src/main/java/cz/jzitnik/Main.java @@ -1,5 +1,7 @@ -import cz.jzitnik.Game; +package cz.jzitnik; -void main() { - new Game().start(); +public class Main { + public static void main(String[] args) { + new Game().start(); + } } \ No newline at end of file diff --git a/src/main/java/cz/jzitnik/annotations/ThreadRegistry.java b/src/main/java/cz/jzitnik/annotations/ThreadRegistry.java new file mode 100644 index 0000000..ea4dfdd --- /dev/null +++ b/src/main/java/cz/jzitnik/annotations/ThreadRegistry.java @@ -0,0 +1,11 @@ +package cz.jzitnik.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ThreadRegistry { +} diff --git a/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java index 69bf627..7fed6a5 100644 --- a/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/ExitEventHandler.java @@ -1,22 +1,30 @@ package cz.jzitnik.events.handlers; import cz.jzitnik.annotations.EventHandler; +import cz.jzitnik.annotations.injectors.InjectDependency; +import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.events.ExitEvent; import cz.jzitnik.states.RunningState; import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.StateManager; +import cz.jzitnik.utils.ThreadManager; import cz.jzitnik.utils.events.AbstractEventHandler; @EventHandler(ExitEvent.class) public class ExitEventHandler extends AbstractEventHandler { + @InjectDependency + private ThreadManager threadManager; + + @InjectState + private RunningState runningState; + public ExitEventHandler(DependencyManager dm) { super(dm); } @Override public void handle(ExitEvent event) { - StateManager stateManager = dm.getDependencyOrThrow(StateManager.class); - RunningState runningState = stateManager.getOrThrow(RunningState.class); + threadManager.shutdownAll(); runningState.setRunning(false); System.exit(0); // Pls don't blame me } diff --git a/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java b/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java index 4dfb578..788fc29 100644 --- a/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/FullRoomDrawHandler.java @@ -121,11 +121,11 @@ public class FullRoomDrawHandler extends AbstractEventHandler { eventManager.emitEvent(new RerenderScreen(partsToRerender.toArray(RerenderScreen.ScreenPart[]::new))); } - gameState.setTerminalTooSmall(false); + renderState.setTerminalTooSmall(false); } catch (ArrayIndexOutOfBoundsException e) { // Screen too small to fit the room eventManager.emitEvent(new TerminalTooSmallEvent()); - gameState.setTerminalTooSmall(true); + renderState.setTerminalTooSmall(true); } } } diff --git a/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java index 0fc6b4b..09e082e 100644 --- a/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/MouseActionEventHandler.java @@ -8,6 +8,7 @@ import cz.jzitnik.events.MouseMoveEvent; import cz.jzitnik.game.GameState; import cz.jzitnik.game.objects.GameObject; import cz.jzitnik.game.objects.Interactable; +import cz.jzitnik.states.RenderState; import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.UIClickHandlerRepository; import cz.jzitnik.utils.events.AbstractEventHandler; @@ -30,6 +31,9 @@ public class MouseActionEventHandler extends AbstractEventHandler { @InjectState private GameState gameState; + @InjectState + private RenderState renderState; + @Override public void handle(MouseAction event) { if (gameState.getScreen() != null) { @@ -37,7 +41,7 @@ public class MouseActionEventHandler extends AbstractEventHandler { return; } - if (gameState.isTerminalTooSmall()) { + if (renderState.isTerminalTooSmall()) { return; } diff --git a/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java b/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java index f662e45..daa87af 100644 --- a/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java +++ b/src/main/java/cz/jzitnik/events/handlers/PlayerMoveEventHandler.java @@ -16,6 +16,7 @@ import cz.jzitnik.game.GameState; import cz.jzitnik.game.Player; import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.states.RenderState; import cz.jzitnik.states.ScreenBuffer; import cz.jzitnik.states.TerminalState; import cz.jzitnik.utils.DependencyManager; @@ -54,9 +55,12 @@ public class PlayerMoveEventHandler extends AbstractEventHandler 0) { + double volume = calculateRMS(buffer, bytesRead); + soundState.setSoundVolume(volume); + } + } + } catch (LineUnavailableException e) { + System.err.println("Microphone line unavailable: " + e.getMessage()); + } finally { + if (line != null) { + line.stop(); + line.close(); + } + } + } + + public void shutdown() { + this.running = false; + this.interrupt(); + } + + private double calculateRMS(byte[] buffer, int bytesRead) { + long sum = 0; + + for (int i = 0; i < bytesRead; i += 2) { + int low = buffer[i]; + int high = buffer[i + 1]; + + int sample = (high << 8) | (low & 0xFF); + + sum += (long) sample * sample; + } + + double rms = Math.sqrt(sum / (bytesRead / 2.0)); + + return (rms / 32768.0) * 100; + } +} diff --git a/src/main/java/cz/jzitnik/utils/ShutdownableThread.java b/src/main/java/cz/jzitnik/utils/ShutdownableThread.java new file mode 100644 index 0000000..20d7c13 --- /dev/null +++ b/src/main/java/cz/jzitnik/utils/ShutdownableThread.java @@ -0,0 +1,6 @@ +package cz.jzitnik.utils; + +public abstract class ShutdownableThread extends Thread { + public abstract void run(); + public abstract void shutdown(); +} diff --git a/src/main/java/cz/jzitnik/utils/ThreadManager.java b/src/main/java/cz/jzitnik/utils/ThreadManager.java new file mode 100644 index 0000000..789e881 --- /dev/null +++ b/src/main/java/cz/jzitnik/utils/ThreadManager.java @@ -0,0 +1,47 @@ +package cz.jzitnik.utils; + +import cz.jzitnik.annotations.Dependency; +import cz.jzitnik.annotations.ThreadRegistry; +import org.reflections.Reflections; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashSet; + +@Dependency +public class ThreadManager { + private final HashSet threads = new HashSet<>(); + private final DependencyManager dependencyManager; + + public ThreadManager(Reflections reflections, DependencyManager dependencyManager) { + this.dependencyManager = dependencyManager; + + var classes = reflections.getTypesAnnotatedWith(ThreadRegistry.class); + for (Class clazz : classes) { + if (!ShutdownableThread.class.isAssignableFrom(clazz)) { + continue; + } + + try { + var instance = (ShutdownableThread) clazz.getDeclaredConstructor().newInstance(); + + threads.add(instance); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + + public void startAll() { + for (ShutdownableThread thread : threads) { + dependencyManager.inject(thread); + thread.start(); + } + } + + public void shutdownAll() { + for (ShutdownableThread thread : threads) { + thread.shutdown(); + } + } +}