SoundEngine.java

net.minecraft.client.sounds.SoundEngine

信息

  • 全限定名:net.minecraft.client.sounds.SoundEngine
  • 类型:public class
  • 包:net.minecraft.client.sounds
  • 源码路径:src/main/java/net/minecraft/client/sounds/SoundEngine.java
  • 起始行号:L47
  • 职责:

    TODO

字段/常量

  • MARKER

    • 类型: Marker
    • 修饰符: private static final
    • 源码定位: L48
    • 说明:

      TODO

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L49
    • 说明:

      TODO

  • PITCH_MIN

    • 类型: float
    • 修饰符: private static final
    • 源码定位: L50
    • 说明:

      TODO

  • PITCH_MAX

    • 类型: float
    • 修饰符: private static final
    • 源码定位: L51
    • 说明:

      TODO

  • VOLUME_MIN

    • 类型: float
    • 修饰符: private static final
    • 源码定位: L52
    • 说明:

      TODO

  • VOLUME_MAX

    • 类型: float
    • 修饰符: private static final
    • 源码定位: L53
    • 说明:

      TODO

  • MIN_SOURCE_LIFETIME

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L54
    • 说明:

      TODO

  • ONLY_WARN_ONCE

    • 类型: Set<Identifier>
    • 修饰符: private static final
    • 源码定位: L55
    • 说明:

      TODO

  • MISSING_SOUND

    • 类型: String
    • 修饰符: public static final
    • 源码定位: L56
    • 说明:

      TODO

  • OPEN_AL_SOFT_PREFIX

    • 类型: String
    • 修饰符: public static final
    • 源码定位: L57
    • 说明:

      TODO

  • OPEN_AL_SOFT_PREFIX_LENGTH

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L58
    • 说明:

      TODO

  • soundManager

    • 类型: SoundManager
    • 修饰符: private final
    • 源码定位: L59
    • 说明:

      TODO

  • options

    • 类型: Options
    • 修饰符: private final
    • 源码定位: L60
    • 说明:

      TODO

  • loaded

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L61
    • 说明:

      TODO

  • library

    • 类型: Library
    • 修饰符: private final
    • 源码定位: L62
    • 说明:

      TODO

  • listener

    • 类型: Listener
    • 修饰符: private final
    • 源码定位: L63
    • 说明:

      TODO

  • soundBuffers

    • 类型: SoundBufferLibrary
    • 修饰符: private final
    • 源码定位: L64
    • 说明:

      TODO

  • executor

    • 类型: SoundEngineExecutor
    • 修饰符: private final
    • 源码定位: L65
    • 说明:

      TODO

  • channelAccess

    • 类型: ChannelAccess
    • 修饰符: private final
    • 源码定位: L66
    • 说明:

      TODO

  • tickCount

    • 类型: int
    • 修饰符: private
    • 源码定位: L67
    • 说明:

      TODO

  • lastSeenDevices

    • 类型: DeviceList
    • 修饰符: private
    • 源码定位: L68
    • 说明:

      TODO

  • deviceTracker

    • 类型: DeviceTracker
    • 修饰符: private final
    • 源码定位: L69
    • 说明:

      TODO

  • instanceToChannel

    • 类型: Map<SoundInstance,ChannelAccess.ChannelHandle>
    • 修饰符: private final
    • 源码定位: L70
    • 说明:

      TODO

  • instanceBySource

    • 类型: Multimap<SoundSource,SoundInstance>
    • 修饰符: private final
    • 源码定位: L71
    • 说明:

      TODO

  • gainBySource

    • 类型: Object2FloatMap<SoundSource>
    • 修饰符: private final
    • 源码定位: L72
    • 说明:

      TODO

  • tickingSounds

    • 类型: List<TickableSoundInstance>
    • 修饰符: private final
    • 源码定位: L73
    • 说明:

      TODO

  • queuedSounds

    • 类型: Map<SoundInstance,Integer>
    • 修饰符: private final
    • 源码定位: L74
    • 说明:

      TODO

  • soundDeleteTime

    • 类型: Map<SoundInstance,Integer>
    • 修饰符: private final
    • 源码定位: L75
    • 说明:

      TODO

  • listeners

    • 类型: List<SoundEventListener>
    • 修饰符: private final
    • 源码定位: L76
    • 说明:

      TODO

  • queuedTickableSounds

    • 类型: List<TickableSoundInstance>
    • 修饰符: private final
    • 源码定位: L77
    • 说明:

      TODO

  • preloadQueue

    • 类型: List<Sound>
    • 修饰符: private final
    • 源码定位: L78
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.sounds.SoundEngine.PlayResult
    • 类型: enum
    • 修饰符: public static
    • 源码定位: L532
    • 说明:

      TODO

