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;
 | |
| 	}
 | |
| }
 |