# Compressor、Expander、Limiter
###### tags: `JUCE`
contributed by < [`reputation0809`](https://github.com/reputation0809/) >
[toc]
## Set up
### Juce環境設定
* 在`setting`中`Plugin formats、Plugin Characteristics`分別將`VST3、AU、Standalone`與`Plugin is a Synth、Plugin MIDI Input`選項打勾。

### Juce介面

* Selected exporter為選擇要使用的coding編輯器
### 新增檔案
* 新增header、cpp
- CircularBuffer.cpp
- CircularBuffer.h
- Compressor.cpp
- Compressor.h

## Circular Buffer
* Circular buffer為用來儲存聲音的資料
* Buffer實作
```header
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
class CircularBuffer {
public:
CircularBuffer();
CircularBuffer(int bufferSize, int delayLength);
float getData();
void setData(float data);
void nextSample();
private:
juce::AudioSampleBuffer buffer;
int writeIndex;
int readIndex;
int delayLength;
};
```
* `CircularBuffer()`用來初始buffer
* `getData()`用來取得buffer裡音訊的值
* `setData()`用來將音訊寫入buffer中
* `nextSample()`用來取得下個音訊的內容
## Compressor
* 在音量過大的時候,自動將音量變小聲,用來縮減聲音動態範圍
### 參數說明
* Threshold: 音量超過時開始作用
* Ratio: 將音量變小的幅度
* Attack: Compressor 反應速度
* Release: Compressor 回復速度
* Make up: 音量經過 Compressor 壓縮過後平均音量會變小,Make up 就是讓整體平均音量補償回來多少的設定值
* Knee: soft knee 會讓 compressor 慢慢的作用,而 hard knee 則是讓 compressor 直接作用
### Compressor實作
* 將參數丟入compressorSample()
```cpp=
float compressorSample(float data,float threshold,float ratio,float attack,float release,float knee,float makeup);
```
* compressorSample內容
```cpp=
rms=(1-tav)*rms+tav*std::pow(data, 2.0f);
float dbRMS=10*std::log10(rms);
```
以上為將輸入的訊號轉為dB
```cpp=
float slope=1-(1/ratio); //1
float dbGain=std::min(0.0f,(slope*(threshold-dbRMS))); //2
float newGain=std::pow(10,dbGain/20); //3
```
1. 將輸入的ratio轉為slope
2. (threshold – dbRMS)*slope並確保值會在threshold之下
3. 算出在linear scale下的值
```cpp=
float coeff;
if(newGain<gain) coeff=attack; //1
else coeff=release; //2
gain=(1-coeff)*gain+coeff*newGain; //3
```
1. 判斷newGain,小於current gain的話將coeff設為attack
2. 大於的話設為release
3. 將current gain根據new gain與coeff設定為新值
```cpp=
float compressedSample=gain*buffer.getData();
buffer.setData(data);
buffer.nextSample();
return compressedSample;
```
將Compressor完的值return回pluginProcessor並取得下個訊號
### Adding a Soft Knee

* 用來讓Compressor處理後的聲音聽起來較自然
#### Soft Knee實作
```cpp=
float Compressor::interpolatePoints(float* xPoints, float* yPoints, float detectedValue) {
float result = 0.0f;
int n = 2;
for (int i = 0; i < n; i++){
float term = 1.0f;
for (int j = 0; j < n; j++{
if (j != i) {
term *= (detectedValue - xPoints[j]) / (xPoints[i] - xPoints[j]);
}
}
result += term * yPoints[i];
}
return result;
}
```
首先新增一個function用來處理slope,將原本的slope替換掉
```cpp=
if (knee > 0 && dbRMS > (threshold - knee / 2.0) && dbRMS < (threshold + knee / 2.0)) {
float kneeBottom = threshold - knee / 2.0, kneeTop = threshold + knee / 2.0;
float xPoints[2], yPoints[2];
xPoints[0] = kneeBottom;
xPoints[1] = kneeTop;
xPoints[1] = std::fmin(0.0f, kneeTop);
yPoints[0] = 0.0f;
yPoints[1] = slope;
slope = interpolatePoints(&xPoints[0], &yPoints[0], threshold);
threshold = kneeBottom;
}
```
再來開始實做soft knee
* 首先確認current是否在knee區域裡,然後用兩個array-xPoints、yPoints,xPoints用來存在knee區域裡bottom、top的dB value;yPoints用來存原本的slope
* 再來利用上面的function得到新的slope與threshold
* 最後再用新的值進行compressor,完成soft knee
## Expander
* 與 compressor 相反,用來擴展聲音動態範圍
### 參數說明
* Threshold: 音量小於臨界值時開始作用
* Ratio: 將音量變**大**的幅度
* Attack: Expander 反應速度
* Release: Expander 回復速度
* Make up: 音量經過 Expander 擴展過後平均音量會變大,Make up 就是讓整體平均音量補償回來多少的設定值
* Knee: soft knee 會讓 Expander 慢慢的作用,而 hard knee 則是讓 Expander 直接作用
### Expander實作
* 實作部分與Compressor大同小異
* 只須改一個小地方
```cpp=
float slope=1-(1/ratio);
float dbGain=std::max(0.0f,(slope*(threshold-dbRMS))); //2
float newGain=std::pow(10,dbGain/20);
```
在2的這行原本為取min改為取max,將聲音由縮減改為擴展
## Limiter
* Ratio 為無限大的 compressor,在音量過大時將聲音自動截斷
### 參數說明
* Threshold: 音量超過時開始作用
* Attack: Limiter 反應速度
* Release: Limiter 回復速度
### Limiter實作
```cpp=
float amplitude = abs(data); //1
if(amplitude > xpeak) coeff = attack; //2
else coeff = release;
xpeak = (1-coeff)*xpeak+coeff*amplitude; //3
```
1. 將current data取絕對值得出振福
2. 如果振幅大於目前的peak,將coeff設為attack,反之設為release
3. 將current peak根據last peak、coefficient、amplitude設為新值
```cpp=
float filter = fmin(1.0f, threshold/xpeak); //1
if(gain > filter) coeff = attack; //2
else coeff = release;
gain = (1-coeff)*gain+coeff*filter; //3
```
1. 與Compressor作法一樣,根據Threshold,如果peak大於threshold,開始做limiter
2. 如果current gain大於filter,將coeff設為attack,反之設為release
3. 將current gain根據last gain、coeficient、filter設為新值
```cpp=
float limitedSample =gain*buffer.getData();
buffer.setData(data);
buffer.nextSample();
return limitedSample;
```
最後將limiter完將值傳回PluginProcessor並取得下個值
## Parameter Setting
* 在PluginProcessor中根據[Matlab compressor](https://ww2.mathworks.cn/help/audio/ref/compressor-system-object.html)設定參數
```cpp=
std::make_unique<juce::AudioParameterFloat>(
"threshold",
"Threshold",
juce::NormalisableRange<float>(-60.0f,20.0f,0.01f),10.0f),
std::make_unique<juce::AudioParameterFloat>(
"ratio",
"Ratio",
juce::NormalisableRange<float>(1.0f,20.0f,0.01f),2.0f),
std::make_unique<juce::AudioParameterFloat>(
"knee",
"KneeWidth",
juce::NormalisableRange<float>(0.0f,24.0f,0.01f),0.0f),
std::make_unique<juce::AudioParameterFloat>(
"attack",
"Attack",
juce::NormalisableRange<float>(0.01f,500.0f,0.01f),100.0f),
std::make_unique<juce::AudioParameterFloat>(
"release",
"Release",
juce::NormalisableRange<float>(0.01f,2000.0f,0.01f),500.0f),
std::make_unique<juce::AudioParameterFloat>(
"make_up",
"Make up",
juce::NormalisableRange<float>(-10.0f,24.0f,0.01f),0.0f)
```
## Coding with PluginProcessor
* 在prepareToPlay中取得使用者輸入的參數
```cpp=
threshParam=state.getRawParameterValue("threshold");
slopeParam=state.getRawParameterValue("ratio");
kneeParam=state.getRawParameterValue("knee");
attackParam=state.getRawParameterValue("attack");
releaseParam=state.getRawParameterValue("release");
makeupParam=state.getRawParameterValue("make_up");
```
* 在processBlock中對聲音進行處理
```cpp=
if((int)*mode_state.getRawParameterValue("modeID")==1)
else if ((int)*mode_state.getRawParameterValue("modeID")==2)
else
```
首先判斷使用者選擇的mode為何
(1為Compressor、2為Expander , else為limiter)
```cpp=
// Compressor
Compressor* comp=&allCompressors.getReference(channel);
data[i]=comp->compressorSample(data[i], *threshParam, *slopeParam, at, rt, *releaseParam,*makeupParam);
// Expander
Compressor* comp=&allCompressors.getReference(channel);
data[i]=comp->expanderSample(data[i], *threshParam, *slopeParam, at, rt, *releaseParam,*makeupParam);
// Limiter
Compressor* comp=&allCompressors.getReference(channel);
data[i]=comp->limiterSample(data[i], *threshParam, *slopeParam, at, rt, *releaseParam,*makeupParam);
```
再來根據不同的Mode對音訊做不同的處理
## Code
* You can find our project [here](https://github.com/reputation0809/Juce_project).
* Feel free to use it or modify it.
## Debug
* 使用AudioPluginHost

選擇開發的專案進行debug
## GUI

* 上圖為簡易GUI,更精美的介面有些問題還在製作當中
* Mode為選擇要使用的音訊處理
- compressor
- expander
- limiter
## Challenge
* GUI介面有點難設計,牽一髮而動全身,目前還有一些bug還沒解決
* 老師有說加一個設定範圍的功能,但還是不太清楚老師想要的意思,所以只有先加參數但還沒實作
## Reference
* [好和弦的混音幼幼班 - 壓縮器(Compressor)篇](https://nicechord.com/post/how-to-compress/)
* [Dynamic Range Control](https://ww2.mathworks.cn/help/audio/ug/dynamic-range-control.html)
* [Matlab compressor](https://ww2.mathworks.cn/help/audio/ref/compressor-system-object.html)
* [How to Build a VST: Compressor](https://audioordeal.co.uk/how-to-build-a-vst-compressor/)
* [How to build a VST – Lesson 5: Limiter 1](https://audioordeal.co.uk/how-to-build-a-vst-lesson-4-limiter-1/)
* [Learn Modern C++ by Building an Audio Plugin (w/ JUCE Framework)](https://www.youtube.com/watch?v=i_Iq4_Kd7Rc&t=13855s)