JUCE学习笔记05——音频输出基础(白噪)

知识点:

1、AudioAppComponent类基础
2、音频应用程序的生命周期

目标:

了解AudioAppComponent类的三个方法与生命周期、生成白噪音、使用Slider控制音量

内容:

一、理解音频应用程序的生命周期

1、启动应用程序时AudioAppComponent的构造函数中设置输入输出通道数,触发音频处理的启动,并会调用prepareToPlay()方法,这里通过获取系统默认采样率和缓存区块大小信息实现该方法,并用组件的paint()方法显示在窗口上。

MainComponent::MainComponent()
{
    setSize (800, 600);
    if (RuntimePermissions::isRequired (RuntimePermissions::recordAudio)
        && ! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
    {
        RuntimePermissions::request (RuntimePermissions::recordAudio,
                                     [&] (bool granted) { if (granted)  setAudioChannels (2, 2); });
    }
    else
    {
        setAudioChannels (0, 2);//注册输入输出通道
    }
}
private:

	String msg;  //调用信息
	String blockSize;  //缓存区块大小
	String sysSampleRate;  //默认系统采样率
void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
	msg = "Call: prepareToPlay()";
	blockSize << samplesPerBlockExpected;
	sysSampleRate << sampleRate;
}
void MainComponent::paint (Graphics& g)
{
	g.setFont(30);  //字体大小
	g.setColour(Colours::red);  //字体颜色
	g.drawText(msg, 10,10,300,30,Justification::left,false);  //绘制文本
	g.drawText("Buffer: "+ blockSize, 10, 40, 300, 30, Justification::left, false);
	g.drawText("SampleRate: "+ sysSampleRate, 10, 70, 300, 30, Justification::left, false);
}

运行效果:

audio_processing怎么使用_ide

2、关闭应用程序时构造函数中的shutdownAudio()会调用releaseResources()方法释放音频源的缓存、关闭音频处理。

MainComponent::~MainComponent()
{
    //释放缓存、关闭音频设备
    shutdownAudio();
}

3、音频应用程序运行中依据音频系统的请求回调getNextAudioBlock ()方法获取播放所需的音频数据

void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
//生成播放音频数据的代码
}
二、实现getNextAudioBlock方法生成白噪音

1、白噪音(百度):白噪声是指在较宽的频率范围内,各等带宽的频带所含的噪声能量
相等的噪声。是一种功率谱密度为常数的随机信号。
简单理解:白噪音是通过在音频流中填充随机数所产生的音频信号

2、生成白噪音的方法

void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
    bufferToFill.clearActiveBufferRegion();  //JUCE自动生成的清理缓存方法,用于避免启动音频处理时爆音
	Random random; //随机数对象
	for (int channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)//遍历需填充音频采样数据的通道
	{
		float* buffer = bufferToFill.buffer->getWritePointer(channel, bufferToFill.startSample);//缓存块的起始点
		for (int sampleIndex = 0; sampleIndex < bufferToFill.numSamples; ++sampleIndex) //遍历采样点
		{
			buffer[sampleIndex] = (random.nextFloat()*2.0 - 1.0)*0.1;  //写入随机采样值
		}
	}
}
三、使用Slider控制音量

1、主组件继承Slider::Listener

class MainComponent   : public AudioAppComponent,
                         public Slider::Listener

2、创建Slider、设置基本属性、添加至主组件、设置位置

MainComponent::MainComponent()
{
	addAndMakeVisible(volumeSlider); 
	volumeSlider.setRange(0, 1, 0.01);
     //......其他代码
}
void MainComponent::resized()
{
	volumeSlider.setBounds(100, 100, 200, 200);
}

3、创建标签添加至Slider

MainComponent::MainComponent()
{
	volumeLabel.setText("Volume", NotificationType::dontSendNotification);
	volumeLabel.attachToComponent(&volumeSlider,true);
    //......其他代码
}

运行效果:

audio_processing怎么使用_C++_02

4、创建音量变量作为随机数的乘数

float volume;

5、实现Listener的sliderValueChanged(Slider * slider)方法,Slider的值与音量绑定

void MainComponent::sliderValueChanged(Slider * slider)
{
	if (slider == &volumeSlider)
		volume=volumeSlider.getValue();
}

6、添加Listener至Slider,初始化音量

