Skip to content

Commit 731799e

Browse files
committed
walkie-talkie
1 parent 09a8b7d commit 731799e

17 files changed

Lines changed: 454 additions & 132 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ mod_name=Channel
3030
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
3131
mod_license=GNU GPL 3.0
3232
# The mod version. See https://semver.org/
33-
mod_version=26.1.2-25+beta
33+
mod_version=26.1.2-26+beta
3434
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
3535
# This should match the base package used for the mod sources.
3636
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html

src/main/java/cn/ussshenzhou/channel/Item/ModItems.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public class ModItems {
5656
public static final Supplier<Item> SPEAKER_HANG_ITEM = ITEMS.register("speaker_hang", () -> new BlockItem(ModBlocks.SPEAKER_HANG_BLOCK.get(), new Item.Properties()
5757
.setId(ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(Channel.MODID, "speaker_hang")))
5858
));
59+
public static final Supplier<Item> WALKIE_TALKIE = ITEMS.register("walkie_talkie", () -> new WalkieTalkie(new Item.Properties()
60+
.stacksTo(1)
61+
.component(CHANNEL.get(), 0)
62+
.setId(ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(Channel.MODID, "walkie_talkie")))
63+
));
5964

6065
//----------Creative Mode Tab----------
6166
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, Channel.MODID);
@@ -74,7 +79,8 @@ public static void onBuildContents(BuildCreativeModeTabContentsEvent event) {
7479
MIC_ITEM,
7580
SPEAKER_ITEM,
7681
SPEAKER_STAND_ITEM,
77-
SPEAKER_HANG_ITEM
82+
SPEAKER_HANG_ITEM,
83+
WALKIE_TALKIE
7884
).map(s -> new ItemStack(s.get())).toList());
7985
}
8086
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cn.ussshenzhou.channel.Item;
2+
3+
import cn.ussshenzhou.channel.gui.ItemChannelSettingScreen;
4+
import cn.ussshenzhou.channel.util.AudioHelper;
5+
import com.mojang.logging.annotations.MethodsReturnNonnullByDefault;
6+
import net.minecraft.client.Minecraft;
7+
import net.minecraft.network.chat.Component;
8+
import net.minecraft.world.InteractionHand;
9+
import net.minecraft.world.InteractionResult;
10+
import net.minecraft.world.entity.player.Player;
11+
import net.minecraft.world.item.Item;
12+
import net.minecraft.world.item.ItemStack;
13+
import net.minecraft.world.item.TooltipFlag;
14+
import net.minecraft.world.item.component.TooltipDisplay;
15+
import net.minecraft.world.level.Level;
16+
17+
import javax.annotation.ParametersAreNonnullByDefault;
18+
import java.util.function.Consumer;
19+
import java.util.function.Supplier;
20+
21+
import static org.lwjgl.openal.AL10.*;
22+
import static org.lwjgl.openal.AL10.AL_FALSE;
23+
import static org.lwjgl.openal.AL10.AL_LOOPING;
24+
import static org.lwjgl.openal.AL10.AL_MAX_GAIN;
25+
import static org.lwjgl.openal.AL10.AL_REFERENCE_DISTANCE;
26+
import static org.lwjgl.openal.AL10.alSourcef;
27+
28+
@ParametersAreNonnullByDefault
29+
@MethodsReturnNonnullByDefault
30+
public class WalkieTalkie extends Item {
31+
public WalkieTalkie(Properties properties) {
32+
super(properties);
33+
}
34+
35+
@Override
36+
public InteractionResult use(Level level, Player player, InteractionHand hand) {
37+
if (!level.isClientSide()) {
38+
return InteractionResult.PASS;
39+
}
40+
return new Supplier<InteractionResult>() {
41+
@Override
42+
public InteractionResult get() {
43+
Minecraft.getInstance().setScreen(new ItemChannelSettingScreen(hand, player.getItemInHand(hand)));
44+
return InteractionResult.SUCCESS;
45+
}
46+
}.get();
47+
48+
}
49+
50+
@SuppressWarnings("deprecation")
51+
@Override
52+
public void appendHoverText(ItemStack itemStack, TooltipContext context, TooltipDisplay display, Consumer<Component> builder, TooltipFlag tooltipFlag) {
53+
builder.accept(Component.translatable("item.channel.walkie_talkie.tooltip"));
54+
}
55+
}

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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,22 @@
1313
import java.util.List;
1414

