地図/信号/音の合成

包絡で波形を整える

波形は速い振動、エンベロープは遅い音量変化。この二つを掛け算すると、鳴り始めて消える一発になる。振幅がエンベロープの形に絞られて、無音から立ち上がり、持続し、減衰して無音に戻る。

波形 s(t) に音量包絡 g(t) を掛けた s(t)·g(t) は、振幅変調(AM)のうち変調が音量域だけに留まる場合。g(t) が音より十分ゆっくりなら、耳は g(t) を音量の時間変化として、s(t) を音色として別々に受け取る。ADSR の各区間を直線で繋ぐのが線形エンベロープで、WebAudio では gain.gain.linearRampToValueAtTime(...) を順に呼んでこの折れ線を引く。指数カーブにすると減衰が自然な楽器の鳴りに近づく。

エンベロープは経過時間 e を受け取って、その瞬間の音量(0〜1)を返す関数。区間ごとに if で分岐し、ADSR の4つの区間が4本の式になる。gate は鍵盤を離す時刻で、そこを境に Release(減衰)へ入る。

const env = (e, gate) => {
  const a = 0.08 // Attack
  const d = 0.12 // Decay
  const s = 0.5 //  Sustain の高さ
  const r = 0.25 // Release
  if (e < a) return e / a // 0 から 1 へ立ち上がる
  if (e < a + d) return 1 - (1 - s) * ((e - a) / d) // 1 から s へ落ちる
  if (e < gate) return s // s のまま持続
  const rel = (e - gate) / r // gate 以降は s から 0 へ
  return rel >= 1 ? 0 : s * (1 - rel)
}

横軸を時間にして env を1本の線で引くと、山ができて、肩で一段下がり、平らに伸びて、最後に落ちる折れ線になる。この折れ線が音量の輪郭。

a を短くすると立ち上がりの角が鋭くなり、長くするとなだらかになる。s を変えると持続の高さが上下し、r を伸ばすと最後の落ちが緩やかになる。4つの値だけで輪郭の形が決まる。静止画なので return () => {} で空のループを返して止めている。

この輪郭を正弦波(キャリア)に掛ける。各 x で正弦波の値に env の値を掛け、その積を縦位置にする。

const carrier = Math.sin((x / w) * Math.PI * 2 * 40) // 速い振動
const py = h / 2 - carrier * env(e, gate) * h * 0.4 // 振幅を包絡で絞る

env が 0 の所では振幅も 0 になり、波が中心線に潰れる。env が 1 なら振幅はそのまま。正弦波の細かい山が、包絡の形に沿って膨らんだりしぼんだりする。上下に env-env を薄く引くと、波がその間に収まる。再生ヘッドが左から右へ走り、鳴り始めから消えるまでをなぞる。

うっすい線が上下のエンベロープ(音量の包み)、濃い線がその中で振動する正弦波。波が包みの形に収まって、鳴り始めて消えていく。gate を伸ばすと Sustain の区間が長くなり、平らな持続が間延びする。enva d s r を動かすと、同じ正弦波のまま包みの形だけが変わる。