870920 Menu

JUCE类库附带示例的源码分析·9

 创建插件实例后,AudioProcessorGraph对象可addNode()添加之。该函数由两个作用,一是添加给出的插件实例,二是返回代表该实例的AudioProcessorGraph::Node节点对象(指针)。可定义一个节点对象,将addNode()的返回值赋给它:

AudioProcessorGraph::Node* node = nullptr;
// 如果刚才创建的插件有效,则本类所持有的AudioProcessorGraph对象添加一个节点
// addNode()函数添加插件实例后,返回代表该插件的节点对象,赋值给刚刚定义的node
if (instance != nullptr)
node = audioProcessorGraph.addNode (instance);
 AudioProcessorGraph::Node节点类有一大批public数据成员,其中数据成员properties的类型是NamedValueSet(键值对数据容器。键是属性名,值是属性值)。可利用该对象存储并获取节点对象的各类属性(可存储任意属性)。
 上述语句的流程为:基于PluginDescription插件描述,创建AudioPluginInstance插件实例,将其添加到AudioProcessorGraph中并返回AudioProcessorGraph::Node节点对象,利用节点对象的数据成员properties,设置该节点的属性。在这个过程中,基于插件描述创建插件实例的时候,可能会失败(这也是定义错误消息字符串的原因),因此,可在最后通过Node节点是否有效来判断创建和添加插件是否成功。如果成功,则设置节点对象的属性,否则,弹出消息窗,报告错误。示例:
// 如果AudioProcessorGraph对象添加插件实例后所返回的节点node有效
if (node != nullptr)
// 节点对象设置自身属性
node->properties.set (“属性名”, 属性值);
// 如果创建和添加节点不成功,则显示错误消息,3参为errorString字符串对象
else
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS(“Couldn’t create filter”),
errorMessage);
 AudioProcessorGraph::Node类继承自ReferenceCountedObject,因此,该类堆对象可享受是引用计数的巨大好处。此时,声明Node指针必须这么写:
AudioProcessorGraph::Node::Ptr node;
 节点(已添加到AudioProcessorGraph中的插件)需要互相连接才能协同工作,从而构成音频效果处理链。节点之间的“连接”是一个重要的概念,JUCE类库中,此概念专门用一个结构体来表示,该结构体嵌套于AudioProcessorGraph类,名字为Connection,它只有构造函数和4个public数据成员,无其他成员函数。这4个public数据成员分别代表来源节点的ID,来源节点的第几个通道,目标节点的ID,目标节点的第几个通道。
 AudioProcessorGraph有个成员函数为getConnectionBetween(),其返回值就是const类型的AudioProcessorGraph::Connection*
 务必要理清这个概念:两个节点之间可以有多个连接,分别对应来源节点的某个输出通道和目标节点的某个输入通道。也就是说,一个AudioProcessorGraph::Connection对象代表一个连接,而不是两个节点之间的所有可能的连接。
 AudioProcessorGraph对象添加和移除Connection(连接)有两种方式:基于该连接的索引和基于给定的两个节点之间的特定通道。前者需要一个int参数,后者需要4个参数,分别是两个uint32(节点的UID)和int(该节点的第几个通道)。同理,给出后者这4个参数,可判断某两个节点之间的特定通道是否可以连接(返回bool值)。
 可在cpp源文件中声明的同时即定义函数,这样的函数为全局函数,出于效率的考虑,可将该函数写成static静态函数。
 AudioProcessorGraph::Node节点对象的getProcessor()可返回该节点所代表的插件实例。注意:该函数的返回类型为AudioProcessor*,需进行强制类型转换。由于插件实例类AudioPluginInstance继承自AudioProcessor,因此,强转语句可这么写:
// 获取node节点所代表的AudioPluginInstance插件实例
AudioPluginInstance* plugin = dynamic_cast<AudioPluginInstance*>(
node->getProcessor());
 有了上一条语句后,可基于已加载的node节点所返回的插件实例,获取该插件的插件描述对象(PluginDescription):
PluginDescription pd; // 先定义一个空白的插件描述对象
// 插件实例对象将自身信息填充到插件描述对象中
plugin->fillInPluginDescription (pd);
 AudioPluginFormat是一个抽象基类,它代表音频插件的某种格式,其派生类可以代表VST、AU等具体的插件格式,或者干脆自定义一种插件格式。本项目中,InternalPluginFormat类就继承自AudioPluginFormat,创建了一个特殊的插件格式:将本机的音频和MIDI进出抽象为一种插件格式,而后利用该格式创建具体的插件实例,也就是说,可以将本机的音频和MIDI进出端口作为音频插件来处理,比如将其添加到AudioProcessorGraph中,成为音频处理信号链中的节点。
 整个音频插件链所产生的最终音频流需播放发声,所需的类(对象)与代码流程为:
