From 1b4e672a4580e2d38edcd06510dabb45359f162b Mon Sep 17 00:00:00 2001 From: SoniEx2 Date: Sat, 15 Mar 2025 22:47:10 -0300 Subject: Make flite work --- .gitignore | 3 ++ build.gradle | 3 ++ flite/build_script.sh | 14 +++--- flite/extrafuncs.c | 20 ++++++++ .../space/autistic/radio/client/cli/Funny.kt | 11 +--- .../autistic/radio/client/cli/OfflineSimulator.kt | 28 +++++++++++ .../autistic/radio/client/flite/FliteFactory.kt | 58 ++++++++++++++++++++++ .../autistic/radio/client/flite/FliteWrapper.kt | 35 +++++++++++++ .../autistic/radio/client/opus/OpusDecoder.kt | 2 +- .../radio/client/reflection/MemoryReflection.kt | 14 ------ .../autistic/radio/reflection/MemoryReflection.kt | 14 ++++++ .../kotlin/space/autistic/radio/wasm/Bindings.kt | 5 ++ .../space/autistic/radio/wasm/WasmExitException.kt | 4 ++ 13 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 flite/extrafuncs.c create mode 100644 src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt create mode 100644 src/client/kotlin/space/autistic/radio/client/flite/FliteWrapper.kt delete mode 100644 src/client/kotlin/space/autistic/radio/client/reflection/MemoryReflection.kt create mode 100644 src/main/kotlin/space/autistic/radio/reflection/MemoryReflection.kt create mode 100644 src/main/kotlin/space/autistic/radio/wasm/WasmExitException.kt diff --git a/.gitignore b/.gitignore index 863eead..ea0871f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ replay_*.log # wasm *.wasm + +# flite +/src/main/resources/flite/ diff --git a/build.gradle b/build.gradle index 2c2c885..95aa747 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,9 @@ dependencies { include implementation("com.dylibso.chicory:aot-experimental:${project.chicory_version}") include implementation("com.github.wendykierp:JTransforms:3.1:with-dependencies") + // cli-only + implementation("com.github.ooxi:jdatauri:1.2.1") + testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}" testImplementation "org.jetbrains.kotlin:kotlin-test:${project.kotlin_test_version}" } diff --git a/flite/build_script.sh b/flite/build_script.sh index 4e18760..2748331 100755 --- a/flite/build_script.sh +++ b/flite/build_script.sh @@ -5,6 +5,9 @@ set -e cd flite-6c9f20dc915b17f5619340069889db0aa007fcdc s_EXPORTED_FUNCTIONS="-s EXPORTED_FUNCTIONS=$(printf '_%s,' \ +flite_wrapper_init \ +flite_text_to_wave \ +delete_val \ malloc )_free" @@ -12,14 +15,13 @@ shared_flags="-Oz -flto -fno-unroll-loops -fno-inline -mnontrapping-fptoint" compile_flags="-DNDEBUG -DNO_STDIO $shared_flags" link_flags="$shared_flags -s MALLOC=emmalloc -s ASSERTIONS=0 -s INITIAL_MEMORY=$((20*1024*1024)) -s TOTAL_STACK=$((2*1024*1024)) -s STANDALONE_WASM=1 -s PURE_WASI=1 -s STACK_OVERFLOW_CHECK=0 -s WASM_BIGINT=1 -s ABORTING_MALLOC=0 -s MEMORY_GROWTH_GEOMETRIC_STEP=0 -s ALLOW_MEMORY_GROWTH=1" -emconfigure ./configure STRIP='emstrip' CFLAGS="$compile_flags" LDFLAGS="$link_flags" --host=wasm32 --disable-sockets --disable-shared --without-pic --with-mmap=none --with-audio=none +emconfigure ./configure STRIP='emstrip' CFLAGS="$compile_flags" LDFLAGS="$link_flags" --host=wasm32 --disable-sockets --disable-shared --without-pic --with-mmap=none --with-audio=none --with-vox=cmu_us_kal emmake make -#emcc --no-entry $s_EXPORTED_FUNCTIONS $link_flags libmp3lame/.libs/libmp3lame.a -#em++ --no-entry $s_EXPORTED_FUNCTIONS $link_flags $compile_flags -o pocketfft.wasm impl.cc +emcc --no-entry $s_EXPORTED_FUNCTIONS $link_flags -o flite.wasm -Lbuild/wasm32-none/lib -Iinclude/ -lflite -lflite_usenglish -lflite_cmulex -lflite_cmu_us_kal ../extrafuncs.c -#mkdir -p ../../src/main/resources/flite/ -#cp a.out.wasm ../../src/main/resources/flite.wasm -#cp COPYING ../../src/main/resources/flite/ +mkdir -p ../../src/main/resources/flite/ +cp flite.wasm ../../src/main/resources/flite.wasm +cp COPYING ../../src/main/resources/flite/ exit 0 diff --git a/flite/extrafuncs.c b/flite/extrafuncs.c new file mode 100644 index 0000000..2831120 --- /dev/null +++ b/flite/extrafuncs.c @@ -0,0 +1,20 @@ +#include "flite.h" + +cst_voice *register_cmu_us_kal(const char *voxdir); + +void usenglish_init(cst_voice *v); +cst_lexicon *cmulex_init(void); + +void flite_set_lang_list(void) +{ + flite_add_lang("eng",usenglish_init,cmulex_init); + flite_add_lang("usenglish",usenglish_init,cmulex_init); +} + +cst_voice *flite_wrapper_init() { + flite_init(); + flite_set_lang_list(); + flite_voice_list = cons_val(voice_val(register_cmu_us_kal(NULL)),flite_voice_list); + flite_voice_list = val_reverse(flite_voice_list); + return flite_voice_select(NULL); +} diff --git a/src/client/kotlin/space/autistic/radio/client/cli/Funny.kt b/src/client/kotlin/space/autistic/radio/client/cli/Funny.kt index ebf6b06..2e64d36 100644 --- a/src/client/kotlin/space/autistic/radio/client/cli/Funny.kt +++ b/src/client/kotlin/space/autistic/radio/client/cli/Funny.kt @@ -1,16 +1,7 @@ package space.autistic.radio.client.cli -import space.autistic.radio.wasm.Bindings -import java.lang.invoke.MethodHandles -import kotlin.reflect.jvm.javaMethod +import space.autistic.radio.client.flite.FliteWrapper -object Funny { - val lookup = MethodHandles.lookup() - - fun test() { - } -} fun main() { - Bindings.bindFunc("", "", Funny.lookup, Funny::test.javaMethod!!, Funny) } \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/cli/OfflineSimulator.kt b/src/client/kotlin/space/autistic/radio/client/cli/OfflineSimulator.kt index 68c4719..2646ed2 100644 --- a/src/client/kotlin/space/autistic/radio/client/cli/OfflineSimulator.kt +++ b/src/client/kotlin/space/autistic/radio/client/cli/OfflineSimulator.kt @@ -1,10 +1,15 @@ package space.autistic.radio.client.cli +import com.github.ooxi.jdatauri.DataUri import org.joml.Vector2f import space.autistic.radio.client.complex.cmul +import space.autistic.radio.client.flite.FliteWrapper import space.autistic.radio.client.fmsim.FmFullConstants import space.autistic.radio.client.fmsim.FmFullDemodulator import space.autistic.radio.client.fmsim.FmFullModulator +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.DataOutputStream import java.io.FileInputStream import java.io.FileOutputStream import java.io.InputStream @@ -126,6 +131,29 @@ fun main(args: Array) { if (inputFile.filename != "file:///dev/zero") { if (inputFile.filename.startsWith("./")) { inputFile.stream = FileInputStream(inputFile.filename) + } else if (inputFile.filename.startsWith("data:")) { + val uri = try { + DataUri.parse(inputFile.filename, Charsets.UTF_8) + } catch (e: IllegalArgumentException) { + println("error parsing data URI") + exitProcess(1) + } + if (!uri.mime.startsWith("text/")) { + println("unsupported data URI format") + exitProcess(1) + } + FliteWrapper.textToWave(uri.data.toString(uri.charset ?: Charsets.UTF_8)) { + // 8k mono -> 48k stereo float + val bbuf = ByteBuffer.allocate(it.capacity() * 2 * 6 * 4) + val fbuf = bbuf.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() + while (it.hasRemaining()) { + val sample = (it.get().toFloat() + 0.5f) / 32767.5f + for (i in 0 until 2 * 6) { + fbuf.put(sample) + } + } + inputFile.stream = ByteArrayInputStream(bbuf.array()) + } } else { inputFile.stream = URI(inputFile.filename).toPath().inputStream() } diff --git a/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt b/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt new file mode 100644 index 0000000..993cbe9 --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/flite/FliteFactory.kt @@ -0,0 +1,58 @@ +package space.autistic.radio.client.flite + +import com.dylibso.chicory.experimental.aot.AotMachineFactory +import com.dylibso.chicory.runtime.ImportValues +import com.dylibso.chicory.runtime.Instance +import com.dylibso.chicory.wasm.Parser +import net.fabricmc.loader.api.FabricLoader +import space.autistic.radio.wasm.Bindings +import space.autistic.radio.wasm.WasmExitException +import java.io.InputStream +import java.lang.invoke.MethodHandles +import kotlin.reflect.jvm.javaMethod + +object FliteFactory : () -> Instance { + private fun fd_read(a: Int, b: Int, c: Int, d: Int): Int { + throw UnsupportedOperationException() + } + + private fun fd_seek(a: Int, b: Long, c: Int, d: Int): Int { + throw UnsupportedOperationException() + } + + private fun fd_close(a: Int): Int { + throw UnsupportedOperationException() + } + + private fun fd_write(a: Int, b: Int, c: Int, d: Int): Int { + throw UnsupportedOperationException() + } + + private fun proc_exit(status: Int): Nothing { + throw WasmExitException(status) + } + + private val lookup = MethodHandles.lookup() + private val defaultImports = ImportValues.builder() + .addFunction( + Bindings.bindFunc("wasi_snapshot_preview1", "fd_close", lookup, ::fd_close.javaMethod!!, this), + Bindings.bindFunc("wasi_snapshot_preview1", "fd_read", lookup, ::fd_read.javaMethod!!, this), + Bindings.bindFunc("wasi_snapshot_preview1", "fd_write", lookup, ::fd_write.javaMethod!!, this), + Bindings.bindFunc("wasi_snapshot_preview1", "fd_seek", lookup, ::fd_seek.javaMethod!!, this), + Bindings.bindFunc("wasi_snapshot_preview1", "proc_exit", lookup, ::proc_exit.javaMethod!!, this), + ).build() + private val module = Parser.parse(getModuleInputStream()) + private val instanceBuilder = + Instance.builder(module) + .withMachineFactory(AotMachineFactory(module)) + .withImportValues(defaultImports) + + override fun invoke(): Instance = instanceBuilder.build() + + private fun getModuleInputStream(): InputStream { + return FabricLoader.getInstance().getModContainer("pirate-radio").flatMap { it.findPath("flite.wasm") } + .map { it.toFile().inputStream() }.orElseGet { + this.javaClass.getResourceAsStream("/flite.wasm") + } + } +} \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/flite/FliteWrapper.kt b/src/client/kotlin/space/autistic/radio/client/flite/FliteWrapper.kt new file mode 100644 index 0000000..e73d4d0 --- /dev/null +++ b/src/client/kotlin/space/autistic/radio/client/flite/FliteWrapper.kt @@ -0,0 +1,35 @@ +package space.autistic.radio.client.flite + +import com.dylibso.chicory.runtime.ByteBufferMemory +import space.autistic.radio.reflection.getBuffer +import java.nio.ByteOrder +import java.nio.ShortBuffer +import java.util.function.Consumer + +object FliteWrapper { + // Produces audio at 8kHz mono + fun textToWave(s: String, consumer: Consumer) { + val instance = FliteFactory.invoke() + instance.export("_initialize").apply() + + val voice = instance.export("flite_wrapper_init").apply()[0] + + val textToWaveImpl = instance.export("flite_text_to_wave") + val mallocImpl = instance.export("malloc") + val memory = instance.memory() as ByteBufferMemory + + val bytes = s.toByteArray() + val space = mallocImpl.apply((bytes.size + 1).toLong())[0].toInt() + memory.getBuffer().run { + put(space, bytes) + put(space + bytes.size, 0) + } + val wavedata = textToWaveImpl.apply(space.toLong(), voice)[0].toInt() + val numSamples = memory.readInt(wavedata + 8) + val samplesPtr = memory.readInt(wavedata + 16) + consumer.accept( + memory.getBuffer().slice(samplesPtr, numSamples * 2).order(ByteOrder.LITTLE_ENDIAN) + .asShortBuffer() + ) + } +} \ No newline at end of file diff --git a/src/client/kotlin/space/autistic/radio/client/opus/OpusDecoder.kt b/src/client/kotlin/space/autistic/radio/client/opus/OpusDecoder.kt index fe78393..98e80d4 100644 --- a/src/client/kotlin/space/autistic/radio/client/opus/OpusDecoder.kt +++ b/src/client/kotlin/space/autistic/radio/client/opus/OpusDecoder.kt @@ -1,7 +1,7 @@ package space.autistic.radio.client.opus import com.dylibso.chicory.runtime.ByteBufferMemory -import space.autistic.radio.client.reflection.getBuffer +import space.autistic.radio.reflection.getBuffer import java.nio.ByteOrder class OpusDecoder(sampleRate: Int, private val channels: Int) { diff --git a/src/client/kotlin/space/autistic/radio/client/reflection/MemoryReflection.kt b/src/client/kotlin/space/autistic/radio/client/reflection/MemoryReflection.kt deleted file mode 100644 index b1da224..0000000 --- a/src/client/kotlin/space/autistic/radio/client/reflection/MemoryReflection.kt +++ /dev/null @@ -1,14 +0,0 @@ -package space.autistic.radio.client.reflection - -import com.dylibso.chicory.runtime.ByteBufferMemory -import java.lang.invoke.MethodHandles -import java.nio.ByteBuffer - -fun ByteBufferMemory.getBuffer(): ByteBuffer { - return MemoryReflection.buffer.get(this) as ByteBuffer -} - -object MemoryReflection { - val buffer = MethodHandles.privateLookupIn(ByteBufferMemory::class.java, MethodHandles.lookup()) - .findVarHandle(ByteBufferMemory::class.java, "buffer", ByteBuffer::class.java) -} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/reflection/MemoryReflection.kt b/src/main/kotlin/space/autistic/radio/reflection/MemoryReflection.kt new file mode 100644 index 0000000..78961da --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/reflection/MemoryReflection.kt @@ -0,0 +1,14 @@ +package space.autistic.radio.reflection + +import com.dylibso.chicory.runtime.ByteBufferMemory +import java.lang.invoke.MethodHandles +import java.nio.ByteBuffer + +fun ByteBufferMemory.getBuffer(): ByteBuffer { + return MemoryReflection.buffer.get(this) as ByteBuffer +} + +object MemoryReflection { + val buffer = MethodHandles.privateLookupIn(ByteBufferMemory::class.java, MethodHandles.lookup()) + .findVarHandle(ByteBufferMemory::class.java, "buffer", ByteBuffer::class.java) +} \ No newline at end of file diff --git a/src/main/kotlin/space/autistic/radio/wasm/Bindings.kt b/src/main/kotlin/space/autistic/radio/wasm/Bindings.kt index cc123a1..5ff4102 100644 --- a/src/main/kotlin/space/autistic/radio/wasm/Bindings.kt +++ b/src/main/kotlin/space/autistic/radio/wasm/Bindings.kt @@ -141,6 +141,11 @@ class Bindings { Value.EMPTY_VALUES } + Void::class.java -> HostFunction(module, name, wasmParameters, emptyList()) { instance, args -> + handle.invokeExact(instance, args) + throw IllegalStateException("unreachable") + } + Int::class.java -> HostFunction(module, name, wasmParameters, listOf(ValueType.I32)) { instance, args -> val result: Int = handle.invokeExact(instance, args) as Int longArrayOf(result.toLong()) diff --git a/src/main/kotlin/space/autistic/radio/wasm/WasmExitException.kt b/src/main/kotlin/space/autistic/radio/wasm/WasmExitException.kt new file mode 100644 index 0000000..43c08be --- /dev/null +++ b/src/main/kotlin/space/autistic/radio/wasm/WasmExitException.kt @@ -0,0 +1,4 @@ +package space.autistic.radio.wasm + +class WasmExitException(val status: Int) : Exception() { +} -- cgit 1.4.1