ならば

音とかで遊んでいたログ

LPF、HPF、BPF、BRF

倍音を多く含む波形から一部の周波数成分を削っていって音色を作る方法を減算合成という。減算合成方式のシンセサイザーでオシレータから出力された波形を加工するのに使うのがフィルタ。これで倍音の一部を削ったり特定の部分を強調したりできる。フィルタには目的に応じていくつか種類があるが、今回は分かりやすいのを四つだけ。

  • ローパスフィルタ(LPF):カットオフ周波数より低い周波数を通す、つまり高い成分をカット
  • ハイパスフィルタ(HPF):カットオフ周波数より高い周波数を通す、つまり低い成分をカット
  • バンドパスフィルタ(BPF):中心周波数の周辺を通して他の成分をカット
  • バンドリジェクトフィルタ(BRF):中心周波数の周辺のみをカット

実際には設定した周波数の境界できっちりカットされるわけではなくて、減衰する形で段々と成分が通りにくくなる。あと、設定周波数の付近の成分を強調して独特なクセを付ける機能をレゾナンスという。
ChucKにはフィルタとしてLPF、HPF、BPF、BRFというそのままな名前のユニットジェネレータがある。設定周波数は.freq、レゾナンスの「強さ」は.Qというパラメータで読み書きする。四種類を全部テストするプログラム。

SinOsc s;
440 => s.freq;
// 「new クラス名」はオブジェクトを作って、その参照を返す
[new LPF, new HPF, new BPF, new BRF] @=> FilterBasic fils[];

fun void testfilter(int idx, float q) {
    s => fils[idx] => dac;
    q => fils[idx].Q;    // 大きいほど強調される
    for (int i; i < 127; i++) {
        i => Std.mtof => fils[idx].freq;
        100::ms => now;
    }
    s =< fils[idx] =< dac;
}

for (int i; i < fils.cap(); i++) testfilter(i, 1.5);

聴いたときに効果を分かりやすくするためサイン波を使ってるけど、単独のサイン波は基音だけで倍音がないから実用上あえてフィルタを通す意味がなくて、減算合成の場合これで音色を作ることはないはず。倍音が多いのはのこぎり波やパルス波。減算合成とは逆に、波の合成でやったみたいに波をどんどん足して音色を作るとき(加算合成)にはサイン波を使う。
FilterBasicへの参照型の配列filsについて。ユニットジェネレータにはクラスの継承による親子関係を持つものがあって(特に全てのユニットジェネレータはUGenというクラスを継承している)、異なるユニットジェネレータでも親クラスが同じならその親クラスへの参照型という形でひとつの配列に格納できる。上のプログラムで使っている四種類のフィルタはFilterBasicクラスを継承していて、さらにFilterBasic自体がフィルタ機能としてのパラメータ.freq、.Qを持つため、filsに格納された各要素を(「$」で型の変換をせずに)フィルタとして使うことができる。


フィルタの設定周波数にエンベロープを付けて、レゾナンスを強くすればクセのある音を作れる。ミョンとかビョンとかそんな感じ。

SawOsc s => LPF f => ADSR amp => dac;
ADSR e => blackhole;  // フィルタの周波数制御用
7 => f.Q;
amp.set(10::ms, 5::ms, .3, 200::ms);
e.set(50::ms, 100::ms, .1, 500::ms);

fun void chcutoff()
{
    while(ms => now) e.value() * 20000 => f.freq;
}
spork ~ chcutoff();

[60, 64, 65, 67, 71] @=> int scale[]; // 琉球音階
while(true) {
    scale[Std.rand2(0, scale.cap() - 1)] => Std.mtof => s.freq;
    1 => amp.keyOn => e.keyOn;
    200::ms => now;
    1 => amp.keyOff => e.keyOff;
    200::ms => now;
}

まだ綺麗な音はなかなか思うように作れない。