构造器

public SoundEngine(SoundManager soundManager, Options options, ResourceProvider resourceProvider) @ L80

  • 构造器名:SoundEngine
  • 源码定位:L80
  • 修饰符:public

参数:

  • soundManager: SoundManager
  • options: Options
  • resourceProvider: ResourceProvider

说明:

TODO

方法

下面的方法块按源码顺序生成。

public void reload() @ L87

  • 方法名:reload
  • 源码定位:L87
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

private synchronized void loadLibrary() @ L104

  • 方法名:loadLibrary
  • 源码定位:L104
  • 返回类型:void
  • 修饰符:private synchronized

参数:

说明:

TODO

public void refreshCategoryVolume(SoundSource source) @ L120

  • 方法名:refreshCategoryVolume
  • 源码定位:L120
  • 返回类型:void
  • 修饰符:public

参数:

  • source: SoundSource

说明:

TODO

public void destroy() @ L131

  • 方法名:destroy
  • 源码定位:L131
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public void emergencyShutdown() @ L140

  • 方法名:emergencyShutdown
  • 源码定位:L140
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public void stop(SoundInstance soundInstance) @ L146

  • 方法名:stop
  • 源码定位:L146
  • 返回类型:void
  • 修饰符:public

参数:

  • soundInstance: SoundInstance

说明:

TODO

public void updateCategoryVolume(SoundSource source, float gain) @ L155

  • 方法名:updateCategoryVolume
  • 源码定位:L155
  • 返回类型:void
  • 修饰符:public

参数:

  • source: SoundSource
  • gain: float

说明:

TODO

public void stopAll() @ L160

  • 方法名:stopAll
  • 源码定位:L160
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public void addEventListener(SoundEventListener listener) @ L175

  • 方法名:addEventListener
  • 源码定位:L175
  • 返回类型:void
  • 修饰符:public

参数:

  • listener: SoundEventListener

说明:

TODO

public void removeEventListener(SoundEventListener listener) @ L179

  • 方法名:removeEventListener
  • 源码定位:L179
  • 返回类型:void
  • 修饰符:public

参数:

  • listener: SoundEventListener

说明:

TODO

private boolean shouldChangeDevice() @ L183

  • 方法名:shouldChangeDevice
  • 源码定位:L183
  • 返回类型:boolean
  • 修饰符:private

参数:

说明:

TODO

public void tick(boolean paused) @ L218

  • 方法名:tick
  • 源码定位:L218
  • 返回类型:void
  • 修饰符:public

参数:

  • paused: boolean

说明:

TODO

private void tickInGameSound() @ L232

  • 方法名:tickInGameSound
  • 源码定位:L232
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private void tickMusicWhenPaused() @ L305

  • 方法名:tickMusicWhenPaused
  • 源码定位:L305
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private static boolean requiresManualLooping(SoundInstance instance) @ L321

  • 方法名:requiresManualLooping
  • 源码定位:L321
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • instance: SoundInstance

说明:

TODO

private static boolean shouldLoopManually(SoundInstance instance) @ L325

  • 方法名:shouldLoopManually
  • 源码定位:L325
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • instance: SoundInstance

说明:

