diff options
author | SoniEx2 <endermoneymod@gmail.com> | 2025-03-19 15:46:15 -0300 |
---|---|---|
committer | SoniEx2 <endermoneymod@gmail.com> | 2025-03-19 15:46:15 -0300 |
commit | ee651c9014db7593fa62e22d0a28bb94bf9f57f5 (patch) | |
tree | 542c8901698fb6fe41b408d56d0280f406e64ca6 | |
parent | 02321a466d5cd069d7f969f51d9485e343d6fae9 (diff) |
Don't cross-thread entities, fix flite loading
3 files changed, 140 insertions, 130 deletions
diff --git a/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt b/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt index 993cbe9..89b9656 100644 --- a/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt +++ b/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt @@ -9,6 +9,7 @@ import space.autistic.radio.wasm.Bindings import space.autistic.radio.wasm.WasmExitException import java.io.InputStream import java.lang.invoke.MethodHandles +import kotlin.io.path.inputStream import kotlin.reflect.jvm.javaMethod object FliteFactory : () -> Instance { @@ -51,7 +52,7 @@ object FliteFactory : () -> Instance { private fun getModuleInputStream(): InputStream { return FabricLoader.getInstance().getModContainer("pirate-radio").flatMap { it.findPath("flite.wasm") } - .map<InputStream?> { it.toFile().inputStream() }.orElseGet { + .map { it.inputStream() }.orElseGet { this.javaClass.getResourceAsStream("/flite.wasm") } } diff --git a/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt b/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt index 51b946d..d3b39b8 100644 --- a/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt +++ b/src/client/kotlin/space/autistic/radio/client/fmsim/FmFullThread.kt @@ -11,12 +11,13 @@ import space.autistic.radio.entity.DisposableTransmitterEntity import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.FloatBuffer +import java.util.UUID import java.util.concurrent.ArrayBlockingQueue import kotlin.math.max object FmFullThread : Runnable { class FmTask( - val trackedTransmitters: Map<DisposableTransmitterEntity, PirateRadioSoundInstance.TrackedTransmitter>, + val trackedTransmitters: Map<UUID, PirateRadioSoundInstance.TrackedTransmitter>, val noiseLevels: FloatArray, ) @@ -35,12 +36,13 @@ object FmFullThread : Runnable { // 3 seconds private const val REPEAT_TIMEOUT = 8000 * 3 + // default to 0.05s (1/20) private val bufferSize = System.getProperty("space.autistic.radio.buffer.size", "").toIntOrNull() ?: 2400 override fun run() { var currentTask = EMPTY_TASK - val modulators = HashMap<DisposableTransmitterEntity, TtsModulator>() + val modulators = HashMap<UUID, TtsModulator>() val mixingBuffers = Array(3) { FloatBuffer.allocate(FmFullConstants.FFT_DATA_BLOCK_SIZE_48K_300K * 2) } val inputBuffer = FloatBuffer.allocate(FmFullConstants.FFT_DATA_BLOCK_SIZE_LPF_48K_15K_3K1 * 2) @@ -64,157 +66,163 @@ object FmFullThread : Runnable { null } - while (!Thread.interrupted()) { - currentTask = trackedTransmitterQueue.poll() ?: currentTask - if (currentTask === EMPTY_TASK) { - Thread.onSpinWait() - continue - } - modulators.keys.retainAll(currentTask.trackedTransmitters.keys) - currentTask.trackedTransmitters.forEach { (k, v) -> - modulators.compute(k) { _, modulator -> - if (modulator != null) { - modulator.power = v.power - modulator.mixingBuffer = mixingBuffers[v.frequencyOffset + 1] - return@compute modulator - } - val audioData = v.audio.getNow(null) - if (audioData != null) { - val buf = FloatBuffer.wrap(audioData) - var actualSampleOffset = Math.floorMod(v.sampleOffset, (buf.capacity() + REPEAT_TIMEOUT)) - var repeatTimeout = max(0, actualSampleOffset - buf.capacity()) - if (repeatTimeout > 0) { - actualSampleOffset = 0 - repeatTimeout = REPEAT_TIMEOUT - repeatTimeout + try { + while (!Thread.interrupted()) { + currentTask = trackedTransmitterQueue.poll() ?: currentTask + if (currentTask === EMPTY_TASK) { + Thread.onSpinWait() + continue + } + modulators.keys.retainAll(currentTask.trackedTransmitters.keys) + currentTask.trackedTransmitters.forEach { (k, v) -> + modulators.compute(k) { _, modulator -> + if (modulator != null) { + modulator.power = v.power + modulator.mixingBuffer = mixingBuffers[v.frequencyOffset + 1] + return@compute modulator } - if (actualSampleOffset == buf.capacity()) { - actualSampleOffset = 0 - repeatTimeout = REPEAT_TIMEOUT + val audioData = v.audio.getNow(null) + if (audioData != null) { + val buf = FloatBuffer.wrap(audioData) + var actualSampleOffset = Math.floorMod(v.sampleOffset, (buf.capacity() + REPEAT_TIMEOUT)) + var repeatTimeout = max(0, actualSampleOffset - buf.capacity()) + if (repeatTimeout > 0) { + actualSampleOffset = 0 + repeatTimeout = REPEAT_TIMEOUT - repeatTimeout + } + if (actualSampleOffset == buf.capacity()) { + actualSampleOffset = 0 + repeatTimeout = REPEAT_TIMEOUT + } + buf.position(actualSampleOffset) + TtsModulator( + buf, FmFullModulator(), v.power, repeatTimeout, mixingBuffers[v.frequencyOffset + 1] + ) + } else { + null } - buf.position(actualSampleOffset) - TtsModulator( - buf, FmFullModulator(), v.power, repeatTimeout, mixingBuffers[v.frequencyOffset + 1] - ) - } else { - null } } - } - mixingBuffers.forEach { - it.clear() - while (it.hasRemaining()) it.put(0f) - it.clear() - } + mixingBuffers.forEach { + it.clear() + while (it.hasRemaining()) it.put(0f) + it.clear() + } - modulators.values.forEach { - inputBuffer.clear() - while (inputBuffer.hasRemaining()) { - val sample = if (it.repeatTimeout > 0) { - it.repeatTimeout-- - 0f - } else { - it.buffer.get() - } - if (!it.buffer.hasRemaining()) { - it.repeatTimeout = REPEAT_TIMEOUT - it.buffer.clear() + modulators.values.forEach { + inputBuffer.clear() + while (inputBuffer.hasRemaining()) { + val sample = if (it.repeatTimeout > 0) { + it.repeatTimeout-- + 0f + } else { + it.buffer.get() + } + if (!it.buffer.hasRemaining()) { + it.repeatTimeout = REPEAT_TIMEOUT + it.buffer.clear() + } + for (i in 0 until 2 * 6) inputBuffer.put(sample) } - for (i in 0 until 2 * 6) inputBuffer.put(sample) - } - inputBuffer.clear() - val mixingBuffer = it.mixingBuffer - it.modulator.process(inputBuffer, it.power, false) { outputBuffer -> - for (i in 0 until mixingBuffer.capacity()) { - mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get()) + inputBuffer.clear() + val mixingBuffer = it.mixingBuffer + it.modulator.process(inputBuffer, it.power, false) { outputBuffer -> + for (i in 0 until mixingBuffer.capacity()) { + mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get()) + } } } - } - if (modulators.any { it.value.mixingBuffer === mixingBuffers[2] }) { - val floatBufferHi = mixingBuffers[2] - val floatView = mixingBuffers[1] - val z = Vector2f() - val w = Vector2f() - for (i in 0 until floatBufferHi.capacity() step 2) { - z.x = floatBufferHi.get(i) - z.y = floatBufferHi.get(i + 1) - if (!plus100k.hasRemaining()) { - plus100k.clear() + if (modulators.any { it.value.mixingBuffer === mixingBuffers[2] }) { + val floatBufferHi = mixingBuffers[2] + val floatView = mixingBuffers[1] + val z = Vector2f() + val w = Vector2f() + for (i in 0 until floatBufferHi.capacity() step 2) { + z.x = floatBufferHi.get(i) + z.y = floatBufferHi.get(i + 1) + if (!plus100k.hasRemaining()) { + plus100k.clear() + } + w.x = plus100k.get() + w.y = plus100k.get() + z.cmul(w) + floatView.put(i, floatView.get(i) + z.x) + floatView.put(i + 1, floatView.get(i + 1) + z.y) } - w.x = plus100k.get() - w.y = plus100k.get() - z.cmul(w) - floatView.put(i, floatView.get(i) + z.x) - floatView.put(i + 1, floatView.get(i + 1) + z.y) } - } - if (modulators.any { it.value.mixingBuffer === mixingBuffers[0] }) { - val floatBufferLo = mixingBuffers[0] - val floatView = mixingBuffers[1] - val z = Vector2f() - val w = Vector2f() - for (i in 0 until floatBufferLo.capacity() step 2) { - z.x = floatBufferLo.get(i) - z.y = floatBufferLo.get(i + 1) - if (!minus100k.hasRemaining()) { - minus100k.clear() + if (modulators.any { it.value.mixingBuffer === mixingBuffers[0] }) { + val floatBufferLo = mixingBuffers[0] + val floatView = mixingBuffers[1] + val z = Vector2f() + val w = Vector2f() + for (i in 0 until floatBufferLo.capacity() step 2) { + z.x = floatBufferLo.get(i) + z.y = floatBufferLo.get(i + 1) + if (!minus100k.hasRemaining()) { + minus100k.clear() + } + w.x = minus100k.get() + w.y = -minus100k.get() + z.cmul(w) + floatView.put(i, floatView.get(i) + z.x) + floatView.put(i + 1, floatView.get(i + 1) + z.y) } - w.x = minus100k.get() - w.y = -minus100k.get() - z.cmul(w) - floatView.put(i, floatView.get(i) + z.x) - floatView.put(i + 1, floatView.get(i + 1) + z.y) } - } - noiseGens.forEachIndexed { index, v -> - if (currentTask.noiseLevels[index] != 0f) { - v.generateNoise(currentTask.noiseLevels[index]) { outputBuffer -> - val mixingBuffer = mixingBuffers[1] - for (i in 0 until mixingBuffer.capacity()) { - mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get()) + noiseGens.forEachIndexed { index, v -> + if (currentTask.noiseLevels[index] != 0f) { + v.generateNoise(currentTask.noiseLevels[index]) { outputBuffer -> + val mixingBuffer = mixingBuffers[1] + for (i in 0 until mixingBuffer.capacity()) { + mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get()) + } } } } - } - noiseFloor.noiseBlock { outputBuffer -> - val mixingBuffer = mixingBuffers[1] - for (i in 0 until mixingBuffer.capacity()) { - mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get()) + noiseFloor.noiseBlock { outputBuffer -> + val mixingBuffer = mixingBuffers[1] + for (i in 0 until mixingBuffer.capacity()) { + mixingBuffer.put(i, mixingBuffer.get(i) + outputBuffer.get()) + } } - } - demodulator.process(mixingBuffers[1], PirateRadioClient.stereo) { _, audioBuffer -> - // TODO stereo pilot - // we *want* backpressure - // FIXME use bigger buffers? - if (ReceiverAudioStream.useNativeAudio) { - while (audioBuffer.hasRemaining()) { - if (!outputBytes.hasRemaining()) { - val written = nativeAudio!!.write(outputBytes.array(), 0, outputBytes.capacity()) - outputBytes.position(written).compact() - if (written == 0) { - nativeAudio.start() - continue + demodulator.process(mixingBuffers[1], PirateRadioClient.stereo) { _, audioBuffer -> + // TODO stereo pilot + // we *want* backpressure + // FIXME use bigger buffers? + if (ReceiverAudioStream.useNativeAudio) { + while (audioBuffer.hasRemaining()) { + if (!outputBytes.hasRemaining()) { + val written = nativeAudio!!.write(outputBytes.array(), 0, outputBytes.capacity()) + outputBytes.position(written).compact() + if (written == 0) { + nativeAudio.start() + continue + } } + val volume = PirateRadioClient.volume + outputBytes.putShort( + (MathHelper.clamp( + (audioBuffer.get() * 32767.5f - 0.5f).toInt(), -32768, 32767 + ) * volume * volume / 100).toShort() + ) } - val volume = PirateRadioClient.volume - outputBytes.putShort( - (MathHelper.clamp( - (audioBuffer.get() * 32767.5f - 0.5f).toInt(), -32768, 32767 - ) * volume * volume / 100).toShort() + } else { + ReceiverAudioStream.bufferQueue.put( + FloatBuffer.allocate(audioBuffer.capacity()).put(audioBuffer).clear() ) } - } else { - ReceiverAudioStream.bufferQueue.put( - FloatBuffer.allocate(audioBuffer.capacity()).put(audioBuffer).clear() - ) } } + } catch (e: Throwable) { + PirateRadio.logger.error("Quitting FM simulation thread, as something went wrong!", e) + // for some reason it doesn't print stack trace but we still wanna propagate the exception to the thread + // itself + throw e } - PirateRadio.logger.error("Quitting FM simulation thread, as it has been interrupted!") } -} \ No newline at end of file +} diff --git a/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt b/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt index 4e3fd51..601b458 100644 --- a/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt +++ b/src/client/kotlin/space/autistic/radio/client/sound/PirateRadioSoundInstance.kt @@ -15,6 +15,7 @@ import space.autistic.radio.client.fmsim.FmSimulatorMode import space.autistic.radio.entity.DisposableTransmitterEntity import java.lang.ref.WeakReference import java.nio.FloatBuffer +import java.util.UUID import java.util.concurrent.CompletableFuture import kotlin.math.PI @@ -68,8 +69,8 @@ class PirateRadioSoundInstance(private val player: ClientPlayerEntity) : MovingS noise + getPowerReceived(entity.pos.squaredDistanceTo(player.pos)) } // updated tracked transmitters - val trackedTransmitters: MutableMap<DisposableTransmitterEntity, TrackedTransmitter> = HashMap() - listOf(lower, main, upper).flatten().associateWithTo(trackedTransmitters) { + val trackedTransmitters: MutableMap<UUID, TrackedTransmitter> = HashMap() + listOf(lower, main, upper).flatten().associateTo(trackedTransmitters) { val text = it.text val audio = futuresCache[text]?.get() ?: CompletableFuture.supplyAsync { lateinit var buffer: FloatBuffer @@ -83,7 +84,7 @@ class PirateRadioSoundInstance(private val player: ClientPlayerEntity) : MovingS buffer.array() } futuresCache[text] = WeakReference(audio) - TrackedTransmitter( + it.uuid to TrackedTransmitter( getPowerReceived(it.pos.squaredDistanceTo(player.pos)), it.age * (8000 / 20), audio, |