JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 550
  • Score
    100M100P100Q88514F
  • License MIT

音字 — realtime speech ⇄ text. Node/Bun bindings for the otoji Rust crate (SenseVoice ASR + multilingual polish + TTS).

Package Exports

  • @otoji/core
  • @otoji/core/main.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@otoji/core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

otoji (音字)

realtime speech ⇄ text — 音を字に

otoji.org — wire mic → STT → translate → speech as a voice graph that runs on your devices. No accounts, no API keys, nothing leaves the browser by default: ASR, translation, and TTS all run on-device (transformers.js / onnxruntime-web / WebGPU / WASM).

Join a room and the graph spans your devices: a phone captures the mic, a laptop runs SenseVoice, any device speaks the result — chained over a WebRTC P2P mesh, Google-Meet-style shareable URLs (otoji.org/keen-gibbon-4a0d).

   phone                         laptop                        any device
 ┌─────────┐   opus/segment    ┌──────────────┐   transcript  ┌──────────┐
 │ Mic+VAD ├══════════════════>│ SenseVoice    ├═════════════>│ Translate │──► 🔊 / 📝 / .srt
 └─────────┘   WebRTC (P2P)    │ STT           │   WebRTC      └──────────┘
                               └──────────────┘

Try it

  • otoji.org — create/join a room, or hit "Try it here" to run the whole graph locally on one device (no room needed).
  • otoji.org/?simple — minimal one-box live transcription, no graph editor.

How it works

Each node has typed ports — segment (audio) or transcript (text) — and an edge is valid only when the source output type matches the target input type. Same-device edges run in-process; cross-device edges become a WebRTC data channel carrying audio segments + transcript frames. The graph itself is authoritative state, synced live to every device in the room through a Cloudflare Durable Object (the only server; it relays signaling + graph patches, never your audio).

Node catalog

Category Nodes
Input Mic + VAD · Mic (raw, no VAD) · Audio file in (drop or URL) · Text file in
Speech → Text SenseVoice STT (ONNX, multilingual + emotion/event tags) · Web Speech (browser streaming) · Vosk (Kaldi/WASM streaming)
Text → Text Translate — in-browser LLM (WebLLM) or the browser Translator API
Text → Speech Text-to-Speech (browser SpeechSynthesis) · Neural TTS (on-device ONNX MMS-TTS)
Output Transcript + Recordings · Audio file (out) · SRT subtitles (out) · Speaker (play)
Custom Any transformers.js model by repo/URL (ASR / text2text / TTS)
Pipe CLI stdio bridge — wire a terminal into the graph via otoji node

Models download once per device and can be shared P2P within a room, so only one device pays the download cost.

Local-first

Everything runs locally by default — no API keys, no cloud, no internet. Cloud providers (Anthropic / OpenAI / iFlytek / ElevenLabs / Gemini) are opt-in fallbacks you can configure with your own keys, stored only in your browser. Resolution order for every component: local in-process > local server > cloud API > disabled. See docs/ROADMAP.md.

CLI: bridge a terminal into a graph

The CLI pipe node bridges any terminal's stdio to the graph over the signaling relay (no WebRTC needed) — copy the command shown on the node:

npx otoji node otoji.org/keen-gibbon-4a0d/pipe-ab12   # text in graph → stdout; stdin → graph
otoji node my-room | grep foo                          # consume transcripts
some-producer | otoji node my-room                     # feed text in

@otoji/core — Node/Bun bindings

The Rust core also ships as an npm package with napi-rs bindings for one-shot and streaming on-device SenseVoice:

import { transcribe, listen } from "@otoji/core";

const { text } = await transcribe("meeting.wav");      // one-shot

const session = listen({                               // streaming
  onPartial: (t) => process.stdout.write(`\r${t}`),
  onFinal:   (t) => console.log(`\n✓ ${t}`),
});
session.push(pcmFloat32);                               // 16kHz mono f32
await session.end();

Repository layout

Path What
web/ The otoji.org app — React Flow voice-graph editor, WebRTC mesh, on-device providers, plus a browser extension + userscript
signal/ Cloudflare Worker + RoomDurableObject — presence, SDP/ICE relay, authoritative graph state
src/ Rust core — SenseVoice ASR, VAD, TTS, LLM polish; cpal/ratatui desktop CLI; napi bindings
cli/otoji-node.mjs otoji node — the stdio↔graph bridge
docs/ Architecture rationale, RT-ASR/model benchmarks, roadmap

See TODO.md for the distributed voice-graph design and milestone status, and docs/ for the deeper architecture/benchmark notes.

Develop

# Web app
cd web && bun install && bun run dev        # http://localhost:5173
bun run test                                # vitest
bun run test:e2e                            # playwright

# Rust core / desktop CLI
cargo build --release
cargo run -- listen                         # live mic → on-device STT in the TUI

License

MIT