feat: Microphone detection
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</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" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
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<ExitEvent> {
|
||||
@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
|
||||
}
|
||||
|
||||
@@ -121,11 +121,11 @@ public class FullRoomDrawHandler extends AbstractEventHandler<FullRoomDraw> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MouseAction> {
|
||||
@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<MouseAction> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameState.isTerminalTooSmall()) {
|
||||
if (renderState.isTerminalTooSmall()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PlayerMoveEvent
|
||||
@InjectConfig
|
||||
private PlayerConfig playerConfig;
|
||||
|
||||
@InjectState
|
||||
private RenderState renderState;
|
||||
|
||||
@Override
|
||||
public void handle(PlayerMoveEvent event) {
|
||||
if (gameState.isTerminalTooSmall()) {
|
||||
if (renderState.isTerminalTooSmall()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import lombok.Setter;
|
||||
@RequiredArgsConstructor
|
||||
@State
|
||||
public class GameState {
|
||||
private final DependencyManager dependencyManager;
|
||||
private final DependencyManager dependencyManager; // Maybe transient in the future
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -28,10 +28,6 @@ public class GameState {
|
||||
@Getter
|
||||
private Screen screen;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean terminalTooSmall = false;
|
||||
|
||||
public void setScreen(Screen screen) {
|
||||
if (screen != null) {
|
||||
dependencyManager.inject(screen);
|
||||
|
||||
@@ -9,4 +9,5 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class RenderState {
|
||||
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