我的笔记

灵感、随想与新技术
04 六月 2020

音频开发进阶(一)保存插件状态 (附源码·门限器)



《音频开发实战(三)》,我们讨论 KSP 脚本语言的时候, 谈及了 make_persistent 这个接口。

它的作用是让一个参数被系统自动保存,这样我们的参数就可以被记录在宿主软件的工程文件里,这个过程叫做 数据持久化

在JUCE中,也是一样的,倘若我们的参数没有做过数据持久化,那么在工程下一次被打开时,所有参数都会全部消失,调好的旋钮、推子都会清零。

这一节,我们看一下JUCE中如何实现数据持久化,即如何保存应用的参数。


制作基本Gate门限器

新建一个 Audio Plugin 工程 MyGate -> IDE中打开

类似《音频开发实战(一)》中写音量推子那样,不同的是这次的运算:判断如果音量没有达到阈值,则不输出。



PluginEditor.h

1
2
3
4
5
6
//PluginEditor.h
...
    MyGateAudioProcessor& processor;
    Slider Threshold;   // 声明一个推子 <-
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyGateAudioProcessorEditor)
...



PluginEditor.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//PluginEditor.cpp
...
MyGateAudioProcessorEditor::MyGateAudioProcessorEditor (MyGateAudioProcessor& p)
    : AudioProcessorEditor (&p), processor (p)
{
    Threshold.setBounds(0, 0, 300, 100);
    Threshold.setRange(0, 1, 0.01);
    Threshold.setSliderStyle(Slider::SliderStyle::LinearHorizontal);
    Threshold.setTextBoxStyle(Slider::TextBoxBelow,true,100,50);
    Threshold.onValueChange = [this] {processor._threshold = Threshold.getValue(); };
    addAndMakeVisible(Threshold);
    setSize (400, 300);
}
...



PluginProcessor.h

1
2
3
4
5
6
//PluginProcessor.h
...
    void setStateInformation (const void* data, int sizeInBytes) override;
    double _threshold = 0.0;     // 声明一个变量  <-
private:
...



PluginProcessor.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//PluginProcessor.cpp
...
void MyGateAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    ScopedNoDenormals noDenormals;
   
    auto* channelL = buffer.getWritePointer(0);
    auto* channelR = buffer.getWritePointer(1);

    for (auto sample = 0; sample < buffer.getNumSamples(); sample++)
    {
        channelL[sample] = std::abs(channelL[sample]) > _threshold ? channelL[sample] : 0;
        channelR[sample] = std::abs(channelR[sample]) > _threshold ? channelR[sample] : 0;
    }

}
...


1.这里我用了一个三目运算符,意思是:【条件 ? 条件成立返回的值 : 条件不成立返回的值】
2.为了方便,接收被赋值的变量时 我用了auto关键字,它就像是c#或js的 var 一样,能够自动判断类型。


验证

OK 没问题


持久化参数


改变参数的类型


我们先把 在 PluginProcessor 定义的变量 换成 JUCE 提供的 AudioParameterFloat 类型的指针. (同理还有 AudioParameterInt 等其他类型)

它在使用的时候和普通Float指针没区别。

PluginProcessor.h

1
2
3
4
5
//PluginProcessor.h
...
    // double _threshold = 0.0;
    AudioParameterFloat* _threshold;
...


从变量改成了指针,那用到它的地方就需要把它当作指针来使用,这样修改。

PlugiProcessor.h

1
2
3
4
5
6
7
//PlugiProcessor.h
...
        // channelL[sample] = std::abs(channelL[sample]) > _threshold ? channelL[sample] : 0;
        // channelR[sample] = std::abs(channelR[sample]) > _threshold ? channelR[sample] : 0;
        channelL[sample] = std::abs(channelL[sample]) > *_threshold ? channelL[sample] : 0;
        channelR[sample] = std::abs(channelR[sample]) > *_threshold ? channelR[sample] : 0;
...

还有这里也要修改。

PluginEditor.cpp

1
2
3
4
5
//PluginEditor.cpp
...
    // Threshold.onValueChange = [this] {processor._threshold = Threshold.getValue(); };
    Threshold.onValueChange = [this] {*(processor._threshold) = Threshold.getValue(); };
...

我们希望每次重新打开工程的时候,界面上的推子的位置也是我们的保存的参数。来到 PluginEditor 的构造方法,加上这句。

PluginEditor.cpp

1
2
3
4
5
6
//PluginEditor.cpp
...
    Threshold.onValueChange = [this] {*(processor._threshold) = Threshold.getValue(); };
    Threshold.setValue(*(processor._threshold));          // 设置推子组件的值为保存的参数 <-
    addAndMakeVisible(Threshold);
...



负责持久化的回调函数


记得在《音频开发技术(三)》中我们提到过,PluginProcessor中有一大堆方法都是可选功能吗。

持久化的回调函数就在这一大堆可选功能中,在其中找到 getStateInformation 和 setStateInformation 这两个回调。

getStateInformation 储存到插件的信息到内存
setStateInformation 从内存中提取插件的信息

getStateInformation 中用 MemoryOutputStream 对象的 writeFloat 方法

setStateInformation 中用 MemoryInputStream 对象的 readFloat 方法

现在编写代码。

PluginProcessor.cpp

1
2
3
4
5
6
7
8
9
10
11
12
//PluginProcessor.cpp
...
void MyGateAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    MemoryOutputStream(destData, true).writeFloat(*_threshold);
}

void MyGateAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    *_threshold = MemoryInputStream(data, static_cast<size_t>(sizeInBytes), false).readFloat();
}
...


生成导出试了一下,已经可以保存参数了。


持久化的细节


我们只是把插件的信息写到了内存中,而最终的持久化还是DAW宿主软件为我们做的。

宿主软件把内存中的信息编码保存在了工程文件中。

 

 

点击这里下载源码

课程进度70%

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