プログラミング
プログラマーのための音楽理論
Music Theory for Programmers (runjs.app)
要約
この記事は、プログラマーが音楽理論を理解するための入門ガイドです。音の物理的な性質から始め、Web Audio APIを使ったコーディングを通じて、周波数、エンベロープ、倍音、そして音階やコードといった音楽の基本概念を、直感的に理解できるように解説しています。楽器の演奏経験がなくても、コードを書きながら音楽の仕組みを解き明かしていきます。
全文翻訳
プログラマーのための音楽理論
Luke Haas
2026年8月17日
私は楽器を演奏できません。何度か試しましたが、そのたびに、なぜそれが正しい音なのかを理解することなく、おおよそ正しい音を出すことができるレベルに達しました。問題は練習ではありませんでした。音楽理論の説明が、物事がどのように、そしてなぜそのようになっているのかという根本的な理由を常に省略しているように思えたのです。ここに五線譜があります。ここに音符があります。これは長音階で、このパターンを覚えてください。なぜその音符なのですか? なぜそのパターンなのですか? それは慣習だからです。物理学と算術から本質的に導き出されるシステムを教えるには、奇妙な方法です。12の音符があるのには理由があります。長音階があのような形をしているのには理由があります。心地よく聞こえるコードが心地よく聞こえるのには理由があり、その理由を計算することができます。そこで、ゼロから始めて音楽を第一原理から学びたいと思い、コーディングをしながらその旅を始めました。この記事はその結果です。それは時間とともに変化する単一の数字から始まり、もしあなたがそれに沿って進めば、12の音符を導き出し、配列から音階やコードを構築し、実際の音楽のように聞こえるコード進行を作成することになります。楽器は不要で、何も信じる必要はありません。記譜法は最後に登場しますが、それは記譜されるものができてから、ごく最後に登場します。
音は時間とともに変化する数字です
音は単なる空気圧の変動です。スピーカーはコーンを前後に動かすことで音を生成し、コンピューターがオーディオで行うすべてのことは、毎秒44,000回、そのコーンがどこにあるべきかを示す数字のリストに集約されます。オーディオファイルはそのリストを書き留めたものです。シンセサイザーは、そのリストをその場で生成し、ブラウザは、どのような形状にしたいかを指定すれば、その部分を処理してくれます。最も単純な形状はサイン波なので、毎秒440回繰り返されるサイン波を次に示します。
440Hzのサイン波
const osc = ctx.createOscillator();
osc.frequency.value = 440;
osc.connect(out);
osc.start();
osc.stop(ctx.currentTime + 1);
実行
ctxとoutはブラウザのものではなく私のものです。それ以外は、Web Audio APIがそのまま提供するものです。他の場所でそのスニペットを実行するには、次のように始めます。
const ctx = new AudioContext();
const out = ctx.destination;
そこにある唯一音楽的な意味を持つ数字は440ですが、それさえも任意です。それは「A」と呼ぶことに同意した周波数であり、システム全体がそれに固定されている音叉です。それを300に変更してもう一度実行してみてください。異なるピッチが得られ、何も壊れません。なぜなら、このレベルではまだ音符はなく、ただの数字だからです。周波数がピッチです。数字が高いほど、音は高くなります。音楽に関する最後の単純なことは、これだけです。
なぜその音符がクリックしたのか
最後に小さなクリック音が聞こえたかもしれません。それはブラウザのバグではなく、物理学が容赦ないのです。オシレーターは停止したときに波の途中にあったため、スピーカーコーンはその移動範囲の端にあり、すぐに元に戻りました。圧力の瞬間的なジャンプがクリック音です。修正策は、ピッチではなく音量を制御する2番目の数字です。ミュージシャンはそれをエンベロープと呼びます。
エンベロープ付きの同じ音符
const osc = ctx.createOscillator();
osc.frequency.value = 440;
const env = ctx.createGain();
const t = ctx.currentTime;
env.gain.setValueAtTime(0, t);
env.gain.linearRampToValueAtTime(0.3, t + 0.01);
env.gain.exponentialRampToValueAtTime(0.001, t + 1);
osc.connect(env).connect(out);
osc.start(t);
osc.stop(t + 1);
実行
エンベロープは単一の音符の音量曲線で、無音から無音に戻ります。前面の上昇はアタック、その後の下降はディケイです。フェードインに10ミリ秒、その後ほぼ無音までゆっくりとディケイします。それはテストトーンと、2回聞きたくなるものの違いです。
アタック 10ms
ディケイ 1.00s
再生
プラック
ピアノ
ボウイング
アタックを約半秒に引き伸ばすと、音符は到着しなくなり、膨らみ始めます。それはもはや打たれたものではなく、弓で弾かれたようなものになり、ピッチは1ヘルツも移動していません。ここでエンベロープが周波数よりも多くの仕事をしているのです。ただし、これは単純化されたエンベロープです。完全版はADSR(アタック、ディケイ、サステイン、リリース)で、サステインはキーが押されている間に音符が保持されるレベル、リリースはキーを離したときにどのようにフェードアウトするかです。以下の関数は、後続の各例で使用されるヘルパーです。
function note(freq, start = 0, length = 0.5, type = "sine") {
const t = ctx.currentTime + start;
const osc = ctx.createOscillator();
const env = ctx.createGain();
osc.type = type;
osc.frequency.value = freq;
env.gain.setValueAtTime(0, t);
env.gain.linearRampToValueAtTime(0.3, t + 0.01);
env.gain.exponentialRampToValueAtTime(0.001, t + length);
osc.connect(env).connect(out);
osc.start(t);
osc.stop(t + length);
}
ティンバーは、あなたが要求しなかった周波数です
サイン波は単一の周波数であり、それ以外は何もなく、だからこそ聴力検査のような音で、どんな楽器とも似ていないのです。440Hzにチューニングされたギター弦を弾くと、毎秒440回繰り返される波が得られますが、弦は同時に、半分、3分の1、4分の1でも振動しています。これらは880、1320、1760Hzといった追加の周波数で、すべてあなたが要求した周波数の上に乗っています。そのスタックは倍音列と呼ばれます。あなたが要求した音は基音であり、その列は基音の周波数を1、2、3、4、5倍にしたものです。
1x 220Hz
2x 440Hz
3x 660Hz
4x 880Hz
5x 1100Hz
6x 1320Hz
7x 1540Hz
8x 1760Hz
220Hzの倍音列。
バーをクリックすると、その倍音を単独で聞くことができます。
バーをクリックしてください。
単独ではかなり退屈です。重要なのは、それらがパッケージとして到着すること、そしてそれらの相対的な音量がどのように構成されているかのレシピが、バイオリンをバイオリンらしく、トランペットらしくなくしているということです。ミュージシャンはそれをティンバーと呼び、どちらの場合も同じ音です。
ブラウザは、そのレシピを4つ、すぐに使える状態で提供しています。
4つの波形、同じピッチ
["sine", "triangle", "square", "sawtooth"].forEach((type, i) => {
note(220, i * 0.7, 0.6, type);
});
実行
同じ220Hz、4つの非常に異なるキャラクター。矩形波は奇数倍音のみを含んでおり、それが中空でわずかに電子的な音に聞こえる理由です。鋸歯状波はそれらすべてを含んでおり、荒々しく buzzy な音に聞こえます。上のスコープは、再生中の各波形の形状を示しており、その形状が倍音のレシピです。倍音列を覚えておいてください。なぜなら、この記事の残りの部分全体を説明することになるからです。あなたが演奏するすべての音は、静かな追加の音のスタックを引きずっており、それらの音が何であるかは私たち次第ではありません。それは算術であり、振動する弦や空気の柱の物理学によって固定されており、それらを基盤とするどの楽器でも同じように出てきます。
周波数を2倍にすると同じ音になる
ここに5つの音があります。それぞれが前の音の周波数の2倍です。
1つの音、5回
[110, 220, 440, 880, 1760].forEach((freq, i) => note(freq, i * 0.45, 0.4));
実行
それらは異なるピッチですが、同じ音のように聞こえます。似ているだけでなく、同じです。互いに接触のない文化でも、これに独立して到達しています。周波数を2倍にすると、同じ名前を付けるに値するほど似たものが得られます。西洋の記譜法では、上記の例のこれらの周波数はすべてAと呼ばれ、それらの間の距離はオクターブです。名前付けは恣意的ではなく、倍音列がその理由を説明しています。440のすべての倍音は、220の倍音列にすでに存在しています。なぜなら、220の列は220、440、660、880、1100であり、440の列は440、880、1320、1760だからです。高い方の音は、低い方がすでに生成していなかった周波数を追加しません。それは新しい色ではなく、同じ色、より明るい色です。
2:1
再生
両方
220Hzと440Hz、低い方の音の各サイクルごとに繰り返されます。
このことから2つのことが直接導き出されます。ピッチは加算的ではなく乗算的です。オクターブを上がるということは、何かを足すのではなく、2倍にすることを意味します。110から220までのギャップは110Hzで、880から1760までのギャップは880Hzですが、それらはまったく同じ距離のように聞こえます。