---
# System prepended metadata

title: 'JUCEでエフェクターを作って遊ぼう:03'

---

# JUCEでエフェクターを作って遊ぼう:03

こちらの記事は デジクリ Advent Calender 2022 18日目の記事です。デジクリとは何ぞや、という方は[こちらのページ](https://digicre.net)をご覧ください。

## はじめに

こんにちは。また1週間ぶりのbayashiです。今回も[前回](https://hackmd.io/@bayashi/HyGN9tZdo)に引き続いて試作品エフェクトの話 ~~と、後半では実用的にプラグインとして使っていくための技術的な部分の話~~ をします。

※ 後半に予定していた話は、数日後の記事として書く運びとなりました。

## やっぱり音を広くしたい

前回、リサージュメーターの4つの領域を分けて各々音量を変えるプラグインを作ってみました。そもそも作った動機は「一般的な方法と違ったやり方でステレオ感を変化させたり広くしてみたい」というものでした。

一般的な方法というのは、ここではL/Rチャンネルの信号をMid/Sideという2つのチャンネルに分解して、Sideの音量を変化させるものを指します。
どういうことかというと、下の図のように、リサージュメーター上での振幅を次のような2軸の座標として捉え直すことで「左右で共通する成分(Mid)」と「左右で打ち消し合う成分(Side)」に分解します。

![](https://i.imgur.com/K34YmdH.jpg)

![](https://i.imgur.com/bstFm1n.png)

ちなみに、図形として見たとき正確にはMid/Sideチャンネルの振幅の最大値は $\sqrt{2}$ となるはずですが、振幅が $1$ に収まってくれると色々と嬉しいのでそうなるように変換する場合が多いです。

これも細かな議論は尽きませんが、おおむね「左右に違う成分が含まれているほど音に広がりを感じる」と考えることが出来るため、Sideの音量を大きくすることで音に広がり感を付加することができると考えられています。[^1]

しかし、単にSideの音量を大きくするだけの操作にはいくつかの問題が伴います。
まずSideとは「左右で打ち消し合う成分」なので、Sideを大きく増幅した音をステレオスピーカーで再生したりモノラル[^2]で再生されたりすると音の聞こえ方が大きく変わってしまう場合があります。

## 試しにSideだけ加工してみる

MidとSideについて説明するため、双方にそれぞれ異なるディストーションを適用するプラグインを作ってみます。今回はクリップディストーションではなく、入力 $x (-1 \leq x \leq 1)$ に対して出力 $y$ が $$\left\{ \begin{array}{1} y = \sqrt{|x|} \  (0 \leq x \leq 1) \\ y = -\sqrt{|x|} \  (-1 \leq x < 0) \end{array} \right.$$ となるように歪ませてみることとします。つまり、こういう関係です。

![](https://i.imgur.com/zcnYY2y.png)

この入出力関係については関数として実装することにします。`PluginProcessor.h`に関数のプロトタイプ宣言を書き、

```
// 前略

private:

// 中略

    double distortionCurve(double);
```

`PluginProcessor.cpp`に次のようなコードを書きます。

```
// "SpaceDistortion"は今回のプラグイン名です

double SpaceDistortionAudioProcessor::distortionCurve(double x)
{
    int sign = 0;
    if (x < 0) sign = -1;
    else sign = 1;
    x = abs(x);
    return (double)sign * sqrt(x);
}
```

さて、この関数から返った結果をMidとSideそれぞれに加えたいですが、Midの信号 $M$ とSideの信号 $S$ は次の式で左チャンネルの信号 $L$ と右チャンネルの信号 $R$ から変換します。[^3] $$M = \frac{R + L}{2}$$  $$S = \frac{R - L}{2}$$

戻す際は、この式を $L$ と $R$ について解くことで次のように変換します。

$$L = M - S$$ $$R = M + S$$

では、これを元信号と一定の割合でブレンドするような処理と組み合わせて、`processBlock()`はこんな感じになります。

```
// midParameter  : Midチャンネル  のディストーション信号比率、0.0~1.0
// sideParameter : Sideチャンネル のディストーション信号比率、0.0~1.0

    if (totalNumInputChannels == 2)
    {
        auto* channelData_L = buffer.getWritePointer (0);
        auto* channelData_R = buffer.getWritePointer (1);
        
        for (int sample = 0; sample < buffer.getNumSamples(); sample++)
        {
            double value_M = (channelData_L[sample] + channelData_R[sample]) / 2.0;
            double value_S = (channelData_R[sample] - channelData_L[sample]) / 2.0;
            
            double distorted_M = midParameter * distortionCurve(value_M) + (1.0 - midParameter) * value_M;
            double distorted_S = sideParameter * distortionCurve(value_S) + (1.0 - sideParameter) * value_S;
            
            channelData_L[sample] = distorted_M - distorted_S;
            channelData_R[sample] = distorted_M + distorted_S;
        }
    }
```

出来たものを動かしてみましょう。再生設定によってはSideを歪ませたとき正しく再生されないかもしれません。

<iframe width="560" height="315" src="https://www.youtube.com/embed/P1JerRFdyyI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

最後、Ozone Imagerの操作で音をモノラルにしていますが、そうするとSideのみ歪ませた場合に　**歪みが消えていく** のが分かるかと思います。

これが「Sideのみを操作する」ということの意味です。Sideにだけ加えた変更というのは、その後の加工や再生環境によって大きく変質してしまう場合があるのです。


## Midも巻き込んだ空間イメージ加工

では、Sideだけを操作する方法とは別のやり方で空間イメージの加工、すなわちリサージュメーターの横方向の分布を広げることを考えてみましょう。例えば、今までのように楕円形の分布を左右に引き伸ばすのではなく、L軸R軸方向に分布を張り出させるようにするのはどうでしょう。こうすることで空間イメージを広げつつ、その影響がSide成分だけで完結しないように出来る気がします。

![](https://i.imgur.com/8fikFEA.png)


ちょっとやってみましょう。

L軸R軸方向に張り出すということは、$|L|$ が0に近い時に $R$ を増幅し、$|R|$ が0に近い時に $L$ を増幅する、ということになります。

0に近いときに大きくなり、絶対値が大きくなるほど小さくなるといえば…正規分布っぽい形の変化がちょうど良さそうです。あの形のグラフは、$1$ より大きな定数に、変数 $-x^2$ をべき乗することで得られます。ここではこの関係を定数 $\alpha (\alpha > 1)$ を使ってこのように表すことにします。$$y = \alpha^{-x^2}$$

例えば $\alpha = 2$ のとき、グラフは次のように書けます。

![](https://i.imgur.com/SKPbSWk.png)


今回はこれを「増幅率」という形にしたいので、増幅後の信号 $L', R'$ はこんな風に定義してみます。$$L' = (1 + \beta \times \alpha^{-R^2}) L \\ R' = (1 + \beta \times \alpha^{-L^2}) R$$

ここで $\beta$ は「どのくらい増幅するか」を決めるパラメータです。$\alpha$ が増幅する範囲の狭さ、$\beta$ が増幅する強さを決めます。

これを`processBlock()`に実装すると次のようになります。

```
// strengthParameter : 上式の beta に該当
// rangeParameter    : 上式の alpha に該当

    if (totalNumInputChannels == 2)
    {
        auto* channelData_L = buffer.getWritePointer (0);
        auto* channelData_R = buffer.getWritePointer (1);
        
        for (int sample = 0; sample < buffer.getNumSamples(); sample++)
        {
            double processed_L = (1.0 + strengthParameter * pow(rangeParameter, -(channelData_R[sample] * channelData_R[sample]))) * channelData_L[sample];
            double processed_R = (1.0 + strengthParameter * pow(rangeParameter, -(channelData_L[sample] * channelData_L[sample]))) * channelData_R[sample];
            
            channelData_L[sample] = processed_L;
            channelData_R[sample] = processed_R;
        }
    }
```

動作の様子がこちらです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/2YstqrrfAIQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

この処理も非線形なので強く増幅すると歪みが発生しますが、今回はディストーションというよりもサチュレーションに近い歪み方になりました。隠し味的に繊細な雰囲気の調整に使ったり、音作りとして飽和感を演出したいときに使えそうな気がします。

将来的には、似たようなアプローチでコンプレッサー的な挙動にして、自分好みの音の広がり方をするリミッタープラグインを作ってみたいな、とも思っていたりします。


## まとめ

今回は(結果的に)空間の広がり感を強調しながら音を歪ませるプラグインを作りました。私個人的には実際に制作の中で使うイメージを想像することが出来るプラグインになりましたが、おそらく(これまで作ったものを含めて)「自分が欲しいのはこんな音じゃない！」と感じた人も多いのではないかと思います。

**ならば是非！この機にあなたの手で作ってみませんか。**
きっとあなたの欲しい音を探す新たな旅が始まるでしょう。

## 次回予告

おそらく[次回](https://hackmd.io/@bayashi/Byd1Klhdo)は木曜日に、今回書き切れなかった、DAW上でプラグインとして実用的に使うための雑多な話題を書く予定です。


[^1]:これは初回の記事で触れた「リサージュメーターの分布の横幅が音の広がり感の強さを表すと考えていい」という部分にも符合します。

[^2]:ステレオ音源をモノラル再生する場合、大抵はLとRを足して2で割っているので、実質的にMidと同じものになることが多いです。

[^3]:L/R \- Mid/Side間の変換式にはいくつか定義の仕方があります。今回はリサージュメーターで見たときにMidの上方向、Sideの右方向がそれぞれ正の向きとなるようにしました。また、 $2$ ではなく $\sqrt{2}$ で割る流派や、そもそも割らない流派もありますが、ここでは $M$ と $S$ をそれぞれ $[-1, 1]$ の範囲で値を取るようにしたかったため $2$ で割っています。