1. はじめに
このブログでは、macOS用のローカル音声入力アプリ「LocalVoice」を、AIを開発パートナーにしながら個人で作ってみた記録を残します。
きっかけは単純です。日々AIの活用方法を学んでいくなかで、「AIとのやり取りをもっと音声でこなしたい」という欲求が強くなってきました。ちょうどそのタイミングで、AquaVoiceという音声入力ツールの完成度の高さに触れる機会があり、「これなら自分でも作れるのではないか」という気持ちが抑えられなくなったのが直接のきっかけです。
思い立ったら即行動しないと気が済まない性格もあり、2026年8月26日、その日のうちに開発に着手しました。macOSのネイティブアプリ開発は未経験でしたが、AIを開発パートナーとして使えば、経験のなさをどこまでカバーできるのかを試す意味も込めての船出でした。
結論から言うと、LocalVoiceはPhase 5まで進んだ時点で、いったん開発を凍結しています。ただしこれは「失敗して終わった」という話ではありません。むしろ「AIを使えば、個人でもここまで作れる」ことと、「作れることと、実用になることは別問題である」ことの両方を、実機での検証を通じて確認できたプロジェクトでした。このブログは、その一部始終の記録です。
2. 「これ、AIを使えば自分で作れるのでは?」
AquaVoiceやTypelessのような音声入力ツールは、単に喋った内容を文字にするだけでなく、AIが自然な文章に整えてから入力欄に渡してくれます。触ってみて率直に感じたのは、「これがあれば入力速度もストレスも大きく変わる」という便利さと同時に、「月額サブスクリプションを払い続ける前に、これは自分で作れないものだろうか」という好奇心でした。
音声を録音し、文字起こしをして、AIで整形して、カーソル位置に貼り付ける。仕組みとして分解してみると、やっていること自体はそれほど複雑ではなさそうに見えます。実際に難しいのは「全部を高い精度で、しかも快適な速度でつなぎ合わせること」のはずで、そこにこそ既製品の価値があるとも言えます。
だからこそ、「自分で同じものを作ってみたら、どこまで再現できて、どこで壁にぶつかるのか」を実際に確かめてみたくなりました。しかも今の生成AI・コーディングAIを使えば、macOSのネイティブアプリ開発が未経験の自分でも、パートナーとして頼りながら形にできるのではないか、という技術的な興味もありました。サブスクを払うか、自分で作るか、という二択の前に、「作れるかどうか自体を試してみる」というのが、LocalVoiceの出発点です。
3. LocalVoiceとは
LocalVoiceは、macOS用のローカル音声入力アプリです。動作はシンプルで、Push-to-Talkのホットキーを押している間だけ録音し、キーを離すとローカルのWhisperKitで日本語の音声認識を行い、任意でローカルLLM(Ollama経由のGemma3 4B)による文章整形を挟んでから、結果を今カーソルがある入力欄に自動で貼り付けます。
音声も認識結果も、一切外部には送信しません。クラウドの音声認識やクラウドLLM、アカウント機能などは範囲外で、あくまで「自分のMacの中だけで完結する音声入力」を目指して作りました。
初めて実機でこのパイプラインが動いた瞬間の感想は、正直にいうと「感動した」の一言に尽きます。macOSのネイティブアプリ開発はまったくの未経験でしたが、AIの補助を得ながら自分の手で組み上げたものが、実際に声を認識してテキストとして入力欄に現れる。未知の技術に手を出して、それが目の前で動いた瞬間というのは、誰しもが心を動かされるものではないでしょうか。
4. 目指した音声入力体験
LocalVoiceで目指していたのは、単純な音声文字起こしではありません。「話した内容をそのまま文字にする」だけなら、実現のハードルはそれほど高くありません。目指していたのはその先、「話す→AIが自然な文章として整える→そのままPC上の入力欄へ出力する」という一連の体験です。
意識していたベンチマークは、とにかくAquaVoiceやTypelessでした。それ以外の何かと比較する発想はなく、シンプルにこの2つのアプリが実現している「話しかけるだけで、整った文章がそのまま入力される」という体験に、どれだけ近づけるかだけを考えていました。
もちろん、これは既製品と性能を測定して比較したという話ではありません。あくまで「目指す体験の基準」として意識していた、という位置づけです。実際にどこまで近づけたのか、そしてどこで壁にぶつかったのかは、後の章で実測データとともに書いていきます。
5. 開発環境

LocalVoiceを動かしていた実機とソフトウェア構成は、以下のとおりです。
| 項目 | 内容 |
|---|---|
| 対象機種 | MacBook Pro / Apple M1 / Unified Memory 8GB |
| macOS | 14.0以降を対象(実機は27.0) |
| Xcode | 未インストール環境。Command Line Toolsのみで、xcodebuildは使えない構成 |
| ビルド方法 | Swift Package Manager(swift build)のみでビルドできるよう構成 |
| Swift | 6.4(swift-tools-version: 5.10) |
| 音声認識 | WhisperKit(argmaxinc/argmax-oss-swift v1.1.0)、モデルはopenai_whisper-medium |
| 文章整形 | Ollama(v0.33.2)経由でGemma3 4B(gemma3:4b-it-q4_K_M) |
正直なところ、開発を始める前に自分で把握していたのは「MacBook Pro、M1、メモリ8GB」ということくらいでした。Xcodeが入っていないこと、Swiftのバージョン、どのライブラリを使うべきかといった詳細は、開発を進める中でAI側が実際に環境を確認し、都度報告してくれたことで判明していったものです。
たとえば「フルのXcodeがインストールされていない」という制約は、開発のかなり早い段階でAIが環境確認をした結果として発覚し、それ以降「Swift Package Managerだけでビルドする」という方針が決まりました。メニューバー常駐アプリとして必要な.appバンドル化(Dockアイコン非表示やマイク権限ダイアログの表示)も、この制約の中で自前のビルドスクリプトを用意することで対応しています。
自分が意識していたのはメモリ8GBという制約だけでしたが、これは結果的に、後の章で説明する「処理速度の問題」の根本原因に直結する、最も重要な制約になりました。
6. AIと一緒に作る
LocalVoiceは、自分がすべてのコードを書いた「従来型の個人開発」ではありません。役割を分けて2つのAIを開発パートナーにしていました。全体の設計・方針決定・進め方の指示はChatGPTが担当し、実際のコーディング・ビルド・実機テストの実行はClaude Codeが担当する、という体制です。
進め方はシンプルな反復サイクルでした。ChatGPT側で方針や次のステップを固め、それをもとにClaude Codeが環境を確認し、制約(Xcodeが無いことなど)を報告する。その制約を踏まえて実装方針を決め、コードを書く。ビルドが通ったら、実際にアプリを起動して「メニューバーのアイコンをクリックして、録音のテストをお願いします」といった具体的な手順を示され、自分の手と声で実機テストを行う。その結果をAIに伝え、うまくいかない部分があれば次の実装に反映する。この繰り返しでした。
印象的だったのは、バグや問題が実機テストの過程で見つかるたびに、AIがそれをただ直すだけでなく、原因を掘り下げて記録していったことです。たとえば、あるビルドがOllamaのモデル常駐によって極端に遅くなった際には、「これは問題であると同時に、メモリ圧迫の重要な実測データにもなる」という形で、不具合そのものを検証材料として扱っていました。凍結の判断についても、最終的にはChatGPT側から具体的な保存手順のテンプレートが渡され、それをClaude Codeが実行してREADMEとgitの状態を整える、という流れで完了しています。
自分の手だけでは絶対に到達できなかった領域だと思います。macOSのネイティブアプリ開発はまったくの未経験でしたが、設計を担うAIと実装を担うAIをそれぞれパートナーにすることで、未経験の技術領域でも「とにかく限界まで探ってみる」という実行力を持てたことが、このプロジェクトを通じて得られた一番の収穫だったと感じています。「人間が目的と要求を決め、複数のAIを設計・実装・検証のパートナーとして使いながら形にしていく」というのが、LocalVoiceの実態に一番近い表現だと思います。
7. Phase 1〜5、開発の流れ
LocalVoiceの開発は、指示書が想定していた「Phase1〜5」ときれいに一致するわけではなく、実際には途中に「Phase 1.1」「Phase 4.5」という追加の検証フェーズが挟まる形で進みました。以下、実際の名称・内容で振り返ります。
Phase 1: 音声入力基盤
録音、WhisperKitによる音声認識、グローバルホットキー、クリップボード経由の自動貼り付けという、パイプラインの土台を作りました。Whisperのモデルは最初smallを試しましたが、日本語の一部が誤認識される事例が確認されたため、精度を優先してmediumに切り替えています。実機での回帰テスト(TextEdit・Safari・VS Code・Apple メモ)はすべてPASSしました。
Phase 1.1: ハードニング
Accessibility権限が起動時にしか検知されず、許可後にアプリの再起動が必要という不便を解消しました。権限の変化を2秒間隔でポーリングし、許可された時点で自動的にホットキーを有効化する仕組みを追加しています。
Phase 2: ローカルLLMによる文章整形
Ollama経由でGemma3 4BとQwen3.5 4Bを比較しました。Qwen3.5は意味を変える誤りが確認され、Gemma3 4Bを採用しています。またこの過程で、「Ollamaの4Bモデルがメモリに常駐しているとswift build自体が詰まるほど重くなる」という、後のPhase 4.5につながる重要な兆候が実測で確認されました。
Phase 3: モデルベンチマーク
Gemma3 1B・4B・Qwen3.5 4Bの3モデルを、日本語品質と「M1 8GBで毎日使えるか」という常用可能性の両面で比較しました。Gemma3 1Bは速度・メモリで圧倒的に優位でしたが、ハルシネーションや日付の改変など、意味を変えてしまう誤りが複数確認されたため不採用。意味保持を最優先する基準に照らし、Gemma3 4Bを既定モデルとして維持しています。
Phase 4: 実用化フェーズ
Fast/Cleanモードの切り替え、ホットキーのカスタマイズ設定UI、権限まわりのUX改善などを実装しました。この過程で、ad-hoc署名がグローバルホットキーを実質的に無効化するという重大な不具合を発見し、安定した自己署名証明書の導入で解決しています。完全オフライン動作(Wi-Fi切断状態)も実機で確認済みです。
Phase 4.5・Phase 5
処理速度のボトルネックを特定し、リアルタイム化(ストリーミング)の可能性を検証したフェーズです。この2つが、実質的に指示書が想定していた「実機検証・凍結判断」フェーズにあたります。詳細は次章以降で扱います。
8. そして、動いた
Phase 1〜4を終えた時点で、LocalVoiceは実機で次のことができるようになっていました。
- マイクからの録音、Push-to-Talkホットキーでの起動
- WhisperKit(medium)による日本語音声認識
- Gemma3 4Bによる文章整形(Fast/Cleanモードの切り替え)
- 認識・整形結果のクリップボード経由の自動貼り付け
- TextEdit・Safari・VS Code・Apple メモなど、複数アプリでの動作確認
- Ollamaが停止していても、認識結果がそのまま貼り付けられるフォールバック
- Wi-Fiを切断した完全オフライン状態での一連の動作
「話す→AIが整える→そのまま入力欄に入る」という、当初目指していた体験そのものが、実機の上で一通り成立していました。
正直に言うと、これは自分にとってかなり大きな出来事でした。端末で動く実用的なアプリケーションなんて、自分のような素人が作れるはずがない、という思い込みがどこかにありました。それが、実際に自分のMacの上で、日本語を認識して、AIが整えて、狙った入力欄に文字が現れる。その一連の動作を目の当たりにしたとき、その思い込みが完全に覆されたと感じました。
ただし、ここで話は終わりません。ここから先、「動く」ことと「実用になる」ことの間にある壁にぶつかることになります。
9. しかし、遅かった
最初にPush-to-Talkが一通り動いた瞬間は、とにかく「動いた」という事実への興奮が勝っていて、正直なところ速度についての感触は薄いものでした。ですが、2回、3回とホットキーを押して試すうちに、だんだんと違和感が輪郭を持ち始めます。「あれ、これ、遅くないか」。そして「駄目だ、遅い」という実感に変わった瞬間、頭の中にはっきりと暗雲が立ち込めたのを覚えています。
ここははっきり書いておく必要があります。「ちょっと遅かった」という曖昧な話ではなく、「技術的には動作するが、実用上のUXとしては成立していない」というレベルの遅さでした。
実機のログで計測したところ、同一のモデル・同一のコードであるにもかかわらず、負荷の状況によって処理時間が大きく変動していました。

