mojira.dev
MC-218145

Elytra ambient noises stop working when refreshing resource packs

When using elytra and flying, if you change your texture pack and go back into the game without landing, the flying sound stops. It resumes once you stop and start flying again.

Start flying and change texture pack while still in mid-air.
Go back into the game and the flying sound will be gone.
Land and fly again to resume wing sound.

Reproduction steps provided by @unknown:

  1. Open world and equip elytra and fireworks

  2. Fly and use fireworks until "air rushing by" sound loop begins

  3. Press F3+T to reload resources

  4. Observe issue: "air rushing by" noise will not play until landing and starting again

Linked issues

Attachments

Comments 17

Occurs when reloading resources.

Repro:

  1. Open world and equip elytra and fireworks

  2. Fly and use fireworks until "air rushing by" sound loop begins

  3. Press F3+T to reload resources

  4. Observe issue: "air rushing by" noise will not play until landing and starting again

Can confirm in 21w11a. Relates to MC-200131.

Can confirm in 21w13a.

Can confirm in 21w14a.

7 more comments

Can confirm in 23w44a.

Mojang should study my code, which will fix this error and fix the looping sounds after reloading resource packs.

package mixin;

import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundEngine;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Mixin(SoundEngine.class)
public class SoundEngineFixMixin {
@Shadow
private Map<SoundInstance, Object> instanceToChannel;
@Unique
private static List<SoundInstance> savedLoopingSounds = new ArrayList<>();
@Unique
private static boolean needsRestore = false;
@Unique
private static int delay = 0;
@Inject(method = "reload", at = @At("HEAD"))
private void onReloadStart(CallbackInfo ci) {
savedLoopingSounds.clear();
for (SoundInstance sound : instanceToChannel.keySet()) {
if (sound.isLooping()) {
savedLoopingSounds.add(sound);
}
}
needsRestore = true;
delay = 0;
}
@Inject(method = "reload", at = @At("RETURN"))
private void onReloadComplete(CallbackInfo ci) {
needsRestore = true;
delay = 0;
}
@Inject(method = "tick", at = @At("HEAD"))
private void onTick(boolean paused, CallbackInfo ci) {
if (!needsRestore) return;
if (paused) return;
delay++;
if (delay < 0) return;
needsRestore = false;
Minecraft client = Minecraft.getInstance();
if (client.player == null || client.level == null) return;
client.execute(() -> {
for (SoundInstance sound : savedLoopingSounds) {
try {
client.getSoundManager().play(sound);
} catch (Exception ignored) {}
}
savedLoopingSounds.clear();
});
}
}

The problem is in the SoundEngine class. This code doesn't return looped sounds after reloading resource packs or F3 + T. My code completely fixes this bug, but I hope Mojang will look at my code.
Additional information can be found in MC-310788

I hope that Mojang will see this comment and fix this bug in version 26.3 snapshot 8-9.

This code will fix all the bugs:
MC-194284
MC-274183
MC-200131
MC-274182
MC-274184
MC-218145

Please spread the word and encourage Mojang to fix this bug!

I wrote some code for Mojang. This is what the SoundEngine class should look like. The code to fix this bug should look something like this so Mojang can quickly implement it.

Just copy and paste:

