870920 Menu

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

Plugin Host

很实用的VST和AU格式的插件宿主程序,不仅可作为插件宿主编程的最佳参考,还可以作为所开发的各类MIDI软音源和音频效果器的测试工具。仔细研究该项目,可尽快熟悉并掌握JUCE类库中音频插件的整个工作流程与内部机制。项目整体类图(大体框架,部分类未画出):

4

图 5 89 Plugin Host项目整体类图

整体分析:
 尽量以功能模块为单位设计编译单元,每个编译单元通常由一个h头文件和一个cpp源文件所构成。仅供本模块使用而不对外的类,通常将其声明和定义以内联的形式写在cpp源文件中。
 AudioProcessorGraph是JUCE类库音频插件模块中相当重要的一个核心类,该类继承自AudioProcessor基类,其主要功能有:本身也是一个可产生音频流的AudioProcessor,同时还是AudioProcessor(插件节点)容器,负责管理插件节点(添加、连接、断开、移除、检测、重置等等),还能够创建插件的GUI界面。AudioProcessorGraph产生的音频流可交给AudioProcessorPlayer,由该类负责播放。而AudioProcessorPlayer继承自AudioIODeviceCallback,可由AudioIODevice音频设备不断回调,从而发出声音(或接收输入的音频/MIDI数据)。
 AudioPluginFormatManager插件格式管理器是一个无父无子无构参的独立功能类,该类负责存储本机所有可用的插件格式(AudioPluginFormat)。其核心功能(成员函数)有:添加一个插件格式,添加所有已知的插件格式,获取当前所有可用的插件格式的数量,返回某个可用的插件格式,创建插件实例(AudioPluginInstance),检测某个具体的插件是否存在。
 本项目中,FilterGraph是最重要的底层模型类,该类聚合了AudioPluginFormatManager(该类对象由主窗口类创建),组合了AudioProcessorGraph。同时,FilterGraph还继承了FilebasedDocument,使之可以处理程序文档(加载/保存插件及插件的状态、连接等等)。该类的三个作用参见上面给出的UML类图。
 项目中其它几个public类大部分是GUI可视化的界面类(MVC架构中的视图类),其嵌套关系为:主窗口组合了基础组件,基础组件包括插件编辑面板、虚拟键盘、状态栏等可视化组件,插件编辑面板则组合了可视化的连接线组件。FilterGraph类的对象由基础组件所持有并创建,此类不仅封装了AudioProcessorGraph插件节点容器对象,另外还继承了FilebasedDocument程序文档的逻辑操作类。FilterGraph类是整个项目的最核心类。该类的对象被连接线和插件编辑面板所使用(通过基础组件以传参的形式聚合而来)。
 本项目的重点有四:一是理清宿主加载、连接、管理和播放插件的流程和所需的类、对象及其重要的成员函数。二是MVC(模型/视图/控制)架构的设计与实现。三是程序中已加载的插件及其连接的保存与读取(程序文档的读写功能)。四是连接线、接头、可视化的插件矩形框(代表已加载的某个插件)的GUI编程与实现技巧。
 另一个重点和难点是:创建自定义的插件格式,给出具体插件的类型描述,而后基于描述创建插件实例,在程序启动时自动加载这些程序内部的音频插件。

程序作为插件宿主,加载、连接、管理即播放插件处理结果的重要知识点:
 构建插件宿主程序,可在主程序类cpp文件的#include语句之后,其它所有语句之前添加:
#if ! (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_AU)
#error “Must enable VST and/or AU support in IntroJucer!”
#endif
 可在最顶层基础组件或主窗口类中声明并持有AudioPluginFormatManager插件格式管理器对象,类的构造函数中,该对象添加本机默认的插件格式和自定义的插件格式(如将本机的音频和MIDI总进出端口当成插件对待,需提前将这种“内部固有插件”封装为一种特殊的插件格式,即:内部插件格式类需继承自AudioPluginFormat类。详情参阅本项目的InternalPluginFormat类):
// 插件格式管理器添加本机默认的插件格式
formatManager.addDefaultFormats();
// 添加自定义的插件格式
formatManager.addFormat (new InternalPluginFormat());
 KnownPluginList是一个重要的辅助类,该类用于存储本程序所有已扫描出处的可用插件。可基于程序属性文件中已保存的插件列表初始化KnownPluginList对象:
// 获取程序属性文件中已保存的插件列表
ScopedPointer<XmlElement> savedPluginList (applicationProperties->
getUserSettings()->getXmlValue (“pluginList”));
// 如果插件列表有内容,则基于此列表初始化KnownPluginList对象
if (savedPluginList != nullptr)
knownPluginList.recreateFromXml (*savedPluginList);
// 基于程序属性文件中保存的排序方式值更新插件排序方法对象
pluginSortMethod = (KnownPluginList::SortMethod) appProperties->
getUserSettings()->getIntValue (“pluginSortMethod”,
KnownPluginList::sortByManufacturer);
// 插件列表对象继承自可变生成类,因此可添加可变捕获器
knownPluginList.addChangeListener (this);
 将插件列表添加到弹出式菜单中:
PopupMenu m;
knownPluginList.addToMenu (m, pluginSortMethod);
 返回插件菜单项的选择结果(注意此函数的返回类型为PluginDescription插件描述指针):
const PluginDescription* getChosenType (const int menuID) const
{
return knownPluginList.getType (knownPluginList.
getIndexChosenByMenu (menuID));
}
 还可以通过KnownPluginList对象批量添加插件文件并获取每个插件的PluginDescription插件描述对象。以下语句可用于文件拖放目标类的filesDropped()函数中:
// 定义一个插件描述数组
OwnedArray <PluginDescription> typesFound;
// 插件列表对象批量添加插件,并将每个插件的描述保存到插件描述数组中
knownPluginList.scanAndAddDragAndDroppedFiles (formatManager,
files, typesFound);
 有了PluginDescription插件描述对象后,可使用AudioPluginFormatManager插件格式管理器创建出PluginDescription对象所描述的插件实例(AudioPluginInstance):
String errorMessage; // 提前定义一个错误消息字符串
// 插件格式管理器基于插件描述对象创建插件实例
AudioPluginInstance* instance = audioPluginformatManager.
createPluginInstance (*pluginDescription, errorMessage);