0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

音声入力のあとEnterを押すのが面倒だったので、無音検知で自動送信するツールを作った

0
Last updated at Posted at 2026-06-28

TL;DR

  • macOSの音声入力(ディクテーション)で文章を入れたあと、毎回Enterを押すのが地味に面倒
  • 「無音がN秒続いたら自動でEnter」をやりたいが、macOS標準にもHammerspoonにも素直な手段がなかった
  • 結局マイクの音量を監視して無音を検知 → osascriptでReturnを送る方式に落ち着いた。
  • コードはこちら 👉 https://github.com/HibikiDaBull/mic-enter (MIT)

何に困っていたか

ターミナルでAIアシスタントと音声でやり取りしていると、こうなる:

  1. ディクテーションで喋る → テキストが入る
  2. 手でEnterを押して送信 ← これが毎回挟まって地味にストレス

「喋り終わったら勝手に送ってほしい」。ただそれだけ。

試したけどダメだった方法

❌ 「改行」と発声するコマンド

音声入力には「改行」と言えば改行が入る機能がある。が、結局また喋らないといけないので「無言で送りたい」要件に反する。

❌ Hammerspoon でキー入力を監視

最初は「ディクテーションが文字を打ち込む → キーイベントを拾って、止まったらEnter」を考えた。
Hammerspoonの eventtap でkeyDownを監視 → しかし1件も拾えない

検証すると、ディクテーションはkeyDownイベントを発行していない(「こんにちは」と入れても keyDown = 0件)。OSの内部でテキストを差し込んでいるらしく、キーイベント前提のアプローチは全滅。

✅ 採用:マイクの無音検知

キーイベントが当てにできないなら、入力の有無=喋っているかを直接見ればいい。

  • マイクの音量(RMS)を常時監視
  • 一度しきい値を超えた(=喋った)あと、指定秒数の無音が続いたらEnterを送る
  • Enter送出は osascriptkey 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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?