音频开发进阶(一)保存插件状态 (附源码·门限器)
在《音频开发实战(三)》,我们讨论 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宿主软件为我们做的。
宿主软件把内存中的信息编码保存在了工程文件中。