JUCE学习笔记08——合成器(一)振荡器与包络发生器
知识点:
1、合成器类
2、扩展库
目标:
利用外部扩展的码实现合成器基本模块中的振荡器与包络发生器
内容:
一、创建合成器类:分别从SynthesiserSound和SynthesiserVoice类派生子类,并实现父类的纯虚函数
//SynthSound.h 决定合成器是否可以播放给定的音符号和midi通道号的声音,如果可以播放则返回true,简单理解为该midi音符是否可播放
class SynthSound : public SynthesiserSound
{
public:
bool appliesToNote(int /*midiNoteNumber*/)
{
return true;//决定传入的音符号是否可播放
}
bool appliesToChannel(int /*midiChannel*/)
{
return true;//决定该通道号的音符是否可播放
}
//...
};
//SynthVoice.h 表示用来播放SynthesiserSound的一个声部(触发声音的载体),简单理解为生成音频流的组件,每个Voice播放一个Sound,代表合成器的复音数
class SynthVoice : public SynthesiserVoice
{
public:
bool canPlaySound(SynthesiserSound* sound)
{
return dynamic_cast<SynthSound*>(sound) != nullptr;检测并动态转换传入的声音类型,转换成功返回true
}
void startNote(int midiNoteNumber, float velocity, SynthesiserSound* sound, int currentPitchWheelPosition)
{
frequency = MidiMessage::getMidiNoteInHertz(midiNoteNumber);//音符输入时获取输入的midi信息中的音符号转换为频率,用于设置振荡器的声音频率
}
void stopNote(float velocity, bool allowTailOff)
{
}
void pitchWheelMoved(int pitchWheelValue)
{
}
void controllerMoved(int controllerNumber, int newControllerValue)
{
}
void renderNextBlock(AudioBuffer<float> &outputBuffer, int starSample, int numSample)
{
//缓冲区生成音频缓数据,在处理器类的音频块处理processBlock()中调用
}
private:
double frequency;
//...
};
二、 音频处理器类中的主要方法
//构造函数
YhySynthDemoAudioProcessor::YhySynthDemoAudioProcessor()
//...预处理块
{
mySynth.clearVoices(); //先清理Voice对象
for (int i = 0; i < 5; ++i)
mySynth.addVoice(new SynthVoice()); //创建Voice(复音数)
mySynth.clearSounds(); //清空Sound
mySynth.addSound(new SynthSound());
}
//prepareToPlay方法在音频通道设置完成时调用,这里主要对采样率做设置,防止在变更设备或采样率时对输出音高的影响
void YhySynthDemoAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
ignoreUnused(samplesPerBlock);
auto lastSampleRate = sampleRate;
mySynth.setCurrentPlaybackSampleRate(lastSampleRate);
}
//缓存块处理
void YhySynthDemoAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
buffer.clear();
mySynth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());//由合成器Voice中的renderNextBlock函数生成
}
三、振荡器模块——引入Maximilian扩展(下载地址:https://github.com/micknoise/Maximilian),使用其提供的基本波形振荡器实现声音的生成
1.Juce工程的导入页设置头文件搜索路径
2.将扩展的源码文件/目录拖拽至JUCE的文件浏览器(可将没必要的文件删除,仅留用到的部分)
3.IDE中打开
4.引入maximillian头文件
//SynthVoice.h
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "SynthSound.h"
#include "../extras/Maximilian/maximilian.h"
5.创建maxi振荡器对象以及存放midi输入时的力度信息
maxiOsc myOsc1;
double level;
void startNote(int midiNoteNumber, float velocity, SynthesiserSound* sound, int currentPitchWheelPosition)
{
frequency = MidiMessage::getMidiNoteInHertz(midiNoteNumber);
level = velocity; //获取力度信息
}
6.生成缓冲区填充数据
void renderNextBlock(AudioBuffer<float> &outputBuffer, int startSample, int numSample)
{
for (auto sample=0;sample<numSample;++sample)//将maxiOsc对象生成的音频数据逐样本地填充各个通道的到缓冲区
{
auto myWave = myOsc1.square(frequency)*level;//获取的力度信息用于控制声音的大小(midi的力度响应)
for (auto channel = 0; channel < outputBuffer.getNumChannels(); ++channel)
{
outputBuffer.addSample(channel, startSample, myWave);
}
++startSample;
}
}
7.设置音符停止方法(音符输入结束),这里只是简单的处理音符结束时将音量设置为0,更多功能由Maximilian中的Envelope类实现
void stopNote(float velocity, bool allowTailOff)
{
level = 0;
}
四、包络发生器模块(Envelope)—— 控制音符播放过程中的音量
1.合成器中创建包络发生器对象(Maximilian中提供该类)
maxiEnv myEnv1;
2.设置包络的触发(attack)与释放(release)的节点:通过maxiEnv类的成员变量trigger设置,当trigger=1时触发attack,不为1时触发release
void startNote(int midiNoteNumber, float velocity, SynthesiserSound* sound, int currentPitchWheelPosition)
{
frequency = MidiMessage::getMidiNoteInHertz(midiNoteNumber);
level = velocity;
myEnv1.trigger = 1;//接收到音符开信息时触发包络启动
}
void stopNote(float velocity, bool allowTailOff)
{
myEnv1.trigger = 0;//接收到音符关信息时触发包络释放(值为非1时)
}
3.设置包络发生器的ADSR值并应用到缓存区中的信号
void renderNextBlock(AudioBuffer<float> &outputBuffer, int startSample, int numSample)
{
myEnv1.setAttack(500); //A
myEnv1.setDecay(100); //D
myEnv1.setSustain(0.8); //S
myEnv1.setRelease(500); //R
for (auto sample=0;sample<numSample;++sample)
{
auto myWave = myOsc1.sinewave(frequency)*level;
auto soundOfEnv= myEnv1.adsr(myWave, myEnv1.trigger);//对振荡器的音频信号应用包络
for (auto channel = 0; channel < outputBuffer.getNumChannels(); ++channel)
{
outputBuffer.addSample(channel, startSample, soundOfEnv);//包络处理后的音频信号传入缓存中
}
++startSample;
}
}