package net.minecraft.client.sounds;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.mojang.blaze3d.audio.Channel;
import com.mojang.blaze3d.audio.DeviceList;
import com.mojang.blaze3d.audio.DeviceTracker;
import com.mojang.blaze3d.audio.Library;
import com.mojang.blaze3d.audio.Listener;
import com.mojang.blaze3d.audio.ListenerTransform;
import com.mojang.blaze3d.audio.Library.Pool;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.SharedConstants;
import net.minecraft.client.Camera;
import net.minecraft.client.Options;
import net.minecraft.client.resources.sounds.Sound;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.resources.sounds.TickableSoundInstance;
import net.minecraft.client.resources.sounds.SoundInstance.Attenuation;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.util.Util;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
@Environment(EnvType.CLIENT)
public class SoundEngine {
    private static final Marker MARKER = MarkerFactory.getMarker("SOUNDS");
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final float PITCH_MIN = 0.5F;
    private static final float PITCH_MAX = 2.0F;
    private static final float VOLUME_MIN = 0.0F;
    private static final float VOLUME_MAX = 1.0F;
    private static final int MIN_SOURCE_LIFETIME = 20;
    private static final Set<Identifier> ONLY_WARN_ONCE = Sets.newHashSet();
    public static final String MISSING_SOUND = "FOR THE DEBUG!";
    public static final String OPEN_AL_SOFT_PREFIX = "OpenAL Soft on ";
    public static final int OPEN_AL_SOFT_PREFIX_LENGTH = "OpenAL Soft on ".length();
    private final SoundManager soundManager;
    private final Options options;
    private boolean loaded;
    private final Library library = new Library();
    private final Listener listener;
    private final SoundBufferLibrary soundBuffers;
    private final SoundEngineExecutor executor;
    private final ChannelAccess channelAccess;
    private int tickCount;
    private DeviceList lastSeenDevices;
    private final DeviceTracker deviceTracker;
    private final Map<SoundInstance, ChannelAccess.ChannelHandle> instanceToChannel;
    private final Multimap<SoundSource, SoundInstance> instanceBySource;
    private final Object2FloatMap<SoundSource> gainBySource;
    private final List<TickableSoundInstance> tickingSounds;
    private final Map<SoundInstance, Integer> queuedSounds;
    private final Map<SoundInstance, Integer> soundDeleteTime;
    private final List<SoundEventListener> listeners;
    private final List<TickableSoundInstance> queuedTickableSounds;
    private final List<Sound> preloadQueue;
    private final List<SoundInstance> savedLoopingSounds = new ArrayList<>();
    private boolean needsRestore = false;
    public SoundEngine(final SoundManager soundManager, final Options options, final ResourceProvider resourceProvider) {
        this.listener = this.library.getListener();
        this.executor = new SoundEngineExecutor();
        this.channelAccess = new ChannelAccess(this.library, this.executor);
        this.deviceTracker = Library.createDeviceTracker();
        this.instanceToChannel = Maps.newHashMap();
        this.instanceBySource = HashMultimap.create();
        this.gainBySource = (Object2FloatMap)Util.make(new Object2FloatOpenHashMap(), (map) -> map.defaultReturnValue(1.0F));
        this.tickingSounds = Lists.newArrayList();
        this.queuedSounds = Maps.newHashMap();
        this.soundDeleteTime = Maps.newHashMap();
        this.listeners = Lists.newArrayList();
        this.queuedTickableSounds = Lists.newArrayList();
        this.preloadQueue = Lists.newArrayList();
        this.soundManager = soundManager;
        this.options = options;
        this.soundBuffers = new SoundBufferLibrary(resourceProvider);
        this.lastSeenDevices = this.deviceTracker.currentDevices();
    }
    public void reload() {
        this.savedLoopingSounds.clear();
        for (SoundInstance sound : this.instanceToChannel.keySet()) {
            if (sound.isLooping()) {
                this.savedLoopingSounds.add(sound);
            }
        }
        this.needsRestore = true;
        ONLY_WARN_ONCE.clear();
        for(SoundEvent sound : BuiltInRegistries.SOUND_EVENT) {
            if (sound != SoundEvents.EMPTY) {
                Identifier location = sound.location();
                if (this.soundManager.getSoundEvent(location) == null) {
                    LOGGER.warn("Missing sound for event: {}", BuiltInRegistries.SOUND_EVENT.getKey(sound));
                    ONLY_WARN_ONCE.add(location);
                }
            }
        }
        this.destroy();
        this.loadLibrary();
    }
    private synchronized void loadLibrary() {
        if (!this.loaded) {
            try {
                String soundDevice = (String)this.options.soundDevice().get();
                DeviceList currentDevices = this.deviceTracker.currentDevices();
                this.library.init(Options.isSoundDeviceDefault(soundDevice) ? null : soundDevice, currentDevices, (Boolean)this.options.directionalAudio().get());
                this.listener.reset();
                CompletableFuture var10000 = this.soundBuffers.preload(this.preloadQueue);
                List var10001 = this.preloadQueue;
                Objects.requireNonNull(var10001);
                var10000.thenRun(var10001::clear);
                this.loaded = true;
                LOGGER.info(MARKER, "Sound engine started");
            } catch (RuntimeException e) {
                LOGGER.error(MARKER, "Error starting SoundSystem. Turning off sounds & music", e);
            }
        }
    }
    public void refreshCategoryVolume(final SoundSource source) {
        if (this.loaded) {
            this.instanceToChannel.forEach((soundInstance, channelHandle) -> {
                if (source == soundInstance.getSource() || source == SoundSource.MASTER) {
                    float newVolume = this.calculateVolume(soundInstance);
                    channelHandle.execute((channel) -> channel.setVolume(newVolume));
                }
            });
        }
    }
    public void destroy() {
        if (this.loaded) {
            this.stopAll();
            this.soundBuffers.clear();
            this.library.cleanup();
            this.loaded = false;
        }
    }
    public void emergencyShutdown() {
        if (this.loaded) {
            this.library.cleanup();
        }
    }
    public void stop(final SoundInstance soundInstance) {
        if (this.loaded) {
            ChannelAccess.ChannelHandle handle = (ChannelAccess.ChannelHandle)this.instanceToChannel.get(soundInstance);
            if (handle != null) {
                handle.execute(Channel::stop);
            }
        }
    }
    public void updateCategoryVolume(final SoundSource source, final float gain) {
        this.gainBySource.put(source, Mth.clamp(gain, 0.0F, 1.0F));
        this.refreshCategoryVolume(source);
    }
    public void stopAll() {
        if (this.loaded) {
            this.executor.shutDown();
            this.instanceToChannel.clear();
            this.channelAccess.clear();
            this.queuedSounds.clear();
            this.tickingSounds.clear();
            this.instanceBySource.clear();
            this.soundDeleteTime.clear();
            this.queuedTickableSounds.clear();
            this.gainBySource.clear();
            this.executor.startUp();
        }
    }
    public void addEventListener(final SoundEventListener listener) {
        this.listeners.add(listener);
    }
    public void removeEventListener(final SoundEventListener listener) {
        this.listeners.remove(listener);
    }
    private boolean shouldChangeDevice() {
        if (this.library.isCurrentDeviceDisconnected()) {
            LOGGER.info("Audio device was lost!");
            this.deviceTracker.forceRefresh();
            return true;
        } else {
            this.deviceTracker.tick();
            boolean shouldChangeDevice = false;
            DeviceList currentDevices = this.deviceTracker.currentDevices();
            if (!currentDevices.equals(this.lastSeenDevices)) {
                String currentDeviceName = this.library.currentDeviceName();
                if (!currentDevices.allDevices().contains(currentDeviceName)) {
                    LOGGER.info("Current audio device has disappeared!");
                    shouldChangeDevice = true;
                }
                String userSelectedDevice = (String)this.options.soundDevice().get();
                if (Options.isSoundDeviceDefault(userSelectedDevice)) {
                    String newDefault = currentDevices.defaultDevice();
                    if (!Objects.equals(currentDeviceName, newDefault)) {
                        LOGGER.info("System default audio device has changed!");
                        shouldChangeDevice = true;
                    }
                } else if (!Objects.equals(currentDeviceName, userSelectedDevice) && currentDevices.allDevices().contains(userSelectedDevice)) {
                    LOGGER.info("Preferred audio device has become available!");
                    shouldChangeDevice = true;
                }
                this.lastSeenDevices = currentDevices;
            }
            return shouldChangeDevice;
        }
    }
    public void tick(final boolean paused) {
        if (this.shouldChangeDevice()) {
            this.reload();
        }
        if (!paused) {
            this.tickInGameSound();
        } else {
            this.tickMusicWhenPaused();
        }
        this.channelAccess.scheduleTick();
        if (this.needsRestore && !paused) {
            if (this.loaded) {
                for (SoundInstance sound : this.savedLoopingSounds) {
                    try {
                        this.play(sound);
                    } catch (Exception ignored) {
                    }
                }
                this.savedLoopingSounds.clear();
            }
            this.needsRestore = false;
        }
    }
    private void tickInGameSound() {
        ++this.tickCount;
        this.queuedTickableSounds.stream().filter(SoundInstance::canPlaySound).forEach(this::play);
        this.queuedTickableSounds.clear();
        for(TickableSoundInstance instance : this.tickingSounds) {
            if (!instance.canPlaySound()) {
                this.stop(instance);
            }
            instance.tick();
            if (instance.isStopped()) {
                this.stop(instance);
            } else {
                float volume = this.calculateVolume(instance);
                float pitch = this.calculatePitch(instance);
                Vec3 position = new Vec3(instance.getX(), instance.getY(), instance.getZ());
                ChannelAccess.ChannelHandle handle = (ChannelAccess.ChannelHandle)this.instanceToChannel.get(instance);
                if (handle != null) {
                    handle.execute((channel) -> {
                        channel.setVolume(volume);
                        channel.setPitch(pitch);
                        channel.setSelfPosition(position);
                    });
                }
            }
        }
        Iterator<Map.Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator = this.instanceToChannel.entrySet().iterator();
        while(iterator.hasNext()) {
            Map.Entry<SoundInstance, ChannelAccess.ChannelHandle> entry = (Map.Entry)iterator.next();
            ChannelAccess.ChannelHandle handle = (ChannelAccess.ChannelHandle)entry.getValue();
            SoundInstance instance = (SoundInstance)entry.getKey();
            if (handle.isStopped()) {
                int minDeleteTime = (Integer)this.soundDeleteTime.get(instance);
                if (minDeleteTime <= this.tickCount) {
                    if (shouldLoopManually(instance)) {
                        this.queuedSounds.put(instance, this.tickCount + instance.getDelay());
                    }
                    iterator.remove();
                    LOGGER.debug(MARKER, "Removed channel {} because it's not playing anymore", handle);
                    this.soundDeleteTime.remove(instance);
                    try {
                        this.instanceBySource.remove(instance.getSource(), instance);
                    } catch (RuntimeException var7) {
                    }
                    if (instance instanceof TickableSoundInstance) {
                        this.tickingSounds.remove(instance);
                    }
                }
            }
        }
        Iterator<Map.Entry<SoundInstance, Integer>> queueIterator = this.queuedSounds.entrySet().iterator();
        while(queueIterator.hasNext()) {
            Map.Entry<SoundInstance, Integer> next = (Map.Entry)queueIterator.next();
            if (this.tickCount >= (Integer)next.getValue()) {
                SoundInstance instance = (SoundInstance)next.getKey();
                if (instance instanceof TickableSoundInstance) {
                    TickableSoundInstance tickableSoundInstance = (TickableSoundInstance)instance;
                    tickableSoundInstance.tick();
                }
                this.play(instance);
                queueIterator.remove();
            }
        }
    }
    private void tickMusicWhenPaused() {
        Iterator<Map.Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator = this.instanceToChannel.entrySet().iterator();
        while(iterator.hasNext()) {
            Map.Entry<SoundInstance, ChannelAccess.ChannelHandle> entry = (Map.Entry)iterator.next();
            ChannelAccess.ChannelHandle handle = (ChannelAccess.ChannelHandle)entry.getValue();
            SoundInstance instance = (SoundInstance)entry.getKey();
            if (instance.getSource() == SoundSource.MUSIC && handle.isStopped()) {
                iterator.remove();
                LOGGER.debug(MARKER, "Removed channel {} because it's not playing anymore", handle);
                this.soundDeleteTime.remove(instance);
                this.instanceBySource.remove(instance.getSource(), instance);
            }
        }
    }
    private static boolean requiresManualLooping(final SoundInstance instance) {
        return instance.getDelay() > 0;
    }
    private static boolean shouldLoopManually(final SoundInstance instance) {
        return instance.isLooping() && requiresManualLooping(instance);
    }
    private static boolean shouldLoopAutomatically(final SoundInstance instance) {
        return instance.isLooping() && !requiresManualLooping(instance);
    }
    public boolean isActive(final SoundInstance instance) {
        if (!this.loaded) {
            return false;
        } else {
            return this.soundDeleteTime.containsKey(instance) && (Integer)this.soundDeleteTime.get(instance) <= this.tickCount ? true : this.instanceToChannel.containsKey(instance);
        }
    }
    public PlayResult play(final SoundInstance instance) {
        if (!this.loaded) {
            return SoundEngine.PlayResult.NOT_STARTED;
        } else if (!instance.canPlaySound()) {
            return SoundEngine.PlayResult.NOT_STARTED;
        } else {
            WeighedSoundEvents soundEvent = instance.resolve(this.soundManager);
            Identifier eventLocation = instance.getIdentifier();
            if (soundEvent == null) {
                if (ONLY_WARN_ONCE.add(eventLocation)) {
                    LOGGER.warn(MARKER, "Unable to play unknown soundEvent: {}", eventLocation);
                }
                if (!SharedConstants.DEBUG_SUBTITLES) {
                    return SoundEngine.PlayResult.NOT_STARTED;
                }
                soundEvent = new WeighedSoundEvents(eventLocation, "FOR THE DEBUG!");
            }
            Sound sound = instance.getSound();
            if (sound == SoundManager.INTENTIONALLY_EMPTY_SOUND) {
                return SoundEngine.PlayResult.NOT_STARTED;
            } else if (sound == SoundManager.EMPTY_SOUND) {
                if (ONLY_WARN_ONCE.add(eventLocation)) {
                    LOGGER.warn(MARKER, "Unable to play empty soundEvent: {}", eventLocation);
                }
                return SoundEngine.PlayResult.NOT_STARTED;
            } else {
                float instanceVolume = instance.getVolume();
                float attenuationDistance = Math.max(instanceVolume, 1.0F) * (float)sound.getAttenuationDistance();
                SoundSource soundSource = instance.getSource();
                float volume = this.calculateVolume(instanceVolume, soundSource);
                float pitch = this.calculatePitch(instance);
                SoundInstance.Attenuation attenuation = instance.getAttenuation();
                boolean isRelative = instance.isRelative();
                if (!this.listeners.isEmpty()) {
                    float range = !isRelative && attenuation != Attenuation.NONE ? attenuationDistance : Float.POSITIVE_INFINITY;
                    for(SoundEventListener listener : this.listeners) {
                        listener.onPlaySound(instance, soundEvent, range);
                    }
                }
                boolean startedSilently = false;
                if (volume == 0.0F) {
                    if (!instance.canStartSilent() && soundSource != SoundSource.MUSIC) {
                        LOGGER.debug(MARKER, "Skipped playing sound {}, volume was zero.", sound.getLocation());
                        return SoundEngine.PlayResult.NOT_STARTED;
                    }
                    startedSilently = true;
                }
                Vec3 position = new Vec3(instance.getX(), instance.getY(), instance.getZ());
                boolean isLooping = shouldLoopAutomatically(instance);
                boolean isStreaming = sound.shouldStream();
                CompletableFuture<ChannelAccess.ChannelHandle> handleFuture = this.channelAccess.createHandle(sound.shouldStream() ? Pool.STREAMING : Pool.STATIC);
                ChannelAccess.ChannelHandle handle = (ChannelAccess.ChannelHandle)handleFuture.join();
                if (handle == null) {
                    if (SharedConstants.IS_RUNNING_IN_IDE) {
                        LOGGER.warn("Failed to create new sound handle");
                    }
                    return SoundEngine.PlayResult.NOT_STARTED;
                } else {
                    LOGGER.debug(MARKER, "Playing sound {} for event {}", sound.getLocation(), eventLocation);
                    this.soundDeleteTime.put(instance, this.tickCount + 20);
                    this.instanceToChannel.put(instance, handle);
                    this.instanceBySource.put(soundSource, instance);
                    handle.execute((channel) -> {
                        channel.setPitch(pitch);
                        channel.setVolume(volume);
                        if (attenuation == Attenuation.LINEAR) {
                            channel.linearAttenuation(attenuationDistance);
                        } else {
                            channel.disableAttenuation();
                        }
                        channel.setLooping(isLooping && !isStreaming);
                        channel.setSelfPosition(position);
                        channel.setRelative(isRelative);
                    });
                    if (!isStreaming) {
                        this.soundBuffers.getCompleteBuffer(sound.getPath()).thenAccept((soundBuffer) -> handle.execute((channel) -> {
                                channel.attachStaticBuffer(soundBuffer);
                                channel.play();
                            }));
                    } else {
                        this.soundBuffers.getStream(sound.getPath(), isLooping).thenAccept((stream) -> handle.execute((channel) -> {
                                channel.attachBufferStream(stream);
                                channel.play();
                            }));
                    }
                    if (instance instanceof TickableSoundInstance) {
                        TickableSoundInstance tickableSoundInstance = (TickableSoundInstance)instance;
                        this.tickingSounds.add(tickableSoundInstance);
                    }
                    return startedSilently ? SoundEngine.PlayResult.STARTED_SILENTLY : SoundEngine.PlayResult.STARTED;
                }
            }
        }
    }
    public void queueTickingSound(final TickableSoundInstance tickableSoundInstance) {
        this.queuedTickableSounds.add(tickableSoundInstance);
    }
    public void requestPreload(final Sound sound) {
        this.preloadQueue.add(sound);
    }
    private float calculatePitch(final SoundInstance instance) {
        return Mth.clamp(instance.getPitch(), 0.5F, 2.0F);
    }
    private float calculateVolume(final SoundInstance instance) {
        return this.calculateVolume(instance.getVolume(), instance.getSource());
    }
    private float calculateVolume(final float volume, final SoundSource source) {
        return Mth.clamp(volume, 0.0F, 1.0F) * Mth.clamp(this.options.getFinalSoundSourceVolume(source), 0.0F, 1.0F) * this.gainBySource.getFloat(source);
    }
    public void pauseAllExcept(final SoundSource... ignoredSources) {
        if (this.loaded) {
            for(Map.Entry<SoundInstance, ChannelAccess.ChannelHandle> instance : this.instanceToChannel.entrySet()) {
                if (!List.of(ignoredSources).contains(((SoundInstance)instance.getKey()).getSource())) {
                    ((ChannelAccess.ChannelHandle)instance.getValue()).execute(Channel::pause);
                }
            }
        }
    }
    public void resume() {
        if (this.loaded) {
            this.channelAccess.executeOnChannels((channels) -> channels.forEach(Channel::unpause));
        }
    }
    public void playDelayed(final SoundInstance instance, final int delay) {
        this.queuedSounds.put(instance, this.tickCount + delay);
    }
    public void updateSource(final Camera camera) {
        if (this.loaded && camera.isInitialized()) {
            ListenerTransform transform = new ListenerTransform(camera.position(), new Vec3(camera.forwardVector()), new Vec3(camera.upVector()));
            this.executor.execute(() -> this.listener.setTransform(transform));
        }
    }
    public void stop(final @Nullable Identifier sound, final @Nullable SoundSource source) {
        if (source != null) {
            for(SoundInstance instance : this.instanceBySource.get(source)) {
                if (sound == null || instance.getIdentifier().equals(sound)) {
                    this.stop(instance);
                }
            }
        } else if (sound == null) {
            this.stopAll();
        } else {
            for(SoundInstance instance : this.instanceToChannel.keySet()) {
                if (instance.getIdentifier().equals(sound)) {
                    this.stop(instance);
                }
            }
        }
    }
    public String getChannelDebugString() {
        return this.library.getChannelDebugString();
    }
    public void getSoundCacheDebugStats(final SoundBufferLibrary.DebugOutput output) {
        this.soundBuffers.enumerate(output);
    }
    public List<String> getAvailableSoundDevices() {
        return this.deviceTracker.currentDevices().allDevices();
    }
    public ListenerTransform getListenerTransform() {
        return this.listener.getTransform();
    }
    @Environment(EnvType.CLIENT)
    public static enum PlayResult {
        STARTED,
        STARTED_SILENTLY,
        NOT_STARTED;
    }
}

Confirmed on version 26.3 Pre-Release 1.

earpiercing

(Unassigned)

Confirmed

Gameplay

Low

Sound

1.16.5, 21w08b, 21w11a, 21w13a, 21w14a, , 23w14a, 1.20.1, 23w44a, 1.20.4, 1.21

Retrieved