TODO

private static boolean shouldLoopAutomatically(SoundInstance instance) @ L329

  • 方法名:shouldLoopAutomatically
  • 源码定位:L329
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • instance: SoundInstance

说明:

TODO

public boolean isActive(SoundInstance instance) @ L333

  • 方法名:isActive
  • 源码定位:L333
  • 返回类型:boolean
  • 修饰符:public

参数:

  • instance: SoundInstance

说明:

TODO

public SoundEngine.PlayResult play(SoundInstance instance) @ L343

  • 方法名:play
  • 源码定位:L343
  • 返回类型:SoundEngine.PlayResult
  • 修饰符:public

参数:

  • instance: SoundInstance

说明:

TODO

public void queueTickingSound(TickableSoundInstance tickableSoundInstance) @ L450

  • 方法名:queueTickingSound
  • 源码定位:L450
  • 返回类型:void
  • 修饰符:public

参数:

  • tickableSoundInstance: TickableSoundInstance

说明:

TODO

public void requestPreload(Sound sound) @ L454

  • 方法名:requestPreload
  • 源码定位:L454
  • 返回类型:void
  • 修饰符:public

参数:

  • sound: Sound

说明:

TODO

private float calculatePitch(SoundInstance instance) @ L458

  • 方法名:calculatePitch
  • 源码定位:L458
  • 返回类型:float
  • 修饰符:private

参数:

  • instance: SoundInstance

说明:

TODO

private float calculateVolume(SoundInstance instance) @ L462

  • 方法名:calculateVolume
  • 源码定位:L462
  • 返回类型:float
  • 修饰符:private

参数:

  • instance: SoundInstance

说明:

TODO

private float calculateVolume(float volume, SoundSource source) @ L466

  • 方法名:calculateVolume
  • 源码定位:L466
  • 返回类型:float
  • 修饰符:private

参数:

  • volume: float
  • source: SoundSource

说明:

TODO

public void pauseAllExcept(SoundSource... ignoredSources) @ L470

  • 方法名:pauseAllExcept
  • 源码定位:L470
  • 返回类型:void
  • 修饰符:public

参数:

  • ignoredSources: SoundSource…

说明:

TODO

public void resume() @ L480

  • 方法名:resume
  • 源码定位:L480
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

public void playDelayed(SoundInstance instance, int delay) @ L486

  • 方法名:playDelayed
  • 源码定位:L486
  • 返回类型:void
  • 修饰符:public

参数:

  • instance: SoundInstance
  • delay: int

说明:

TODO

public void updateSource(Camera camera) @ L490

  • 方法名:updateSource
  • 源码定位:L490
  • 返回类型:void
  • 修饰符:public

参数:

  • camera: Camera

说明:

TODO

public void stop(Identifier sound, SoundSource source) @ L497

  • 方法名:stop
  • 源码定位:L497
  • 返回类型:void
  • 修饰符:public

参数:

  • sound: Identifier
  • source: SoundSource

说明:

TODO

public String getChannelDebugString() @ L515

  • 方法名:getChannelDebugString
  • 源码定位:L515
  • 返回类型:String
  • 修饰符:public

参数:

说明:

TODO

public void getSoundCacheDebugStats(SoundBufferLibrary.DebugOutput output) @ L519

  • 方法名:getSoundCacheDebugStats
  • 源码定位:L519
  • 返回类型:void
  • 修饰符:public

参数:

  • output: SoundBufferLibrary.DebugOutput

说明:

TODO

public List<String> getAvailableSoundDevices() @ L523

  • 方法名:getAvailableSoundDevices
  • 源码定位:L523
  • 返回类型:List
  • 修饰符:public

参数:

说明:

TODO

public ListenerTransform getListenerTransform() @ L527

  • 方法名:getListenerTransform
  • 源码定位:L527
  • 返回类型:ListenerTransform
  • 修饰符:public

参数:

说明:

TODO

代码

