Claude Code の応答を Kokoro TTS でローカル音声読み上げしてみた

Claude Code の応答を Kokoro TTS でローカル音声読み上げしてみた

2026.03.24

はじめに

データ事業本部のkasamaです。
先日、プロジェクト・ヘイル・メアリーという映画を観ました。作中で主人公のグレースが、異星の生物「ロッキー」とコミュニケーションを取るシーンがあります。ロッキーは音波で意思疎通する生物で、グレースはロッキーの出す音を録音・解析し、音と意味を対応づけるプログラムを作っていきます。最終的にテキストから音声を生成してロッキーに話しかけるという流れがとても印象的でした。

https://projecthm.movie/

これを観ていて、「Claude Code の応答もテキストから音声にできるのでは」と思い立ちました。今回は Claude Code の Stop フックと Kokoro TTS を組み合わせて、応答完了時にローカルで音声読み上げする仕組みを作ってみたので紹介します。Apple Silicon を活用して、音声合成を実行します。

前提

環境

  • macOS(Apple Silicon / M1 以降)
  • Claude Code がインストール済み
  • Python 3.12 以降
  • uv(Python パッケージマネージャ)がインストール済み

Claude Code の Hooks とは

Claude Code の Hooks は、特定のライフサイクルイベント(ツール実行前後、応答完了時など)にシェルコマンドを差し込める仕組みです。~/.claude/settings.jsonhooks フィールドに定義します。

今回使用する Stop イベントは Claude Code の応答が完了したタイミングで発火し、標準入力に以下のような JSON を受け取ります。

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Stop",
  "stop_hook_active": true,
  "last_assistant_message": "リファクタリングが完了しました。..."
}

last_assistant_message フィールドに Claude の最終応答テキストが格納されているため、これを TTS エンジンに渡して読み上げます。

Hooks の公式ドキュメントは以下を参照してください。

https://code.claude.com/docs/en/hooks#stops

Kokoro TTS と mlx-audio

Kokoro は 82M パラメータの軽量な TTS モデルです。mlx-audio ライブラリを通じて Apple の MLX フレームワーク上で動作し、Apple Silicon をゼロコピーで活用できます。日本語の男性話者 jm_kumo を使用します。

https://huggingface.co/mlx-community/Kokoro-82M-bf16

処理フロー

Claude Code の応答完了時に以下の順序で処理が実行されます。

  1. Claude Code が応答を完了し Stop フックが発火
  2. 環境変数 CLAUDE_VOICE=1 が設定されている場合、TTS スクリプトを非同期で実行
  3. TTS スクリプトがマークダウンを除去し、英語をカタカナに変換して音声合成

TTS は "async": true で非同期実行するため、Claude Code の操作をブロックしません。

実装

実装コードはGitHubに格納しています。

https://github.com/cm-yoshikikasama/blog_code/tree/main/68_claude_code_tts_hook

68_claude_code_tts_hook/
├── hooks/
│   ├── kokoro-tts/
│   │   ├── pyproject.toml       # uv プロジェクト定義(TTS 依存関係)
│   │   └── uv.lock              # 依存バージョンの固定
│   └── say-response.py          # TTS メインスクリプト
├── settings-example.json        # settings.json の設定例
└── README.md

settings.json

https://github.com/cm-yoshikikasama/blog_code/blob/main/68_claude_code_tts_hook/settings-example.json

settings-example.json では、Stop イベントに TTS 読み上げのフックを定義しています。環境変数 CLAUDE_VOICE1 のときだけ実行する条件分岐と、"async": true による非同期実行を組み合わせることで、音声合成中も Claude Code の操作を継続できます。

CLAUDE_VOICE は Claude Code の公式環境変数ではなく、自分で定義したフラグです。CLAUDE_VOICE=1 claude のように起動時に指定して有効化できます。フラグを付けなければ音声出力されないため、複数セッションを立ち上げた際に大量の音声応答が同時に返ってくるような事態を防げます。TTS の初回実行時にモデルのダウンロードが走るため、常時 ON にせず必要なときだけ有効にする運用にしています。

TTS スクリプト

https://github.com/cm-yoshikikasama/blog_code/blob/main/68_claude_code_tts_hook/hooks/say-response.py

