エフェクト(2)

波形の演算

前回はディレイでもっていろいろなエフェクトを作ってみましたが、今度は波形を演算する方法を考える。音データとは波形であり、波形は数字で表現されているのであった。というわけで音量を上げ下げするには単に掛け算すればよい、となります。

SynthDef("amp", {
var in,out;
in = SoundIn.ar(0);
out = in*0.5;
Out.ar(0,out!2); 
}).play;

「*0.5」のかわりに「/2」でも意味は同じ。次にLFOとしてSinOscを掛け算するとトレモロになります。

SynthDef("tremolo", {
var in,out;
in = SoundIn.ar(0);
out = in*SinOsc.ar(5);
Out.ar(0,out!2); 
}).play;

さらにSinOscの周波数を上げていくとリングモジュレーションになる。これというのは二つの周波数を足した周波数と引いた周波数を発生させて音色を変化させる。

SynthDef("ring_mod", {
var in,out;
in = SoundIn.ar(0);
out = in*SinOsc.ar(2000);
Out.ar(0,out!2); 
}).play;

ところで私事ですが、リングモジュレーションなどという単体機でもなかなか売ってないようなマニアなエフェクトが、掛け算だけでできる!というので盛り上がったのが、この道にのめり込むきっかけだった気がします。いわゆる電子音楽の響きよね。
オートパンはPan2の中にSinOscを入れてやる。

SynthDef("auto_pan", {
var in,out;
in = SoundIn.ar(0);
out = Pan2.ar(in,SinOsc.ar(2));
Out.ar(0,out); 
}).play;

音を歪ませるにはどうすればいいだろうか。一番簡単なのは適当な数値で波形をぶった切って潰してしまうことですね。次の例では、MouseXで元の音量を100倍まで変えられますが、clip2でもって波形を±1に制限している。

SynthDef("distortion", {
var in,mod,out;
in = SoundIn.ar(0);
mod = MouseX.kr(1,100,1);
out = clip2(in*mod,1);
Out.ar(0,out!2); 
}).play;

ただ個人的には「波形をべき乗する」という方法が書き方も楽で良いとおもいます。

SynthDef("distortion_2", {
var in,mod,out;
in = SoundIn.ar(0);
mod = MouseX.kr(0.001,1);
out = in**mod;
Out.ar(0,out!2); 
}).play;

もちろんこれもあれこれ作りこまないと、とてもエフェクタ並みの歪みにはなりませんがねえ。
リミッタをかけるにはLimiter、これまたそのまんまである。音量の上限と先読み時間を指定して音量を制限します。ここでは意味なく音量を10倍してから±0.7にリミットしてますが、これだとリミッタというよりコンプレッサですがな。

SynthDef("limiter", {
var in,mod,out;
in = SoundIn.ar(0);
mod = in*10;
out = Limiter.ar(mod,0.7,0.01);
Out.ar(0,out!2); 
}).play;

ノイズゲートにはCompanderが使えます。MouseXでしきい値を指定してそれ以下の音量をミュートしている。

SynthDef("noise_gate", {
var in,th,out;
in = SoundIn.ar(0);
th = MouseX.kr(0.01,1);
out = Compander.ar(in,in,th,10,1,0.01,0.01);
Out.ar(0,out!2); 
}).play;

他にもCompanderでコンプやリミッタが作れる、とあるのですが、どういうことなんだか各自ご研究くだされ(実はよくわかってない)。
いわゆるローファイエフェクトというか下位サンプリングは、Latchを使ったサンプルホールドで作れる。Impulseでトリガされると只今の値をホールドするわけです。

SynthDef("subsampling", {
var in,mod,out;
in = SoundIn.ar(0);
out = Latch.ar(in,Impulse.ar(1000));
Out.ar(0,out!2); 
}).play;

サンプルホールドには他にもいろいろな用途が考えられる。

フィルタ

さてヘルプを見てもらえればわかりますが、scには多種多様のフィルタが用意されております。
よってその全てを説明するのもナニであるので、主たるものを試してみるが、sc for winの新しいバージョンではMouseX、MouseYが使えるようになりましたので、之で戯れてみませう。
マウスの横位置(X軸)でフィルタの周波数を操作します。HPFも試されたし。