1515
import static org.lwjgl.openal.AL10.*;
16-
import static org.lwjgl.openal.AL10.AL_FALSE;
17-
import static org.lwjgl.openal.AL10.AL_LOOPING;
18-
import static org.lwjgl.openal.AL10.AL_MAX_GAIN;
19-
import static org.lwjgl.openal.AL10.AL_PITCH;
20-
import static org.lwjgl.openal.AL10.AL_REFERENCE_DISTANCE;
21-
import static org.lwjgl.openal.AL10.AL_ROLLOFF_FACTOR;
22-
import static org.lwjgl.openal.AL10.alSourcef;
23-
import static org.lwjgl.openal.AL10.alSourcei;
24-
import static org.lwjgl.openal.AL11.alSource3i;
16+
import static org.lwjgl.openal.AL11.*;
2517
import static org.lwjgl.openal.EXTEfx.*;
26-
import static org.lwjgl.openal.EXTEfx.AL_AUXILIARY_SEND_FILTER;
2718

2819
public abstract class Audio {
2920
protected static final int MAX_BUFFER_10MS = 3 * 100;
3021
protected int readyBufferMs = 0;
31-
protected final int alSource, sampleRate, alDirectFilter, alReverbFilter;
22+
protected int alSource, sampleRate, alDirectFilter, alReverbFilter;
3223
protected double x, y, z;
3324

34-
public Audio(int sampleRate) {
35-
this.sampleRate = sampleRate;
25+
public Audio() {
26+
this.sampleRate = 48000;
3627
this.alSource = alGenSources();
28+
initAL();
29+
}
30+
31+
protected void initAL() {
3732
alSourcef(alSource, AL_GAIN, 1);
3833
alSourcef(alSource, AL_PITCH, 1);
3934
alSourcef(alSource, AL_LOOPING, AL_FALSE);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ public static void reset() {
9191
* @return true, if want to remove player and their audio. Must close audio before return true.
9292
*/
9393
protected static boolean play(Level level, Audio audio) {
94+
if (audio instanceof WalkieTalkieAudio) {
95+
return audio.play();
96+
}
9497
var pos = audio.getPos(level);
9598
if (ChannelClientConfig.get().rayTraceAudio) {
9699
return RayTraceManager.play(audio, pos);

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

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

3+
import cn.ussshenzhou.channel.Item.ModItems;
34
import cn.ussshenzhou.channel.audio.client.send.WebRTCHelper;
45
import cn.ussshenzhou.channel.blockentity.SpeakerBlockEntity;
56
import cn.ussshenzhou.channel.config.ChannelClientConfig;
@@ -25,6 +26,7 @@
2526
import net.neoforged.neoforge.event.tick.ServerTickEvent;
2627

2728
import java.util.Collections;
29+
import java.util.Objects;
2830
import java.util.Set;
2931
import java.util.UUID;
3032

@@ -50,7 +52,7 @@ public static void onExit(ClientPlayerNetworkEvent.LoggingOut event) {
5052
}
5153
}
5254

53-
public static void add(SpeakerBlockEntity blockEntity){
55+
public static void add(SpeakerBlockEntity blockEntity) {
5456
synchronized (CHANNELED_BLOCK_CACHE_C) {
5557
//noinspection MapOrSetKeyShouldOverrideHashCodeEquals
5658
AudioReceiveHandler.CHANNELED_BLOCK_CACHE_C.add(blockEntity);
@@ -93,17 +95,6 @@ private static void handleInternal(AudioPacket2C packet, Level level) throws Exc
9395
}
9496
var earPos = AudioHelper.getEarPos();
9597
var decoded = OpusManager.decode(packet.opus, packet.from);
96-
var localPlayer = Minecraft.getInstance().player;
97-
if (localPlayer != null) {
98-
boolean hearingSelf = ChannelClientConfig.get().hearMyself && localPlayer.getUUID().equals(packet.from);
99-
boolean hearingOther = !localPlayer.getUUID().equals(packet.from) && earPos.distanceToSqr(x, y, z) <= 64 * 64;
100-
if (hearingSelf || hearingOther) {
101-
// direct talking sound always apply
102-
var audio = getDirectAudioAndCheckSampleRate(packet.from, 48000);
103-
audio.push(decoded);
104-
audio.setPos(x, y, z);
105-
}
106-
}
10798
// through speaker
10899
if (packet.channels.length == 0) {
109100
return;
@@ -118,35 +109,34 @@ private static void handleInternal(AudioPacket2C packet, Level level) throws Exc
118109
if (earPos.distanceToSqr(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5) > 64 * 64) {
119110
continue;
120111
}
121-
var audio = getSpeakerAudioAndCheckSampleRate(speaker, 48000);
112+
var audio = (SpeakerAudio) AudioManager.audios.compute(SpeakerAudio.hashcode(speaker.getBlockPos()), (_, old) -> old == null ? new SpeakerAudio(speaker) : old);
122113
audio.push(packet.from, decoded);
123114
}
124115
}
125-
}
126-
127-
private static DirectAudio getDirectAudioAndCheckSampleRate(UUID from, int sampleRate) {
128-
return (DirectAudio) AudioManager.audios.compute(from.hashCode(), (_, old) -> {
129-
if (old == null) {
130-
return new DirectAudio(from, sampleRate);
131-
} else if (old.sampleRate != sampleRate) {
132-
old.close();
133-
return new DirectAudio(from, sampleRate);
134-
} else {
135-
return old;
136-
}
137-
});
138-
}
116+
//direct audio
117+
var localPlayer = Minecraft.getInstance().player;
118+
if (localPlayer == null) {
119+
return;
120+
}
139121

140-
private static SpeakerAudio getSpeakerAudioAndCheckSampleRate(SpeakerBlockEntity blockEntity, int sampleRate) {
141-
return (SpeakerAudio) AudioManager.audios.compute(SpeakerAudio.hashcode(blockEntity.getBlockPos()), (_, old) -> {
142-
if (old == null) {
143-
return new SpeakerAudio(blockEntity, sampleRate);
144-
} else if (old.sampleRate != sampleRate) {
145-
old.close();
146-
return new SpeakerAudio(blockEntity, sampleRate);
147-
} else {
148-
return old;
122+
boolean hearingSelf = ChannelClientConfig.get().hearMyself && localPlayer.getUUID().equals(packet.from);
123+
boolean hearingOther = !localPlayer.getUUID().equals(packet.from) && earPos.distanceToSqr(x, y, z) <= 64 * 64;
124+
if (hearingSelf || hearingOther) {
125+
// direct talking sound always apply
126+
var audio = (DirectAudio) AudioManager.audios.compute(packet.from.hashCode(), (_, old) -> old == null ? new DirectAudio(packet.from) : old);
127+
audio.push(decoded);
128+
audio.setPos(x, y, z);
129+
}
130+
// through walkie-talkie
131+
for (var item : localPlayer.getInventory()) {
132+
if (item.is(ModItems.WALKIE_TALKIE.get())) {
133+
var ch = item.get(ModItems.CHANNEL.get());
134+
if (ch != null && ch > 0 && channels.contains(ch.intValue())) {
135+
var audio = (WalkieTalkieAudio) AudioManager.audios.compute(0, (_, old) -> old == null ? new WalkieTalkieAudio() : old);
136+
audio.push(packet.from, decoded);
137+
break;
138+
}
149139
}
150-
});
140+
}
151141
}
152142
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package cn.ussshenzhou.channel.audio.client.receive;
2+
3+
import cn.ussshenzhou.channel.config.ChannelClientConfig;
4+
import cn.ussshenzhou.channel.util.AudioHelper;
5+
import io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueue;
6+
import org.jspecify.annotations.Nullable;
7+
8+
import java.nio.ByteBuffer;
9+
import java.nio.ByteOrder;
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
import java.util.UUID;
14+
import java.util.concurrent.ConcurrentHashMap;
15+
16+
public abstract class BaseSharedAudio extends Audio {
17+
protected final ConcurrentHashMap<UUID, MpscArrayQueue<short[]>> audioBuffers = new ConcurrentHashMap<>();
18+
19+
public void push(UUID from, short[] audio) {
20+
int length = sampleRate / 100;
21+
var queue = audioBuffers.computeIfAbsent(from, _ -> new MpscArrayQueue<>((int) (1.1 * MAX_BUFFER_10MS)));
22+
for (int i = 0; i < audio.length / length; i++) {
23+
queue.offer(Arrays.copyOfRange(audio, i * length, (i + 1) * length));
24+
}
25+
}
26+
27+
@Override
28+
protected float getGain() {
29+
return AudioHelper.db2factor(ChannelClientConfig.get().outputAdjust);
30+
}
31+
32+
@Override
33+
@Nullable
34+
public List<ByteBuffer> read(int sizeIn10Ms) {
35+
if (audioBuffers.isEmpty()) {
36+
return null;
37+
}
38+
audioBuffers.entrySet().removeIf(e -> e.getValue().isEmpty());
39+
checkTooMuchDelay();
40+
int maxAvailable = 0;
41+
for (var queue : audioBuffers.values()) {
42+
maxAvailable = Math.max(maxAvailable, queue.size());
43+
}
44+
if (maxAvailable == 0) {
45+
return null;
46+
}
47+
int toRead = Math.min(sizeIn10Ms, maxAvailable);
48+
int length = sampleRate / 100;
49+
List<ByteBuffer> buffers = new ArrayList<>(toRead);
50+
for (int i = 0; i < toRead; i++) {
51+
var mixed = mix(length);
52+
if (mixed != null) {
53+
buffers.add(write(mixed));
54+
}
55+
}
56+
return buffers;
57+
}
58+
59+
private int[] mix(int length) {
60+
int[] mixed = new int[length];
61+
boolean hasData = false;
62+
for (var queue : audioBuffers.values()) {
63+
var chunk = queue.poll();
64+
if (chunk != null) {
65+
hasData = true;
66+
for (int j = 0; j < length; j++) {
67+
mixed[j] += chunk[j];
68+
}
69+
}
70+
}
71+
return hasData ? mixed : null;
72+
}
73+
74+
private ByteBuffer write(int[] mixed) {
75+
var buffer = ByteBuffer.allocateDirect(mixed.length * 2).order(ByteOrder.LITTLE_ENDIAN);
76+
var shortBuffer = buffer.asShortBuffer();
77+
for (int i : mixed) {
78+
shortBuffer.put((short) (Short.MAX_VALUE * Math.tanh((double) i / Short.MAX_VALUE)));
79+
}
80+
buffer.rewind();
81+
return buffer;
82+
}
83+
84+
private void checkTooMuchDelay() {
85+
for (var queue : audioBuffers.values()) {
86+
checkTooMuchDelay(queue);
87+
}
88+
}
89+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public class DirectAudio extends Audio {
2020
private final MpscArrayQueue<short[]> audioBuffer = new MpscArrayQueue<>((int) (1.1 * MAX_BUFFER_10MS));
2121
public final UUID playerId;
2222

23-
public DirectAudio(UUID playerId, int sampleRate) {
24-
super(sampleRate);
23+
public DirectAudio(UUID playerId) {
24+
super();
2525
this.playerId = playerId;
2626
}
2727

0 commit comments

Comments
 (0)