GainNodeを使って音量の変化を自然に感じるようにする
はじめに
こんにちは!PortalKeyの渋谷です。
今回はWebAudioAPIのGainNodeについてのお話です。
GainNodeというのは音量調整を行えるノードです。
マイクから取得した音、音楽や動画の音、通話の音等の音量調整を行う際に使います。
UI操作によって音量調整できるようにしたところ
「なんか直感的じゃないんだけど…」
と非難轟々だった件について
なぜ起きたのか、どう直したのか、順を追ってお話ししていきます。
なぜ起きたのか
「音量をn倍にすると聞こえる音の大きさがn倍にならない。」
まずこの謎について知る必要があります。
我々の耳は「n倍になった音を聞いた時にn倍になった」と認識する事ができません。
「音の強さ」をリニアにではなく、対数的に感じる仕組みになっています。
そのため、UI上で2倍に設定していても体感では2倍になったと感じることができない訳です。
え、対数的って言われても…って思いましたよね?
自分も思いました。理解したいそこのあなたはこのWikipediaをどうぞ。
音の専門家ではないのでここの解説は省かせていただきます。
どう直したのか
では対数の計算を実装して正しい音量調節を実装してみましょう。
GainNodeの音量調整はgainのvalueを書き換える事で音量が変わります。
const gainNode = this.context.createGain()
// 音量そのまま
gainNode.gain.value = 1
// 音量2倍(耳には2倍じゃない)
gainNode.gain.value = 2
「耳には2倍じゃない」と書いた通りここの値を対数にする必要があります。
先ほどのWikipediaを含めデシベル(dB)を理解すると分かるのですが、デシベルを用いると人間の耳に優しいn倍の実装が可能になります。
ゲインノードに渡す値は、Wikipediaに書かれた計算式を紐解くと
10^(dB/20)
で求める事が可能です。
デシベルについて調べてみるとどうやら10dB上がる事で耳にとっての2倍になるらしい…
なのでシンプルに2倍したい場合は
10^(10/20)=3.16227766
となります。
const value = 2
const db = 10 * value
gainNode.gain.value = Math.pow(10, db / 20)
これで解決…ではないんです。
マイナス側がちょっとめんどくさいです。
半減させたい時は-10dBすればいいので
const value = 0.5
const db = -20 * value
gainNode.gain.value = Math.pow(10, db / 20)
こんな感じ…?にすると…これvalue=0にしても一生音消えなくね?
ということでvalueが0になった時に明示的に0を代入した方がよいです。
なのでこんな感じの式でできそうです。
(2よりも大きいrangeに対応させるならvalue - 1では破綻します…)
// value range is 0.0 to 2.0
function calcGainNodeValue(value: number, maxDecibel: number = 10, minDecibel: number = -20) {
if (value <= 0) {
return 0
}
const dB= (value - 1) * (value < 1 ? -minDecibel : maxDecibel)
return Math.pow(10, dB / 20)
}
Excelで計算式整理したらこんな感じになりました。
これで0の時明示的に0にすればいい感じですね。
0.1 -> 0で急に変わる感じにはなりますがほぼ分からなかったです。
近似値について
「2乗が近似値になる」というのを調べている過程で見つけました。
一体どのくらい近いんでしょう…Excelで検証してみます。
確かに結構似てる…ちょっと寄せてみます。
最大音量を12dBにしてみたらほぼ一緒では…?
正直自分の耳では違いがほぼ分かりませんでした…
なので現在のプロジェクトでは
function calcGainNodeValue(value: number) {
return Math.pow(value, 2)
}
これで実装してます。困ったら上の式を改良しようかな…
最後に
「長々と説明したけど結局2乗でいい」という内容の記事になりました。
いかがでしたでしょうか?
プロジェクトメンバーと話していたところ音程も似たような事が必要なようです。
音周りでは「1」という数字はキリのいい数字として扱わせて貰えないようですね。
node側でその変カバーしてくれると嬉しいんだけどなぁ…
Discussion