Juce实现动态压缩曲线绘制
动态范围压缩算法(Dynamic Range Compression,DRC)是将音频信号的动态范围映射到一个较小的范围内的过程,即降低较高的峰值的信号电平,而不处理较安静的部分。DRC被广泛用于音频录制、制作工作、降噪、广播和现场表演等应用中。
1.压缩算法的参数
-
**Threshold:**定义了开始压缩的音量。任何超过阈值的信号都将被压缩。例如当 Threshold = -10db,当信号音量超过 -10db 时,它将被压缩。
-
**Ratio:**控制超过 Threshold 的信号的压缩比率。例如当 Threshold = -10,input = -5,此时信号超过 Threshold 有 5db,它将被压缩,那么压缩多少呢?这就由 Ratio 控制,当 Ratio = 5 时,信号增量从原来的 5db 被抑制为 1db,当 Ratio = 2 时,则抑制为 2.5db,以此类推。
-
Attack Time: Attack Time 和 Release Time 在一定程度上控制 Compressor "灵敏度"。 Attack Time 定义了一旦信号超过 Threshold,Compressor 将增益降低到期望水平所需要的时间。
-
Release Time : 定义了一旦信号低于 Threshold,将增益恢复至正常水平需要的时间。
-
**Make-up Gain:**Compressor 降低信号的增益,因此可以施加一个额外的增益使得输入信号与输出信号的响度水平相当。
-
**Knee Width:**它控制了压缩曲线的特性(如下图),曲线是尖锐的拐角,还是想膝盖一样有弧度的曲线。

