163 lines
5.9 KiB
Kotlin
163 lines
5.9 KiB
Kotlin
package org.neoflock.neocomputers.sounds
|
|
|
|
import dev.architectury.registry.registries.DeferredRegister
|
|
import net.minecraft.client.Minecraft
|
|
import net.minecraft.core.registries.Registries
|
|
import net.minecraft.resources.ResourceLocation
|
|
import net.minecraft.sounds.SoundEvent
|
|
import net.minecraft.world.phys.Vec3
|
|
import org.lwjgl.BufferUtils
|
|
import org.neoflock.neocomputers.NeoComputers
|
|
import org.lwjgl.openal.AL10
|
|
import java.nio.ByteBuffer
|
|
import kotlin.experimental.xor
|
|
import kotlin.math.PI
|
|
import kotlin.math.max
|
|
import kotlin.math.sign
|
|
import kotlin.math.sin
|
|
|
|
object Sounds {
|
|
val SOUNDS = DeferredRegister.create(NeoComputers.MODID, Registries.SOUND_EVENT)!!
|
|
|
|
val COMPUTER_RUNNING = registerSound("computer_running")
|
|
|
|
fun registerSound(name: String) = SOUNDS.register(name) {
|
|
SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, name))
|
|
}!!
|
|
|
|
val BEEP_SAMPLERATE = 44100
|
|
val BEEP_AMPLITUDE = 32f
|
|
val BEEP_MAXDIST = 16f
|
|
|
|
// Also largely taken from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/util/Audio.scala
|
|
|
|
val allSounds = ThreadLocal.withInitial { mutableListOf<CustomSoundBuffer>() }
|
|
|
|
class CustomSoundBuffer {
|
|
var dead: Boolean = true
|
|
var buffer: Int = -1
|
|
var source: Int = -1
|
|
|
|
fun start(x: Float, y: Float, z: Float, data: ByteBuffer, gain: Float): Int? {
|
|
// clear errors or smth idk
|
|
AL10.alGetError()
|
|
|
|
// written in a C style by a C dev
|
|
// all this work on a JVM project and I'm still writing C
|
|
// would be better if Kotlin had goto btw just saying
|
|
val ok = AL10.AL_NO_ERROR
|
|
var err = ok
|
|
buffer = AL10.alGenBuffers()
|
|
err = AL10.alGetError()
|
|
if(err != ok) return err
|
|
|
|
AL10.alBufferData(buffer, AL10.AL_FORMAT_MONO8, data, BEEP_SAMPLERATE)
|
|
err = AL10.alGetError()
|
|
if(err != ok) {
|
|
AL10.alDeleteBuffers(buffer)
|
|
return err
|
|
}
|
|
|
|
source = AL10.alGenSources()
|
|
err = AL10.alGetError()
|
|
if(err != ok) {
|
|
AL10.alDeleteBuffers(buffer)
|
|
return err
|
|
}
|
|
|
|
AL10.alSourceQueueBuffers(source, buffer)
|
|
err = AL10.alGetError()
|
|
if(err != ok) {
|
|
AL10.alDeleteBuffers(buffer)
|
|
AL10.alDeleteSources(source)
|
|
return err
|
|
}
|
|
|
|
AL10.alSource3f(source, AL10.AL_POSITION, x, y, z)
|
|
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, BEEP_MAXDIST)
|
|
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, BEEP_MAXDIST)
|
|
AL10.alSourcef(source, AL10.AL_GAIN, gain * 0.3f)
|
|
err = AL10.alGetError()
|
|
if(err != ok) {
|
|
AL10.alDeleteBuffers(buffer)
|
|
AL10.alDeleteSources(source)
|
|
return err
|
|
}
|
|
|
|
AL10.alSourcePlay(source)
|
|
err = AL10.alGetError()
|
|
if(err != ok) {
|
|
AL10.alDeleteBuffers(buffer)
|
|
AL10.alDeleteSources(source)
|
|
return err
|
|
}
|
|
|
|
dead = false
|
|
return null
|
|
}
|
|
|
|
fun checkDone(): Boolean {
|
|
if(dead) return true
|
|
if(AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING) return false
|
|
dead = true
|
|
AL10.alDeleteSources(source)
|
|
AL10.alDeleteBuffers(buffer)
|
|
return true
|
|
}
|
|
}
|
|
|
|
fun beep(pos: Vec3, pattern: String, frequency: Int = 1000, duration: Int = 200) {
|
|
NeoComputers.LOGGER.info("Beep: $pattern, $frequency Hz, $duration ms")
|
|
val mc = Minecraft.getInstance()
|
|
val playerPos = mc.player?.position() ?: pos
|
|
val distanceBasedGain = max(0.0, 1 - pos.distanceTo(playerPos) / BEEP_MAXDIST).toFloat()
|
|
val volume = 1.0
|
|
val gain = distanceBasedGain * volume
|
|
if (gain <= 0 || BEEP_AMPLITUDE <= 0) return
|
|
|
|
// Algorithm effectively ported over from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/util/Audio.scala
|
|
// We do add support for spaces tho
|
|
val charArr = pattern.toCharArray()
|
|
val sampleCounts = charArr.map { if(it == '.') duration else 2 * duration }.map { it * BEEP_SAMPLERATE / 1000 }
|
|
val pauseSample = 50 * BEEP_SAMPLERATE / 1000
|
|
|
|
val finalBuf = BufferUtils.createByteBuffer(sampleCounts.sum() + pauseSample * sampleCounts.lastIndex)
|
|
val step = frequency.toFloat() / BEEP_SAMPLERATE
|
|
var off = 0f
|
|
for((i, sampleCount) in sampleCounts.withIndex()) {
|
|
if(charArr[i] == ' ') {
|
|
for(sample in 0..<sampleCount) {
|
|
finalBuf.put(127)
|
|
}
|
|
} else {
|
|
for(sample in 0..<sampleCount) {
|
|
val angle = 2 * PI * off
|
|
val value = (sin(angle).sign * BEEP_AMPLITUDE).toInt().toByte().xor(0x80.toByte())
|
|
off += step
|
|
if(off > 1) off -= 1f
|
|
finalBuf.put(value)
|
|
}
|
|
}
|
|
if(finalBuf.hasRemaining()) {
|
|
for(sample in 0..<pauseSample) {
|
|
finalBuf.put(127)
|
|
}
|
|
}
|
|
}
|
|
finalBuf.rewind()
|
|
|
|
val sound = CustomSoundBuffer()
|
|
val soundErr = sound.start(pos.x.toFloat(), pos.y.toFloat(), pos.z.toFloat(), finalBuf, gain.toFloat())
|
|
if(soundErr != null) {
|
|
NeoComputers.LOGGER.error("Playing beep failed, OpenAL exit code of $soundErr")
|
|
return
|
|
}
|
|
|
|
NeoComputers.LOGGER.info("Beeping with ${finalBuf.capacity()} samples")
|
|
allSounds.get().addLast(sound)
|
|
}
|
|
|
|
fun tickCustomSounds() {
|
|
allSounds.get().removeIf { it.checkDone() }
|
|
}
|
|
} |