AudioProcessorPlayer audioProcessorPlayer; // 插件播放器对象
AudioProcessGraph audioProcessorGraph; // 插件(节点)容器对象
// 插件播放器设置音频来源为插件(节点)容器
audioProcessorPlayer.setProcessor (audioProcessorGraph);
AudioDeviceManager audioDeviceManager; // 设备管理器对象
// 设备管理器添加音频回调,参数为audioProcessorPlayer堆对象
audioDeviceManager.addAudioCallback (&audioProcessorPlayer);
 设备管理器同时还可处理AudioSource和进入系统的MIDI消息,处理进入的MIDI消息,同样需要添加回调:
// 设备管理器添加MIDI回调,参数为AudioProcessorPlayer所持有的MIDI消息采集器对象
deviceManager->addMidiInputCallback (String::empty,
&audioProcessorPlayer.getMidiMessageCollector());
 往往,内容组件中创添显设删虚拟键盘,在接收并处理进来的MIDI消息时,该虚拟键盘可以实时反映该MIDI消息所对应的键被按下或抬起。此时,需要另外两个类的对象,一是MIDI键盘状态对象,二是虚拟键盘组件:
MidiKeyboardState keyboardState; // MIDI键盘状态对象
// MIDI键盘状态对象添加捕获器,该捕获器为插件播放器所持有的MIDI消息采集器
keyboardState.addListener
(&audioProcessorPlayer.getMidiMessageCollector());
// 创添显设虚拟键盘
MidiKeyboardComponent* midiKeyboard = new MidiKeyboardComponent
(keyState,MidiKeyboardComponent::horizontalKeyboard); // 水平放置
addAndMakeVisible (midiKeyboard);
 上述流程可所需的类(对象)可参阅本项目的GraphDocumentComponent类。所在的文件:
GraphEditorPanel.h和GraphEditorPanel.cpp

 保存和加载当前所有插件(即AudioProcessorGraph中的节点)及其连接,需借助XmlElement类。完整的示例:
/* 保存当前所有已加载的插件。定义XML总节点,遍历当前已加载的所有节点,XML添加子节点(所有node的文本描述)。而后遍历当前所有的连接,将每个连接的4个属性值(来源插件的ID,来源插件的通道索引,目标插件的ID,目标插件的通道索引)保存到临时定义的xml子节点中,总节点添加连接子节点,最后返回总节点 */
XmlElement* FilterGraph::createXml() const
{
// 定义一个XmlElement总节点
XmlElement* xml = new XmlElement (“FILTERGRAPH”);
/* 遍历当前已加载的所有插件,总节点xml添加子节点。所添加的子节点为每个节点的xml描述。graph为本类的数据成员,AudioProcessorGraph对象 */
for (int i = 0; i < graph.getNumNodes(); ++i)
{
xml->addChildElement (createNodeXml (graph.getNode (i)));
}
// 遍历当前所有的连接
for (int i = 0; i < graph.getNumConnections(); ++i)
{
// 每次循环均定义一个Connection连接,初始化为当前遍历的连接
// 并定义一个xml子节点对象,添加当前连接的4个属性
const AudioProcessorGraph::Connection* const fc = graph.getConnection(i);
XmlElement* e = new XmlElement (“CONNECTION”);
e->setAttribute (“srcFilter”, (int) fc->sourceNodeId);
e->setAttribute (“srcChannel”, fc->sourceChannelIndex);
e->setAttribute (“dstFilter”, (int) fc->destNodeId);
e->setAttribute (“dstChannel”, fc->destChannelIndex);
// xml总节点添加当前连接的xml子节点
xml->addChildElement (e);
}
return xml; // 返回xml总节点
}
/* 加载已保存的插件。首先调用clear()函数,遍历参数xml,基于该节点的每个子节点,创建对应的Node对象。而后再次遍历参数XML,添加该XML中存储的每个Connection连接,最后移除所有违例(无效)的连接 */
void FilterGraph::restoreFromXml (const XmlElement& xml)
{
graph.clear();
forEachXmlChildElementWithTagName (xml, e, “FILTER”)
createNodeFromXml (*e);
forEachXmlChildElementWithTagName (xml, e, “CONNECTION”)
graph.addConnection (
(uint32) e->getIntAttribute (“srcFilter”),
e->getIntAttribute (“srcChannel”),
(uint32) e->getIntAttribute (“dstFilter”),
e->getIntAttribute (“dstChannel”));
graph.removeIllegalConnections();
}