Faust:自分好みのステレオコンプを作ってみた


DAWを64bit環境にしたら、コンプの感じが全然違っちゃったり使いにくかったりなので、今まで使ってたのと同じに使える自分好みのコンプを作ってみました。

## Faustには元々ステレオコンプが入ってます。
compressors.libのこの部分を元にしました。

```Java:compressors.lib

compressor_stereo(ratio,thresh,att,rel,x,y) = cgm * x, cgm * y 
	with {
  		cgm = compression_gain_mono(ratio, thresh, att, rel, abs(x) + abs(y) );
	};

compression_gain_mono( ratio, thresh, att, rel ) =
	an.amp_follower_ar( att, rel ) : ba.linear2db 
	: outminusindb( ratio, thresh ) : kneesmooth( att ) : ba.db2linear
	with {
  		kneesmooth( att )  = si.smooth( ba.tau2pole( att / 2.0 ) );
    	outminusindb( ratio, thresh, level ) = max( level - thresh, 0.0 ) * ( 1.0 / float( ratio ) - 1.0 );
	};
```
※ 自分が見易いように整形してます。

## 変更するところ：

+ センター定位の物を圧縮しすぎない -> レベル検知用の入力を ``` abs(x) + abs(y) ``` から ```sqrt( x^2 + y^2 )``` に。
+ soft kneeの感じを自分ごのみに。
+ パラメータを追加。kneeとpost。
+ かかり具合が把握しやすいようなメーター表示。

## 試行錯誤の結果、こんな風になりました。

```cpp:myComp.dsp
declare name "myComp";
import("stdfaust.lib");

process = compST(
  ratioSlider
  , thrSlider
  , ( attSlider / 1000.0 )
  , ( relSlider / 1000.0 )
  , (kneeSlider * 2  + 0.1 )
  , postSlider
) ;
compST(ratio, thr, att, rel, knee, post, x, y ) = cgm * x, cgm * y
  with {
    cgm = compGainMono(ratio, thr, att, rel, knee, post, sqrt(x^2 + y^2) );
  };

compGainMono(ratio, thr, att, rel, knee, post, xPos ) =
  inDB : dBmeter( vuMeterIN )
  : gainDB : dBmeter( vuMeterGR )
  : +( post ) : dBmeter( +( inDB ) : vuMeterOUT )
  : ba.db2linear
  with {
    inDB = xPos : an.amp_follower_ar(att,rel) : ba.linear2db ;
    gainDB( xx ) = f0,f1,f2 : ba.selectn(3,nc)
      with{
        f0 = 0 ;
        f1 = 0-(xx^2) * rr/(4 * knee) + xx * rr * tmk /(2 * knee) - rr * (tmk^2) / (4 * knee);
        f2 = (thr-xx) * rr ;
        nc = 0 : ba.if(c1, 1) : ba.if(c2, 2);
        c1 = (xx > tmk) & (xx <= tpk) ;
        c2 = xx > tpk ;
        tmk = thr - knee ;
        tpk = thr + knee ;
        rr = 1 - 1 / ratio ;
      };
  };

dBmeter(m) = _ <: _, ( max(-80) : si.smoo : m) : attach ;

compGrp(x) = hgroup("[0]comp", x ) ;
ctrlGrp(x) = compGrp(hgroup("[0]Ctrl", x )) ;
meterGrp(x) = compGrp(hgroup("[1]Meter", x )) ;
thrSlider = ctrlGrp(vslider("[0]thr[unit:dB]", -20, -60, 0, 0.5 )) ;
ratioSlider = ctrlGrp(vslider("[1]ratio", 2.0, 1.1, 30.0, 0.1 )) ;
kneeSlider = ctrlGrp(vslider("[2]knee", 0.0, 0.0, 5.0, 1.0 )) ;
attSlider = ctrlGrp(vslider("[3]attack[unit:ms]", 80, 1, 200, 1 )) ;
relSlider = ctrlGrp(vslider("[4]rel[unit:ms]", 160, 100, 500, 1 )) ;
postSlider = ctrlGrp(vslider("[5]post", 0.0, 0.0, 20.0, 0.5 )) ;
vuMeterIN = meterGrp(vbargraph( "[0]IN[unit:dB]", -60, 1 ));
vuMeterGR = meterGrp(vbargraph( "[1]GR[unit:dB]", -60, 1 ));
vuMeterOUT = meterGrp(vbargraph( "[2]OUT[unit:dB]", -60, 1 ));

```

## soft knee込みの圧縮のグラフです。
![スクリーンショット 2017-12-25 19.35.44.png](https://qiita-image-store.s3.amazonaws.com/0/61597/2d209192-1d74-271c-5524-0489b61c5e84.png "スクリーンショット 2017-12-25 19.35.44.png")

## VSTにしてみました。
[Faust&JUCEによる超高速VST開発](https://qiita.com/tomoyanonymous/items/79b3e551984a707b5c9a)
ちゃんと動いてます。
![スクリーンショット 2017-12-25 19.31.31.png](https://qiita-image-store.s3.amazonaws.com/0/61597/6e73af5f-cb7f-0e52-1b1f-5020c87a2116.png "スクリーンショット 2017-12-25 19.31.31.png")

## 音は... 
そうそう、こんな感じ。めっちゃ使いやすい! 僕にはね。
他の人はどうか知らん。

## 追記：soft kneeには二種類あるっぽい
好みの問題かい!って突込みが入りそうなので、調べてみました。自分でももやもやしてたので。
どうも"soft knee"の解釈(と実装)には二種類あるっぽいです。

### まずはよく見るこんな感じのやつ。
Tresholdの手前からゆるやかに圧縮が始まります。
![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Compression_knee.svg/450px-Compression_knee.svg.png)

### もうひとつ。
Thresholdに逹してから時間軸上で滑かに変化させます。
> Soft Kneeでは瞬間的にボリュームをオリジナルの「コンプレッションされていない直線」に沿わせ、その後以下の図のように設定されたRatioの角度に沿って変化します。
![コンプレッションを理解する](https://jp.residentadvisor.net/images/features/2012/tech-compress-level-knee.jpg)
([コンプレッションを理解する](https://jp.residentadvisor.net/features/1609)から引用。)


### ぼくの感じる音の違い。
soft kneeを上げていくと:

+ 前者は音量が下がる。後者はほとんど変らない印象。
+ 前者はattackTimeの変化は感じられない。後者はattack timeが長くなった印象。


ぼくの好みは前者で、Faustの compressor_stereo は後者のようです。

### アルゴリズムの違い。
ざっくりいって、コンプレッサーの仕組みは：

+ attとrelを使って音声入力の音量を検知して
+ その音量と他のパラメータで係数を決めて
+ 音声入力にかける

という流れになるかと思います。

前者は、係数を決める計算にkneeを含めて一気に計算してるんだと思います。ぼくはそうしてみました。
後者は、まずkneeを含めずに(hard kneeということ)係数を計算し、その結果をkneeで平滑化してるようです。前出のFaustのコードでは```att/2.0```時間で平滑化しています。

```Java:compressors.lib
        kneesmooth( att )  = si.smooth( ba.tau2pole( att / 2.0 ) );
```