okay so this is TTSmodachi.
it is a Discord TTS bot that talks through the Tomodachi Life / Talkmodachi 3DS voice renderer. you run the Discord bot, a renderer service, and a patched Citra worker pool. people type in the voice channel chat, or a text channel you pick with /channel, and the bot reads it out loud with goofy Tomodachi voices.
this repo does not include ROMs, bring your own legally dumped game files.
- Discord slash commands like
/join,/leave,/channel,/skip,/voice,/voices,/replace, and/settings - a warm renderer service so Citra is not booting for every message
- SQLite storage for server settings, user voices, linked dashboard accounts, and voice targets
- a web voice panel for changing pitch, speed, quality, tone, accent, intonation, language, and volume
- optional second bot support for another voice channel in the same server
- Docker Compose setup for the bot, optional bot2, and renderer worker
- cache pruning so repeated messages can play faster
you need:
- Docker Desktop on Windows, or Docker Engine on Linux
- Git
- a Discord application with a bot user
- Message Content Intent enabled for the bot in the Discord Developer Portal
- a legally dumped Tomodachi Life CXI
- enough CPU for Citra workers. start with 1 worker if you are not sure
do not upload ROMs to GitHub. keep them in roms/ only.
- go to the Discord Developer Portal
- create a new application
- open Bot, then add a bot
- copy the bot token
- enable Message Content Intent
- open OAuth2, copy the Client ID
- keep the token private. if it ever gets posted somewhere, reset it
the default permission integer in .env.example is 36785216. that is what this setup uses for the invite URL.
git clone https://github.com/coah80/ttsmodachi.git
cd ttsmodachi
cp .env.example .envon Windows PowerShell, use:
Copy-Item .env.example .envnow edit .env.
minimum local test values:
DISCORD_TOKEN=replace_me
TTSMODACHI_BOT_CLIENT_ID=your_discord_application_client_id_here
TTSMODACHI_PANEL_URL=http://127.0.0.1:18080
TTSMODACHI_WORKER_ROMS=US
TTSMODACHI_US_WORKERS=1
TTSMODACHI_MAX_INFLIGHT_RENDERS=4
TTSMODACHI_BOT_RENDER_CONCURRENCY=2for a public host, change these:
TTSMODACHI_PANEL_URL=https://your-domain.example
TTSMODACHI_PUBLIC_HOSTS=your-domain.example
TTSMODACHI_PANEL_SIGNING_KEY=replace_me
TTSMODACHI_SUPPORT_INVITE_URL=https://discord.gg/your-support-serveryou can make a signing key with:
python -c "import secrets; print(secrets.token_urlsafe(48))"this part is the least friendly part, sorry. there is a tool way now, and then the manual way if the tool is annoying on your setup.
you need a legal Tomodachi Life dump as a CXI. the renderer expects patched files named like this:
roms/US.cxi
roms/EU.cxi
roms/JP.cxi
roms/KR.cxi
only put the regions you are actually using in .env. for example:
TTSMODACHI_WORKER_ROMS=US
TTSMODACHI_US_WORKERS=1you still need 3dstool and Magikoopa installed. the helper does the extract and rebuild steps, then pauses while you run the Magikoopa patch step.
from the repo folder:
python tools/patch_rom.py --input ./TomodachiLife.cxi --region USon Windows, if python is not on PATH:
py -3 tools\patch_rom.py --input C:\path\to\TomodachiLife.cxi --region USif 3dstool is not on PATH:
python tools/patch_rom.py --input ./TomodachiLife.cxi --region US --three-dstool /path/to/3dstoolif you want the helper to launch Magikoopa for you:
python tools/patch_rom.py --input ./TomodachiLife.cxi --region US --magikoopa /path/to/Magikoopawhat the tool does:
- extracts the CXI into
.rom-patch-work/ - extracts ExeFS and tries to decompress
code.bin - stages a Magikoopa working folder at
.rom-patch-work/gamePatch/ - waits for you to press Make and Insert in Magikoopa
- rebuilds the CXI into
roms/US.cxi
if Magikoopa is easier to run manually, start with:
python tools/patch_rom.py --input ./TomodachiLife.cxi --region US --prepare-onlyopen .rom-patch-work/gamePatch/ in Magikoopa, press Make and Insert, then run the resume command the tool prints.
the original Talkmodachi patch flow is:
- extract
code.binandexheader.binfrom your CXI with3dstool - if
code.binis compressed, decompress it - put
code.binandexheader.biningamePatch/ - build the patch with Magikoopa
- put the patched files back into the extracted CXI contents
- rebuild the CXI
- put the final patched CXI in
roms/
example 3dstool commands, using a US dump:
3dstool -xvtf cxi ./TomodachiLife.cxi --header header --exefs exefs --exh exheader.bin --logo logo --plain plain --romfs romfs
3dstool -xvtf exefs ./exefs --exefs-dir exefsd --header exfsheader
3dstool -u --file ./exefsd/code.bin --compress-type blz --compress-out ./exefsd/code_unc.bin
mv ./exefsd/code_unc.bin ./exefsd/code.binafter patching with Magikoopa:
3dstool -cvtf exefs ./exefs --exefs-dir exefsd --header exfsheader
3dstool -cvtf cxi ./US.cxi --header header --exefs exefs --exh exheader.bin --logo logo --plain plain --romfs romfs --not-encrypt
mkdir -p roms
mv ./US.cxi ./roms/US.cxitools you probably need:
3dstool: https://github.com/dnasdw/3dstool- Magikoopa: https://github.com/RicBent/Magikoopa
- GodMode9 on your own 3DS for dumping your own game
build and start the normal one-bot setup:
docker compose up --buildor run it in the background:
docker compose up --build -d
docker compose logs -f tts-worker botopen the panel locally:
http://127.0.0.1:18080
check renderer health:
curl http://127.0.0.1:18080/healthif you also want the second bot container:
docker compose --profile bot2 up --build -dthen set DISCORD_TOKEN_2 and TTSMODACHI_SECOND_BOT_CLIENT_ID in .env.
when the renderer is up, visit:
http://127.0.0.1:18080
if TTSMODACHI_BOT_CLIENT_ID is set, the page can generate an invite link. you can also build one yourself:
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=36785216&scope=bot%20applications.commands
inside Discord:
- join a voice channel
- run
/join - type in that voice channel's built-in chat, or run
/channel #some-channelto pick a normal text channel - use
/voiceto open your personal voice dashboard
/joinjoins your current voice channel/leaveleaves voice/channelsets the text channel the bot reads into the voice channel it is joined in/skipclears queued TTS and stops current playback/settingsshows current server settings/set read_non_vc_messages truereads voice-channel chat from people not in that voice channel/voiceopens the signed voice dashboard link/unlinkremoves your dashboard link and panel voice preset/voices listshows built-in and saved voices/voices savesaves a custom voice/voices useselects your voice/voices defaultsets the server default/replace add/remove/list/clearmanages pronunciation replacements
text shortcuts also exist:
-skipskips the current playback-message hereskips your own message from TTS
/channel #some-channel sets a normal text channel for TTS. messages sent there get read into the voice channel TTSmodachi is currently joined in. run /channel with no channel to clear it and go back to just voice channel chat.
/set bot_ignore false lets other bots and webhooks get read from the current voice channel chat or configured /channel text channel too, including embed-only messages. Automated messages do not need a prefix or required role once you opt in. TTSmodachi still needs to already be in voice, because bots and webhooks do not tell it which voice channel to join.
/set read_non_vc_messages true is off by default. when it is on, people who are not in voice can type in a voice channel's built-in chat and be read in that same voice channel. it still needs text_in_voice on, and it does not make TTSmodachi read the rest of the server.
start small:
TTSMODACHI_US_WORKERS=1
TTSMODACHI_MAX_INFLIGHT_RENDERS=4
TTSMODACHI_BOT_RENDER_CONCURRENCY=2
TTSMODACHI_OUTPUT_GAIN_PERCENT=125
TTSMODACHI_GRAMMAR_PAUSES=trueif your CPU has room, raise workers later. each worker is a warm Citra instance. more workers can make latency worse if the machine is already out of CPU.
the defaults in .env.example are intentionally small. once it works, raise workers and concurrency slowly.
TTSMODACHI_OUTPUT_GAIN_PERCENT is the master volume boost after rendering. 100 is normal, 125 is the louder default, and 150 is pretty spicy.
TTSMODACHI_GRAMMAR_PAUSES adds short native pauses after punctuation so sentences read less flat. leave it on unless the renderer starts acting weird.
by default, the bot joins voice undeafened so Discord does not show it as deafened. if you want the old behavior:
TTSMODACHI_VOICE_SELF_DEAF=truekeep the renderer bound to localhost unless you know what you are doing:
RENDERER_BIND=127.0.0.1if you put a reverse proxy in front of it, set:
TTSMODACHI_PANEL_URL=https://your-domain.example
TTSMODACHI_PUBLIC_HOSTS=your-domain.example
TTSMODACHI_PANEL_SIGNING_KEY=replace_me
TTSMODACHI_PANEL_TOKEN=replace_meTTSMODACHI_PANEL_TOKEN locks public /render and /api/config requests. internal Docker calls from the Discord bot still work without sending that token.
.env- Discord bot tokens
- panel signing keys
- patched or unpatched ROMs
- save files, databases, and logs
- SSH keys
- server IPs or private deploy notes
.gitignore and .dockerignore try to help, but still check before pushing.
this is based on:
- Talkmodachi by dylanpdx: https://github.com/dylanpdx/talkmodachi
- Discord-TTS/Bot: https://github.com/Discord-TTS/Bot
Talkmodachi is the reason the Tomodachi Life renderer part exists. Discord-TTS/Bot inspired a lot of the Discord bot shape and command ideas.
there is no new license file in this repo right now because the upstream Talkmodachi repo does not publish a license file at the time this README was written. check the upstream projects before redistributing modified copies or using this for anything serious.