266 lines
6.8 KiB
Java
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;
|
|
}
|
|
}
|