say-response.py は4つのステップで動作します。

ステップ1では、標準入力から Stop フックが渡す JSON を読み取り、last_assistant_message フィールドから Claude の応答テキストを取得します。

ステップ2のマークダウン除去では、コードブロック、テーブル、リスト記法、見出し記号、括弧内の補足、URL などを正規表現で除去しています。コードブロックは in_code フラグで開始・終了を追跡し、ブロック内のテキストを丸ごとスキップします。Claude の応答にはコードが多く含まれるため、これを読み上げると意味不明になります。最終的にテキストを先頭600文字に切り詰めています。

ステップ3の英語→カタカナ変換は、日本語 TTS の発音精度を上げるための処理です。CUSTOM 辞書に技術用語の読みを定義し、長い文字列から優先的に置換します(CloudFormationCloud + Formation に分割されないように)。辞書にない一般的な英単語は alkana ライブラリでカタカナに変換します。

ステップ4では、mlx-audio の load_model で Kokoro モデルをロードし、generate メソッドで音声を生成します。モデル ID やボイス名などのパラメータはファイル先頭の定数で管理しています。生成された WAV を一時ファイルに書き出し、macOS 標準の afplay コマンドで再生します。再生完了後は threading.Thread で一時ファイルを自動削除しています。

uv プロジェクト

https://github.com/cm-yoshikikasama/blog_code/blob/main/68_claude_code_tts_hook/hooks/kokoro-tts/pyproject.toml

pyproject.tomlmisaki[ja]==0.7.4 をピン留めしています。misaki は Kokoro が内部で使用する音素変換ライブラリで、0.8 以降は unidic との互換性に問題があるためバージョンを固定しています。espeakng-loader は phonemizer のバックエンドとして必要です。alkana は英語→カタカナ変換のライブラリです。

セットアップ

1. リポジトリのクローンと配置

GitHub からファイルをクローンし、~/.claude/hooks/ に配置します。

git clone https://github.com/cm-yoshikikasama/blog_code.git
cp -r blog_code/68_claude_code_tts_hook/hooks/* ~/.claude/hooks/

2. 依存関係のインストール

cd ~/.claude/hooks/kokoro-tts
uv sync

3. settings.json の編集

~/.claude/settings.jsonhooks フィールドに settings-example.json の内容をマージしてください。

4. 環境変数の設定

TTS を有効にするには、Claude Code の起動時に環境変数を指定します。

CLAUDE_VOICE=1 claude

この方法であれば、そのセッションだけ TTS が有効になります。

試してみた

動作確認

CLAUDE_VOICE=1 を設定した状態で Claude Code に質問を投げてみます。

CLAUDE_VOICE=1 claude

試してみた動画です。Kokoro TTS の日本語音声は自然で聞き取りやすいと思います。
技術用語の読みについては、CUSTOM 辞書で定義した AWS や CDK などの略語は正しくカタカナで発音されます。辞書に登録していない英単語は alkana で自動変換されますが、固有名詞は正確に読めない場合があります。必要に応じて CUSTOM 辞書にエントリを追加してください。
https://youtu.be/Zqa14Yf5Gso

最後に

ロッキーのような声にはまだ程遠いですが、音声サンプルから生成した声で回答させる TTS もあるようなので、今後試してみたいと思っています。また、英語で会話するようにすれば日常業務の中で英語に触れる機会が増え、英語学習としても効果的だと感じました。Anthropic が公式で音声応答機能を提供してくれる日も近いかもしれませんが、それまではこの仕組みを活用して、対話形式のタスクを進めていきたいと思います。なお、複数セッションで同時に使うと音声が重なって聞こえるため、一つのセッションだけで有効にすることをおすすめします。

音声モデルの導入が難しい場合や社内ポリシーで制限がある場合は、macOS 標準の say コマンドでも音声応答を実現できます。応答テキストからマークダウンやコードブロックを除去して読み上げに適したテキストを抽出し、say に渡す実装にはなりますが、Kokoro TTS のセットアップに比べれば導入しやすいので、まずはそちらから試してみるのも良いかもしれません。

映画「プロジェクト・ヘイル・メアリー」もとても面白かったので、まだ観ていない方はぜひ。

この記事をシェアする

FacebookHatena blogX

関連記事