Skip to content

perf(mic): マイクON時の負荷削減 + AirPods(HFP 16kHz)対応 (v1.56.0) - #2

Merged
torishoichi merged 1 commit into
mainfrom
fix/mic-perf-airpods
Sep 6, 2026
Merged

torishoichi merged 1 commit into
mainfrom
fix/mic-perf-airpods

Conversation

@torishoichi

Copy link
Copy Markdown
Owner

概要

マイクON時に動作が重く、AirPods使用時は特に「反応しない」件の恒久対応。ヘッドレスChrome実測(偽マイク注入・関数ラップ計測)と静的監査で原因を特定した。

原因

  • detectPitchYIN が毎フレーム全域探索(1.87 ms/frame, drawVisualizer の51%)。H1メーターがさらに4096点の全域YINを90 msごとに実行(4.1 ms/回)
  • resizeCanvas が毎フレーム同値で canvas.width を再代入 → バッキングストア再生成。clientWidth 読みが DOM 書込と交互に走り強制レイアウト 3.5回/frame
  • AirPods(HFP): Play より先にマイクを立てると new AudioContext() が 16 kHz で生成 → fftSize 窓が 3 倍長(読み出し遅延 3 倍)、LPC の decimFactor=4 固定で解析ナイキストが 2 kHz → F2 以上・Vowel Space・H1*–H2* 補正が消失。CPU はむしろ減っており「重さ」の正体は遅延と欠落

変更内容

  • A1/A2 canvasCssSize(): ResizeObserver で全 canvas の CSS サイズをキャッシュし rAF 経路から layout 読みを排除。canvas.width/height は変化時のみ代入
  • A3 Loudness meter を 33 ms 間引き、setTextIfChanged / setStyleIfChanged で変化時のみ DOM 書込(各バッジ・VQ 読み出しも同様)
  • A4 detectPitchFromMic: sr ≥ 32 kHz では 2:1 ペア平均して sr/2 で YIN(窓の時間長は不変 → ビブラート sinc 補正の校正を維持)
  • A5 DSP.refinePeriod() 新設(シード付き狭帯域周期精密化、再入可能)。H1メーターの第2YINを置換。validate.mjs に 1b ゲート追加(48k/16k × 110–880 Hz、誤差 < 0.3 Hz、最悪 0.217 Hz)
  • A6 ovMonTap(ScriptProcessor + 30 s リング)を Overview/Waveform 表示時のみ遅延起動
  • A7 スペクトラム描画の毎フレーム TypedArray 確保をスクラッチ再利用に
  • B1 initAudio: new AudioContext({ sampleRate: 48000 })、非対応時は従来コンストラクタにフォールバック
  • B2 decimFactor = max(1, round(sr/12000))、factor 1 ではフィルタ省略。decSr > 12.5 kHz のときのみ LPC 次数を Praat 則で拡張(48k/44.1k/24k は従来どおり 12)
  • B3 APP_VERSION / sw.js VERSION → v1.56.0

動作確認

  • node docs/dev/validate.mjs 全ゲート合格(59 → 74)
  • PC 版(index.html)ヘッドレス Chrome で確認 — コンソールエラー 0、4 ビュー(spectrum / spectrogram / waveform / overview)+ 全解析パネル open
  • モバイル版(mobile.html)ヘッドレス Chrome でロード確認 — JS エラー 0
  • マイク入力 / 録音・再生に影響する場合、実機(特に iOS Safari)で確認 — 未実施。実機 AirPods でも未検証
  • docs/sw.js / APP_VERSION を更新

実測(ヘッドレス、10 s、偽マイク 220 Hz + 5.5 Hz ビブラート)

条件 drawVisualizer ms/frame >20 ms フレーム 強制レイアウト/frame canvas.width 代入/frame
48k spectrum 3.46 → 2.10 13.7% → 3.2% 2.50 → 0.08 1.00 → 0
48k 全パネル open 3.95 → 2.31 9.5% → 4.3% 3.59 → 0 1.00 → 0
16k フォールバック 2.93 → 2.79 1.7% → 0.2% 3.48 → 0 1.00 → 0

16 kHz フォールバックでの F1/F2: 263/662 Hz → 664/1402 Hz(48k 基準 674/1428)。偽マイク 16 kHz でも audioCtx.sampleRate === 48000 を確認。

ユーザーが気づきうる挙動変化: Loudness バーの更新が 30 Hz(EMA の見かけの立ち上がりがやや遅い)。H1 バッジはピッチトラッカーの有声判定に追従(400 ms ホールド付き)。

未対応・フォローアップ: 16 kHz コンテキスト(B1 フォールバック時のみ到達)では detectPitchYIN の前処理 0.97 プリエンファシスで無声判定になる既存バグが残る。docs/dev/validation.html に 1b 節は未反映。

関連 Issue

なし

🤖 Generated with Claude Code

https://claude.ai/code/session_01X79e2xF8tEbfRg38pf8heF

…56.0)

原因(ヘッドレスChrome実測 + 静的監査):
- detectPitchYIN が毎フレーム全域探索(1.87ms/frame, 51%) + H1メーターが別途4096点YIN(4.1ms/90ms)
- resizeCanvas が毎フレーム同値で canvas.width を再代入 → バッキングストア再生成 + clientWidth読みで強制レイアウト(3.5回/frame)
- AirPods: Play前にマイクを立てると AudioContext が16kHzで生成 → fftSize窓が3倍長(遅延)、LPCの decimFactor=4 固定で解析ナイキスト2kHz → F2以上・派生指標が消失

対策:
- A1/A2 ResizeObserver で全canvasのCSSサイズをキャッシュ、rAF経路から layout 読みを排除。canvas.width は変化時のみ代入
- A3 Loudness meter を33ms間引き + 変化時のみDOM書込(setTextIfChanged/setStyleIfChanged)
- A4 YIN入力を2:1平均で sr/2 解析(窓の時間長は不変、ビブラートsinc補正の校正を維持)
- A5 DSP.refinePeriod(シード付き狭帯域周期精密化)を新設し H1メーターの第2YINを置換。validate.mjs 1b ゲート追加
- A6 Overview/Waveform 表示時のみ ScriptProcessor(ovMonTap) を遅延起動
- A7 スペクトラム描画の毎フレーム TypedArray 確保をスクラッチ再利用に
- B1 AudioContext を {sampleRate:48000} で生成(非対応時はフォールバック)
- B2 decimFactor = round(sr/12000)、factor 1 時はフィルタ省略、16kHz時のみLPC次数を Praat則で拡張

実測(48k, spectrum): drawVisualizer 3.46→2.10 ms/frame, 強制レイアウト 2.5→0.08 回/frame, >20ms フレーム 13.7→3.2%
16kHz フォールバック: F1/F2 263/662 → 664/1402 Hz(48k基準 674/1428)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X79e2xF8tEbfRg38pf8heF
@torishoichi
torishoichi merged commit 4997bfa into main Sep 6, 2026
1 check passed
@torishoichi
torishoichi deleted the fix/mic-perf-airpods branch September 6, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant