🎵

GainNodeを使って音量の変化を自然に感じるようにする

2025/04/16に公開

はじめに

こんにちは!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側でその変カバーしてくれると嬉しいんだけどなぁ…

PortalKey Tech Blog

Discussion