96 lines
3.3 KiB
Java
96 lines
3.3 KiB
Java
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.0–1.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();
|
||
}
|
||
}
|