1#!/usr/bin/env bash
Fetch the offline neural voice Whiskers speaks with when the natural voice (ElevenLabs) is unavailable and the tablet's own voice has no data: sherpa-onnx's native library (arm64) and one Piper voice. Idempotent; every download is checked against a pinned sha256.
nix develop ~/whiskers#android -c tools/fetch-offline-voice.sh
Outputs (all gitignored, none ever committed): android/app/src/main/jniLibs/arm64-v8a/libonnxruntime.so, libsherpa-onnx-jni.so android/app/src/main/assets/offline-voice/{model.onnx,tokens.txt,espeak-ng-data/}
The Kotlin half (com.k2fsa.sherpa.onnx.Tts.kt) is committed, because it must be the file of the same release as the library (the JNI layer reads its fields by name); this script checks that it still is. A failure here exits non-zero: Gradle ignores that, and the app is then built without the voice and simply does not offer it. Licences: android/NOTICE.md.
The full-precision model, not int8 or fp16. Measured on a Fire HD 10 Kids tablet (MT8169, 2026-10-04): the int8 model took 11 to 14 s to load and made speech no faster than it plays; the fp32 one loads in about 4 s and runs at a fifth of real time; the fp16 one is half the size but sherpa-onnx 1.13.8's runtime refuses to load it ("Type (tensor(float16)) of output arg (/enc_p/Cast_1_output_0) ... does not match expected type (tensor(float))").
29kt=android/app/src/main/kotlin/com/k2fsa/sherpa/onnx/Tts.kt 30echo "$tts_kt_sha $kt" | sha256sum -c - >&2 \ 31 || { echo "$kt is not the Tts.kt of sherpa-onnx $ver: the library and its Kotlin half must match" >&2; exit 1; } 32 33jni=android/app/src/main/jniLibs/arm64-v8a 34assets=android/app/src/main/assets/offline-voice 35tmp=$(mktemp -d) 36trap 'rm -rf "$tmp"' EXIT 37 38if [ ! -f "$jni/libsherpa-onnx-jni.so" ] || [ ! -f "$jni/libonnxruntime.so" ]; then 39 curl -sSL -o "$tmp/lib.tar.bz2" "https://github.com/k2-fsa/sherpa-onnx/releases/download/$ver/sherpa-onnx-$ver-android.tar.bz2" 40 echo "$lib_sha $tmp/lib.tar.bz2" | sha256sum -c - >&2 41 tar -xjf "$tmp/lib.tar.bz2" -C "$tmp" ./jniLibs/arm64-v8a/libonnxruntime.so ./jniLibs/arm64-v8a/libsherpa-onnx-jni.so 42 mkdir -p "$jni" 43 cp "$tmp"/jniLibs/arm64-v8a/libonnxruntime.so "$tmp"/jniLibs/arm64-v8a/libsherpa-onnx-jni.so "$jni"/ 44fi 45 46if [ ! -f "$assets/model.onnx" ] || [ ! -f "$assets/tokens.txt" ] || [ ! -d "$assets/espeak-ng-data" ]; then 47 curl -sSL -o "$tmp/voice.tar.bz2" "https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/$voice.tar.bz2" 48 echo "$voice_sha $tmp/voice.tar.bz2" | sha256sum -c - >&2 49 tar -xjf "$tmp/voice.tar.bz2" -C "$tmp" 50 rm -rf "$assets" 51 mkdir -p "$assets" 52 cp "$tmp/$voice/en_US-ljspeech-medium.onnx" "$assets/model.onnx" 53 cp "$tmp/$voice/tokens.txt" "$assets/tokens.txt" 54 cp -r "$tmp/$voice/espeak-ng-data" "$assets/espeak-ng-data" 55 # The dictionaries of the other 100-odd languages (11 MB, Russian alone 8.5) are never read by an English voice. 56 find "$assets/espeak-ng-data" -maxdepth 1 -name '*_dict' ! -name en_dict -delete 57fi