Fourier級数で音を作る — 倍音構成と波形合成の実装
このトピックを深掘りする
クリエイティブコーディング
ブラウザでの数学的シミュレーション・自然現象の可視化・生成アートの実装技法
全12本中 10 本目。
次に読む記事
アリの巣シミュレーション — フェロモンで創発する群知能
フェロモンベースの経路探索をエージェントベースモデルで実装し、アリの群知能をブラウザで再現する。蒸発・拡散パラメータの調整で創発パターンがどう変わるかも検証した。
Canvas APIで60fpsパーティクル — 200個でもヌルヌル動く設計
requestAnimationFrame、固定タイムステップ、DPI対応、空間分割を駆使して、Canvas APIで200個以上のパーティクルを60fpsで動かす設計手法を解説。
セルオートマトンの世界 — ライフゲームから波動関数崩壊まで
ライフゲーム、WaveFunctionCollapse、SandFallの実装を通じて、ルールベースのセルオートマトンの設計パターンとUint8Arrayによるグリッド最適化手法を解説する。
シリーズ全体の流れを見ながら、次に読む記事へ進めます。 初めての方はホームへ →
なぜFourier級数なのか
「すべての周期関数は sin と cos の重ね合わせで表現できる」。1807年にJoseph Fourierが提唱したこの主張は、当時の数学者たちに懐疑的に受け止められた。しかしこれは正しく、現代の信号処理・音響工学・画像圧縮の基盤になっている。
Fourier級数が面白いのは、抽象的な数学が「音色」という身体的な感覚に直結する点だ。矩形波が鋭く聞こえるのも、三角波が柔らかく聞こえるのも、すべて倍音構成——つまりFourier係数——で説明できる。
FourierSeriesコンポーネント(402行)では、Canvas APIを使って3種類の波形をリアルタイムでFourier合成し、エピサイクル(回転円の連鎖)で視覚化する。項数を1から30まで動かすと、sin波の重ね合わせが目的の波形に「収束していく」過程が見える。
3つの波形とその級数展開
矩形波 (Square Wave)
矩形波のFourier級数展開は奇数次の倍音のみを含む:
f(x) = Σ [4/(π·n) · sin(2πnfx)], n = 1, 3, 5, ... (奇数のみ)実装では k をインデックスとして n = 2k + 1 で奇数を生成する:
case 'square': {
n = 2 * k + 1
amplitude = 4 / (Math.PI * n)
break
}振幅が 1/n で減衰するため、高次の倍音が比較的強く残る。これが矩形波の「鋭い」「中空的な」音色を生む。クラリネットの音が矩形波に近いと言われるのは、奇数倍音が偶数倍音より卓越する管楽器の物理特性による。
項数1の時点ではただのsin波だが、3倍音(n=3)を足すと肩が少し角張り、5倍音、7倍音と増やしていくと徐々に矩形に近づいていく。ただし、どれだけ項数を増やしても不連続点の近傍に「ヒゲ」が残る。これがギブス現象だ。
鋸歯状波 (Sawtooth Wave)
鋸歯状波は全倍音を含む。偶数次も奇数次も全部入っている:
f(x) = Σ [(2/π) · (-1)^(n+1) / n · sin(2πnfx)], n = 1, 2, 3, ...case 'sawtooth': {
n = k + 1
amplitude = (2 / Math.PI) * ((-1) ** (n + 1) / n)
break
}(-1)^(n+1) は符号の交替を表す。n=1 で正、n=2 で負、n=3 で正……と交互に反転する。全倍音を含むということは、倍音構成が最も「豊か」ということだ。
弦楽器のボウイング音(弓で弦を弾く音)が鋸歯状波に似るのは、弦が弓に引っ張られて直線的に変位し、滑った瞬間に急速に戻る——という運動パターンが鋸歯状の波形を生むからだ。ヴァイオリンの開放弦をゆっくり弾いた音をオシロスコープで見ると、かなり鋸歯状波に近い。
三角波 (Triangle Wave)
三角波は矩形波と同じく奇数次倍音のみだが、振幅が 1/n² で減衰する:
f(x) = Σ [(8/π²) · (-1)^k / (2k+1)² · sin(2π(2k+1)fx)], k = 0, 1, 2, ...case 'triangle': {
n = 2 * k + 1
amplitude = ((8 / (Math.PI * Math.PI)) * (-1) ** k) / (n * n)
break
}1/n² の減衰は 1/n より圧倒的に速い。3倍音の振幅は基音の1/9、5倍音は1/25、7倍音は1/49……と急速に小さくなる。つまり高周波成分がほとんど残らない。
これがフルートに近い柔らかい音色を生む理由だ。フルートは管楽器の中でも倍音が少ない(純音に近い)楽器として知られている。三角波の「丸い」音色は、高次倍音が極めて弱いことの聴覚的な表現にほかならない。
エピサイクルによる可視化
FourierSeriesコンポーネントの画面は左右に分かれている。左40%がエピサイクル領域、右60%が波形描画領域だ:
const EPICYCLE_SPLIT = 0.4
const WAVEFORM_GAP = 20
const TRAIL_LENGTH = 400エピサイクルとは「回転する円の上に回転する円を載せる」描画手法だ。各Fourier項が1つの円に対応する:
for (let i = 0; i < coeffs.length; i++) {
const { n, amplitude, phase } = coeffs[i]
const r = Math.abs(amplitude) * scale
const angle = n * t + phase
// 円を描画
ctx.beginPath()
ctx.arc(x, y, r, 0, Math.PI * 2)
ctx.stroke()
// 次の円の中心 = 現在の円の縁上の点
x += Math.cos(angle) * Math.abs(amplitude) * scale
y -= Math.sin(angle) * Math.abs(amplitude) * scale
}radius = |amplitude_n| × scale でスケーリングされる。基音(n=1)の円が最も大きく、高次倍音の円は順に小さくなる。三角波では 1/n² 減衰のため、3番目以降の円はほぼ点に見える。矩形波では 1/n 減衰なので、高次の円もそれなりの大きさを保つ。
最後の円の先端(tip)のy座標が、その瞬間の波形の値になる。この値を毎フレーム記録して右側に400点のトレイルとして描画する:
const trail = trailRef.current
trail.unshift(y)
if (trail.length > TRAIL_LENGTH) {
trail.length = TRAIL_LENGTH
}項数1ではただ1つの円が回転し、純粋なsin波が描かれる。項数を5、10、20と増やすと、エピサイクルの先端が複雑な軌道を描き始め、右側の波形が目的の形に近づいていく。矩形波の場合、「角張っていく」過程が特に劇的だ。
収束速度の違い
3つの波形の数学的な違いは、Fourier係数の減衰速度に集約される:
| 波形 | 減衰 | 30項の近似 | 音色 |
|---|---|---|---|
| 矩形波 | 1/n | ギブス現象が残る | 鋭い、中空的 |
| 鋸歯状波 | 1/n | 矩形波と同等 | 豊か、明るい |
| 三角波 | 1/n² | ほぼ完全 | 柔らかい、丸い |
三角波は5項もあればほぼ完璧な近似になる。これは高次の倍音が無視できるほど小さいからだ。一方、矩形波と鋸歯状波は30項でも不完全で、不連続点の近傍にリンギングが残る。
ここに音色との対応がある: 収束が遅い = 高周波成分が多い = 明るく鋭い音。逆に 収束が速い = 高周波成分が少ない = 暗く柔らかい音。シンセサイザーのフィルターで高域をカットする操作は、まさにFourier係数の高次項を削ることに相当する。
ギブス現象
矩形波と鋸歯状波の不連続点には、項数をどれだけ増やしても消えないオーバーシュートが残る。これがギブス現象(Gibbs phenomenon)だ。
具体的には、不連続点の近傍で波形の約9%(正確には Si(π)/π - 1/2 ≈ 0.0895)のオーバーシュートが発生する。項数を増やすとオーバーシュートの「幅」は狭くなるが、「高さ」は変わらない。
項数 5: ████████████░░░░ オーバーシュート幅 大
項数 10: ██████░░ オーバーシュート幅 中(高さは同じ)
項数 30: ██░ オーバーシュート幅 小(高さはまだ同じ)数学的な意味では、Fourier級数は不連続関数にも各点収束する(不連続点では中間値に収束する)。しかし 一様収束 はしない。エピサイクルの可視化で項数スライダーを動かすと、「数学的には収束しているが、視覚的にはいつまでもピクピクしている」という不思議な状況が体感できる。
この現象は音声処理でも実際に問題になる。矩形波をFourier合成で近似する際、ギブス現象によるオーバーシュートがクリッピングやノイズの原因になることがある。対策としてはLanczosのσ近似(Fejer和)などの平滑化手法がある。
インタラクティブなパラメータ
FourierSeriesコンポーネントは4つのパラメータをリアルタイムに操作できる:
- termCount (1-30): Fourier級数の項数。1はただのsin波、30で高精度近似
- speed (0.1-3.0): エピサイクルの回転速度。遅くすると各倍音の寄与が見やすい
- waveType: square / sawtooth / triangle の切り替え
- showCircles: エピサイクルの円表示トグル。非表示にすると先端の軌跡だけが見える
特に効果的な使い方は、まず項数1にして波形を観察し、1ずつ増やしていくことだ。矩形波の場合:
- 1項: 純粋なsin波
- 2項 (n=3): 肩に小さなうねりが加わる
- 3項 (n=5): 上辺と下辺が平坦に近づく
- 5項: かなり矩形に近いが角が丸い
- 10項: ほぼ矩形。ただし立ち上がり部分にリンギング
- 30項: 精密だが不連続点のオーバーシュートは健在
波形タイプを切り替えると resetTrail() が呼ばれ、トレイルがクリアされる:
const handleWaveChange = useCallback(
(type: WaveType) => {
playClick()
setWaveType(type)
resetTrail()
},
[playClick, resetTrail],
)前の波形の残像が混ざらないようにするためだ。
実装で学んだこと
Fourier級数の実装を通じて得られた最大の学びは、「周波数領域と時間領域は同じ現象の別の見方である」ということだ。
時間領域では波形は「時間とともに変化する振幅」として見える。周波数領域では同じ波形が「各周波数成分の強さ」として見える。Fourier級数はこの2つの表現を橋渡しする道具だ。
音色が倍音構成で決まるという事実は、シンセサイザーの動作原理そのものでもある。アナログシンセの「オシレーター」は基本波形(矩形波、鋸歯状波、三角波、sin波)を生成し、「フィルター」で特定の周波数成分を削り、「エンベロープ」で時間変化を与える。ブラウザ上でもこの仕組みはWeb Audio APIで再現できる。これはFourier係数を操作しているのと本質的に同じだ。
エピサイクルによる可視化は「1つの円 = 1つの倍音」という直感的な対応を作る。大きな円がゆっくり回る(基音)、その上で小さな円が速く回る(倍音)。この入れ子構造が生む先端の軌跡が、我々が聞いている音の波形そのものだ。
402行のコンポーネントが示しているのは、200年前の数学が現代の音響技術の基盤であり続けているという事実だ。Fourier変換がなければ、MP3もJPEGもWi-Fiも存在しない。
まとめ:Fourier級数と音合成の道具たち
Fourier解析と音響物理の数学的背景を深掘りするなら、道具棚の書籍が参考になる。特に信号処理の入門書は、ここで扱ったFourier級数から離散Fourier変換(DFT)、高速Fourier変換(FFT)への橋渡しに役立つ。