About

オーディオのテストは画面ではなくAnalyserNodeで — バックグラウンドタブで17個すべて「無音」と出た理由

絵は止まっても音は鳴る。ピーク値を測ればクリッピングまで見つかる

オーディオのコードは画面ではなく AnalyserNode で検査する。バックグラウンドタブでは絵が止まっても音は鳴っている。そしてピーク値を測れば「音が出ているか」だけでなく「割れているか(クリッピング)」までわかる。この記事のスニペット10行で、オーディオデモを自動で検査できる。

17個すべて無音だった

学習用アプリに実行ボックスを17個作り、全部音が出るかをブラウザ自動化で確かめようとした。最初は画面のオシロスコープのキャンバスを読んで「波形が描かれたか」で判定した。結果は17個すべて無音。ところが手で押すと全部鳴った。

自動化が開いたタブは document.visibilityState = "hidden"、つまりバックグラウンドタブだった。そこではこうなる。

バックグラウンドタブでは
requestAnimationFrame 停止 → キャンバスのオシロスコープが描かれない
Turbo.visit 画面の差し替えがrAF待ちで turbo:load が来ない
Tone.getDraw().schedule rAFベースなので実行されない
setInterval 1秒単位に間引かれる
AudioContext 動き続ける。音は鳴る

止まっていたのは絵を描く側だけで、音を作る側は無事だった。画面で判定すれば外れるに決まっている。

document.visibilityState = "hidden" AudioContext: running canvas oscilloscope drawn by requestAnimationFrame: paused reads "silent" (wrong) AnalyserNode getFloatTimeDomainData() peak 0.2 = gain 0.2 01.0 silentsound, OK clipping: 1.17, 1.23

検査コード

ページが作るanalyserを横取りして持っておき、時間領域データの絶対値の最大を見る。

const orig = BaseAudioContext.prototype.createAnalyser
BaseAudioContext.prototype.createAnalyser = function () {
  const a = orig.call(this)
  window.__lastAnalyser = a
  return a
}

const peak = () => {
  const buf = new Float32Array(__lastAnalyser.fftSize)
  __lastAnalyser.getFloatTimeDomainData(buf)
  return buf.reduce((m, v) => Math.max(m, Math.abs(v)), 0)
}
// 0なら無音、1を超えたらクリッピング

ページにanalyserがなければ、検査用にひとつ作って出力の手前に挟めばいい。ページのスクリプトより先にこのコードを注入しないと横取りできない。

測ってわかったこと

ローカルのChrome、バックグラウンドタブ(2026-09-19)。キャンバス基準では17個すべて無音(誤判定)。AnalyserNode基準では、コードに書いた音量がそのまま出た。gain 0.2 → peak 0.2、0.3 → 0.3、0.15 → 0.15。

おまけにクリッピングが3件出てきた。耳で聞いたときには気づかなかったものだ。

  • 自作の16ステップドラムマシン:1.17。 キック0.8+スネア0.4+ハット0.2が同じ拍に重なった

  • Tone.js Synthの既定音量(0dB):0.99。 ぎりぎり

  • Tone.jsのベース+キック:1.23

全部音量を下げて直した。デジタルオーディオでは±1を超えた値は切り捨てられ、それが「バリッ」という割れになる。複数の音が同じ瞬間に重なるドラムは特に超えやすい。

他の方法と比べると

方法 バックグラウンドタブ クリッピング メモ
人が聞く – 見逃しやすい 0.99と1.17は耳では区別しにくい
キャンバスの波形を読む 誤判定 わからない rAFが止まる
AnalyserNodeのピーク 使える わかる ページにanalyserがあるか、挟む必要がある

「音の機能のE2Eテスト」を初めて書くとき、スクリーンショット比較をまず思い浮かべがちだが、音は音の側で測るのが筋だ。

まだ確かめていないこと

AudioContextはスクリプトでボタンを押して作ったのに running だった。その前に実際のクリックが一度あったからだと思われる(sticky activation。一度ユーザー操作があると、その後も残る許可)。実際のクリックなしでも running になるか、Playwrightのヘッドレスで同じ検査ができるか、OfflineAudioContext でレンダリングしてCIに入れられるかは、まだだ。

音の出るWeb機能を自動テストでまとめたいなら、negabaroに相談してほしい。うちもこの検査で実際のバグを3つ見つけた。

キーポイント

1

バックグラウンドタブではrAFが止まり、キャンバスの波形で判定すると無音と誤判定する

2

createAnalyserを横取りしてgetFloatTimeDomainDataの絶対値の最大を見る

3

ピーク0は無音、1超えはクリッピング。ドラムのように重なる音で起きやすい

ユースケース

オーディオデモや効果音のある画面の自動点検 開発中にミックスの音量が1を超えていないかを確認
← イヤホンの「左だけ」は二種類 — StereoPanner(寄せる)とChannelSplitter(選ぶ) Strudelはビートのスケッチには最強、曲の完成には弱い — そしてlazy importするとエフェクトが静かに消える →