| 状況 | Whisper medium(ウォーム) | Gemma3 4B cleanup(ウォーム) |
|---|---|---|
| 低負荷時 | 約1.7〜2.1秒 | 約0.8〜1.4秒 |
| 高負荷時 | 13〜18秒 | 11〜16秒 |
同じコマンドを、負荷の低いタイミングと高いタイミングで実行しただけで、10倍以上の差が出ています。しかも、この「高負荷時」というのが特殊な状況ではなく、自分の普段の使い方(Chrome、Claude、Gemini、ChatGPT/Codexなど複数のAIアプリを同時に立ち上げっぱなしにしている状態)そのものだったという点が、一番厄介なところでした。つまり、遅さの原因はLocalVoiceのコードの出来不出来ではなく、8GBというメモリに対して、自分の普段の使い方自体が重すぎたということです。
「動く」という一段目のハードルは越えられた。しかし、「日常的にストレスなく使える」という二段目のハードルの手前に、想定していなかった高い壁が立ちはだかっていることに気づいた瞬間でした。
10. 「動く」と「実用になる」は違う
ここからはひたすら、「これは物理的な限界なのか、まだ探れる余地があるのか」という攻防の連続でした。原因を決めつける前に、一つひとつ仮説を潰していく作業です。
まず疑ったのは、LocalVoice自身のコードでした。メニューバー常駐アプリであるためにmacOSがバックグラウンド扱いして処理優先度を下げているのではないか(App Nap)、WhisperKitが毎回無駄な再初期化をしているのではないか。実機で対策を入れて検証しましたが、どちらも体感速度の改善にはつながらず、原因ではないと判断しました。
次に疑ったのが、Whisperのモデルサイズです。tiny・base・small・mediumを同じ負荷条件で比較したところ、モデルが小さいほど負荷変動の影響を受けにくいことが分かりました。ただしtiny・baseは実際の発話で明確な誤認識(「大阪」が「大さか」になるなど)が出て不採用、smallに切り替えて実機の生音声で試したところ、速度改善は限定的(17秒→11秒程度)な一方で認識精度が悪化したため、最終的にmediumへ差し戻しています。
最後に疑ったのが、処理方式そのものでした。WhisperKitにはストリーミング(逐次)文字起こしのAPIが実際に存在することをソースコードで確認し、これを使えば体感速度が改善するのではないかと期待して実測しました。しかし結果は逆で、ストリーミング方式は「最初の部分結果が出るまでの時間」は短縮できるものの、「全体の処理が完了するまでの時間」はバッチ処理よりむしろ悪化することが分かりました。バッファが伸びるたびに音声全体をほぼフルコストで再デコードするという設計上、総計算量が増えてしまうためです。
一つひとつ仮説を潰していった結果たどり着いたのは、「ボトルネックはLocalVoiceのコードではなく、システム全体のメモリ負荷そのものだった」という結論でした。ここで、LocalVoiceが実際に何を達成し、どこに限界があったのかを整理しておきます。

