ならば

音とかで遊んでいたログ

波の合成

一般にいくつかの波を合成して別の波を作ることができる。まずは全く同じサイン波を足して振幅(波の振動の大きさ)が二倍のサイン波を鳴らすコード。10月8日追記:複数のオシレータの出力をdacに直につなぐと歪みが生じてしまう。これを避けるには途中でGainをかまして、それにオシレータをつないでやればよい。参照:[http://d.hatena.ne.jp/naraba/20071008:title=10月8日のメモ]。

SinOsc s1 => dac;
SinOsc s2 => dac;
440 => s1.freq => s2.freq;
2::second => now;

s1とs2をdacにつなぐと、それぞれのオシレータの出力がdacに入る。入力された複数の波の扱いはopというint型のパラメータ*1の値によって決まるが、デフォルト(1)の場合入力された波を全部足して出力する。他には例えば減算は2だから、上記のコードの中に「2 => dac.op;」という行を挟めば今度は同じ波形同士で引き算されて無音になる。
片方の波の位相(例えば、波の山が来るところ)をずらして、逆位相にして合成するとお互いの山と谷で打ち消しあうのでこのときも無音になる。位相はphaseというパラメータで読み書きできる。

SinOsc s1 => dac;
SinOsc s2 => dac;
440 => s1.freq => s2.freq;
.5 => s2.phase;  // phaseは[0,1]の範囲の値
2::second => now;

周波数を少し変えた音を合成するとうなりが聞こえる。下のコードを実行するとs2の周波数が増加するにつれてうなりの間隔が短くなっていくのがわかる。

TriOsc s1 => dac;
TriOsc s2 => dac;
.5 => s1.gain => s2.gain;
440 => s1.freq => s2.freq;
while(2::second => now)
    s2.freq() + 1 => s2.freq;


三つ以上の波の合成も同じ。波の合成の究極が多分フーリエ級数。どんな波形も色々なサイン波の合成で表せる(近似できる)。当然現実には全部そうやって波形を作るわけじゃないが、合成が一番簡単そうな「のこぎり波」でやってみた。のこぎり波は基音*2に、振幅を次数の逆数倍にした倍音*3を全て足すことで近似できる。

わざわざ作らなくてもChucKにはSawOscとして用意されている。比較ってことで並べた。

// 用意されている のこぎり波
SawOsc s => dac;
440 => s.freq;
.3 => s.gain;
2::second => now;
s =< dac;

// 合成で作ってみる のこぎり波
Gain g => dac;
SinOsc sin[50];  // 仮にアナログの場合、多いほどSawOscに近づく
s.freq() => float f;
.3 => g.gain;
for (0 => int k; k < sin.cap(); k++) {
    (k+1) * f => sin[k].freq;
    2 / (pi * (k+1)) => sin[k].gain;
    sin[k] => g;
}
2::second => now;

dacはスコープがグローバルということもあって今後の習慣も考えると一気にdacにぶち込むのはイヤだったから途中でGainをかました。10月8日追記:くどいけど直にdacに突っ込むのは歪みが生じるからという理由もあるので避けるべき。足すのは全ての整数倍音なので数式の上では無限に足していく必要があるけど、デジタルシンセサイザーの場合はナイキスト周波数(サンプリング周波数の半分)までの倍音でいいらしい。だから今回は50倍まででいいはず*4。うん。人が聴くぶんには同じ音なんじゃない?

*1:opはdacに限らず全てのユニットジェネレータにある

*2:基本となる周波数を持つサイン波。これで音高が決まる

*3:基音の整数倍の周波数を持つサイン波

*4:50 * 440Hz = 22kHz < 22.05kHz