# 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`選項打勾。 ![](https://i.imgur.com/OId3jdH.png) ### Juce介面 ![](https://i.imgur.com/CdaU6d6.jpg) * Selected exporter為選擇要使用的coding編輯器 ### 新增檔案 * 新增header、cpp - CircularBuffer.cpp - CircularBuffer.h - Compressor.cpp - Compressor.h ![](https://i.imgur.com/BGKCAtD.png) ## 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 ![](https://i.imgur.com/SBXZkNC.png) * 用來讓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 ![](https://i.imgur.com/9oihQBA.png) 選擇開發的專案進行debug ## GUI ![](https://i.imgur.com/GQx1J01.jpg) * 上圖為簡易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)