2.计算公式
Gain Computer:
Gain Computer 根据输入信号的电平(音量)来计算得到需要的增益。这个阶段涉及到了 Threshold(T)、Ratio(R)、Knee Width(W) 三个参数。一旦输入信号电平超过 T,那么它会根据 R 进行衰减,计算公式如下:
为了让 Compressor 有更加平滑的变化曲线,我们增加了 Knee Width(参考 Knee Width 参数说明图),这中模式我们称为 "Soft Knee",其计算公式为:
3.juce代码实现动态压缩曲线
-
1.声明所需要的参数,及绘制区域
cppint attackTime; int releasTime; int makeUpGain; int threshold; float knee; float ratio; Slider LmtRatioSlider; Slider LmtThresholdSlider; Slider LmtKneeSlider; Slider LmtAttackTimeSlider; Slider LmtReleaseTimeSlider; Slider LmtMakeUpGainSlider; Label LabelRatioSlider; Label LabelThresholdSlider; Label LabelKneeSlider; Label LabelAttackTimeSlider; Label LabelReleaseTimeSlider; Label LabelMakeUpGainSlider;
-
2.在构造函数中创建并MakeVisible
cppsetSize (600, 400); addAndMakeVisible(LmtRatioSlider); LmtRatioSlider.setRange(30, 270, 1); LmtRatioSlider.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); LmtRatioSlider.setTextBoxStyle(Slider::TextBoxBelow, false, 40, 15); LmtRatioSlider.setValue(ratio); LmtRatioSlider.setRotaryParameters({ 3.925f, 8.635f, true }); LmtRatioSlider.setColour(Slider::rotarySliderOutlineColourId, Colour(0xFF00CAFF)); LmtRatioSlider.setColour(Slider::rotarySliderFillColourId, Colour(16, 153, 212)); LmtRatioSlider.addListener(this); addAndMakeVisible(LmtKneeSlider); LmtKneeSlider.setRange(0, 1, 0.01); LmtKneeSlider.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); LmtKneeSlider.setTextBoxStyle(Slider::TextBoxBelow, false, 40, 15); LmtKneeSlider.setValue(knee); LmtKneeSlider.setRotaryParameters({ 3.925f, 8.635f, true }); LmtKneeSlider.setColour(Slider::rotarySliderOutlineColourId, Colour(0xFF00CAFF)); LmtKneeSlider.setColour(Slider::rotarySliderFillColourId, Colour(16, 153, 212)); LmtKneeSlider.addListener(this); addAndMakeVisible(LmtThresholdSlider); LmtThresholdSlider.setRange(-60, 0, 1.0); LmtThresholdSlider.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); LmtThresholdSlider.setTextBoxStyle(Slider::TextBoxBelow, true, 40, 15); LmtThresholdSlider.setValue(threshold); LmtThresholdSlider.setRotaryParameters({ 3.925f, 8.635f, true }); LmtThresholdSlider.setColour(Slider::rotarySliderOutlineColourId, Colour(0xFF00CAFF)); LmtThresholdSlider.setColour(Slider::rotarySliderFillColourId, Colour(16, 153, 212)); LmtThresholdSlider.addListener(this); addAndMakeVisible(LmtAttackTimeSlider); LmtAttackTimeSlider.setRange(30, 270, 0.1); LmtAttackTimeSlider.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); LmtAttackTimeSlider.setTextBoxStyle(Slider::TextBoxBelow, false, 40, 15); LmtAttackTimeSlider.setValue(attackTime); LmtAttackTimeSlider.setRotaryParameters({ 3.925f, 8.635f, true }); LmtAttackTimeSlider.setColour(Slider::rotarySliderOutlineColourId, Colour(0xFF00CAFF)); LmtAttackTimeSlider.setColour(Slider::rotarySliderFillColourId, Colour(16, 153, 212)); LmtAttackTimeSlider.addListener(this); addAndMakeVisible(LmtReleaseTimeSlider); LmtReleaseTimeSlider.setRange(30, 270, 0.1); LmtReleaseTimeSlider.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); LmtReleaseTimeSlider.setTextBoxStyle(Slider::TextBoxBelow, false, 40, 15); LmtReleaseTimeSlider.setValue(releasTime); LmtReleaseTimeSlider.setRotaryParameters({ 3.925f, 8.635f, true }); LmtReleaseTimeSlider.setColour(Slider::rotarySliderOutlineColourId, Colour(0xFF00CAFF)); LmtReleaseTimeSlider.setColour(Slider::rotarySliderFillColourId, Colour(16, 153, 212)); LmtReleaseTimeSlider.addListener(this); addAndMakeVisible(LmtMakeUpGainSlider); LmtMakeUpGainSlider.setRange(0, 24, 1); LmtMakeUpGainSlider.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); LmtMakeUpGainSlider.setTextBoxStyle(Slider::TextBoxBelow, false, 40, 15); LmtMakeUpGainSlider.setValue(makeUpGain); LmtMakeUpGainSlider.setColour(Slider::rotarySliderOutlineColourId, Colour(0xFF00CAFF)); LmtMakeUpGainSlider.setColour(Slider::rotarySliderFillColourId, Colour(16, 153, 212)); LmtMakeUpGainSlider.addListener(this); addAndMakeVisible(&LabelRatioSlider); LabelRatioSlider.setColour(Label::textColourId, Colour(195, 203, 206)); LabelRatioSlider.setText("Ratio", dontSendNotification); addAndMakeVisible(&LabelThresholdSlider); LabelThresholdSlider.setColour(Label::textColourId, Colour(195, 203, 206)); LabelThresholdSlider.setText("Threshold", dontSendNotification); addAndMakeVisible(&LabelKneeSlider); LabelKneeSlider.setColour(Label::textColourId, Colour(195, 203, 206)); LabelKneeSlider.setText("Knee",dontSendNotification); addAndMakeVisible(&LabelAttackTimeSlider); LabelAttackTimeSlider.setColour(Label::textColourId, Colour(195, 203, 206)); LabelAttackTimeSlider.setText("Attack",dontSendNotification); addAndMakeVisible(&LabelReleaseTimeSlider); LabelReleaseTimeSlider.setColour(Label::textColourId, Colour(195, 203, 206)); LabelReleaseTimeSlider.setText("Release", dontSendNotification); addAndMakeVisible(&LabelMakeUpGainSlider); LabelMakeUpGainSlider.setColour(Label::textColourId, Colour(195, 203, 206)); LabelMakeUpGainSlider.setText("Makeup", dontSendNotification); area.setBounds(40, 30, 111, 110);
-
3.在Resize函数中放置
cppauto area = getLocalBounds(); auto slider_area = area.removeFromBottom(100); slider_area.removeFromBottom(10); int width = (slider_area.getWidth() - 80) / 6; slider_area.removeFromLeft(10); LmtAttackTimeSlider.setBounds(slider_area.removeFromLeft(width)/*.withTrimmedTop(10)*/); slider_area.removeFromLeft(10); LmtReleaseTimeSlider.setBounds(slider_area.removeFromLeft(width)/*.withTrimmedTop(10)*/); slider_area.removeFromLeft(10); LmtKneeSlider.setBounds(slider_area.removeFromLeft(width)/*.withTrimmedTop(10)*/); slider_area.removeFromLeft(10); LmtRatioSlider.setBounds(slider_area.removeFromLeft(width)/*.withTrimmedTop(10)*/); slider_area.removeFromLeft(10); LmtThresholdSlider.setBounds(slider_area.removeFromLeft(width)/*.withTrimmedTop(10)*/); slider_area.removeFromLeft(10); LmtMakeUpGainSlider.setBounds(slider_area.removeFromLeft(width)/*.withTrimmedTop(10)*/); area.removeFromBottom(10); auto label_area = area.removeFromBottom(20); label_area.removeFromLeft(10); LabelAttackTimeSlider.setBounds(label_area.removeFromLeft(width).withSizeKeepingCentre(width,20)); label_area.removeFromLeft(10); LabelReleaseTimeSlider.setBounds(label_area.removeFromLeft(width).withSizeKeepingCentre(width, 20)); label_area.removeFromLeft(10); LabelKneeSlider.setBounds(label_area.removeFromLeft(width).withSizeKeepingCentre(width, 20)); label_area.removeFromLeft(10); LabelRatioSlider.setBounds(label_area.removeFromLeft(width).withSizeKeepingCentre(width, 20)); label_area.removeFromLeft(10); LabelThresholdSlider.setBounds(label_area.removeFromLeft(width).withSizeKeepingCentre(width, 20)); label_area.removeFromLeft(10); LabelMakeUpGainSlider.setBounds(label_area.removeFromLeft(width).withSizeKeepingCentre(width, 20));
-
4.在paint函数中绘制
cppconst float cornerSize = 4.0f; g.setColour(Colour(24, 25, 29)); g.fillRect(area.getX(), area.getY(), area.getWidth(), area.getHeight()); float mGain[601]; for (int i = 0; i < 601; i++) { mGain[i] = drc_plot_calc(i * 0.1f - 60.0f, threshold, knee * 100, ratio, makeUpGain); } g.setColour(Colours::orange); int scopeSize = 600; float width = area.getWidth(); float height = area.getHeight(); // 绘制波形 for (int i = 1; i < scopeSize; ++i) { if (mGain[i] > 0) { break; } g.drawLine({ (float)jmap(i - 1, 0, scopeSize, 41,(int)width + 41), jmap((float)jlimit(-60.0f,0.0f,mGain[i - 1]), -60.0f, 0.0f, height + 30.0f, 0.0f + 30.0f), (float)jmap(i, 0, scopeSize, 41 , (int)width + 41), jmap((float)jlimit(-60.0f,0.0f,mGain[i]), -60.0f, 0.0f, height + 30.0f, 0.0f + 30.0f) }); } g.setColour(Colour(46, 141, 159)); g.setFont(10.0f);
4.重点算法实现函数
绘制动态压缩曲线按公式实现的函数
cppfloat MainComponent::drc_plot_calc(float in, float T, float W, float R, float gain) { float dGain = 0.0; if (0 == W) { if (in >= T) { dGain = (T + (in - T) / R); } else { dGain = in; } } else { if (in > (T + W / 2)) { dGain = (T + (in - T) / R); } else if (in < (T - W / 2)) { dGain = in; } else { dGain = in + (1 / R - 1) * (in - T + W / 2) * (in - T + W / 2) / (2 * W); } } dGain = dGain + gain; return dGain; }
5.绘制界面展示
通过调节slider参数,曲线根据slider滑动展示。
else if (in < (T - W / 2)) { dGain = in; } else { dGain = in + (1 / R - 1) * (in - T + W / 2) * (in - T + W / 2) / (2 * W); }
}
dGain = dGain + gain;
return dGain;
}
### 5.绘制界面展示 通过调节slider参数,曲线根据slider滑动展示。

完整代码地址:https://download.csdn.net/download/huangyifei_1111/91250409