feat: Microphone detection
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" default="true" project-jdk-name="22" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,6 +2,7 @@ package cz.jzitnik;
|
|||||||
|
|
||||||
import cz.jzitnik.game.setup.GameSetup;
|
import cz.jzitnik.game.setup.GameSetup;
|
||||||
import cz.jzitnik.utils.DependencyManager;
|
import cz.jzitnik.utils.DependencyManager;
|
||||||
|
import cz.jzitnik.utils.ThreadManager;
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
public class Game {
|
public class Game {
|
||||||
@@ -11,7 +12,10 @@ public class Game {
|
|||||||
Cli cli = dependencyManager.getDependencyOrThrow(Cli.class);
|
Cli cli = dependencyManager.getDependencyOrThrow(Cli.class);
|
||||||
|
|
||||||
GameSetup gameSetup = dependencyManager.getDependencyOrThrow(GameSetup.class);
|
GameSetup gameSetup = dependencyManager.getDependencyOrThrow(GameSetup.class);
|
||||||
|
ThreadManager threadManager = dependencyManager.getDependencyOrThrow(ThreadManager.class);
|
||||||
|
|
||||||
gameSetup.setup();
|
gameSetup.setup();
|
||||||
|
threadManager.startAll();
|
||||||
|
|
||||||
cli.run();
|
cli.run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import cz.jzitnik.Game;
|
package cz.jzitnik;
|
||||||
|
|
||||||
void main() {
|
public class Main {
|
||||||
new Game().start();
|
public static void main(String[] args) {
|
||||||
|
new Game().start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
11
src/main/java/cz/jzitnik/annotations/ThreadRegistry.java
Normal file
11
src/main/java/cz/jzitnik/annotations/ThreadRegistry.java
Normal file
@@ -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 {
|
||||||
|
}
|
||||||
@@ -1,22 +1,30 @@
|
|||||||
package cz.jzitnik.events.handlers;
|
package cz.jzitnik.events.handlers;
|
||||||
|
|
||||||
import cz.jzitnik.annotations.EventHandler;
|
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.events.ExitEvent;
|
||||||
import cz.jzitnik.states.RunningState;
|
import cz.jzitnik.states.RunningState;
|
||||||
import cz.jzitnik.utils.DependencyManager;
|
import cz.jzitnik.utils.DependencyManager;
|
||||||
import cz.jzitnik.utils.StateManager;
|
import cz.jzitnik.utils.StateManager;
|
||||||
|
import cz.jzitnik.utils.ThreadManager;
|
||||||
import cz.jzitnik.utils.events.AbstractEventHandler;
|
import cz.jzitnik.utils.events.AbstractEventHandler;
|
||||||
|
|
||||||
@EventHandler(ExitEvent.class)
|
@EventHandler(ExitEvent.class)
|
||||||
public class ExitEventHandler extends AbstractEventHandler<ExitEvent> {
|
public class ExitEventHandler extends AbstractEventHandler<ExitEvent> {
|
||||||
|
@InjectDependency
|
||||||
|
private ThreadManager threadManager;
|
||||||
|
|
||||||
|
@InjectState
|
||||||
|
private RunningState runningState;
|
||||||
|
|
||||||
public ExitEventHandler(DependencyManager dm) {
|
public ExitEventHandler(DependencyManager dm) {
|
||||||
super(dm);
|
super(dm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(ExitEvent event) {
|
public void handle(ExitEvent event) {
|
||||||
StateManager stateManager = dm.getDependencyOrThrow(StateManager.class);
|
threadManager.shutdownAll();
|
||||||
RunningState runningState = stateManager.getOrThrow(RunningState.class);
|
|
||||||
runningState.setRunning(false);
|
runningState.setRunning(false);
|
||||||
System.exit(0); // Pls don't blame me
|
System.exit(0); // Pls don't blame me
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,11 +121,11 @@ public class FullRoomDrawHandler extends AbstractEventHandler<FullRoomDraw> {
|
|||||||
eventManager.emitEvent(new RerenderScreen(partsToRerender.toArray(RerenderScreen.ScreenPart[]::new)));
|
eventManager.emitEvent(new RerenderScreen(partsToRerender.toArray(RerenderScreen.ScreenPart[]::new)));
|
||||||
}
|
}
|
||||||
|
|
||||||
gameState.setTerminalTooSmall(false);
|
renderState.setTerminalTooSmall(false);
|
||||||
} catch (ArrayIndexOutOfBoundsException e) {
|
} catch (ArrayIndexOutOfBoundsException e) {
|
||||||
// Screen too small to fit the room
|
// Screen too small to fit the room
|
||||||
eventManager.emitEvent(new TerminalTooSmallEvent());
|
eventManager.emitEvent(new TerminalTooSmallEvent());
|
||||||
gameState.setTerminalTooSmall(true);
|
renderState.setTerminalTooSmall(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import cz.jzitnik.events.MouseMoveEvent;
|
|||||||
import cz.jzitnik.game.GameState;
|
import cz.jzitnik.game.GameState;
|
||||||
import cz.jzitnik.game.objects.GameObject;
|
import cz.jzitnik.game.objects.GameObject;
|
||||||
import cz.jzitnik.game.objects.Interactable;
|
import cz.jzitnik.game.objects.Interactable;
|
||||||
|
import cz.jzitnik.states.RenderState;
|
||||||
import cz.jzitnik.utils.DependencyManager;
|
import cz.jzitnik.utils.DependencyManager;
|
||||||
import cz.jzitnik.utils.UIClickHandlerRepository;
|
import cz.jzitnik.utils.UIClickHandlerRepository;
|
||||||
import cz.jzitnik.utils.events.AbstractEventHandler;
|
import cz.jzitnik.utils.events.AbstractEventHandler;
|
||||||
@@ -30,6 +31,9 @@ public class MouseActionEventHandler extends AbstractEventHandler<MouseAction> {
|
|||||||
@InjectState
|
@InjectState
|
||||||
private GameState gameState;
|
private GameState gameState;
|
||||||
|
|
||||||
|
@InjectState
|
||||||
|
private RenderState renderState;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MouseAction event) {
|
public void handle(MouseAction event) {
|
||||||
if (gameState.getScreen() != null) {
|
if (gameState.getScreen() != null) {
|
||||||
@@ -37,7 +41,7 @@ public class MouseActionEventHandler extends AbstractEventHandler<MouseAction> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameState.isTerminalTooSmall()) {
|
if (renderState.isTerminalTooSmall()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import cz.jzitnik.game.GameState;
|
|||||||
import cz.jzitnik.game.Player;
|
import cz.jzitnik.game.Player;
|
||||||
import cz.jzitnik.game.ResourceManager;
|
import cz.jzitnik.game.ResourceManager;
|
||||||
import cz.jzitnik.game.utils.RoomCords;
|
import cz.jzitnik.game.utils.RoomCords;
|
||||||
|
import cz.jzitnik.states.RenderState;
|
||||||
import cz.jzitnik.states.ScreenBuffer;
|
import cz.jzitnik.states.ScreenBuffer;
|
||||||
import cz.jzitnik.states.TerminalState;
|
import cz.jzitnik.states.TerminalState;
|
||||||
import cz.jzitnik.utils.DependencyManager;
|
import cz.jzitnik.utils.DependencyManager;
|
||||||
@@ -54,9 +55,12 @@ public class PlayerMoveEventHandler extends AbstractEventHandler<PlayerMoveEvent
|
|||||||
@InjectConfig
|
@InjectConfig
|
||||||
private PlayerConfig playerConfig;
|
private PlayerConfig playerConfig;
|
||||||
|
|
||||||
|
@InjectState
|
||||||
|
private RenderState renderState;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(PlayerMoveEvent event) {
|
public void handle(PlayerMoveEvent event) {
|
||||||
if (gameState.isTerminalTooSmall()) {
|
if (renderState.isTerminalTooSmall()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import lombok.Setter;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@State
|
@State
|
||||||
public class GameState {
|
public class GameState {
|
||||||
private final DependencyManager dependencyManager;
|
private final DependencyManager dependencyManager; // Maybe transient in the future
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@@ -28,10 +28,6 @@ public class GameState {
|
|||||||
@Getter
|
@Getter
|
||||||
private Screen screen;
|
private Screen screen;
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean terminalTooSmall = false;
|
|
||||||
|
|
||||||
public void setScreen(Screen screen) {
|
public void setScreen(Screen screen) {
|
||||||
if (screen != null) {
|
if (screen != null) {
|
||||||
dependencyManager.inject(screen);
|
dependencyManager.inject(screen);
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ import lombok.Setter;
|
|||||||
@Setter
|
@Setter
|
||||||
public class RenderState {
|
public class RenderState {
|
||||||
private boolean isFirstRender = true;
|
private boolean isFirstRender = true;
|
||||||
|
private boolean terminalTooSmall = false;
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main/java/cz/jzitnik/states/SoundState.java
Normal file
10
src/main/java/cz/jzitnik/states/SoundState.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package cz.jzitnik.states;
|
||||||
|
|
||||||
|
import cz.jzitnik.annotations.State;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@State
|
||||||
|
public class SoundState {
|
||||||
|
private double soundVolume;
|
||||||
|
}
|
||||||
75
src/main/java/cz/jzitnik/threads/MicrophoneThread.java
Normal file
75
src/main/java/cz/jzitnik/threads/MicrophoneThread.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package cz.jzitnik.threads;
|
||||||
|
|
||||||
|
import cz.jzitnik.annotations.ThreadRegistry;
|
||||||
|
import cz.jzitnik.annotations.injectors.InjectState;
|
||||||
|
import cz.jzitnik.states.SoundState;
|
||||||
|
import cz.jzitnik.utils.ShutdownableThread;
|
||||||
|
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
@ThreadRegistry
|
||||||
|
public class MicrophoneThread extends ShutdownableThread {
|
||||||
|
@InjectState
|
||||||
|
private SoundState soundState;
|
||||||
|
|
||||||
|
private volatile boolean running = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
AudioFormat format = new AudioFormat(44100, 16, 1, true, false);
|
||||||
|
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
|
||||||
|
|
||||||
|
if (!AudioSystem.isLineSupported(info)) {
|
||||||
|
System.err.println("Line not supported: " + info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetDataLine line = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
line = (TargetDataLine) AudioSystem.getLine(info);
|
||||||
|
line.open(format);
|
||||||
|
line.start();
|
||||||
|
|
||||||
|
byte[] buffer = new byte[2048];
|
||||||
|
|
||||||
|
while (running && !Thread.currentThread().isInterrupted()) {
|
||||||
|
int bytesRead = line.read(buffer, 0, buffer.length);
|
||||||
|
|
||||||
|
if (bytesRead > 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/main/java/cz/jzitnik/utils/ShutdownableThread.java
Normal file
6
src/main/java/cz/jzitnik/utils/ShutdownableThread.java
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package cz.jzitnik.utils;
|
||||||
|
|
||||||
|
public abstract class ShutdownableThread extends Thread {
|
||||||
|
public abstract void run();
|
||||||
|
public abstract void shutdown();
|
||||||
|
}
|
||||||
47
src/main/java/cz/jzitnik/utils/ThreadManager.java
Normal file
47
src/main/java/cz/jzitnik/utils/ThreadManager.java
Normal file
@@ -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<ShutdownableThread> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user