MainComponent::MainComponent()
{
	volumeSlider.addListener(this); //Slider上添加监听
	volume = 0;  //音量默认值
}

7、将音量乘数应用到音频数据流中

void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
    bufferToFill.clearActiveBufferRegion();  //JUCE自动生成的清理缓存方法,用于避免启动音频处理时爆音
	Random random;
	
	for (int channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)//遍历需填充音频采样数据的通道
	{
		auto* buffer = bufferToFill.buffer->getWritePointer(channel, bufferToFill.startSample);//缓存块的起始点
		for (int sampleIndex = 0; sampleIndex < bufferToFill.numSamples; ++sampleIndex) //遍历采样点
		{
			buffer[sampleIndex] = (random.nextFloat()*2.0 - 1.0)*volume;// 写入随机采样值
		}
	}
}

完整代码:

//MainComponent.h
#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

//==============================================================================
/*
    This component lives inside our window, and this is where you should put all
    your controls and content.
*/
class MainComponent   : public AudioAppComponent,
						public Slider::Listener
{
public:
    //==============================================================================
    MainComponent();
    ~MainComponent();

    //=============AudioSource类方法================================================
    void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
    void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override;
    void releaseResources() override;

    //=============Component类方法==================================================
    void paint (Graphics& g) override;
    void resized() override;

	//=============Slider::Listener类方法===========================================
	void sliderValueChanged(Slider* slider) override;

private:

	String msg;  //调用信息
	String blockSize;  //缓存区块大小
	String sysSampleRate;  //默认系统采样率

	Slider volumeSlider; //音量Slider对象
	Label volumeLabel;
	float volume;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
//MainComponent.cpp
#include "MainComponent.h"

//==============================================================================
MainComponent::MainComponent()
{
    setSize (800, 600);
	addAndMakeVisible(volumeSlider);
	volumeSlider.setRange(0, 1, 0.01f);
	volumeLabel.setText("Volume", NotificationType::dontSendNotification);
	volumeLabel.attachToComponent(&volumeSlider,true);
	volumeSlider.addListener(this);
	volume = 0;
    // Some platforms require permissions to open input channels so request that here
    if (RuntimePermissions::isRequired (RuntimePermissions::recordAudio)
        && ! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
    {
        RuntimePermissions::request (RuntimePermissions::recordAudio,
                                     [&] (bool granted) { if (granted)  setAudioChannels (2, 2); });
    }
    else
    {
        // 注册所需的输入和输出数,然后触发音频处理并调用prepareToPlay()方法
        setAudioChannels (0, 2);
    }
}

MainComponent::~MainComponent()
{
    //释放缓存、关闭音频设备
    shutdownAudio();
}

//==============================================================================
void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
	msg = "Call: prepareToPlay()";
	blockSize << samplesPerBlockExpected;
	sysSampleRate << sampleRate;
}

void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
    bufferToFill.clearActiveBufferRegion();  //JUCE自动生成的清理缓存方法,用于避免启动音频处理时爆音
	Random random;
	
	for (int channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)//遍历需填充音频采样数据的通道
	{
		auto* buffer = bufferToFill.buffer->getWritePointer(channel, bufferToFill.startSample);//缓存块的起始点
		for (int sampleIndex = 0; sampleIndex < bufferToFill.numSamples; ++sampleIndex) //遍历采样点
		{
			buffer[sampleIndex] = (random.nextFloat()*2.0 - 1.0)*volume;// 写入随机采样值
		}
	}
}

void MainComponent::releaseResources()
{

}

//==============================================================================
void MainComponent::paint (Graphics& g)
{
	g.setFont(30);
	g.setColour(Colours::red);
	g.drawText(msg, 10,10,300,30,Justification::left,false);
	g.drawText("BlockSize: "+ blockSize, 10, 40, 300, 30, Justification::left, false);
	g.drawText("SampleRate: "+ sysSampleRate, 10, 70, 300, 30, Justification::left, false);

	volumeSlider.setTextBoxStyle(Slider::TextBoxLeft,true,50,20);
}

void MainComponent::resized()
{
	volumeSlider.setBounds(100, 100, 200, 200);
}

void MainComponent::sliderValueChanged(Slider * slider)
{
	if (slider == &volumeSlider)
		volume=volumeSlider.getValue();

}