Settings → Robot voice: a checkbox and a voice dropdown. When on, every finished sentence is spoken back (sentences, not words: TTS needs context for its intonation). speak(text) is the single entry point, so a future conversational mode can reuse the same voice. - Providers, both on this computer only: Piper (offline neural TTS run by the engine on the CPU; six LibriTTS-R speakers offered as "Offline voice A–F") and the browser's own voices, restricted to localService === true (some browsers synthesise "online" voices on a server — never offered). - backend/rudder-engine/tts.go: POST /api/rudder/tts (text → audio/wav, 1–2000 characters, one synthesis at a time), GET /api/rudder/tts/voices, POST /api/rudder/duck. The spoken text is never logged, only its length. Piper 2023.11.14-2 (MIT) bundles ONNX Runtime 1.14.1: no telemetry client; a synthesis traced with strace made 0 network calls. ~150 MB RAM; a 10.7 s sentence rendered in 2.2 s on CPU. - While the robot speaks through loudspeakers, the engine feeds the transcriber silence, so it never transcribes itself (and that silence is not mistaken for a dead mic). With a Bluetooth headset or headphones it keeps listening — pausing would only lose the person's words. - setup-runtime.sh: Piper and the en_US-libritts_r-medium voice (CC BY 4.0), pinned by SHA-256. - Verified in Firefox against a dev server: dropdown lists A–F, each finished sentence is synthesised (200 audio/wav) between duck on/off, zero requests to any other host.
74 lines
4.2 KiB
Bash
Executable file
74 lines
4.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# setup-runtime.sh — fetch Rudder's speech runtime + model, verified by SHA-256, then build the engine.
|
|
#
|
|
# Rudder (plugin-designs/rudder-voice) transcribes on THIS computer. It needs two things that are too big
|
|
# for git: NVIDIA's NeMo-Speech.cpp runtime (~4 MB, CPU build) and the Nemotron 3.5 ASR Streaming GGUF
|
|
# (~742 MB). Both are pinned by version and checksum; a mismatch aborts instead of running an unverified
|
|
# file. After this, `bun run dev` starts the engine and Rudder appears in the Plugin Studio.
|
|
#
|
|
# bash backend/rudder-engine/setup-runtime.sh # installs into ~/.local/share/rudder
|
|
# RUDDER_HOME=/some/dir bash backend/rudder-engine/setup-runtime.sh
|
|
#
|
|
# Linux x86_64 only for now (the runtime ships other platforms; add them when a desktop build needs them).
|
|
set -euo pipefail
|
|
|
|
RUDDER_HOME="${RUDDER_HOME:-$HOME/.local/share/rudder}"
|
|
RT_VER="0.1.0"
|
|
RT_NAME="nemo-speech-${RT_VER}-linux-x86_64-cpu"
|
|
RT_URL="https://github.com/NVIDIA/NeMo-Speech.cpp/releases/download/v${RT_VER}/${RT_NAME}.tar.gz"
|
|
RT_SHA="0f74131d631ad2c694cf0ec53490866bb6461147959589a69fb6fc231944065b"
|
|
# NVIDIA re-converted the GGUF on 2026-09-10 (embedded tokenizer); this revision is the one Rudder is tested with.
|
|
MODEL_REV="ea30d66debe3"
|
|
MODEL_FILE="nemotron-3.5-asr-streaming-0.6b.q8_0.gguf"
|
|
MODEL_URL="https://huggingface.co/nvidia/nemotron-3.5-asr-streaming-0.6b/resolve/${MODEL_REV}/${MODEL_FILE}"
|
|
MODEL_SHA="3fc991d3badad7277c11030a7519832cddaf2057aafed6d4b25147e953a070b1"
|
|
# Robot voice (optional text-to-speech): Piper 2023.11.14-2 (MIT; ONNX Runtime 1.14.1 inside — no telemetry
|
|
# client, 0 network calls in a traced synthesis) and one CC BY 4.0 voice (LibriTTS-R, 904 speakers).
|
|
PIPER_URL="https://github.com/rhasspy/piper/releases/download/2023.11.14-2/piper_linux_x86_64.tar.gz"
|
|
PIPER_SHA="a50cb45f355b7af1f6d758c1b360717877ba0a398cc8cbe6d2a7a3a26e225992"
|
|
VOICE_REV="c10ece1aade47bb51c153c893d14e5bf8e5b7117"
|
|
VOICE="en_US-libritts_r-medium"
|
|
VOICE_BASE="https://huggingface.co/rhasspy/piper-voices/resolve/${VOICE_REV}/en/en_US/libritts_r/medium"
|
|
VOICE_SHA="10bb85e071d616fcf4071f369f1799d0491492ab3c5d552ec19fb548fac13195"
|
|
VOICE_JSON_SHA="b471dc60d2d8335e819c393d196d6fbf792817f40051257b269878505bc9afb3"
|
|
|
|
say() { printf '\033[1;34m→\033[0m %s\n' "$*"; }
|
|
die() { printf '\033[1;31m✗\033[0m %s\n' "$*" >&2; exit 1; }
|
|
|
|
[ "$(uname -s)-$(uname -m)" = "Linux-x86_64" ] || die "This script currently supports Linux x86_64 only."
|
|
command -v curl >/dev/null || die "curl is required."
|
|
command -v sha256sum >/dev/null || die "sha256sum is required."
|
|
|
|
fetch() { # url dest sha
|
|
local url="$1" dest="$2" sha="$3"
|
|
if [ -f "$dest" ] && [ "$(sha256sum "$dest" | cut -d' ' -f1)" = "$sha" ]; then
|
|
say "already have $(basename "$dest") (checksum OK)"; return
|
|
fi
|
|
say "downloading $(basename "$dest")"
|
|
curl -fL --retry 3 -C - -o "$dest.part" "$url"
|
|
[ "$(sha256sum "$dest.part" | cut -d' ' -f1)" = "$sha" ] || { rm -f "$dest.part"; die "checksum mismatch for $(basename "$dest") — refusing to use it"; }
|
|
mv "$dest.part" "$dest"
|
|
}
|
|
|
|
mkdir -p "$RUDDER_HOME/bin" "$RUDDER_HOME/models"
|
|
fetch "$RT_URL" "$RUDDER_HOME/bin/$RT_NAME.tar.gz" "$RT_SHA"
|
|
tar -xzf "$RUDDER_HOME/bin/$RT_NAME.tar.gz" -C "$RUDDER_HOME/bin"
|
|
fetch "$MODEL_URL" "$RUDDER_HOME/models/$MODEL_FILE" "$MODEL_SHA"
|
|
|
|
mkdir -p "$RUDDER_HOME/tts"
|
|
fetch "$PIPER_URL" "$RUDDER_HOME/tts/piper_linux_x86_64.tar.gz" "$PIPER_SHA"
|
|
tar -xzf "$RUDDER_HOME/tts/piper_linux_x86_64.tar.gz" -C "$RUDDER_HOME/tts"
|
|
fetch "$VOICE_BASE/$VOICE.onnx" "$RUDDER_HOME/tts/$VOICE.onnx" "$VOICE_SHA"
|
|
fetch "$VOICE_BASE/$VOICE.onnx.json" "$RUDDER_HOME/tts/$VOICE.onnx.json" "$VOICE_JSON_SHA"
|
|
|
|
if command -v go >/dev/null; then
|
|
say "building backend/bin/rudder-engine"
|
|
(cd "$(dirname "$0")" && go build -o ../bin/rudder-engine .)
|
|
else
|
|
printf '! Go not found — install Go, then: cd backend/rudder-engine && go build -o ../bin/rudder-engine .\n'
|
|
fi
|
|
|
|
for tool in parec pactl; do command -v "$tool" >/dev/null || printf '! %s not found — install pipewire-pulse or pulseaudio-utils for microphone capture.\n' "$tool"; done
|
|
|
|
say "done. Runtime: $RUDDER_HOME/bin/$RT_NAME Model: $RUDDER_HOME/models/$MODEL_FILE"
|
|
say "run \`bun run dev\` and open Rudder (rudder-voice) in the Plugin Studio."
|