我的笔记

灵感、随想与新技术
03 6月 2020

音频开发实战(二)第一个乐器 正弦合成器 (附源码)

 

输出波形

 

《音频开发技术(三)》中,我们了解到:

1.波形信号在计算机中是一串用数值表示的点。

2.信号是在 processBlock 回调方法中被获取、处理与返回。

 

—信号输入到插件—> | processBlock | —信号从插件输出—>

 

具体要怎么做呢?

WHERE: 根据以上的信息,首先确定声音是在processBlock回调方法中产生的。

HOW: 之前的课程我已经讲过,在 processBlock 中,通过 getWritePointer 接口可以获取到写入数组的指针,然后用这个指针对 得到的写入数组的内容直接赋值,就可以实现输出信号的功能。(详见《音频开发技术(三)》

WHAT: 正弦波信号 即 正弦波形状的点集 , 我们只需要向数组写入时域上是正弦波形状的点集就可以了。

 

正弦波形状的点集

 

示例: 输出 1000 Hz 的正弦波

 

分析:

1.确定用正弦函数 y = sin(x) 绘制正弦波的形状。

 

2.1000Hz 即 每秒振动1000次。设采样频率为N,则1秒内有有N个采样点,即每个过一个点,就完成(1000/N)次振动。

 

3.sin(x) 中  2Pi 个单位 为一次振动。

结合以上三点,得出 每经过一个采样点就相当于 在sin(x) 的横轴上移动了 2Pi * (1000/N) 个单位。 

 

Sin(x) 的周期为 2Pi

好了,现在我们可以编写代码了,首先要知道当前的采样率是多少。JUCE 中用 getSampleRate() 接口获取 当前采样频率。

 

在JUCE中获取采样频率的接口
编写代码

新建名为 MyOsc 的工程,在IDE中打开,点开 PluginProcessor.h 文件,定义要用到的double字段

1
2
3
4
5
6
...
private:
    //==============================================================================
    double currentAngle = 0.0, volume = 0.1, frequency = -1;;     //  定义需要的字段 <-
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyOscAudioProcessor)
...

然后来到 PluginProcessor.cpp 文件中 ,编写 processBlock 回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void MyOscAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;
    auto samepleRate = getSampleRate();

    auto* channelData = buffer.getWritePointer (0);
    auto* channelDataR = buffer.getWritePointer(1);
   

    for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
    {
        auto sample_pos = (float)std::sin(currentAngle);
        currentAngle += 2 * MathConstants<double>::pi * 1000 / samepleRate;

        channelData[sample] = sample_pos * volume;
        channelDataR[sample] = sample_pos * volume;
       
    }
}

以上代码实现了分析中内容。


验证

导出 -> 宿主中加载-> 成功听到了一个 1kHz 的纯音。


播放MIDI按键对应音符的频率


好了,现在我们已经完成了从频率到波形的输出,接下来实现音符到频率的转换。

 

获取Midi信号

processBlock回调的第二个参数,MidiBuffer中包含多个MidiMessage,而每个MidiMessage对象都相当于一个MIDI事件,每个MidiMessage都有它的事件类型(按键按下、抬起、哪一个键,力度多少等)

这里我们先用迭代器获取到MidiMessage对象,然后再通过MidiMessage对象的 getNoteNumber 接口访问到它的 NoteNumber(哪个键)
然后又通过 MidiMessage::getMidiNoteInHertz() 接口把MIDI音符转化成频率,有了频率就可以用我们之前的代码发声了。

MidiMessage 对象的 isNoteOn() 意味着当前事件是按下按键触发的,isNoteOff() 则是代表是松开按键触发的。这里按键松开时 frequency 被赋值成 -1 以停止播放。

修改后的代码如下

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
34
35
36
37
38
39
40
41
void MyOscAudioProcessor::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;

    int pos;

    MidiMessage midi_event;

    for (MidiBuffer::Iterator i(midiMessages); i.getNextEvent(midi_event, pos);)
    {
        if (midi_event.isNoteOn())
        {
            frequency = MidiMessage::getMidiNoteInHertz(midi_event.getNoteNumber());
        }
        else if (midi_event.isNoteOff())
        {
            frequency = -1;
        }
    }

    if (frequency > 0)
    {
        auto totalNumInputChannels = getTotalNumInputChannels();
        auto totalNumOutputChannels = getTotalNumOutputChannels();

        auto* channelData = buffer.getWritePointer(0);
        auto* channelDataR = buffer.getWritePointer(1);

        auto samepleRate = getSampleRate();

        for (auto sample = 0; sample < buffer.getNumSamples(); ++sample)
        {
            auto sample_pos = (float)std::sin(currentAngle);
            currentAngle += 2 * MathConstants<double>::pi * frequency / samepleRate;

            channelData[sample] = sample_pos * volume;
            channelDataR[sample] = sample_pos * volume;
        }
    }
   
}


验证

成功了,按下按键,听到了对应的声音。


合成音色

合成器自然是指多个振荡器的合成,上方我们只写了一个振荡器,那么多个振荡器要如何合成呢。
很简单,就是每个采样点算数相加即可。

我们加一个高八度的音,高八度即频率翻倍,即 output = sin(x) + sin(2x)

只用改这一句

1
2
3
...
auto sample_pos = (float)std::sin(currentAngle) + (float)std::sin(2 * currentAngle);
...


验证

成功了,打开频谱仪,按下按键,看到了两个频率。

注意

插件导出前要设置成乐器才能接受到MIDI键盘的信号。

这样设置之后再导出到IDE,在生成的插件就可以接受MIDI键盘的信号了。



一个正弦波合成器就完成了。

 



扩展阅读

输出三角波

有这种

这种

还有这种

 

输出方波

 

加法合成

加法合成即 通过多个正弦波相加模拟某种波形。

用加法合成的三角波

用加法合成的方波

 

加法合成的亮点在于,它可以模拟任何一种波形。

 

 

市场中的合成器

简单的合成器 3x Osc

复杂的合成器 Massive X

 

点击这里下载源码

课程进度50%

上海外滩 – StudioEIM // MapleStory
  1. 上海外滩 – StudioEIM // MapleStory
  2. 神木村 – StudioEIM // MapleStory
  3. MapleStory – StudioEIM // MapleStory
  4. Pantheon – StudioEIM // MapleStory
  5. 逐梦飞翔 – StudioEIM // MapleStory
  6. 魔法密林 – StudioEIM // MapleStory