108 lines
3.9 KiB
Markdown
108 lines
3.9 KiB
Markdown
This is a good fit for a local TypeScript CLI.
|
|
|
|
The clean architecture is:
|
|
|
|
1. Watch the EverQuest log file.
|
|
2. Parse incoming chat/dialog lines into events.
|
|
3. Resolve a stable speaker identity.
|
|
4. Look up or assign a voice for that speaker.
|
|
5. Send the text to Kokoro-FastAPI.
|
|
6. Play the returned audio locally.
|
|
7. Persist the speaker-to-voice mapping so the same NPC or guild member keeps the same voice.
|
|
|
|
A CLI can absolutely emit sound. It just needs to hand audio to something that can play it. On Linux and Windows, the most portable pattern is: have the CLI request WAV/MP3 from your TTS backend, then either play it through a Node audio library or spawn a bundled player such as `ffplay` or `mpv`.
|
|
|
|
A practical design would be:
|
|
|
|
* `watcher`: tails the EQ log
|
|
* `parser`: classifies lines as NPC speech, guild chat, tells, combat spam, system text
|
|
* `speaker-store`: `speakers.json` or SQLite
|
|
* `voice-selector`: deterministic initial assignment plus manual overrides
|
|
* `tts-client`: calls Kokoro-FastAPI over HTTP
|
|
* `audio-out`: queues and plays audio
|
|
* `config`: filters, volume, rate limits, voice pool, per-channel rules
|
|
|
|
For voice reuse, do not key only on raw display text. Use a normalized speaker key such as:
|
|
|
|
* `npc:<normalized name>`
|
|
* `player:guild:<character name>`
|
|
* optionally `zone:<zone name>` if the game reuses names across contexts
|
|
|
|
Then store something like:
|
|
|
|
```json
|
|
{
|
|
"npc:guard_thom": {
|
|
"voice": "en_female_04",
|
|
"last_seen": "2026-07-14T19:22:10"
|
|
},
|
|
"player:guild:sysadmin": {
|
|
"voice": "en_male_07"
|
|
}
|
|
}
|
|
```
|
|
|
|
For assignment, a good default is deterministic hashing:
|
|
|
|
* first encounter of a speaker hashes to one voice from a fixed pool
|
|
* manual overrides can replace that later
|
|
* the same speaker always gets the same voice on every machine if the voice pool matches
|
|
|
|
That gives you consistency without needing to curate every NPC by hand.
|
|
|
|
For NPC dialog versus guild chat, I would split policies:
|
|
|
|
* NPCs: one stable voice per NPC
|
|
* guild chat: one stable voice per guild member
|
|
* system/combat spam: usually ignore
|
|
* unknown speakers: assign a temporary voice and persist only if it repeats
|
|
|
|
A few implementation choices matter:
|
|
|
|
* Use a long-running local HTTP TTS server, not shelling out per line to generate everything from scratch.
|
|
* Add a queue so overlapping lines do not talk over each other.
|
|
* Cache generated clips for repeated lines.
|
|
* Add line deduping, because MMOs often repeat log lines.
|
|
* Add optional “interrupt” behavior for urgent lines, and “drop” behavior for spam.
|
|
|
|
For the TypeScript stack, I would use:
|
|
|
|
* `commander` or `oclif` for the CLI
|
|
* `chokidar` or a small tailing library for file watching
|
|
* `zod` for config validation
|
|
* `better-sqlite3` if you want a durable speaker database
|
|
* `execa` to invoke an audio player if you keep playback external
|
|
|
|
For cross-platform sharing, a CLI is still fine. The main constraint is audio playback. The simplest distribution path is:
|
|
|
|
* package the CLI as an npm tool
|
|
* have it talk to a locally running Kokoro-FastAPI instance
|
|
* require either `ffmpeg`/`mpv` installed, or bundle a playback method
|
|
* ship platform-specific startup scripts for Linux and Windows
|
|
|
|
If you want the widest compatibility, keep the core app platform-neutral and avoid Linux-only assumptions like PipeWire-specific commands. Generate standard WAV and play it with a cross-platform backend.
|
|
|
|
The likely best prototype is:
|
|
|
|
```text
|
|
eq-tts watch --log ~/.wine/drive_c/.../eqlog.txt --voice-mode hash --playback mpv
|
|
```
|
|
|
|
Then expand it with:
|
|
|
|
```text
|
|
eq-tts map npc "Guard Thomas" --voice en_male_03
|
|
eq-tts mute "combat"
|
|
eq-tts channel guild --enabled
|
|
eq-tts export-speakers
|
|
```
|
|
|
|
My recommendation: build the first version as a Node/TypeScript CLI with three boundaries only:
|
|
|
|
* file tailing
|
|
* speaker/voice mapping
|
|
* audio playback
|
|
|
|
Keep the Kokoro integration behind a single HTTP client. That will make it easy to swap in another local TTS backend later, and easy for Windows users to run the same app.
|
|
|