This commit is contained in:
2025-10-22 16:18:35 +02:00
commit 4aaf8a56c8
28 changed files with 1614 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
package systems.brn.textvoice.client;
import systems.brn.textvoice.client.audio.Speaker;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class PlayerAudioMixer {
private final Speaker speaker;
private final Map<String, Queue<short[]>> streams = new ConcurrentHashMap<>();
private final int frameSamples = 160;
private final int sampleRate = 8000;
private final Map<String, PlayerHUDState> hudStates;
// Master volume in 0-100 range
private volatile short masterVolume = 100;
public PlayerAudioMixer(Speaker speaker, Map<String, PlayerHUDState> hudStates) {
this.speaker = speaker;
this.hudStates = hudStates;
}
public void setMasterVolume(short volume) {
if (volume < 0) volume = 0;
if (volume > 100) volume = 100;
this.masterVolume = volume;
}
public void enqueuePlayerFrame(String playerName, short[] pcm) {
Queue<short[]> queue = streams.computeIfAbsent(playerName, k -> new LinkedList<>());
synchronized (queue) {
queue.add(pcm);
}
}
public void start() {
Thread thread = new Thread(() -> {
short[] outputBuffer = new short[frameSamples]; // 32-bit int output
long nextFrameTime = System.nanoTime();
while (true) {
float[] mixBuffer = new float[frameSamples];
Arrays.fill(mixBuffer, 0.0f);
for (Map.Entry<String, Queue<short[]>> entry : streams.entrySet()) {
String playerName = entry.getKey();
Queue<short[]> queue = entry.getValue();
short[] frame = null;
synchronized (queue) {
if (!queue.isEmpty()) frame = queue.poll();
}
if (frame != null) {
PlayerHUDState state = hudStates.computeIfAbsent(playerName, k -> new PlayerHUDState());
float playerVolume = state.volume / 100f; // per-player volume 0.01.0
for (int i = 0; i < Math.min(frame.length, mixBuffer.length); i++) {
mixBuffer[i] += frame[i] * playerVolume; // apply per-player volume
}
}
}
// Apply master volume and convert to 32-bit int
float masterVol = masterVolume / 100f;
for (int i = 0; i < frameSamples; i++) {
float sample = mixBuffer[i] * masterVol; // apply master volume
// clamp to 16-bit PCM range
if (sample > 32767f) sample = 32767f;
if (sample < -32768f) sample = -32768f;
outputBuffer[i] = (short) sample;
}
speaker.play(outputBuffer);
// Schedule next frame (20ms)
nextFrameTime += (long) (frameSamples / (double) sampleRate * 1_000_000_000);
long sleepTime = (nextFrameTime - System.nanoTime()) / 1_000_000;
if (sleepTime > 0) {
try { Thread.sleep(sleepTime); } catch (InterruptedException ignored) {}
}
}
}, "textvoice-Mixer");
thread.setDaemon(true);
thread.start();
}
}