TL;DR
- macOSの音声入力(ディクテーション)で文章を入れたあと、毎回Enterを押すのが地味に面倒。
- 「無音がN秒続いたら自動でEnter」をやりたいが、macOS標準にもHammerspoonにも素直な手段がなかった。
- 結局マイクの音量を監視して無音を検知 →
osascriptでReturnを送る方式に落ち着いた。 - コードはこちら 👉 https://github.com/HibikiDaBull/mic-enter (MIT)
何に困っていたか
ターミナルでAIアシスタントと音声でやり取りしていると、こうなる:
- ディクテーションで喋る → テキストが入る
- 手でEnterを押して送信 ← これが毎回挟まって地味にストレス
「喋り終わったら勝手に送ってほしい」。ただそれだけ。
試したけどダメだった方法
❌ 「改行」と発声するコマンド
音声入力には「改行」と言えば改行が入る機能がある。が、結局また喋らないといけないので「無言で送りたい」要件に反する。
❌ Hammerspoon でキー入力を監視
最初は「ディクテーションが文字を打ち込む → キーイベントを拾って、止まったらEnter」を考えた。
Hammerspoonの eventtap でkeyDownを監視 → しかし1件も拾えない。
検証すると、ディクテーションはkeyDownイベントを発行していない(「こんにちは」と入れても keyDown = 0件)。OSの内部でテキストを差し込んでいるらしく、キーイベント前提のアプローチは全滅。
✅ 採用:マイクの無音検知
キーイベントが当てにできないなら、入力の有無=喋っているかを直接見ればいい。
- マイクの音量(RMS)を常時監視
- 一度しきい値を超えた(=喋った)あと、指定秒数の無音が続いたらEnterを送る
- Enter送出は
osascriptでkey code 36(Return)
ディクテーションの内部挙動に一切依存しないのが利点。
実装のキモ
1. 起動時に環境音から閾値を自動決定
固定閾値だと環境で外すので、最初の2秒で環境音を測って動的に決める。
def calibrate(stream, seconds=2.0) -> float:
levels = []
for _ in range(int(seconds / BLOCK_SEC)):
data, _ = stream.read(BLOCK)
levels.append(rms(data[:, 0]))
base, peak = float(np.mean(levels)), float(np.max(levels))
# 環境平均×5 と 環境ピーク×1.5 の大きい方、さらに最低床
return max(base * 5.0, peak * 1.5, 0.01)
2. 「喋った→無音が続いた」だけ発火(誤連打しない)
if level > thresh:
armed = True # 喋り検知
last_loud = now
elif armed and (now - last_loud) >= idle:
if no_front_check or frontmost_bundle_id() in ALLOWED_BUNDLE_IDS:
send_return() # 無音がidle秒続いた → Enter
armed = False # 次に喋るまで再発火しない
3. ターミナルが最前面のときだけ動かす
ブラウザ等で黙っても誤爆しないよう、最前面アプリのバンドルIDをホワイトリストで判定。
ALLOWED_BUNDLE_IDS = {
"com.apple.Terminal", "com.googlecode.iterm2",
"com.mitchellh.ghostty", "com.github.wez.wezterm",
"com.anthropic.claudefordesktop",
}
4. 自己修復(地味だけど効く)
マイクが「例外を投げずに無音を垂れ流して死ぬ」ことがある。生きたマイクは常に微小な環境音(>0)を返すので、ほぼ完全なゼロが一定時間続いたら停止とみなして異常終了。launchdのKeepAliveで自動復活させる。
if level < ZERO_EPS:
if zero_since is None: zero_since = now
elif now - zero_since >= DEAD_STREAM_SEC:
sys.exit(EXIT_AUDIO_ERROR) # launchdが再起動
else:
zero_since = None
セットアップ
brew install portaudio
git clone https://github.com/HibikiDaBull/mic-enter.git
cd mic-enter
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
初回は「マイク」と「アクセシビリティ(実行するターミナルに対して)」の許可が必要。
使い方
micenter-bg # バックグラウンド起動(メイン運用)
micenter --debug # 音量バーで閾値を確認
micenter --idle 2.5 # 無音待ち秒数を変更(既定3.0)
micenter-stop # 停止
ログイン時に常駐させたいなら ./install-autostart.sh(launchd登録)。
まとめ
- 「キーイベントを拾う」発想がダメでも、問題の本質(喋っているか)を別レイヤー(音量)で見ると解けることがある。
- ディクテーション×ターミナルな人にはそこそこ効くはず。よかったら使ってみてください 🙌
→ https://github.com/HibikiDaBull/mic-enter