doxa-maps/plugin-designs/rudder-voice
Doxa Maps 416cc06eac
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Rudder speaks and fills the box sentence by sentence; Robot voice in a Settings menu, male by default
- Engine: start the speech server with --asr.endpointing.enable=true. End-of-sentence detection is OFF in
  NeMo-Speech.cpp by default, and session.update's endpointing_ms only sets the pause length, so every
  recording came back as ONE final when Stop was pressed (measured: a minute of speech → one 437-character
  final; the robot voice then spoke a minute late). Now each sentence is final ~1 s after it ends: in a
  Firefox run the robot spoke the first sentence 13 s before Stop.
- Settings menu in the full layout too (⚙, closed by default, works at phone width): Language, Microphone
  and Robot voice live in it; Record and the level meter stay visible.
- Robot voice: exactly ten presets, five male then five female, chosen by measured pitch (men ~92–108 Hz,
  women ~206–237 Hz); Male voice 1 is the default. The browser's own voices appear only as a fallback when
  the offline voices are missing, at most ten. A blocked or failed playback is shown instead of silence.
2026-09-28 14:30:56 +00:00
..
index.html Add Rudder (rudder-voice): offline streaming voice-to-text, a desktop-target plugin 2026-09-27 08:23:17 +00:00
index.js Add Rudder (rudder-voice): offline streaming voice-to-text, a desktop-target plugin 2026-09-27 08:23:17 +00:00
README.md Rudder: Robot voice — speak each sentence in a synthetic voice, offline, for videos without your real voice 2026-09-28 14:30:56 +00:00
rudder-voice.meta.json bun run desktop:build <plugin> packages a desktop plugin as an app; check:targets keeps non-web plugins out of the web build 2026-09-28 06:44:14 +00:00
rudder-voice.vue Rudder speaks and fills the box sentence by sentence; Robot voice in a Settings menu, male by default 2026-09-28 14:30:56 +00:00

rudder-voice — Rudder, private voice-to-text (desktop target)

Rudder is a plugin that isn't a map. It shows that the same dx-maps pattern — one folder in plugin-designs/, developed live in the Plugin Studio — can ship a desktop app with its own local backend, not only embeddable maps. It is also a genuinely useful tool for the people who build here: talk to your agents instead of typing, with nothing leaving your computer.

  • Streaming. Words appear in the text box while you speak; talk for as long as you like.

  • Type and talk at once. Type, paste links or code anywhere in the box; everything you say is added to the end. While you're typing, Rudder holds its words and adds them when you pause.

  • CPU only. No graphics card. About 1 GB of memory; keeps up with normal speech on a laptop CPU.

  • Offline, no telemetry. NVIDIA Nemotron 3.5 ASR Streaming (0.6B) run by NVIDIA's open-source NeMo-Speech.cpp (ggml). Traced with strace while transcribing: zero sockets, zero DNS lookups.

  • Microphones like a video-call app. Pick the input, hot-plug updates, a 3-second mic test, and plain-language errors (muted, disconnected, dead Bluetooth mic link — which it tries to recover).

  • Robot voice (optional). Settings → Robot voice: every sentence you say is spoken back in a synthetic voice, so you can record a video that uses dictation without your real voice in it. Offline neural voices (Piper, run by the engine on the CPU) or the browser's own on-device voices; nothing leaves the computer. While the robot talks through loudspeakers, the engine stops listening, so it never transcribes itself; with headphones it keeps listening.

Run it in the Plugin Studio

bash backend/rudder-engine/setup-runtime.sh   # once: runtime + model (verified by SHA-256) + engine build
bun run dev                                   # starts the engine too; open "rudder-voice" in the studio

Linux with PipeWire or PulseAudio (parec, pactl). The engine listens on 127.0.0.1 only.

How it fits together

<rudder-voice> (this folder)  ──HTTP + SSE /api/rudder──►  backend/rudder-engine (Go)
                                                            ├─ parec / pw-record  (mic, open only while recording)
                                                            └─ WebSocket ► nemo-speech serve (127.0.0.1, model stays loaded)

bun run dev starts backend/bin/rudder-engine through internal/backend-dev-plugin.mjs and forwards /api/rudder to it, so the plugin calls the same API in the browser as it will inside the desktop app, where Tauri runs the engine as a sidecar and passes its address in profile-config (apiBase).

rudder-voice.meta.json declares "targets": ["desktop"]: on a plain website there is no local engine, so this plugin is not meant for web embeds.

Settings (environment variables)

Variable Default What it does
RUDDER_PORT 5193 engine HTTP port (loopback)
RUDDER_NEMO_PORT 5194 speech server port (loopback)
RUDDER_MODEL / RUDDER_NEMO_BIN ~/.local/share/rudder/... model and runtime paths; the engine refuses a model that isn't a local file
RUDDER_LANG en-US default language (the model supports ~30)
RUDDER_KEEP_MSBC unset keep Bluetooth headsets on mSBC instead of switching them to the more stable CVSD
RUDDER_BT_RESET unset optional command that fully resets a Bluetooth headset, used when its mic link dies

Not yet

  • Desktop build (desktop:build) with the engine as a Tauri sidecar.
  • A saved word-correction table (PocketBase) for technical terms the model mishears.