Update
This commit is contained in:
@@ -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.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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user