Spaces:
Build error
Build error
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.Library; | |
import com.mojang.blaze3d.audio.Listener; | |
import com.mojang.blaze3d.audio.ListenerTransform; | |
import com.mojang.blaze3d.audio.SoundBuffer; | |
import com.mojang.logging.LogUtils; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.Map.Entry; | |
import java.util.concurrent.CompletableFuture; | |
import java.util.concurrent.atomic.AtomicReference; | |
import java.util.stream.Stream; | |
import javax.annotation.Nullable; | |
import net.minecraft.SharedConstants; | |
import net.minecraft.Util; | |
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.core.registries.BuiltInRegistries; | |
import net.minecraft.resources.ResourceLocation; | |
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.world.phys.Vec3; | |
import net.minecraftforge.api.distmarker.Dist; | |
import net.minecraftforge.api.distmarker.OnlyIn; | |
import org.slf4j.Logger; | |
import org.slf4j.Marker; | |
import org.slf4j.MarkerFactory; | |
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<ResourceLocation> ONLY_WARN_ONCE = Sets.newHashSet(); | |
private static final long DEFAULT_DEVICE_CHECK_INTERVAL_MS = 1000L; | |
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 long lastDeviceCheckTime; | |
private final AtomicReference<SoundEngine.DeviceCheckState> devicePoolState = new AtomicReference<>(SoundEngine.DeviceCheckState.NO_CHANGE); | |
private final Map<SoundInstance, ChannelAccess.ChannelHandle> instanceToChannel = Maps.newHashMap(); | |
private final Multimap<SoundSource, SoundInstance> instanceBySource = HashMultimap.create(); | |
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 p_120236_, Options p_120237_, ResourceProvider p_249332_) { | |
this.soundManager = p_120236_; | |
this.options = p_120237_; | |
this.soundBuffers = new SoundBufferLibrary(p_249332_); | |
} | |
public void reload() { | |
ONLY_WARN_ONCE.clear(); | |
for (SoundEvent soundevent : BuiltInRegistries.SOUND_EVENT) { | |
if (soundevent != SoundEvents.EMPTY) { | |
ResourceLocation resourcelocation = soundevent.location(); | |
if (this.soundManager.getSoundEvent(resourcelocation) == null) { | |
LOGGER.warn("Missing sound for event: {}", BuiltInRegistries.SOUND_EVENT.getKey(soundevent)); | |
ONLY_WARN_ONCE.add(resourcelocation); | |
} | |
} | |
} | |
this.destroy(); | |
this.loadLibrary(); | |
} | |
private synchronized void loadLibrary() { | |
if (!this.loaded) { | |
try { | |
String s = this.options.soundDevice().get(); | |
this.library.init("".equals(s) ? null : s, this.options.directionalAudio().get()); | |
this.listener.reset(); | |
this.listener.setGain(this.options.getSoundSourceVolume(SoundSource.MASTER)); | |
this.soundBuffers.preload(this.preloadQueue).thenRun(this.preloadQueue::clear); | |
this.loaded = true; | |
LOGGER.info(MARKER, "Sound engine started"); | |
} catch (RuntimeException runtimeexception) { | |
LOGGER.error(MARKER, "Error starting SoundSystem. Turning off sounds & music", (Throwable)runtimeexception); | |
} | |
} | |
} | |
private float getVolume( { SoundSource p_120259_) | |
return p_120259_ != null && p_120259_ != SoundSource.MASTER ? this.options.getSoundSourceVolume(p_120259_) : 1.0F; | |
} | |
public void updateCategoryVolume(SoundSource p_120261_, float p_120262_) { | |
if (this.loaded) { | |
if (p_120261_ == SoundSource.MASTER) { | |
this.listener.setGain(p_120262_); | |
} else { | |
this.instanceToChannel.forEach((p_120280_, p_120281_) -> { | |
float f = this.calculateVolume(p_120280_); | |
p_120281_.execute(p_174990_ -> { | |
if (f <= 0.0F) { | |
p_174990_.stop(); | |
} else { | |
p_174990_.setVolume(f); | |
} | |
}); | |
}); | |
} | |
} | |
} | |
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 p_120275_) { | |
if (this.loaded) { | |
ChannelAccess.ChannelHandle channelaccess$channelhandle = this.instanceToChannel.get(p_120275_); | |
if (channelaccess$channelhandle != null) { | |
channelaccess$channelhandle.execute(Channel::stop); | |
} | |
} | |
} | |
public void setVolume(SoundInstance p_377242_, float p_376894_) { | |
if (this.loaded) { | |
ChannelAccess.ChannelHandle channelaccess$channelhandle = this.instanceToChannel.get(p_377242_); | |
if (channelaccess$channelhandle != null) { | |
channelaccess$channelhandle.execute(p_374745_ -> p_374745_.setVolume(p_376894_ * this.calculateVolume(p_377242_))); | |
} | |
} | |
} | |
public void stopAll() { | |
if (this.loaded) { | |
this.executor.flush(); | |
this.instanceToChannel.values().forEach(p_120288_ -> p_120288_.execute(Channel::stop)); | |
this.instanceToChannel.clear(); | |
this.channelAccess.clear(); | |
this.queuedSounds.clear(); | |
this.tickingSounds.clear(); | |
this.instanceBySource.clear(); | |
this.soundDeleteTime.clear(); | |
this.queuedTickableSounds.clear(); | |
} | |
} | |
public void addEventListener(SoundEventListener p_120296_) { | |
this.listeners.add(p_120296_); | |
} | |
public void removeEventListener(SoundEventListener p_120308_) { | |
this.listeners.remove(p_120308_); | |
} | |
private boolean shouldChangeDevice() { | |
if (this.library.isCurrentDeviceDisconnected()) { | |
LOGGER.info("Audio device was lost!"); | |
return true; | |
} else { | |
long i = Util.getMillis(); | |
boolean flag = i - this.lastDeviceCheckTime >= 1000L; | |
if (flag) { | |
this.lastDeviceCheckTime = i; | |
if (this.devicePoolState.compareAndSet(SoundEngine.DeviceCheckState.NO_CHANGE, SoundEngine.DeviceCheckState.ONGOING)) { | |
String s = this.options.soundDevice().get(); | |
Util.ioPool().execute(() -> { | |
if ("".equals(s)) { | |
if (this.library.hasDefaultDeviceChanged()) { | |
LOGGER.info("System default audio device has changed!"); | |
this.devicePoolState.compareAndSet(SoundEngine.DeviceCheckState.ONGOING, SoundEngine.DeviceCheckState.CHANGE_DETECTED); | |
} | |
} else if (!this.library.getCurrentDeviceName().equals(s) && this.library.getAvailableSoundDevices().contains(s)) { | |
LOGGER.info("Preferred audio device has become available!"); | |
this.devicePoolState.compareAndSet(SoundEngine.DeviceCheckState.ONGOING, SoundEngine.DeviceCheckState.CHANGE_DETECTED); | |
} | |
this.devicePoolState.compareAndSet(SoundEngine.DeviceCheckState.ONGOING, SoundEngine.DeviceCheckState.NO_CHANGE); | |
}); | |
} | |
} | |
return this.devicePoolState.compareAndSet(SoundEngine.DeviceCheckState.CHANGE_DETECTED, SoundEngine.DeviceCheckState.NO_CHANGE); | |
} | |
} | |
public void tick(boolean p_120303_) { | |
if (this.shouldChangeDevice()) { | |
this.reload(); | |
} | |
if (!p_120303_) { | |
this.tickNonPaused(); | |
} | |
this.channelAccess.scheduleTick(); | |
} | |
private void tickNonPaused() { | |
this.tickCount++; | |
this.queuedTickableSounds.stream().filter(SoundInstance::canPlaySound).forEach(this::play); | |
this.queuedTickableSounds.clear(); | |
for (TickableSoundInstance tickablesoundinstance : this.tickingSounds) { | |
if (!tickablesoundinstance.canPlaySound()) { | |
this.stop(tickablesoundinstance); | |
} | |
tickablesoundinstance.tick(); | |
if (tickablesoundinstance.isStopped()) { | |
this.stop(tickablesoundinstance); | |
} else { | |
float f = this.calculateVolume(tickablesoundinstance); | |
float f1 = this.calculatePitch(tickablesoundinstance); | |
Vec3 vec3 = new Vec3(tickablesoundinstance.getX(), tickablesoundinstance.getY(), tickablesoundinstance.getZ()); | |
ChannelAccess.ChannelHandle channelaccess$channelhandle = this.instanceToChannel.get(tickablesoundinstance); | |
if (channelaccess$channelhandle != null) { | |
channelaccess$channelhandle.execute(p_194478_ -> { | |
p_194478_.setVolume(f); | |
p_194478_.setPitch(f1); | |
p_194478_.setSelfPosition(vec3); | |
}); | |
} | |
} | |
} | |
Iterator<Entry<SoundInstance, ChannelAccess.ChannelHandle>> iterator = this.instanceToChannel.entrySet().iterator(); | |
while (iterator.hasNext()) { | |
Entry<SoundInstance, ChannelAccess.ChannelHandle> entry = iterator.next(); | |
ChannelAccess.ChannelHandle channelaccess$channelhandle1 = entry.getValue(); | |
SoundInstance soundinstance = entry.getKey(); | |
float f2 = this.options.getSoundSourceVolume(soundinstance.getSource()); | |
if (f2 <= 0.0F) { | |
channelaccess$channelhandle1.execute(Channel::stop); | |
iterator.remove(); | |
} else if (channelaccess$channelhandle1.isStopped()) { | |
int i = this.soundDeleteTime.get(soundinstance); | |
if (i <= this.tickCount) { | |
if (shouldLoopManually(soundinstance)) { | |
this.queuedSounds.put(soundinstance, this.tickCount + soundinstance.getDelay()); | |
} | |
iterator.remove(); | |
LOGGER.debug(MARKER, "Removed channel {} because it's not playing anymore", channelaccess$channelhandle1); | |
this.soundDeleteTime.remove(soundinstance); | |
try { | |
this.instanceBySource.remove(soundinstance.getSource(), soundinstance); | |
} catch (RuntimeException runtimeexception) { | |
} | |
if (soundinstance instanceof TickableSoundInstance) { | |
this.tickingSounds.remove(soundinstance); | |
} | |
} | |
} | |
} | |
Iterator<Entry<SoundInstance, Integer>> iterator1 = this.queuedSounds.entrySet().iterator(); | |
while (iterator1.hasNext()) { | |
Entry<SoundInstance, Integer> entry1 = iterator1.next(); | |
if (this.tickCount >= entry1.getValue()) { | |
SoundInstance soundinstance1 = entry1.getKey(); | |
if (soundinstance1 instanceof TickableSoundInstance) { | |
((TickableSoundInstance)soundinstance1).tick(); | |
} | |
this.play(soundinstance1); | |
iterator1.remove(); | |
} | |
} | |
} | |
private static boolean requiresManualLooping(SoundInstance p_120316_) { | |
return p_120316_.getDelay() > 0; | |
} | |
private static boolean shouldLoopManually(SoundInstance p_120319_) { | |
return p_120319_.isLooping() && requiresManualLooping(p_120319_); | |
} | |
private static boolean shouldLoopAutomatically(SoundInstance p_120322_) { | |
return p_120322_.isLooping() && !requiresManualLooping(p_120322_); | |
} | |
public boolean isActive(SoundInstance p_120306_) { | |
if (!this.loaded) { | |
return false; | |
} else { | |
return this.soundDeleteTime.containsKey(p_120306_) && this.soundDeleteTime.get(p_120306_) <= this.tickCount ? true : this.instanceToChannel.containsKey(p_120306_); | |
} | |
} | |
public void play(SoundInstance p_120313_) { | |
if (this.loaded) { | |
if (p_120313_.canPlaySound()) { | |
WeighedSoundEvents weighedsoundevents = p_120313_.resolve(this.soundManager); | |
ResourceLocation resourcelocation = p_120313_.getLocation(); | |
if (weighedsoundevents == null) { | |
if (ONLY_WARN_ONCE.add(resourcelocation)) { | |
LOGGER.warn(MARKER, "Unable to play unknown soundEvent: {}", resourcelocation); | |
} | |
} else { | |
Sound sound = p_120313_.getSound(); | |
if (sound != SoundManager.INTENTIONALLY_EMPTY_SOUND) { | |
if (sound == SoundManager.EMPTY_SOUND) { | |
if (ONLY_WARN_ONCE.add(resourcelocation)) { | |
LOGGER.warn(MARKER, "Unable to play empty soundEvent: {}", resourcelocation); | |
} | |
} else { | |
float f = p_120313_.getVolume(); | |
float f1 = Math.max(f, 1.0F) * (float)sound.getAttenuationDistance(); | |
SoundSource soundsource = p_120313_.getSource(); | |
float f2 = this.calculateVolume(f, soundsource); | |
float f3 = this.calculatePitch(p_120313_); | |
SoundInstance.Attenuation soundinstance$attenuation = p_120313_.getAttenuation(); | |
boolean flag = p_120313_.isRelative(); | |
if (f2 == 0.0F && !p_120313_.canStartSilent()) { | |
LOGGER.debug(MARKER, "Skipped playing sound {}, volume was zero.", sound.getLocation()); | |
} else { | |
Vec3 vec3 = new Vec3(p_120313_.getX(), p_120313_.getY(), p_120313_.getZ()); | |
if (!this.listeners.isEmpty()) { | |
float f4 = !flag && soundinstance$attenuation != SoundInstance.Attenuation.NONE ? f1 : Float.POSITIVE_INFINITY; | |
for (SoundEventListener soundeventlistener : this.listeners) { | |
soundeventlistener.onPlaySound(p_120313_, weighedsoundevents, f4); | |
} | |
} | |
if (this.listener.getGain() <= 0.0F) { | |
LOGGER.debug(MARKER, "Skipped playing soundEvent: {}, master volume was zero", resourcelocation); | |
} else { | |
boolean flag1 = shouldLoopAutomatically(p_120313_); | |
boolean flag2 = sound.shouldStream(); | |
CompletableFuture<ChannelAccess.ChannelHandle> completablefuture = this.channelAccess | |
.createHandle(sound.shouldStream() ? Library.Pool.STREAMING : Library.Pool.STATIC); | |
ChannelAccess.ChannelHandle channelaccess$channelhandle = completablefuture.join(); | |
if (channelaccess$channelhandle == null) { | |
if (SharedConstants.IS_RUNNING_IN_IDE) { | |
LOGGER.warn("Failed to create new sound handle"); | |
} | |
} else { | |
LOGGER.debug(MARKER, "Playing sound {} for event {}", sound.getLocation(), resourcelocation); | |
this.soundDeleteTime.put(p_120313_, this.tickCount + 20); | |
this.instanceToChannel.put(p_120313_, channelaccess$channelhandle); | |
this.instanceBySource.put(soundsource, p_120313_); | |
channelaccess$channelhandle.execute(p_194488_ -> { | |
p_194488_.setPitch(f3); | |
p_194488_.setVolume(f2); | |
if (soundinstance$attenuation == SoundInstance.Attenuation.LINEAR) { | |
p_194488_.linearAttenuation(f1); | |
} else { | |
p_194488_.disableAttenuation(); | |
} | |
p_194488_.setLooping(flag1 && !flag2); | |
p_194488_.setSelfPosition(vec3); | |
p_194488_.setRelative(flag); | |
}); | |
if (!flag2) { | |
this.soundBuffers | |
.getCompleteBuffer(sound.getPath()) | |
.thenAccept(p_377326_ -> channelaccess$channelhandle.execute(p_194495_ -> { | |
p_194495_.attachStaticBuffer(p_377326_); | |
p_194495_.play(); | |
})); | |
} else { | |
this.soundBuffers | |
.getStream(sound.getPath(), flag1) | |
.thenAccept(p_376862_ -> channelaccess$channelhandle.execute(p_194498_ -> { | |
p_194498_.attachBufferStream(p_376862_); | |
p_194498_.play(); | |
})); | |
} | |
if (p_120313_ instanceof TickableSoundInstance) { | |
this.tickingSounds.add((TickableSoundInstance)p_120313_); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
public void queueTickingSound(TickableSoundInstance p_120283_) { | |
this.queuedTickableSounds.add(p_120283_); | |
} | |
public void requestPreload(Sound p_120273_) { | |
this.preloadQueue.add(p_120273_); | |
} | |
private float calculatePitch(SoundInstance p_120325_) { | |
return Mth.clamp(p_120325_.getPitch(), 0.5F, 2.0F); | |
} | |
private float calculateVolume(SoundInstance p_120328_) { | |
return this.calculateVolume(p_120328_.getVolume(), p_120328_.getSource()); | |
} | |
private float calculateVolume(float p_235258_, SoundSource p_235259_) { | |
return Mth.clamp(p_235258_ * this.getVolume(p_235259_), 0.0F, 1.0F); | |
} | |
public void pause() { | |
if (this.loaded) { | |
this.channelAccess.executeOnChannels(p_194510_ -> p_194510_.forEach(Channel::pause)); | |
} | |
} | |
public void resume() { | |
if (this.loaded) { | |
this.channelAccess.executeOnChannels(p_194508_ -> p_194508_.forEach(Channel::unpause)); | |
} | |
} | |
public void playDelayed(SoundInstance p_120277_, int p_120278_) { | |
this.queuedSounds.put(p_120277_, this.tickCount + p_120278_); | |
} | |
public void updateSource(Camera p_120271_) { | |
if (this.loaded && p_120271_.isInitialized()) { | |
ListenerTransform listenertransform = new ListenerTransform(p_120271_.getPosition(), new Vec3(p_120271_.getLookVector()), new Vec3(p_120271_.getUpVector())); | |
this.executor.execute(() -> this.listener.setTransform(listenertransform)); | |
} | |
} | |
public void stop( { ResourceLocation p_120300_, SoundSource p_120301_) | |
if (p_120301_ != null) { | |
for (SoundInstance soundinstance : this.instanceBySource.get(p_120301_)) { | |
if (p_120300_ == null || soundinstance.getLocation().equals(p_120300_)) { | |
this.stop(soundinstance); | |
} | |
} | |
} else if (p_120300_ == null) { | |
this.stopAll(); | |
} else { | |
for (SoundInstance soundinstance1 : this.instanceToChannel.keySet()) { | |
if (soundinstance1.getLocation().equals(p_120300_)) { | |
this.stop(soundinstance1); | |
} | |
} | |
} | |
} | |
public String getDebugString() { | |
return this.library.getDebugString(); | |
} | |
public List<String> getAvailableSoundDevices() { | |
return this.library.getAvailableSoundDevices(); | |
} | |
public ListenerTransform getListenerTransform() { | |
return this.listener.getTransform(); | |
} | |
static enum DeviceCheckState { | |
ONGOING, | |
CHANGE_DETECTED, | |
NO_CHANGE; | |
} | |
} |