@OnlyIn(Dist.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 = this.library.getListener();
    private final SoundBufferLibrary soundBuffers;
    private final SoundEngineExecutor executor = new SoundEngineExecutor();
    private final ChannelAccess channelAccess = new ChannelAccess(this.library, this.executor);
    private int tickCount;
    private DeviceList lastSeenDevices;
    private final DeviceTracker deviceTracker = Library.createDeviceTracker();
    private final Map<SoundInstance, ChannelAccess.ChannelHandle> instanceToChannel = Maps.newHashMap();
    private final Multimap<SoundSource, SoundInstance> instanceBySource = HashMultimap.create();
    private final Object2FloatMap<SoundSource> gainBySource = Util.make(new Object2FloatOpenHashMap<>(), map -> map.defaultReturnValue(1.0F));
    private final List<TickableSoundInstance> tickingSounds = Lists.newArrayList();
    private final Map<SoundInstance, Integer> queuedSounds = Maps.newHashMap();
    private final Map<SoundInstance, Integer> soundDeleteTime = Maps.newHashMap();
    private final List<SoundEventListener> listeners = Lists.newArrayList();
    private final List<TickableSoundInstance> queuedTickableSounds = Lists.newArrayList();
    private final List<Sound> preloadQueue = Lists.newArrayList();
 
    public SoundEngine(SoundManager soundManager, Options options, ResourceProvider resourceProvider) {
        this.soundManager = soundManager;
        this.options = options;
        this.soundBuffers = new SoundBufferLibrary(resourceProvider);
        this.lastSeenDevices = this.deviceTracker.currentDevices();
    }
 
    public void reload() {
        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 = this.options.soundDevice().get();
                DeviceList currentDevices = this.deviceTracker.currentDevices();
                this.library.init(Options.isSoundDeviceDefault(soundDevice) ? null : soundDevice, currentDevices, this.options.directionalAudio().get());
                this.listener.reset();
                this.soundBuffers.preload(this.preloadQueue).thenRun(this.preloadQueue::clear);
                this.loaded = true;
                LOGGER.info(MARKER, "Sound engine started");
            } catch (RuntimeException var3) {
                LOGGER.error(MARKER, "Error starting SoundSystem. Turning off sounds & music", (Throwable)var3);
            }
        }
    }
 
    public void refreshCategoryVolume(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(SoundInstance soundInstance) {
        if (this.loaded) {
            ChannelAccess.ChannelHandle handle = this.instanceToChannel.get(soundInstance);
            if (handle != null) {
                handle.execute(Channel::stop);
            }
        }
    }
 
    public void updateCategoryVolume(SoundSource source, 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(SoundEventListener listener) {
        this.listeners.add(listener);
    }
 
    public void removeEventListener(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 disapeared!");
                    shouldChangeDevice = true;
                }
 
                String userSelectedDevice = 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(boolean paused) {
        if (this.shouldChangeDevice()) {
            this.reload();
        }
 
        if (!paused) {
            this.tickInGameSound();
        } else {
            this.tickMusicWhenPaused();
        }
 
        this.channelAccess.scheduleTick();
    }
 
    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 = this.instanceToChannel.get(instance);
                if (handle != null) {
                    handle.execute(channel -> {
                        channel.setVolume(volume);
                        channel.setPitch(pitch);
                        channel.setSelfPosition(position);
                    });
                }
            }
        }
 
        Iterator<Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator = this.instanceToChannel.entrySet().iterator();
 
        while (iterator.hasNext()) {
            Entry<SoundInstance, ChannelAccess.ChannelHandle> entry = iterator.next();
            ChannelAccess.ChannelHandle handle = entry.getValue();
            SoundInstance instance = entry.getKey();
            if (handle.isStopped()) {
                int minDeleteTime = 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<Entry<SoundInstance, Integer>> queueIterator = this.queuedSounds.entrySet().iterator();
 
        while (queueIterator.hasNext()) {
            Entry<SoundInstance, Integer> next = queueIterator.next();
            if (this.tickCount >= next.getValue()) {
                SoundInstance instance = next.getKey();
                if (instance instanceof TickableSoundInstance) {
                    ((TickableSoundInstance)instance).tick();
                }
 
                this.play(instance);
                queueIterator.remove();
            }
        }
    }
 
    private void tickMusicWhenPaused() {
        Iterator<Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator = this.instanceToChannel.entrySet().iterator();
 
        while (iterator.hasNext()) {
            Entry<SoundInstance, ChannelAccess.ChannelHandle> entry = iterator.next();
            ChannelAccess.ChannelHandle handle = entry.getValue();
            SoundInstance instance = 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(SoundInstance instance) {
        return instance.getDelay() > 0;
    }
 
    private static boolean shouldLoopManually(SoundInstance instance) {
        return instance.isLooping() && requiresManualLooping(instance);
    }
 
    private static boolean shouldLoopAutomatically(SoundInstance instance) {
        return instance.isLooping() && !requiresManualLooping(instance);
    }
 
    public boolean isActive(SoundInstance instance) {
        if (!this.loaded) {
            return false;
        } else {
            return this.soundDeleteTime.containsKey(instance) && this.soundDeleteTime.get(instance) <= this.tickCount
                ? true
                : this.instanceToChannel.containsKey(instance);
        }
    }
 
    public SoundEngine.PlayResult play(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) * 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 != SoundInstance.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() ? Library.Pool.STREAMING : Library.Pool.STATIC);
                ChannelAccess.ChannelHandle handle = 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 == SoundInstance.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) {
                        this.tickingSounds.add((TickableSoundInstance)instance);
                    }
 
                    return startedSilently ? SoundEngine.PlayResult.STARTED_SILENTLY : SoundEngine.PlayResult.STARTED;
                }
            }
        }
    }
 
    public void queueTickingSound(TickableSoundInstance tickableSoundInstance) {
        this.queuedTickableSounds.add(tickableSoundInstance);
    }
 
    public void requestPreload(Sound sound) {
        this.preloadQueue.add(sound);
    }
 
    private float calculatePitch(SoundInstance instance) {
        return Mth.clamp(instance.getPitch(), 0.5F, 2.0F);
    }
 
    private float calculateVolume(SoundInstance instance) {
        return this.calculateVolume(instance.getVolume(), instance.getSource());
    }
 
    private float calculateVolume(float volume, 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(SoundSource... ignoredSources) {
        if (this.loaded) {
            for (Entry<SoundInstance, ChannelAccess.ChannelHandle> instance : this.instanceToChannel.entrySet()) {
                if (!List.of(ignoredSources).contains(instance.getKey().getSource())) {
                    instance.getValue().execute(Channel::pause);
                }
            }
        }
    }
 
    public void resume() {
        if (this.loaded) {
            this.channelAccess.executeOnChannels(channels -> channels.forEach(Channel::unpause));
        }
    }
 
    public void playDelayed(SoundInstance instance, int delay) {
        this.queuedSounds.put(instance, this.tickCount + delay);
    }
 
    public void updateSource(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(@Nullable Identifier sound, @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 instancex : this.instanceToChannel.keySet()) {
                if (instancex.getIdentifier().equals(sound)) {
                    this.stop(instancex);
                }
            }
        }
    }
 
    public String getChannelDebugString() {
        return this.library.getChannelDebugString();
    }
 
    public void getSoundCacheDebugStats(SoundBufferLibrary.DebugOutput output) {
        this.soundBuffers.enumerate(output);
    }
 
    public List<String> getAvailableSoundDevices() {
        return this.deviceTracker.currentDevices().allDevices();
    }
 
    public ListenerTransform getListenerTransform() {
        return this.listener.getTransform();
    }
 
    @OnlyIn(Dist.CLIENT)
    public static enum PlayResult {
        STARTED,
        STARTED_SILENTLY,
        NOT_STARTED;
    }
}

引用的其他类