• #Electron
  • #Vue 3
  • #TypeScript
  • #Deepgram
  • #リアルタイム文字起こし
  • #音声処理
開発misc-devメモ

即録くん(Sokuroku) -- リアルタイム文字起こしアプリの設計を詰めた日

Web会議中にリアルタイムで音声を文字起こしして、会議の最中にAIへテキストを渡せるアプリを作りたい。構想を計画書に落とし込み、Codexにレビューを投げたら致命的な指摘が2件返ってきて、設計を書き直すところまでが今日の作業だった。実装は翌日に持ち越し。

何を作るのか

Zoom/Google Meetで会議している最中に、マイク入力と相手の音声の両方をキャプチャして、Deepgramでリアルタイムに文字起こしする。文字起こし結果はデスクトップアプリの画面に流れ続け、そのテキストをそのままAIツールに渡して議事録や要約を生成できる。名前は「即録くん(Sokuroku)」。

Windows向けにゼロから設計する。macOS版の移植ではなく、Windowsの音声キャプチャAPIの特性に合わせた構成にした。

技術スタック選定 -- ReactからVueへ変更

最初の計画書ではReact 19で書いていた。しかし自分が普段触っているのはVue 3であり、Electronとの連携で余計なハマりを増やしたくないので、Vue 3 + TypeScriptに切り替えた。

最終的なスタックはこうなった:

  • フレームワーク: Electron 33+
  • フロントエンド: Vue 3 + TypeScript 5.x
  • ビルドツール: electron-vite(Vite統合)
  • 音声認識: Deepgram Nova-3(WebSocketストリーミング)
  • 後処理: Gemini 2.0 Flash(誤字修正・要約)

electron-viteを選んだのは、Viteの開発体験をElectronにそのまま持ち込めるから。Vue + TSのテンプレートが公式に用意されている点も決め手だった。

音声処理パイプラインの設計

設計の核心は「マイクとシステム音声をどう混ぜてDeepgramに流すか」だった。

WindowsではElectronの desktopCapturer APIでシステム音声を取得できる。macOSだとBlackHoleのような仮想オーディオデバイスが必要になるが、Windowsでは追加ソフトなしで動く(はず)。

パイプラインの流れを図にすると:

マイク (48kHz) ──→ GainNode ──┐
                               ├→ ChannelMerger → ダウンサンプル(16kHz) → WebSocket → Deepgram
システム音声   ──→ GainNode ──┘
                                                                                       ↓
                                                                                  文字起こし結果
                                                                                       ↓
                                                                              Gemini Flash 誤字修正
                                                                                       ↓
                                                                                  画面に表示

ダウンサンプリングにはAudioWorkletを使う。ScriptProcessorNodeは非推奨なので避けた。48kHzのFloat32を16kHzのInt16に変換して、WebSocket経由でDeepgramに送る。

Deepgramの責務はメインプロセスに

APIキーの管理とWebSocket接続はElectronのメインプロセスが担う。レンダラー(Vue側)が直接Deepgramと通信するとAPIキーがブラウザコンテキストに露出するので、IPC経由でやりとりする設計にした。

レンダラー(Vue) → IPC → メインプロセス → WebSocket → Deepgram
                  ↑                                       ↓
           文字起こし結果 ← ──── IPC ← ──── 結果受信

APIキーは electron-store で暗号化してローカル保存する。初回起動時にユーザーが自分のキーを入力する方式。開発時だけ .env を使う。

Codexレビューで致命的指摘が2件

計画書を書き終えてStep 1(スキャフォールド)に着手したところ、いきなり詰まった。pnpm create @electron-vite が対話型CLIで、スクリプトから自動実行できない。手動で入力を送り込もうとしたが失敗し、degit でテンプレートをクローンする方法も試みたがうまくいかない。

ここでCodexにレビューを依頼した。返ってきた指摘は2件、どちらも致命的だった。

指摘1: スキャフォールドが対話型CLIで動かない

計画書のStep 1に書いていた pnpm create @electron-vite は対話型のCLIツールで、CI環境やスクリプト内では動かない。まさに自分が直面していた問題そのものだった。

修正として、create-electron-vite--template vue-ts オプションを明示的に指定する形に書き換えた:

pnpm create @electron-vite sokuroku --template vue-ts

これで対話プロンプトをスキップできる。

指摘2: Deepgramの責務がrendererとmainで矛盾

計画書のプロジェクト構成ではDeepgramクライアントが renderer/lib/deepgram-client.ts に配置されていたが、APIキー管理のセクションでは「メインプロセスがWebSocket接続を管理」と書いていた。責務がrendererとmainの間で矛盾している。

これを修正して、Deepgramクライアントをメインプロセス側(src/main/deepgram.ts)に移動し、レンダラーはIPC経由でのみやりとりする設計に統一した。Geminiの後処理も同様にメインプロセスで実行し、結果をIPCで返す形に揃えた。

PoC検証ステップの追加

Codexのレビューを受けて、もう一つ計画に追加したのがPoC(概念検証)ステップだった。

desktopCapturer でシステム音声を取得する方式は、Windows 11 + 現行Electronで本当に動くか保証がない。このPoCが通らないと後続の設計が全部崩れる。そこでStep 2(マイク文字起こし)の直後にStep 2.5としてPoCを挟んだ:

  1. 最小限のElectronアプリで desktopCapturer.getSources() を呼ぶ
  2. YouTubeを再生しながらシステム音声がAudioTrackとして取れるか確認
  3. 取得した音声に実際に波形データが入っているか確認

PoCが通らなければWASAPI Loopbackのネイティブモジュールに切り替える。この判定基準を計画書に明記した。

Gemini後処理の設計

Deepgramの文字起こし結果には誤認識がつきもので、特にIT用語や固有名詞がズレる。これをGemini 2.0 Flashで非同期に修正する。

設計はfire-and-forget方式を採った。finalテキストが確定した瞬間にUIへ即座に表示し、裏でGeminiに誤字修正リクエストを投げる。修正結果が返ってきたらUIのテキストを差し替える。UIはブロックされない。

Gemini 2.0 Flashは無料枠で1日1,500リクエスト処理できるので、個人利用ならコストは実質ゼロ。Deepgramも$200分の無料クレジットがあり、約775時間分の文字起こしに相当する。

今日やったこと・明日やること

計画書のVue.js化とCodexレビュー反映まで完了した。計画書は sokuroku/memo/2026-03-18/sokuroku-win-plan.md に保存してある。

明日はStep 1(create-electron-vite --template vue-ts でスキャフォールド)から着手して、Step 2(マイク音声のリアルタイム文字起こし)までを目指す。

Codexに指摘されるまで、対話型CLIの問題とDeepgramの責務矛盾に気づいていなかった。計画段階でレビューを挟んだおかげで、実装に入ってから手戻りするのを避けられた。設計書は書いた本人の盲点を突かれてこそ価値がある。