diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f2c33983..85dcaa14a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## [8.0.2] ### Features - Added Frame Timer Precision option. Higher values improve timing precision, but increase CPU usage. +- Added Directional Audio option. This was previously always enabled, disabling this might provide a better experience when using speakers. - Entities can now turn either left or right during death animation ### Fixes diff --git a/game/client/src/main/java/net/betterthanadventure/sound/LibraryLWJGL3OpenAL.java b/game/client/src/main/java/net/betterthanadventure/sound/LibraryLWJGL3OpenAL.java index e9a1d6cfe..1275cb860 100644 --- a/game/client/src/main/java/net/betterthanadventure/sound/LibraryLWJGL3OpenAL.java +++ b/game/client/src/main/java/net/betterthanadventure/sound/LibraryLWJGL3OpenAL.java @@ -1,7 +1,11 @@ package net.betterthanadventure.sound; +import net.minecraft.client.option.GameSettings; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.BufferUtils; import org.lwjgl.openal.*; +import org.lwjgl.system.MemoryStack; import paulscode.sound.*; import javax.sound.sampled.AudioFormat; @@ -24,6 +28,17 @@ public class LibraryLWJGL3OpenAL extends Library { private FloatBuffer listenerVelocity = null; private Map ALBufferMap = null; private static boolean alPitchSupported = true; + private boolean extAlcSoftHrtf; + private boolean isHrtfEnabled; + + public enum HRTF { + DONT_CARE, + FALSE, + TRUE, + } + + // TODO this is terrible, but we can't modify paulscode classes so this will have to do for now + private static @Nullable LibraryLWJGL3OpenAL instance; public LibraryLWJGL3OpenAL() { this.ALBufferMap = new HashMap<>(); @@ -36,12 +51,46 @@ public class LibraryLWJGL3OpenAL extends Library { return; } ALCCapabilities deviceCaps = ALC.createCapabilities(device); + this.extAlcSoftHrtf = ALC10.alcIsExtensionPresent(device, "ALC_SOFT_HRTF"); this.context = ALC10.alcCreateContext(device, (IntBuffer) null); + this.setHRTF(GameSettings.DIRECTIONAL_AUDIO.value); if (context == 0L) { this.errorMessage("OpenAL context could not be created."); } ALC10.alcMakeContextCurrent(context); AL.createCapabilities(deviceCaps); + + instance = this; + } + + public static @Nullable LibraryLWJGL3OpenAL getInstance() { + return instance; + } + + /** + * Enables/disables HRTF if the ALC_SOFT_HRTF extensions is supported by the device. + */ + public void setHRTF(@NotNull final HRTF hrtf) { + if (this.extAlcSoftHrtf) { + final int enabledVal = switch (hrtf) { + case FALSE -> ALC10.ALC_FALSE; + case TRUE -> ALC10.ALC_TRUE; + default -> SOFTHRTF.ALC_DONT_CARE_SOFT; + }; + + try (final MemoryStack stack = MemoryStack.stackPush()) { + final IntBuffer attrList = stack.callocInt(3); // Zero terminated + attrList.put(SOFTHRTF.ALC_HRTF_SOFT).put(enabledVal); + + SOFTHRTF.alcResetDeviceSOFT(this.device, attrList.put(0).flip()); + } + } + + this.isHrtfEnabled = ALC10.alcGetInteger(this.device, SOFTHRTF.ALC_HRTF_SOFT) == 1; + } + + public boolean getHRTF() { + return this.isHrtfEnabled; } private void destroy() { diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java index 8a6feedd0..d051b16f7 100644 --- a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java +++ b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java @@ -135,6 +135,7 @@ public abstract class OptionsPages { .withComponent(new ToggleableOptionComponent<>(GameSettings.MUSIC_FREQUENCY)) .withComponent(new BooleanOptionComponent(GameSettings.MUTE_ON_LOST_FOCUS)) .withComponent(new BooleanOptionComponent(GameSettings.PLAY_TITLE_SCREEN_MUSIC)) + .withComponent(new ToggleableOptionComponent<>(GameSettings.DIRECTIONAL_AUDIO)) .withComponent(new FloatOptionComponent(GameSettings.MASTER_VOLUME)) .withComponent(new FloatOptionComponent(GameSettings.MUSIC_VOLUME)) .withComponent(new FloatOptionComponent(GameSettings.WORLD_SOUNDS_VOLUME)) diff --git a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java index 66c89a3fd..363dcbef2 100644 --- a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java +++ b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java @@ -3,6 +3,7 @@ package net.minecraft.client.option; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.betterthanadventure.sound.LibraryLWJGL3OpenAL; import net.minecraft.client.Minecraft; import net.minecraft.client.ScaledResolution; import net.minecraft.client.discord.RichPresenceHandlerThread; @@ -68,6 +69,23 @@ public final class GameSettings { public static final @NotNull OptionInteger WINDOW_WIDTH = register(new OptionInteger("windowWidth", 854)); public static final @NotNull OptionInteger WINDOW_HEIGHT = register(new OptionInteger("windowHeight", 480)); + public static final @NotNull OptionEnum DIRECTIONAL_AUDIO = register(new OptionEnum<>("sound.directionalAudio", LibraryLWJGL3OpenAL.HRTF.class, LibraryLWJGL3OpenAL.HRTF.DONT_CARE) + .addOnChangeCallback((mc, opt) -> mc.sndManager.lockedOperation(() -> { + final LibraryLWJGL3OpenAL library = LibraryLWJGL3OpenAL.getInstance(); + if (library != null) library.setHRTF(opt.value); + })) + .withDisplayStringProvider((mc, i18n, opt) -> { + // No locking needed + final LibraryLWJGL3OpenAL library = LibraryLWJGL3OpenAL.getInstance(); + final LibraryLWJGL3OpenAL.HRTF hrtf = opt.value; + + if (hrtf == LibraryLWJGL3OpenAL.HRTF.DONT_CARE) { + final String innerString = library == null ? "?" : i18n.translateKey(library.getHRTF() ? "options.on" : "options.off"); + return i18n.translateKey("options.sound.directionalAudio.auto") + " (" + innerString + ")"; + } else { + return i18n.translateKey(hrtf == LibraryLWJGL3OpenAL.HRTF.TRUE ? "options.on" : "options.off"); + } + })); public static final @NotNull OptionVolume MASTER_VOLUME = register(new OptionVolume("sound.master", 1.0f, SoundCategory.GUI_SOUNDS)); public static final @NotNull OptionVolume MUSIC_VOLUME = register(new OptionVolume("sound.music", 1.0f, SoundCategory.MUSIC)); public static final @NotNull OptionVolume WORLD_SOUNDS_VOLUME = register(new OptionVolume("sound.world", 1.0f, SoundCategory.WORLD_SOUNDS)); diff --git a/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java b/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java index 03925b384..89136e649 100644 --- a/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java +++ b/game/client/src/main/java/net/minecraft/client/sound/SoundEngine.java @@ -15,6 +15,7 @@ import net.minecraft.core.entity.Mob; import net.minecraft.core.sound.SoundCategory; import net.minecraft.core.util.helper.MathHelper; import net.minecraft.client.option.GameSettings; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import paulscode.sound.SoundSystem; @@ -66,6 +67,8 @@ public class SoundEngine lock.unlock(); } } + + public void setMuted(final boolean muted) { @@ -482,6 +485,15 @@ public class SoundEngine } } + public void lockedOperation(@NotNull final Runnable runnable) { + try { + lock.lock(); + runnable.run(); + } finally { + lock.unlock(); + } + } + public void stopMusic() { try { lock.lock(); diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang index 3174f951a..8d0d02ca7 100644 --- a/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang +++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/options.lang @@ -5,6 +5,8 @@ options.controls=Controls... options.keybinds=Keybinds... options.video=Video Settings... options.videoTitle=Video Settings +options.sound.directionalAudio=Directional Audio +options.sound.directionalAudio.auto=Auto options.sound.music=Music Volume options.sound.master=Master Volume options.sound.world=World Volume