From 504302025f28bccd4657d37de6e897a2eb10b5dd Mon Sep 17 00:00:00 2001 From: Bixilon Date: Mon, 24 May 2021 18:25:24 +0200 Subject: [PATCH] more sounds --- .../block/outline/BlockOutlineRenderer.kt | 3 +- .../models/renderable/ElementRenderer.kt | 3 +- .../gui/rendering/input/LeftClickHandler.kt | 7 ++ .../gui/rendering/sound/AudioPlayer.kt | 115 ++++++++++++------ .../gui/rendering/sound/SoundConstants.kt | 21 ++++ .../gui/rendering/sound/SoundListener.kt | 1 + .../gui/rendering/sound/SoundSource.kt | 15 ++- .../gui/rendering/sound/sounds/Sound.kt | 56 ++++++--- .../minosoft/gui/rendering/util/VecUtil.kt | 23 ++-- .../packets/s2c/play/ContainerOpenS2CP.kt | 4 +- .../java/de/bixilon/minosoft/util/Queue.kt | 2 +- 11 files changed, 182 insertions(+), 68 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundConstants.kt diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/block/outline/BlockOutlineRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/block/outline/BlockOutlineRenderer.kt index 182259fbc..c00b96f31 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/block/outline/BlockOutlineRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/block/outline/BlockOutlineRenderer.kt @@ -26,6 +26,7 @@ import de.bixilon.minosoft.gui.rendering.chunk.models.renderable.ElementRenderer import de.bixilon.minosoft.gui.rendering.shader.Shader import de.bixilon.minosoft.gui.rendering.util.VecUtil import de.bixilon.minosoft.gui.rendering.util.VecUtil.getWorldOffset +import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3 import de.bixilon.minosoft.protocol.network.connection.PlayConnection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.BitByte.isBit @@ -165,7 +166,7 @@ class BlockOutlineRenderer( collisionMesh?.unload() outlineMesh = BlockOutlineMesh() - val blockOffset = raycastHit.blockPosition.getWorldOffset(raycastHit.blockState.block).plus(raycastHit.blockPosition) + val blockOffset = raycastHit.blockPosition.toVec3 + raycastHit.blockPosition.getWorldOffset(raycastHit.blockState.block) drawVoxelShape(raycastHit.blockState.outlineShape, blockOffset, outlineMesh) outlineMesh.load() diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt index b2709e216..d96ab91af 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt @@ -29,6 +29,7 @@ import de.bixilon.minosoft.gui.rendering.textures.TextureTransparencies import de.bixilon.minosoft.gui.rendering.util.VecUtil import de.bixilon.minosoft.gui.rendering.util.VecUtil.plus import de.bixilon.minosoft.gui.rendering.util.VecUtil.rotate +import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3 import glm_.vec3.Vec3 class ElementRenderer( @@ -83,7 +84,7 @@ class ElementRenderer( for ((drawPositionIndex, texturePositionIndex) in DRAW_ODER) { val input = drawPositions[drawPositionIndex] - val output = context.blockPosition plus context.offset + input + DRAW_OFFSET + val output = context.blockPosition.toVec3 + input + DRAW_OFFSET + context.offset mesh.addVertex( position = output, textureCoordinates = texturePositions[texturePositionIndex]!!, diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt index a9765aabc..308ce407e 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/LeftClickHandler.kt @@ -29,6 +29,7 @@ import de.bixilon.minosoft.modding.event.events.BlockBreakAckEvent import de.bixilon.minosoft.protocol.packets.c2s.play.ArmSwingC2SP import de.bixilon.minosoft.protocol.packets.c2s.play.BlockBreakC2SP import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import de.bixilon.minosoft.util.KUtil.asResourceLocation import de.bixilon.minosoft.util.KUtil.synchronizedMapOf import glm_.pow import glm_.vec3.Vec3i @@ -131,6 +132,8 @@ class LeftClickHandler( connection.sendPacket(BlockBreakC2SP(BlockBreakC2SP.BreakType.FINISHED_DIGGING, raycastHit.blockPosition, raycastHit.hitDirection)) clearDigging() connection.world.setBlockState(raycastHit.blockPosition, null) + + renderWindow.rendering.audioPlayer.playSoundEvent(connection.registries.soundEventRegistry[BLOCK_BREAK_SOUND]!!) // ToDO } val canStartBreaking = currentTime - breakSent >= ProtocolDefinition.TICK_TIME @@ -268,4 +271,8 @@ class LeftClickHandler( } swingArm() } + + companion object { + val BLOCK_BREAK_SOUND = "minecraft:block.metal.break".asResourceLocation() + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt index 2a1d2a546..2209366fc 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/AudioPlayer.kt @@ -22,19 +22,20 @@ import de.bixilon.minosoft.gui.rendering.Rendering import de.bixilon.minosoft.gui.rendering.sound.sounds.Sound import de.bixilon.minosoft.gui.rendering.sound.sounds.SoundList import de.bixilon.minosoft.protocol.network.connection.PlayConnection +import de.bixilon.minosoft.protocol.protocol.ConnectionStates import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.CountUpAndDownLatch import de.bixilon.minosoft.util.KUtil.asResourceLocation +import de.bixilon.minosoft.util.KUtil.synchronizedListOf +import de.bixilon.minosoft.util.KUtil.toSynchronizedList +import de.bixilon.minosoft.util.Queue import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType -import org.lwjgl.BufferUtils.createShortBuffer import org.lwjgl.openal.AL -import org.lwjgl.openal.AL10.* import org.lwjgl.openal.ALC import org.lwjgl.openal.ALC10.* import org.lwjgl.openal.EXTThreadLocalContext.alcSetThreadContext -import org.lwjgl.stb.STBVorbis.stb_vorbis_get_samples_short_interleaved import org.lwjgl.system.MemoryUtil import java.nio.ByteBuffer import java.nio.IntBuffer @@ -53,16 +54,35 @@ class AudioPlayer( private var device = 0L private var context = 0L - private var source = 0 + private val queue = Queue() + private lateinit var listener: SoundListener + private val sources: MutableList = synchronizedListOf() private var pcm: ShortBuffer? = null + private fun preloadSounds() { + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Preloading sounds..." } + if (SoundConstants.DISABLE_PRELOADING) { + return + } + + for (soundList in sounds.values) { + for (sound in soundList.sounds) { + if (SoundConstants.PRELOAD_ALL_SOUNDS || sound.preload) { + sound.load(connection.assetsManager) + } + } + } + } + + fun init(latch: CountUpAndDownLatch) { Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Loading OpenAL..." } - Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Loading sounds.json" } loadSounds() + preloadSounds() + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Initializing OpenAL..." } @@ -77,59 +97,84 @@ class AudioPlayer( val deviceCaps = ALC.createCapabilities(device) AL.createCapabilities(deviceCaps) - val listener = SoundListener() - - val source = SoundSource(false) - - - // Testing, ToDo - val sound = sounds[connection.registries.soundEventRegistry[0]]!!.sounds.iterator().next() - - sound.load(connection.assetsManager) + listener = SoundListener() Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "OpenAL loaded!" } - val pcm = createShortBuffer(sound.samplesLength) - - pcm.limit(stb_vorbis_get_samples_short_interleaved(sound.handle, sound.channels, pcm) * sound.channels) - - val buffer = alGenBuffers() - - alBufferData(buffer, sound.format, pcm, sound.sampleRate) - - source.buffer = buffer - source.play() - - while (source.isPlaying) { - Thread.sleep(1L) - } - Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Sound played!" } - - initialized = true latch.countDown() } + fun playSoundEvent(soundEvent: SoundEvent) { + playSound(sounds[soundEvent]!!.getRandom()) + } + + + private fun getAvailableSource(): SoundSource? { + for (source in sources.toSynchronizedList()) { + if (source.available) { + return source + } + } + // no source available + if (sources.size > SoundConstants.MAX_SOURCES_AMOUNT) { + return null + } + val source = SoundSource(false) + sources += source + + return source + } + + + fun playSound(sound: Sound) { + queue += add@{ + sound.load(connection.assetsManager) + if (sound.loadFailed) { + return@add + } + val source = getAvailableSource() ?: let { + Log.log(LogMessageType.RENDERING_GENERAL, LogLevels.WARN) { "Can not play sound: No source available!" } + return@add + } + source.sound = sound + source.play() + } + } + fun startLoop() { - while (connection.isConnected) { + while (connection.connectionState != ConnectionStates.DISCONNECTING) { + queue.work() Thread.sleep(1L) } } fun exit() { - // alDeleteBuffers(buffers) - alDeleteSources(source) + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Unloading OpenAL..." } - //MemoryUtil.memFree(buffers) + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Unloading sounds..." } + for (soundList in sounds.values) { + for (sound in soundList.sounds) { + sound.unload() + } + } + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Unloading sources..." } + for (source in sources.toSynchronizedList()) { + source.unload() + } + + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Destroying OpenAL context..." } MemoryUtil.memFree(pcm) alcSetThreadContext(MemoryUtil.NULL) alcDestroyContext(context) alcCloseDevice(device) + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Unloaded OpenAL!" } } private fun loadSounds() { + Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Loading sounds.json" } val data = connection.assetsManager.readJsonAsset(SOUNDS_INDEX_FILE) for ((soundEventResourceLocation, json) in data.entrySet()) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundConstants.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundConstants.kt new file mode 100644 index 000000000..76091a4af --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundConstants.kt @@ -0,0 +1,21 @@ +/* + * Minosoft + * Copyright (C) 2021 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.sound + +object SoundConstants { + const val PRELOAD_ALL_SOUNDS = false + const val DISABLE_PRELOADING = false + + const val MAX_SOURCES_AMOUNT = 32 +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundListener.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundListener.kt index 471bd0f4c..2fae1866b 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundListener.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundListener.kt @@ -24,6 +24,7 @@ class SoundListener(position: Vec3 = Vec3.EMPTY) { alListener3f(AL_POSITION, value.x, value.y, value.z) field = value } + var velocity: Vec3 = Vec3.EMPTY set(value) { alListener3f(AL_VELOCITY, value.x, value.y, value.z) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundSource.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundSource.kt index 949c1aedc..f506de72b 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundSource.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/SoundSource.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.gui.rendering.sound +import de.bixilon.minosoft.gui.rendering.sound.sounds.Sound import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY import glm_.vec3.Vec3 import org.lwjgl.openal.AL10.* @@ -52,15 +53,23 @@ class SoundSource(loop: Boolean = false) { field = value } - var buffer: Int = -1 + var sound: Sound? = null set(value) { - alSourcei(source, AL_BUFFER, value) + stop() + if (value?.loaded != true || value.loadFailed) { + field = null + return + } + alSourcei(source, AL_BUFFER, value.buffer) field = value } val isPlaying: Boolean get() = alGetSourcei(source, AL_SOURCE_STATE) == AL_PLAYING + val available: Boolean + get() = isPlaying + fun play() { alSourcePlay(source) } @@ -73,7 +82,7 @@ class SoundSource(loop: Boolean = false) { alSourceStop(source) } - fun delete() { + fun unload() { stop() alDeleteSources(source) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/sounds/Sound.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/sounds/Sound.kt index e1e5f6c1a..b092d6274 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/sound/sounds/Sound.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/sound/sounds/Sound.kt @@ -19,8 +19,7 @@ import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType import org.lwjgl.BufferUtils -import org.lwjgl.openal.AL10.AL_FORMAT_MONO16 -import org.lwjgl.openal.AL10.AL_FORMAT_STEREO16 +import org.lwjgl.openal.AL10.* import org.lwjgl.stb.STBVorbis.* import org.lwjgl.stb.STBVorbisInfo import org.lwjgl.system.MemoryUtil @@ -41,47 +40,74 @@ data class Sound( private set var loadFailed: Boolean = false private set - var buffer: ByteBuffer? = null - var handle: Long = -1L var channels: Int = -1 + private set var sampleRate: Int = -1 + private set var samplesLength: Int = -1 + private set var sampleSeconds: Float = -1.0f + private set + var buffer = -1 + private set - var format: Int = -1 + private var vorbisBuffer: ByteBuffer? = null + @Synchronized fun load(assetsManager: AssetsManager) { if (loaded || loadFailed) { return } Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Loading audio file: $path" } try { - - val buffer = assetsManager.readByteAsset(path) - this.buffer = buffer - + val vorbisBuffer = assetsManager.readByteAsset(path) + this.vorbisBuffer = vorbisBuffer val error = BufferUtils.createIntBuffer(1) - handle = stb_vorbis_open_memory(buffer, error, null) - if (handle == MemoryUtil.NULL) { + val vorbis = stb_vorbis_open_memory(vorbisBuffer, error, null) + if (vorbis == MemoryUtil.NULL) { throw IllegalStateException("Can not load vorbis: ${path}: ${error[0]}") } - val info = stb_vorbis_get_info(handle, STBVorbisInfo.malloc()) + val info = stb_vorbis_get_info(vorbis, STBVorbisInfo.malloc()) channels = info.channels() - format = when (channels) { + val format = when (channels) { 1 -> AL_FORMAT_MONO16 2 -> AL_FORMAT_STEREO16 else -> TODO("Channels: $channels") } sampleRate = info.sample_rate() - samplesLength = stb_vorbis_stream_length_in_samples(handle) - sampleSeconds = stb_vorbis_stream_length_in_seconds(handle) + samplesLength = stb_vorbis_stream_length_in_samples(vorbis) + sampleSeconds = stb_vorbis_stream_length_in_seconds(vorbis) + + val pcm = BufferUtils.createShortBuffer(samplesLength) + + pcm.limit(stb_vorbis_get_samples_short_interleaved(vorbis, channels, pcm) * channels) + //ToDo: Somehow crashed?: MemoryUtil.memFree(vorbisBuffer) + + this.buffer = alGenBuffers() + + alBufferData(buffer, format, pcm, sampleRate) loaded = true } catch (exception: FileNotFoundException) { loadFailed = true Log.log(LogMessageType.RENDERING_LOADING, LogLevels.WARN) { "Can not load sound: $path: $exception" } } } + + @Synchronized + fun unload() { + if (!loaded) { + return + } + alDeleteBuffers(buffer) + vorbisBuffer?.let { MemoryUtil.memFree(it) } + buffer = -1 + channels = -1 + sampleRate = -1 + samplesLength = -1 + sampleSeconds = -1.0f + loaded = false + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt index c21050c30..204e25e44 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/VecUtil.kt @@ -148,14 +148,10 @@ object VecUtil { } val Vec3i.entityPosition: Vec3 - get() { - return Vec3(x + 0.5f, y, z + 0.5f) // ToDo - } + get() = Vec3(x + 0.5f, y, z + 0.5f) // ToDo val Vec3.blockPosition: Vec3i - get() { - return Vec3i((x - 0.5f).toInt(), y.toInt(), (z - 0.5f).toInt()) // ToDo - } + get() = Vec3i((x - 0.5f).toInt(), y.toInt(), (z - 0.5f).toInt()) // ToDo fun Vec3i.Companion.of(chunkPosition: Vec2i, sectionHeight: Int, inChunkSectionPosition: Vec3i): Vec3i { return Vec3i( @@ -198,8 +194,8 @@ object VecUtil { fun Vec3i.getWorldOffset(block: Block): Vec3 { if (block.randomOffsetType == null || !Minosoft.config.config.game.other.flowerRandomOffset) { return EMPTY_VEC3 - } + val positionHash = generatePositionHash(x, 0, z) val maxModelOffset = 0.25f // ToDo: use block.model.max_model_offset @@ -239,18 +235,22 @@ object VecUtil { position[axis].ceilInt - 1 } } + fun getLengthMultiplier(direction: Vec3, position: Vec3, axis: Axes): Float { return (getTarget(direction, position, axis) - position[axis]) / direction[axis] } + val directionXDistance = getLengthMultiplier(direction, position, Axes.X) val directionYDistance = getLengthMultiplier(direction, position, Axes.Y) val directionZDistance = getLengthMultiplier(direction, position, Axes.Z) return glm.min(directionXDistance, directionYDistance, directionZDistance) } - val Vec3.min: Float get() = glm.min(this.x, this.y, this.z) + val Vec3.min: Float + get() = glm.min(this.x, this.y, this.z) - val Vec3.max: Float get() = glm.max(this.x, this.y, this.z) + val Vec3.max: Float + get() = glm.max(this.x, this.y, this.z) val Vec3.signs: Vec3 get() { @@ -283,8 +283,11 @@ object VecUtil { return minDistanceDirection } + val Vec3i.toVec3: Vec3 + get() = Vec3(this) + operator fun Vec3.get(axis: Axes): Float { - return when(axis) { + return when (axis) { Axes.X -> this.x Axes.Y -> this.y Axes.Z -> this.z diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ContainerOpenS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ContainerOpenS2CP.kt index ef3aca97f..65785ac3a 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ContainerOpenS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ContainerOpenS2CP.kt @@ -26,7 +26,7 @@ import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType class ContainerOpenS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { - val containerId = if (buffer.versionId <= V_1_16) { // ToDo: This is completely guessed + val containerId = if (buffer.versionId <= V_1_14) { // ToDo: This is completely guessed, it has changed between 1.13 and 1.14, same as #L38 buffer.readUnsignedByte() } else { buffer.readVarInt() @@ -35,7 +35,7 @@ class ContainerOpenS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { buffer.versionId < V_14W03B -> { buffer.connection.registries.containerTypeRegistry[buffer.readUnsignedByte()] } - buffer.versionId >= V_1_16 -> { // ToDo: This is completely guessed + buffer.versionId >= V_1_14 -> { // ToDo: This is completely guessed buffer.connection.registries.containerTypeRegistry[buffer.readVarInt()] } else -> { diff --git a/src/main/java/de/bixilon/minosoft/util/Queue.kt b/src/main/java/de/bixilon/minosoft/util/Queue.kt index c4eb97ebe..905f75143 100644 --- a/src/main/java/de/bixilon/minosoft/util/Queue.kt +++ b/src/main/java/de/bixilon/minosoft/util/Queue.kt @@ -27,7 +27,7 @@ class Queue { add(runnable) } - fun work(maxJobs: Int) { + fun work(maxJobs: Int = Int.MAX_VALUE) { var jobsDone = 0 for (runnable in queue.toSynchronizedList()) { this.queue.remove(runnable)