Skip to content

Commit 1366867

Browse files
committed
RayTrace audio prototype
1 parent a58eb18 commit 1366867

22 files changed

Lines changed: 1056 additions & 62 deletions

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ plugins {
22
id 'java-library'
33
id 'maven-publish'
44
id 'idea'
5-
id 'net.neoforged.moddev' version '2.0.103'
5+
id 'net.neoforged.moddev' version '2.0.121'
66
}
77

88
version = mod_version
@@ -140,14 +140,14 @@ dependencies {
140140
// compileOnly "mezz.jei:jei-${mc_version}-forge-api:${jei_version}"
141141
// runtimeOnly "mezz.jei:jei-${mc_version}-forge:${jei_version}"
142142

143-
implementation("cn.ussshenzhou:t88:0.+") {
143+
implementation("cn.ussshenzhou:t88:+") {
144144
exclude group: "curse.maven", module: "irisshaders-455508"
145145
exclude group: "org.sinytra.forgified-fabric-api", module: "fabric-renderer-api-v1"
146146
exclude group: "org.sinytra.forgified-fabric-api", module: "fabric-api-base"
147147
exclude group: "org.sinytra.forgified-fabric-api", module: "fabric-rendering-data-attachment-v1"
148148
exclude group: "org.sinytra.forgified-fabric-api", module: "fabric-block-view-api-v2"
149149
}
150-
annotationProcessor("cn.ussshenzhou:t88:0.+") {
150+
annotationProcessor("cn.ussshenzhou:t88:+") {
151151
exclude group: "curse.maven", module: "irisshaders-455508"
152152
exclude group: "org.sinytra.forgified-fabric-api", module: "fabric-renderer-api-v1"
153153
exclude group: "org.sinytra.forgified-fabric-api", module: "fabric-api-base"

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ neo_version_range=[21,)
2323
# The loader version range can only use the major version of FML as bounds
2424
loader_version_range=[4,)
2525
parchment_minecraft_version=1.21.8
26-
parchment_mappings_version=2025.07.20
26+
parchment_mappings_version=2025.09.14
2727
## Mod Properties
2828
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
2929
# Must match the String constant located in the main mod class annotated with @Mod.

src/main/java/cn/ussshenzhou/channel/audio/client/receive/AudioManagerManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public static void init() {
1313
TALK.init();
1414
}
1515

16+
public static void reset() {
17+
TALK.reset();
18+
}
19+
1620
public static void handlePacket(BaseAudioPacket2C packet) {
1721
switch (packet) {
1822
case TalkPacket2C talk -> TALK.handle(packet.from, packet.sampleRate, packet.opus);

src/main/java/cn/ussshenzhou/channel/audio/client/receive/BaseAudioManager.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package cn.ussshenzhou.channel.audio.client.receive;
22

3-
import cn.ussshenzhou.channel.network.BaseAudioPacket2C;
43
import cn.ussshenzhou.channel.util.OpusHelper;
54
import com.mojang.logging.LogUtils;
65
import io.github.jaredmdobson.concentus.OpusException;
@@ -25,20 +24,26 @@
2524
* @author USS_Shenzhou
2625
*/
2726
public abstract class BaseAudioManager {
28-
protected final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
27+
public static final ScheduledExecutorService AUDIO_EXECUTOR = Executors.newSingleThreadScheduledExecutor(r ->
28+
Thread.ofPlatform()
29+
.name("Channel Audio Thread")
30+
.daemon(true)
31+
.factory()
32+
.newThread(r)
33+
);
2934
protected final ConcurrentHashMap<UUID, PlayerAudio> playerAudios = new ConcurrentHashMap<>();
30-
protected final int BUFFER_LENGTH = 5;
35+
public static final int PLAY_RATE10 = 2;
3136
protected long alCtx, alDevice;
3237

3338
public void init() {
34-
scheduler.submit(() -> {
39+
AUDIO_EXECUTOR.submit(() -> {
3540
while (!Minecraft.getInstance().getSoundManager().soundEngine.loaded) {
3641
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
3742
}
3843
initAL();
3944
alDistanceModel(AL_EXPONENT_DISTANCE);
4045
});
41-
scheduler.scheduleAtFixedRate(this::playing, 0, BUFFER_LENGTH * 10, TimeUnit.MILLISECONDS);
46+
AUDIO_EXECUTOR.scheduleAtFixedRate(this::playing, 0, PLAY_RATE10 * 10, TimeUnit.MILLISECONDS);
4247
}
4348

4449
@SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
@@ -59,7 +64,7 @@ public void handle(UUID from, int sampleRate, byte[] opus) {
5964
}
6065
var fromEntity = level.getEntity(from);
6166
if (fromEntity instanceof Player player) {
62-
playerAudios.compute(player.getUUID(), (id, old) -> {
67+
AUDIO_EXECUTOR.execute(() -> playerAudios.compute(player.getUUID(), (id, old) -> {
6368
if (old == null) {
6469
return new PlayerAudio(id, sampleRate);
6570
} else if (old.sampleRate != sampleRate) {
@@ -68,7 +73,7 @@ public void handle(UUID from, int sampleRate, byte[] opus) {
6873
} else {
6974
return old;
7075
}
71-
}).push(decoded);
76+
}).push(decoded));
7277
}
7378
} catch (OpusException e) {
7479
LogUtils.getLogger().error(e.toString());
@@ -79,14 +84,12 @@ protected void playing() {
7984
try {
8085
var level = Minecraft.getInstance().level;
8186
if (level == null) {
82-
playerAudios.values().forEach(PlayerAudio::close);
83-
playerAudios.clear();
87+
reset();
8488
return;
8589
}
8690
var library = Minecraft.getInstance().getSoundManager().soundEngine.library;
8791
if (library.context != alCtx || library.currentDevice != alDevice) {
88-
playerAudios.values().forEach(PlayerAudio::close);
89-
playerAudios.clear();
92+
reset();
9093
initAL();
9194
}
9295
playerAudios.entrySet().removeIf(entry -> play(level, entry.getKey(), entry.getValue()));
@@ -95,6 +98,11 @@ protected void playing() {
9598
}
9699
}
97100

101+
public void reset() {
102+
playerAudios.values().forEach(PlayerAudio::close);
103+
playerAudios.clear();
104+
}
105+
98106
/**
99107
* @return true, if want to remove player and their audio. Must close audio before return true.
100108
*/

src/main/java/cn/ussshenzhou/channel/audio/client/receive/PlayerAudio.java

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package cn.ussshenzhou.channel.audio.client.receive;
22

3-
import org.lwjgl.openal.AL11;
3+
import cn.ussshenzhou.channel.audio.client.rt.RayTraceManager;
4+
import cn.ussshenzhou.channel.config.ChannelClientConfig;
5+
import cn.ussshenzhou.channel.config.ChannelPlayerConfig;
6+
import cn.ussshenzhou.channel.util.AudioHelper;
7+
import com.mojang.logging.LogUtils;
8+
import net.minecraft.world.entity.player.Player;
49

10+
import javax.annotation.Nullable;
511
import java.nio.ByteBuffer;
612
import java.nio.ByteOrder;
713
import java.util.Arrays;
@@ -10,15 +16,19 @@
1016
import java.util.concurrent.ArrayBlockingQueue;
1117
import java.util.concurrent.BlockingQueue;
1218

13-
import static org.lwjgl.openal.AL10.*;
19+
import static org.lwjgl.openal.AL11.*;
20+
import static org.lwjgl.openal.EXTEfx.*;
1421

1522
/**
1623
* @author USS_Shenzhou
1724
*/
1825
public class PlayerAudio {
19-
private final BlockingQueue<short[]> audioBuffer = new ArrayBlockingQueue<>(20);
20-
protected final int alSource, sampleRate;
21-
protected final UUID playerId;
26+
private static final int MAX_BUFFER = 50;
27+
private static final int MIN_PLAY_THRESHOLD = 4;
28+
private final BlockingQueue<short[]> audioBuffer = new ArrayBlockingQueue<>(MAX_BUFFER);
29+
private static int readyBuffer10ms = 0;
30+
public final int alSource, sampleRate, alDirectFilter, alReverbFilter;
31+
public final UUID playerId;
2232

2333
public PlayerAudio(UUID playerId, int sampleRate) {
2434
this.playerId = playerId;
@@ -27,9 +37,22 @@ public PlayerAudio(UUID playerId, int sampleRate) {
2737
alSourcef(alSource, AL_GAIN, 1);
2838
alSourcef(alSource, AL_PITCH, 1);
2939
alSourcef(alSource, AL_LOOPING, AL_FALSE);
30-
alSourcef(alSource, AL_SOURCE_TYPE, AL11.AL_STREAMING);
31-
alSourcef(alSource, AL_REFERENCE_DISTANCE, 3);
32-
alSourcef(alSource, AL_ROLLOFF_FACTOR, 1);
40+
alSourcef(alSource, AL_REFERENCE_DISTANCE, 1.5f);
41+
alSourcef(alSource, AL_MAX_GAIN, AudioHelper.db2factor(30));
42+
if (ChannelClientConfig.get().rayTraceAudio) {
43+
alSourcef(alSource, AL_ROLLOFF_FACTOR, 0);
44+
alDirectFilter = alGenFilters();
45+
alFilteri(alDirectFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
46+
alSourcei(alSource, AL_DIRECT_FILTER, alDirectFilter);
47+
48+
alReverbFilter = alGenFilters();
49+
alFilteri(alReverbFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
50+
alSource3i(alSource, AL_AUXILIARY_SEND_FILTER, RayTraceManager.getSlot(), 0, alReverbFilter);
51+
} else {
52+
alSourcef(alSource, AL_ROLLOFF_FACTOR, 1);
53+
alDirectFilter = -1;
54+
alReverbFilter = -1;
55+
}
3356
}
3457

3558
public void push(short[] audio) {
@@ -39,19 +62,44 @@ public void push(short[] audio) {
3962
}
4063
}
4164

65+
@Nullable
4266
public ByteBuffer read(int sizeIn10Ms) {
67+
int available = audioBuffer.size();
68+
if (available == 0) {
69+
return null;
70+
}
71+
int toRead = Math.min(sizeIn10Ms, available);
4372
int length = sampleRate / 100;
44-
var buffer = ByteBuffer.allocateDirect(sizeIn10Ms * length * 2).order(ByteOrder.LITTLE_ENDIAN);
73+
var buffer = ByteBuffer.allocateDirect(toRead * length * 2).order(ByteOrder.LITTLE_ENDIAN);
4574
for (int i = 0; i < sizeIn10Ms; i++) {
46-
if (i < sizeIn10Ms - audioBuffer.size()) {
47-
continue;
75+
var chunk = audioBuffer.poll();
76+
if (chunk != null) {
77+
buffer.slice(i * length * 2, length * 2).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(chunk);
4878
}
49-
buffer.slice(i * length * 2, length * 2).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(audioBuffer.poll());
5079
}
51-
buffer.limit(buffer.capacity());
80+
buffer.rewind();
5281
return buffer;
5382
}
5483

84+
public void play() {
85+
int processed = alGetSourcei(alSource, AL_BUFFERS_PROCESSED);
86+
while (processed-- > 0) {
87+
int buf = alSourceUnqueueBuffers(alSource);
88+
readyBuffer10ms -= alGetBufferi(buf, AL_SIZE) * 1000 / 2 / sampleRate / 10;
89+
alDeleteBuffers(buf);
90+
}
91+
ByteBuffer pcm = read(MAX_BUFFER);
92+
if (pcm != null) {
93+
int buf = alGenBuffers();
94+
alBufferData(buf, AL_FORMAT_MONO16, pcm, sampleRate);
95+
alSourceQueueBuffers(alSource, buf);
96+
readyBuffer10ms += pcm.capacity() * 1000 / 2 / sampleRate / 10;
97+
}
98+
if (alGetSourcei(alSource, AL_SOURCE_STATE) != AL_PLAYING && readyBuffer10ms > MIN_PLAY_THRESHOLD) {
99+
alSourcePlay(alSource);
100+
}
101+
}
102+
55103
public void close() {
56104
alSourceStop(this.alSource);
57105
int alBuf = alGetSourcei(this.alSource, AL_BUFFERS_QUEUED);
Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
11
package cn.ussshenzhou.channel.audio.client.receive;
22

3+
import cn.ussshenzhou.channel.audio.client.rt.RayTraceManager;
4+
import cn.ussshenzhou.channel.config.ChannelClientConfig;
35
import cn.ussshenzhou.channel.config.ChannelPlayerConfig;
6+
import cn.ussshenzhou.channel.util.AudioHelper;
47
import net.minecraft.client.Minecraft;
8+
import net.minecraft.world.entity.Entity;
59
import net.minecraft.world.entity.player.Player;
610
import net.minecraft.world.level.Level;
11+
import net.minecraft.world.phys.Vec3;
712

813
import java.nio.ByteBuffer;
914
import java.util.UUID;
1015

1116
import static org.lwjgl.openal.AL10.*;
12-
import static org.lwjgl.openal.AL10.AL_BUFFERS_PROCESSED;
13-
import static org.lwjgl.openal.AL10.AL_BUFFERS_QUEUED;
14-
import static org.lwjgl.openal.AL10.AL_FORMAT_MONO16;
15-
import static org.lwjgl.openal.AL10.AL_PLAYING;
16-
import static org.lwjgl.openal.AL10.AL_SOURCE_STATE;
17-
import static org.lwjgl.openal.AL10.alBufferData;
18-
import static org.lwjgl.openal.AL10.alDeleteBuffers;
19-
import static org.lwjgl.openal.AL10.alGenBuffers;
20-
import static org.lwjgl.openal.AL10.alGetSourcei;
21-
import static org.lwjgl.openal.AL10.alSourcePlay;
22-
import static org.lwjgl.openal.AL10.alSourceQueueBuffers;
23-
import static org.lwjgl.openal.AL10.alSourceUnqueueBuffers;
2417

2518
/**
2619
* @author USS_Shenzhou
@@ -35,23 +28,19 @@ protected boolean play(Level level, UUID playerId, PlayerAudio audio) {
3528
}
3629
var partialTick = Minecraft.getInstance().getDeltaTracker().getGameTimeDeltaPartialTick(false);
3730
var pos = player.getEyePosition(partialTick);
31+
if (ChannelClientConfig.get().rayTraceAudio) {
32+
RayTraceManager.play(pos.x, pos.y, pos.z, audio);
33+
} else {
34+
simplePlay(audio, pos, player);
35+
}
36+
return false;
37+
}
38+
39+
private void simplePlay(PlayerAudio audio, Vec3 pos, Entity player) {
3840
alSource3f(audio.alSource, AL_POSITION, (float) pos.x, (float) pos.y, (float) pos.z);
3941
if (player instanceof Player p) {
40-
alSourcef(audio.alSource, AL_GAIN, ChannelPlayerConfig.getOrDefault(p));
42+
alSourcef(audio.alSource, AL_GAIN, AudioHelper.db2factor(ChannelPlayerConfig.getOrDefault(p)));
4143
}
42-
int processed = alGetSourcei(audio.alSource, AL_BUFFERS_PROCESSED);
43-
while (processed-- > 0) {
44-
int buf = alSourceUnqueueBuffers(audio.alSource);
45-
alDeleteBuffers(buf);
46-
}
47-
ByteBuffer pcm = audio.read(BUFFER_LENGTH);
48-
int buf = alGenBuffers();
49-
alBufferData(buf, AL_FORMAT_MONO16, pcm, audio.sampleRate);
50-
alSourceQueueBuffers(audio.alSource, buf);
51-
int state = alGetSourcei(audio.alSource, AL_SOURCE_STATE);
52-
if (state != AL_PLAYING && alGetSourcei(audio.alSource, AL_BUFFERS_QUEUED) > 0) {
53-
alSourcePlay(audio.alSource);
54-
}
55-
return false;
44+
audio.play();
5645
}
5746
}

0 commit comments

Comments
 (0)