minecraft-src/com/mojang/blaze3d/audio/Channel.java
2025-07-04 01:41:11 +03:00

266 lines
6.8 KiB
Java

package com.mojang.blaze3d.audio;
import com.mojang.logging.LogUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.AudioFormat;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.openal.AL10;
import org.slf4j.Logger;
/**
* Represents an OpenAL audio channel.
*/
@Environment(EnvType.CLIENT)
public class Channel {
private static final Logger LOGGER = LogUtils.getLogger();
private static final int QUEUED_BUFFER_COUNT = 4;
public static final int BUFFER_DURATION_SECONDS = 1;
private final int source;
private final AtomicBoolean initialized = new AtomicBoolean(true);
private int streamingBufferSize = 16384;
@Nullable
private AudioStream stream;
/**
* Creates a new OpenAL audio channel.
* {@return a new OpenAL audio channel or {@code null} if its creation failed}
*/
@Nullable
static Channel create() {
int[] is = new int[1];
AL10.alGenSources(is);
return OpenAlUtil.checkALError("Allocate new source") ? null : new Channel(is[0]);
}
private Channel(int source) {
this.source = source;
}
/**
* Stops the audio channel and releases resources.
*/
public void destroy() {
if (this.initialized.compareAndSet(true, false)) {
AL10.alSourceStop(this.source);
OpenAlUtil.checkALError("Stop");
if (this.stream != null) {
try {
this.stream.close();
} catch (IOException var2) {
LOGGER.error("Failed to close audio stream", (Throwable)var2);
}
this.removeProcessedBuffers();
this.stream = null;
}
AL10.alDeleteSources(new int[]{this.source});
OpenAlUtil.checkALError("Cleanup");
}
}
/**
* Starts playing the audio channel.
*/
public void play() {
AL10.alSourcePlay(this.source);
}
/**
* {@return the state of the audio channel}
*/
private int getState() {
return !this.initialized.get() ? 4116 : AL10.alGetSourcei(this.source, 4112);
}
/**
* Pauses the audio channel.
*/
public void pause() {
if (this.getState() == 4114) {
AL10.alSourcePause(this.source);
}
}
/**
* Resumes playing the audio channel if it was paused.
*/
public void unpause() {
if (this.getState() == 4115) {
AL10.alSourcePlay(this.source);
}
}
/**
* Stops playing the audio channel.
*/
public void stop() {
if (this.initialized.get()) {
AL10.alSourceStop(this.source);
OpenAlUtil.checkALError("Stop");
}
}
/**
* {@return {@code true} if the audio channel is currently playing, {@code false} otherwise}
*/
public boolean playing() {
return this.getState() == 4114;
}
/**
* {@return {@code true} if the audio channel is stopped, {@code false} otherwise}
*/
public boolean stopped() {
return this.getState() == 4116;
}
/**
* Sets the position of the audio channel.
*
* @param source the position of the audio channel
*/
public void setSelfPosition(Vec3 source) {
AL10.alSourcefv(this.source, 4100, new float[]{(float)source.x, (float)source.y, (float)source.z});
}
/**
* Sets the pitch of the audio channel.
*
* @param pitch the pitch of the audio channel
*/
public void setPitch(float pitch) {
AL10.alSourcef(this.source, 4099, pitch);
}
/**
* Sets whether the audio channel should loop.
*
* @param looping {@code true} if the audio channel should loop, {@code false} otherwise
*/
public void setLooping(boolean looping) {
AL10.alSourcei(this.source, 4103, looping ? 1 : 0);
}
/**
* Sets the volume of the audio channel.
*
* @param volume the volume of the audio channel
*/
public void setVolume(float volume) {
AL10.alSourcef(this.source, 4106, volume);
}
/**
* Disables attenuation for the audio channel.
*/
public void disableAttenuation() {
AL10.alSourcei(this.source, 53248, 0);
}
/**
* Sets linear attenuation for the audio channel.
*
* @param linearAttenuation the linear attenuation of the audio channel
*/
public void linearAttenuation(float linearAttenuation) {
AL10.alSourcei(this.source, 53248, 53251);
AL10.alSourcef(this.source, 4131, linearAttenuation);
AL10.alSourcef(this.source, 4129, 1.0F);
AL10.alSourcef(this.source, 4128, 0.0F);
}
/**
* Sets whether the audio channel should be relative to the listener's position.
*
* @param relative {@code true} if the audio channel should be relative, {@code false} otherwise
*/
public void setRelative(boolean relative) {
AL10.alSourcei(this.source, 514, relative ? 1 : 0);
}
/**
* Attaches a static buffer to the audio channel.
*
* @param buffer the buffer to attach
*/
public void attachStaticBuffer(SoundBuffer buffer) {
buffer.getAlBuffer().ifPresent(i -> AL10.alSourcei(this.source, 4105, i));
}
/**
* Attaches a buffer stream to the audio channel.
*
* @param stream the stream to attach
*/
public void attachBufferStream(AudioStream stream) {
this.stream = stream;
AudioFormat audioFormat = stream.getFormat();
this.streamingBufferSize = calculateBufferSize(audioFormat, 1);
this.pumpBuffers(4);
}
/**
* Calculates the buffer size for an audio stream.
* @return the buffer size
*
* @param format the audio format of the stream
* @param sampleAmount the number of samples to buffer
*/
private static int calculateBufferSize(AudioFormat format, int sampleAmount) {
return (int)(sampleAmount * format.getSampleSizeInBits() / 8.0F * format.getChannels() * format.getSampleRate());
}
/**
* Reads and queues audio buffers from the stream.
*
* @param readCount the number of buffers to read and queue
*/
private void pumpBuffers(int readCount) {
if (this.stream != null) {
try {
for (int i = 0; i < readCount; i++) {
ByteBuffer byteBuffer = this.stream.read(this.streamingBufferSize);
if (byteBuffer != null) {
new SoundBuffer(byteBuffer, this.stream.getFormat()).releaseAlBuffer().ifPresent(ix -> AL10.alSourceQueueBuffers(this.source, new int[]{ix}));
}
}
} catch (IOException var4) {
LOGGER.error("Failed to read from audio stream", (Throwable)var4);
}
}
}
/**
* Updates the audio stream by removing processed buffers and queuing new ones.
*/
public void updateStream() {
if (this.stream != null) {
int i = this.removeProcessedBuffers();
this.pumpBuffers(i);
}
}
/**
* Removes processed audio buffers from the audio channel.
* @return the number of processed buffers removed
*/
private int removeProcessedBuffers() {
int i = AL10.alGetSourcei(this.source, 4118);
if (i > 0) {
int[] is = new int[i];
AL10.alSourceUnqueueBuffers(this.source, is);
OpenAlUtil.checkALError("Unqueue buffers");
AL10.alDeleteBuffers(is);
OpenAlUtil.checkALError("Remove processed buffers");
}
return i;
}
}