| 項目 | 結果 |
|---|---|
| 技術的成立(完全ローカルでの音声入力パイプライン) | ○ |
| 個人によるアプリ開発 | ○ |
| ローカル音声認識 | ○ |
| ローカルLLMによる文章整形 | ○ |
| 音声→文章→自動入力の一連の流れ | ○ |
| 理想的なリアルタイムUX(常時低遅延) | × |
| 現在のハードウェア(M1 8GB、AIアプリ常時起動)での実用性 | × |
「動く」ことと「実用になる」ことは、まったく別の問題でした。前者はコードを書けば到達できますが、後者はハードウェアの余力や、自分自身の日常的な使い方まで含めた話になります。ここまで来て、Phase 5をどう締めくくるかを判断する段階に入りました。
11. Phase 5で凍結
処理速度のボトルネックがシステム全体のメモリ負荷にあると特定できたところで、Phase 5ではもう一段踏み込んで、WhisperKitのStreaming APIが根本的な解決になり得るかを実測で検証しました。結論は「有効ではない」でした。ストリーミング化によって最初の部分結果が出るまでの時間(Time To First Partial)は改善するものの、全体の処理が完了するまでの時間はバッチ処理よりも悪化する。しかも部分結果自体が確定前は誤変換を含みやすく、単純に画面へ表示するとかえって読者を混乱させかねない挙動でした。
この結果を受けて、Phase 5をもって開発をいったん完了状態とし、凍結することを決めました。「Phase 6へ継続開発する」のではなく、「技術検証は完了した、将来また再開できる状態で保存する」という位置づけです。ソースコードは動作確認済みの状態を維持し、README.mdには実測値・判断根拠・今後再開する場合の候補までを整理して記録し、最終的にPhase 1〜5の成果を1つのコミットとして確定しました。
正直、悔しい気持ちが無かったと言えば嘘になります。ただ、「なんとなく遅い」で終わらせるのではなく、「なぜ遅いのか」「何を試して、何が効かなかったのか」を一つひとつ実測で潰していった結果、「これは今の環境では事実として不可能である」という限界ラインを、納得した上で理解できたことは大きな収穫でした。この「曖昧な諦め」ではなく「根拠のある結論」にたどり着けたこと自体が、AIを開発パートナーとして使わなければ、自分一人では到達が難しかった結果だと感じています。
12. LocalVoiceから分かったこと
今回のLocalVoiceを通じて分かったことを、いくつかの層に分けて整理します。
まず、AIを活用すれば、個人でもかなり本格的なアプリケーションを短期間で構築できるということです。macOSのネイティブアプリ開発が未経験の自分でも、AIを開発パートナーにすることで、音声認識・ローカルLLM・グローバルホットキー・自動入力までを組み合わせた、完全ローカルの音声入力パイプラインを実際に動かすところまでたどり着けました。
一方で、「動くものを作ること」と「実用的なUXを実現すること」はまったく別問題だということも、今回はっきり体験しました。コードとしては正しく動作していても、実際に日常使いできるレベルの体験を実現できるかどうかは、また別の話です。
さらに踏み込むと、AIによるコード生成能力がどれだけ高くなっても、最終的なボトルネックはハードウェア性能や実行環境の側にあるケースがあるということも分かりました。今回の場合、遅さの原因はコードの品質ではなく、8GBというメモリに対する実際の使用状況(複数のAIアプリを同時起動する自分の普段の運用)そのものでした。
技術的な学びとしては、README にまとめた以下の点が今回の一番の収穫です。
- Whisperはモデルサイズが大きいほど精度は安定するが、システムメモリが逼迫すると推論速度が10倍以上変動しうる
- Ollama上の4Bクラスのモデルが常駐しているだけで、ビルド作業を含むシステム全体に負荷がかかる
- WhisperKitのストリーミングAPIは「存在すること」と「速いこと」は別問題で、今回のケースでは総処理時間の改善にはつながらなかった
- グローバルホットキーの安定動作には、ad-hoc署名ではなく安定したコード署名が必要
- ハードウェアの余力次第で、同じコードでも体感速度が「ストレスなし」から「明確に遅い」まで大きく変わる
そして最後に、個人開発では、無限に改善を続けるよりも、実機検証で限界を確認し、合理的なところで一度立ち止まることにも価値があるということです。今回のLocalVoiceで得られた一番の成果は、LocalVoiceというアプリそのものだけではありません。「今の自分の環境で、どこまでならローカル音声入力を実用化できるのか」という実測に基づいた知見を得られたことで、次に何かを作るときに、根拠を持って判断できる経験を手に入れられたことこそが、一番の収穫だと思っています。
13. もし今後再開するとしたら
Phase 5の実測が示しているのは、「今の環境(M1 8GB、AIアプリを複数同時起動する使い方)を維持する限り、コードの改善だけでは理想の低遅延には届かない」という結論です。ということは、同じ条件のまま再開発しても、同じ壁に当たるだけだろうと思っています。
もし今後同じ路線で再開発するなら、まずは実機のアップグレードが前提になります。とはいえ、これは明日あさってといった近い話ではありません。しばらくはローカルLLMの進化そのものを見届けながら、時期を待つことになりそうです。README に記録した再開候補(より高メモリのMacへの移行、真の増分Whisperデコード、より高速な音声認識エンジンの検証など)は、いずれも「今すぐ着手する予定」ではなく、あくまで将来の選択肢としての記録です。
そんなことを書いているさなかに、Googleのgeminiアプリに「Speak to Window」という機能が追加されました。Fnキーを長押しすると、今開いているどのウインドウにも、AIが整えた音声入力の結果をそのまま挿入してくれるという、まさに自分がLocalVoiceで目指していた体験に近い機能です。これはローカル完結ではなくクラウド側の処理ですが、こうした機能が大手のプラットフォームからどんどん出てくるスピード感を目の当たりにすると、開発競争がどれだけ速く進んでいるかを実感せずにはいられません。AquaVoiceやTypelessのような先行の専業サービスも、この流れの中でどう立ち位置を保っていくのか、他人事ながら少し気になるところです。
14. まとめ
AIを使えば、個人でもAquaVoiceやTypelessのような音声入力アプリを、macOS開発未経験の状態からでも実際に作れます。これは今回、はっきりと確認できたことです。
ただし、それだけでは終わりませんでした。「作れる」ことと、「実用として日常的に使える」ことの間には、コードの完成度とは別の壁があります。今回の場合、その壁の正体は、M1 8GBというハードウェアと、自分自身の普段のアプリの使い方でした。AIによってソフトウェアを作るハードル自体は大きく下がりましたが、実用的なプロダクトを成立させるには、性能・UX・ハードウェア・実機検証、そして「どこで止めるか」という判断までが必要になります。
LocalVoiceはPhase 5をもっていったん凍結しました。これは諦めではなく、実機検証によって「今の環境における実用上の限界」を確認し、根拠を持って一度立ち止まった結果です。将来、Macを買い替えるタイミングか、ローカルLLMがさらに進化したタイミングで、今回のコードとベンチマークをそのまま出発点に再開できる状態にしてあります。
これは完成した製品の紹介ではありません。実際に作って、実際に動かして、実際に限界を確認した記録です。


コメント