オーディオのテストは画面ではなくAnalyserNodeで — バックグラウンドタブで17個すべて「無音」と出た理由
絵は止まっても音は鳴る。ピーク値を測ればクリッピングまで見つかる
オーディオのコードは画面ではなく AnalyserNode で検査する。バックグラウンドタブでは絵が止まっても音は鳴っている。そしてピーク値を測れば「音が出ているか」だけでなく「割れているか(クリッピング)」までわかる。この記事のスニペット10行で、オーディオデモを自動で検査できる。
17個すべて無音だった
学習用アプリに実行ボックスを17個作り、全部音が出るかをブラウザ自動化で確かめようとした。最初は画面のオシロスコープのキャンバスを読んで「波形が描かれたか」で判定した。結果は17個すべて無音。ところが手で押すと全部鳴った。
自動化が開いたタブは document.visibilityState = "hidden"、つまりバックグラウンドタブだった。そこではこうなる。
| バックグラウンドタブでは | |
|---|---|
requestAnimationFrame |
停止 → キャンバスのオシロスコープが描かれない |
Turbo.visit |
画面の差し替えがrAF待ちで turbo:load が来ない |
Tone.getDraw().schedule |
rAFベースなので実行されない |
setInterval |
1秒単位に間引かれる |
AudioContext |
動き続ける。音は鳴る |
止まっていたのは絵を描く側だけで、音を作る側は無事だった。画面で判定すれば外れるに決まっている。
検査コード
ページが作る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つ見つけた。
キーポイント
バックグラウンドタブではrAFが止まり、キャンバスの波形で判定すると無音と誤判定する
createAnalyserを横取りしてgetFloatTimeDomainDataの絶対値の最大を見る
ピーク0は無音、1超えはクリッピング。ドラムのように重なる音で起きやすい