前言

阅读此教程需要一定的C++和JUCE基础。
我的Bilibili频道:香芋派Taro
我的个人博客:taropie0224.github.io
我的公众号:香芋派的烘焙坊
我的音频技术交流群:1136403177
我的个人微信:JazzyTaroPie

最近学生的合成器设计课要结课了,为了帮他更好地梳理一遍(也为了帮我自己更好地梳理一遍…),所以还是开了这个坑…从零到一来聊聊怎么去搭建一个合成器。当然在这之前我还是希望你有一定的C++基础,最好知道什么指针什么是虚函数balabala的,否则可能阅读起来还是比较痛苦的。

创建项目

创建一个叫做synthTutorial的Plugin项目:

如下创建SynthVoice.h/.cpp:

如下创建SynthSound.h:

确保勾选了这些选项:

记得添加dsp module:

完成SynthSound和SynthVoice的基本框架

SynthSound

参考官方doc:https://docs.juce.com/master/classSynthesiserSound.html
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//SynthSound.h
#pragma once

#include <JuceHeader.h>

class SynthSound : public juce::SynthesiserSound
{
public:
bool appliesToNote (int midiNoteNumber) override { return true; }
bool appliesToChannel (int midiChannel) override { return true; }

};

注意由于是虚函数,记得使用override增加可读性。

SynthVoice

参考官方doc:https://docs.juce.com/master/classSynthesiserVoice.html
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//SynthVoice.h
#pragma once

#include <JuceHeader.h>
#include "SynthSound.h"

class SynthVoice : public juce::SynthesiserVoice
{
public:
bool canPlaySound (juce::SynthesiserSound* sound) override;
void startNote (int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition) override;
void stopNote (float velocity, bool allowTailOff) override;
void controllerMoved (int controllerNumber, int newControllerValue) override;
void pitchWheelMoved (int newPitchWheelValue) override;
void renderNextBlock (juce::AudioBuffer< float > &outputBuffer, int startSample, int numSamples) override;
private:

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//SynthVoice.cpp
#include "SynthVoice.h"

bool SynthVoice::canPlaySound (juce::SynthesiserSound* sound)
{
return dynamic_cast<juce::SynthesiserSound*>(sound) != nullptr;
//如果可以运行,返回true
}

void SynthVoice::startNote (int midiNoteNumber, float velocity, juce::SynthesiserSound* sound, int currentPitchWheelPosition)
{

}

void SynthVoice::stopNote (float velocity, bool allowTailOff)
{

}

void SynthVoice::controllerMoved (int controllerNumber, int newControllerValue)
{

}

void SynthVoice::pitchWheelMoved (int newPitchWheelValue)
{

}

void SynthVoice::renderNextBlock (juce::AudioBuffer< float > &outputBuffer, int startSample, int numSamples)
{

}

搭建Synth基本框架

首先来到PluginProcessor.h中的private处,初始化synth对象:

1
2
3
4
5
6
7
//PluginProcessor.h
private:
juce::Synthesiser synth;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SynthTutorialAudioProcessor)
};

addSound/addVoice

一切从简,我们暂时只搭建一个只有一个SynthSound一个SynthVoice且单声道的synth,来到PluginProcessor.cpp的构造函数中,添加如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SynthTutorialAudioProcessor::SynthTutorialAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)
#endif
)
#endif
{
synth.addSound(new SynthSound());
synth.addVoice(new SynthVoice());
}

oh,记得要在PluginProcessor.h中先引入,否则会报错:

1
2
3
4
5
6
7
//PluginProcessor.h
#pragma once

#include <JuceHeader.h>

#include "SynthSound.h"
#include "SynthVoice.h"

来到PluginProcessor.cpp中的prepareToPlay设定samplerate:

1
2
3
4
5
6
7
//PluginProcessor.cpp
...
void SynthTutorialAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
synth.setCurrentPlaybackSampleRate(sampleRate); //设定synth的samplerate
}
...

来到PluginProcessor.cpp中的processBlock设定处理框架和renderNextBlock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//PluginProcessor.cpp
...
void SynthTutorialAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
juce::ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();

for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear (i, 0, buffer.getNumSamples());

for (int i = 0; i < synth.getNumVoices(); ++i)
{
if (auto voice = dynamic_cast<juce::SynthesiserVoice*>(synth.getVoice(i)));
{
// Osc
// ADSR
// LFO
}
}

synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
}
...