音频动态压缩算法曲线实现

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.声明所需要的参数,及绘制区域

    cpp 复制代码
    int   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

    cpp 复制代码
    setSize (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函数中放置

    cpp 复制代码
    auto 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函数中绘制

    cpp 复制代码
    const 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.重点算法实现函数

    绘制动态压缩曲线按公式实现的函数

    cpp 复制代码
    float 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

相关推荐
哦***712 天前
华为 FreeArc耳机不弹窗?
华为·音频
FF-Studio15 天前
【DSP笔记 · 第7章】信号处理的“整形”大师:FIR滤波器与线性相位的奥秘
笔记·自动化·音视频·音频·信号处理
FF-Studio18 天前
【DSP笔记 · 第5章】数字滤波器的蓝图:从数学公式到硬件实现的艺术
笔记·fpga开发·自动化·音视频·音频·信号处理
riveting21 天前
明远智睿SD2351核心板:边缘计算时代的工业级核心引擎深度解析
人工智能·功能测试·音频·智能家居·边缘计算·智能硬件
哦***71 个月前
华为FreeArc能和其他华为产品共用充电线吗?
华为·音频
声光界1 个月前
小米MUJIA智能音频眼镜来袭
音频·声学·眼镜·声学技术
Panesle2 个月前
ACE-Step:扩散自编码文生音乐基座模型快速了解
大模型·transformer·音频·扩散模型·文本生成音乐
unbeliverpool2 个月前
Android audio系统六 AudioEffect音效加载
android·音频
riveting2 个月前
明远智睿2351开发板四核1.4G Linux处理器:驱动创新的引擎
linux·运维·服务器·人工智能·功能测试·音频·智能硬件