SynthDef("lowpass_filter", {
var in,freq,out;
in = SoundIn.ar(0);
freq = MouseX.kr(50,5000,1);
out = LPF.ar(in,freq);	// or HPF
Out.ar(0,out!2);
}).play;

次の例では横位置に加えマウスの縦位置(Y軸)でフィルタの幅を操作します。BRF、RLPF、RHPFなんかもあるでよ。

SynthDef("bandpass_filter", {
var in,freq,rq,out;
in = SoundIn.ar(0);
freq = MouseX.kr(50,5000,1);
rq = MouseY.kr(0.01,2,1);
out = BPF.ar(in,freq,rq);	// or BRF,RLPF,RHPF 
Out.ar(0,out!2);
}).play;

バーブも言わばフィルタの一種でして、全ての音域を通して位相だけを変えるオールパスフィルタという特殊なフィルタの集まりです。AllpassNはオールパスフィルタにディレイを組み合わせたもので、これを4回繰り返している。

SynthDef("mono_reverb", {
var in;
in = SoundIn.ar(0);
4.do{ in = AllpassN.ar(in,0.05,0.05.rand,2) };
Out.ar(0,in!2); 
}).play;

「0.05.rand」でもってdelaytimeを0〜0.05の範囲でランダムにしてますので、音がいろいろな場所にぶつかって散乱している感じになります。また左右別々にdelaytimeを指定すれば擬似ステレオリバーブになる。

SynthDef("pseudo_stereo_reverb", {
var in;
in = SoundIn.ar(0);
4.do{ in = AllpassN.ar(in,0.05,[0.05.rand,0.05.rand],2) };
Out.ar(0,in); 
}).play;

これはAllpassNだけの例なのでアレですが、他のフィルタを組み合わせるなどすれば、もう少しまともなリバーブになると思われます。

FFT

FFT高速フーリエ変換)が何かというのをちゃんと説明できる能力が私にはないのだが、要は入ってきた音を何百とか何千のサイン波に分解するというものです。で、分解されたサイン波を逆FFT(IFFT)すると元の音に戻るんですってよ。すげーなおい。
だがしかーしFFTをIFFTしただけでは単なるCPUの無駄遣いであるので、分解されたサイン波でもって演算することでいろいろなエフェクトが作れます。例えばボコーダと呼ばれるやつ。

SynthDef("vocoder", {
var inA,inB,chainA,chainB,chain,out; 
inA = SoundIn.ar(0);	// 音源A
inB = Mix.fill(4,{|n| LFSaw.ar(1.5**n*200,0,0.1)});	// 音源B
chainA = FFT(LocalBuf(2048),inA);	// 音源AをFFT
chainB = FFT(LocalBuf(2048),inB);	// 音源BをFFT
chain = PV_MagMul(chainA,chainB);	// 音源Aと音源Bの振幅スペクトルを乗算
out = IFFT(chain)/10;	// 逆FFT(なぜか音量下げる)
Out.ar(0,out!2); 
}).play;

IFFTに対する「/10」というのはどこから出てきた数字なんだ、とは思いますけどIFFTから出てくる音は音量が爆発しがちです。よしなにしてくれよとも思うのですが仕様らしい。
さて分解されたサイン波というのは元の音の周波数分布を表しているのですが、音の周波数分布は単にスペクトルとも言う。で、ボコーダは一方のスペクトルでもう一方のスペクトルを削ってやってる感じ、というようなイメージがあります。数学的に正しいかは知らんけど。
FFT関係のヘルプを見るといろいろなやり口があるようですが、なかなか理解が難しい。ここでは簡単にもうひとつだけ、スペクトルのべき乗という例を挙げます。圧縮オーディオってこんな音。

SynthDef("powered_spectrum", {
var in,chain,out; 
in = SoundIn.ar(0);
chain = FFT(LocalBuf(2048),in);
2.do{ chain = PV_MagSquared(chain) };
out = IFFT(chain)/100;
Out.ar(0,out!2); 
}).play;

「/100」というのもやはり適当。