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);
}
运行效果:
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);
//......其他代码
}
运行效果:
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();
}