地図/信号/ノイズとゆらぎ

ノイズに色をつける — 白・ピンク・茶

ノイズには「色」がある。光の白色光が全周波数を均等に含むのになぞらえて、全周波数が均等なノイズが白色ノイズ。一様乱数を一粒ずつ並べたものがこれで、各サンプルが互いに無相関、隣が前の値を覚えていない、いちばんザラザラした状態になる。

ノイズの「色」はパワースペクトルの傾きのラベル。周波数 f に対しエネルギーが 1/f⁰ で平らなのが白色ノイズ、1/f² と急に低音へ傾くのが赤(ブラウン)ノイズ、その中間の 1/f がピンクノイズ。一様乱数を独立に並べると相関ゼロで全周波数が均等になり白色、それを積分すると低周波が強まって 1/f² の赤、周波数を倍々にしたいくつかの成分を高い方ほど弱く足すと 1/f のピンクに近づく。

白を一本だけ引く。各サンプルが Math.random() * 2 - 1-1〜1 を独立に取るだけ。隣との相関がないので、線は毎サンプルてっぺんと底を行き来して、とげとげに潰れる。

全周波数が同じだけ入っているので、どこを見ても同じ細かさで暴れる。茶はこの逆で、一様乱数を積分して 1/f² の坂にしたもの。前のサンプルに足し込む一行が積分にあたる。* 0.99 は漂いすぎを抑えて値を中心へ少しずつ引き戻している。

// 茶(1/f²): 前のサンプルに足し込む = 積分。* 0.99 で漂いすぎを抑える
brown += (Math.random() - 0.5) * 0.5
brown *= 0.99

ピンクは、周波数を倍々にしたいくつかの正弦波を、高い方ほど弱くして足し合わせる。phase[i] をそれぞれ違う速さ freq[i] = 0.04 * 2 ** i で回し、weight を一段ごとに半分にしながら合算する。低いオクターブが大きな波を、高いオクターブが細かい震えを担当して、足すと低音側に傾いた 1/f になる。

// ピンク(1/f): 倍々の周波数を、高い方ほど弱く足す
const pink = () => {
  let s = 0
  let weight = 1
  let wsum = 0
  for (let i = 0; i < octaves; i++) {
    s += Math.sin(phase[i]) * weight
    wsum += weight
    weight *= 0.5 // 高いオクターブほど弱く
    phase[i] += freq[i]
  }
  return s / wsum
}

オクターブを重ねるこの足し算は、画像の fBm(複数スケールのノイズを重ねる手法)と同じ骨格を持つ。下に白・ピンク・茶を縦に並べる。上から白(とげとげ)、ピンク(ほどよい揺れ)、茶(大きくうねる)。

上の白は隣どうしが無関係なので、線が毎サンプル跳ねてギザギザになる。下の茶は前を引きずるので、ゆったり大きく波打つ。真ん中のピンクはその中間で、細かい揺れと大きなうねりが両方ある。色はスペクトルの傾きのラベルで、傾きを乱数の積分やオクターブ加算で作り分けている。