VST 制作技能
非官方社区技能。非此处提及的任何插件、合成器或框架供应商制作或认可。
如何阅读置信度标签:
- (V:来源)已对照指定来源验证。
- (R)经验法则或品味偏好。一种测试起点值,通过测量和聆听来确定,绝非定律。
如果某个事实没有标签,则属于标准实践,但您仍应在发布前验证其可靠性。
0. 基本原则
- 在构建前询问:插件类型(合成器、效果器、控制器)、目标操作系统(Linux、macOS、Windows)、格式(优先 VST3;CLAP 或 LV2 可选)、调色板(切勿假设;如果用户未提供,则提供中性默认值)、许可证(见第 1 节)。
- 免费和开源插件必须能离线工作:无需账户、许可服务器,安装或运行时无需下载。插件所需的一切都已捆绑。
- 切勿声称声音听起来不错。请测量它(见第 12 节),让用户的耳朵做最终判断。
- 对每个数字进行引用或测试。当数字是经验法则时,请注明“未经验证”。
- 当用户不是程序员时,一次只完成一步。先给出简短回答,细节根据要求提供。
1. 项目设置和许可证
- JUCE 采用双许可证:JUCE 许可证和 AGPLv3。如果没有付费的 JUCE 计划,您必须在 AGPLv3 下使用它,而 AGPLv3 的要求不仅仅是发布源代码(V:JUCE 论坛,员工回复)。在选择之前,请阅读您所用版本的当前 JUCE 许可证,并告知用户适用的许可证。不要凭记忆陈述层级价格或收入限制。
- 使用 CMake。保持 DSP 代码独立于 JUCE GUI 类型,以便可以在没有窗口的情况下进行单元测试。
- 优先构建 VST3。CLAP 和 LV2 是可选的:在承诺支持其中任何一种之前,请查阅当前的 JUCE 文档和 clap-juce-extensions。
- 主机扫描 VST3 的位置(V:Steinberg VST 3 开发者门户,插件位置):
- Linux:
$HOME/.vst3/优先,然后是/usr/lib64/vst3/、/usr/lib/vst3/、/usr/local/lib64/vst3/、/usr/local/lib/vst3/。 - macOS:
~/Library/Audio/Plug-ins/VST3/(用户),/Library/Audio/Plug-ins/VST3/(全局)。 - Windows:
C:\Program Files\Common Files\VST3\(全局),%LOCALAPPDATA%\Programs\Common\VST3\(用户,主要用于开发)。 - 主机使用给定 Processor UID 找到的第一个插件,按优先级顺序扫描。
- Linux:
2. 实时安全
processBlock 在具有严格截止时间的高优先级音频线程上运行。JUCE 文档指出,在那里与 UI 的任何交互都是不可能的,并且代码必须能够处理可变块大小(V:JUCE AudioProcessor 文档)。
绝不能在音频线程上执行以下操作:
- 分配或释放内存(new、delete、malloc、增长向量或字符串、构建 juce::String)
- 获取另一个线程可能持有的锁(std::mutex、CriticalSection)
- 文件、网络或控制台 I/O
- 调用 UI 或 MessageManager
- 抛出异常或调用任何具有未知实时行为的内容
相反:
- 在
prepareToPlay中分配(采样率在播放停止前保持不变;块大小只是一个提示,文档说要以防御性方式编程,以防主机超过该大小)(V:JUCE 文档)。在releaseResources或析构函数中释放。 - 使用无锁队列(
juce::AbstractFifo)或原子操作与其他线程通信。UI 使用 Timer 轮询原子;或从音频线程触发 AsyncUpdater。 - 在
processBlock顶部放置juce::ScopedNoDenormals;它在函数返回前保持活动状态,因此辅助函数无需重复(V:JUCE 论坛共识,非官方文档)。同时设计反馈路径,使其不会退化为反规格化数。 - 推荐阅读:Ross Bencina,《实时音频编程 101:时间不等人》(此处未引用,编写此技能时无法获取)。
3. 参数和状态
- 使用
AudioProcessorValueTreeState:宿主自动化、撤销和状态保存随之而来。 - 平滑每个影响增益、频率、混合或延迟时间的参数(
juce::SmoothedValue),否则会出现拉链噪声和咔嗒声。 - 除非值是按样本平滑的,否则每块读取一次值到局部变量。
- 为每个参数分配一个稳定的字符串 ID。发布后切勿重命名或重新排序 ID:这会破坏已保存的项目和自动化。使用新 ID 添加新参数,并保持状态版本号。
- 验证从状态或预设加载的所有内容:钳制范围,忽略未知键,永远不要信任大小或计数。
- 保持处理器(DSP 和状态)与编辑器(UI)严格分离。处理器必须在编辑器窗口关闭的情况下完美运行。VST3 本身将插件分为处理器和控制器(V:Steinberg VST 3 教程,“编写你的第一个插件”)。
- 即使 JUCE 封装了它们,也值得了解的 VST3 事实(V:同一教程):参数始终归一化为 0.0 到 1.0;设置 automatable 标志以便宿主可以自动化;一个处理块可能包含同一参数的多个更改,因此手动编写的处理器必须正确处理队列(教程只应用最后一个更改,这是一个简化);如果输入静音,则设置并遵守静音标志并清除输出缓冲区;拒绝不支持的母线布局,而不是猜测;当没有输入或没有样本时,仍然应用待处理的参数更改。
更多 VST3 架构事实(V:Steinberg VST 3 开发者门户,API 文档):
- 处理器必须在没有加载控制器的情况下工作,因此 DSP 状态绝不依赖于编辑器。
process()和setProcessing()可能在音频线程上运行:不允许分配。Initialize和terminate在 UI 线程上运行。- 母线(Bus)和扬声器排列只能在插件处于非活动状态时更改。为所有母线提供缓冲区,即使是活动状态的。输入和输出缓冲区可能共享内存,因此不要盲目清除输出。
- 音符作为事件在
process()中到达。参数更改通过参数队列以样本精度到达。速度和传输信息在处理上下文中到达。 - GUI 到宿主的编辑使用
beginEdit、performEdit、endEdit的顺序,仅限 UI 线程。 - 状态恢复时,先恢复处理器;然后控制器接收组件状态,并且不得触发处理程序回调。
- 不要从
process()发送私有的处理器到控制器消息;使用 UI 线程或数据交换 API。 - 单元(Units)对参数进行分组;MIDI 映射接口将 MIDI CC 映射到参数。
- 需要时阅读的门户页面:参数和自动化、工作流图、多动态 IO(侧链)、VST 3 单元、数据交换。
4. 性能:CPU、RAM、GPU
- 在优化前进行测量(见第 12 节)。
- CPU(R):按块处理,在参数更改时预计算系数,避免内部循环中的每样本分支和虚函数调用,避免在每样本中使用
std::pow、std::exp、std::sin(如果递推或查表方法更有效且测量更快)。 - RAM:一次预分配,在声音之间共享波表和表,在
releaseResources中释放大缓冲区。 - GPU 和 UI:仅在编辑器可见且在前台时进行绘制。在隐藏时暂停计时器、仪表和画布动画;音频、自动化和 MIDI 继续运行。限制重绘速率,仅重绘脏区域(R)。仅在明确需要且在 Linux 上测试后才使用 GPU 渲染。
- 隐藏或关闭的编辑器不得保持每帧工作运行。
5. 延迟
- 每当插件延迟音频时,使用
setLatencySamples(“它引入的样本延迟数”,V:JUCE 文档)报告实际延迟:前瞻限制器或压缩器、线性相位滤波器、过采样滤波器。在宿主中验证报告已应用。 - 乐器从音符开(note-on)到发声的延迟为零。
- 线性相位过采样滤波器会增加延迟;最小相位滤波器不会,但会改变相位。有目的地选择并记录。
juce::dsp::Oversampling报告其延迟;报告它。 - 保持
getTailLengthSeconds对混响和延迟的准确性,以便宿主不会切断尾部。
6. 音质
混叠(Aliasing)
- 原始的锯齿波和方波存在高于奈奎斯特频率的谐波,会折叠成非谐波噪声。解决方法:带限步进方法(BLEP 系列)、微分多项式波形、每八度带限波表或过采样。
- 一种已发表的方法:微分多项式波形;作者的听觉模型评估发现,四阶方法在整架大钢琴的音域内感知上无混叠(V:Välimäki, Nam, Smith, Abel, IEEE TASLP 第 18 卷,第 4 期,2010 年)。
- 任何非线性(失真、饱和、波形折叠、硬限幅)都会产生新的谐波,从而导致混叠。在非线性阶段周围进行过采样,或使用基于反导数的抗混叠技术。Zavalishin 涵盖了波形整形的抗混叠(V:“The Art of VA Filter Design”,第 6.13 节)。
- 通过测量(见第 12 节)测试混叠,而不是通过肉眼观察振荡器代码。
滤波器
- 对于模拟建模滤波器,使用零延迟反馈(拓扑保持变换)结构。该书在第 3 章(第 3.9 节,零延迟反馈)中涵盖,Moog 式梯形滤波器在第 5 章,反馈环路饱和在第 6 章(V:Zavalishin,“The Art of VA Filter Design”,修订版 2.1.0,目录)。
- 对于普通 EQ 和实用滤波器,使用双二阶滤波器。音频 EQ 手册(R. Bristow-Johnson,W3C 工作组注释 2021)提供了低通、高通、带通、陷波、全通、峰值均衡和搁架滤波器(V)。
- 平滑截止和共振变化,将截止频率限制在奈奎斯特频率以下,保持共振在极端情况下的稳定性,并补偿电平以使高共振不会导致音量跳跃。
平滑而非刺耳(R,来自制作人的实践;每个都需测试)
- 刺耳通常来自混叠、硬限幅、中高频段的强共振峰以及过多未滤波的高频。
- 一个低通完全打开的领奏(lead)声音可能听起来太亮。大约 12 kHz 的起始截止频率是用户报告的个人偏好值,并非规则。
- 滤波器反馈环路内的饱和是模拟风格滤波器听起来平滑的常见原因(V:Zavalishin 涵盖了反馈环路饱和;感知上的主张是 R)。
- 非常小且缓慢的调制使静态音调保持生动,例如几个音分的音高漂移。在转换前计算:0.1% 大约为 1.7 音分,1% 大约为 17 音分,而 17 音分是明显可闻的失谐。保持在低端,除非失谐是目标。
- 厚实的音色来自分层、带有失谐扩展的齐奏(unison)、子振荡器和立体声宽度。检查单声道兼容性(将左和右相加,监听空洞或抵消的声音)。
增益分级和直流(DC)
- 使用浮点数运行内部链路,并保留足够的余量。切勿在链路内部削波。只在输出端有目的地放置限幅器或限制器。
- 在不对称非线性之后,使用单位数到几十赫兹范围的温和高通滤波器去除直流(R);用第 12 节中的直流测量方法验证。
- 匹配预设响度(见第 9 节),以便切换预设时电平不会跳跃。
7. 合成器设计
- 声音(Voices):预分配每个声音,绝不在音符开时分配。将复音数设为常量或用户设置。以短暂淡出方式偷取声音,绝不硬性切断。
- 包络:使用几毫秒的最小起音和释放时间以避免咔嗒声;在重新触发时从当前电平开始,而不是从零。
- 齐奏:随机或扩展起始相位、失谐扩展曲线、立体声扩展,以及不随声音数量增长的电平补偿。
- 超级锯齿波:Roland JP-8000 超级锯齿波被描述为类似于七个锯齿振荡器相互失谐(V:维基百科,Roland JP-8000)。存在详细分析:Adam Szabo,《如何模拟超级锯齿波》(KTH,2010)和 Alex Shore(2013),均在页面中引用。请阅读它们以了解失谐曲线和混合,而不是猜测数字。
- 漂移和模拟感:慢速随机调制音高和截止,每个声音单独进行,幅度小且不相关。
- 商业超级锯齿波合成器中看到的想法,仅用于学习;编写自己的代码和设计,绝不复制产品的代码、外观、名称或预设(R,通过听觉和测量验证):
- 每个振荡器一个慢速 LFO,频率间隔为无理比例(如黄金比例),以便组合运动不会明显重复。
- 一个“漂移”宏,按比例缩放失谐和漂移深度,在第二页上提供精细控制。
- 一个键跟踪高通滤波器作为独立控制(JP-8000 风格的超级锯齿波声音使用一个)。
- 可选的 2x 或 4x 过采样滤波器;将过采样延迟报告给宿主。
- 检查仪表上的 dB 刻度、每声音立体声扩展以及支持侧链的效果:一些产品中用户注意到的空白点。
- MIDI:使用块内每个 MIDI 事件的样本位置,使音符以样本精度开始。处理音高轮(默认范围两个半音,可配置)、延音踏板、全音符关闭和带有释放的音符关闭。如果乐器面向领奏或贝斯,提供单音、连奏和滑音。
- 波表:每八度或每音高范围进行带限;在帧之间交叉淡变以避免拉链噪声;插值(线性插值成本低,高阶插值消耗 CPU,测量混叠)。
8. 效果器设计
- 压缩器和限制器:在对数域中进行检测,具有平滑的起音和释放;应用增益平滑;添加自动或手动补偿增益;如果采用前瞻,则报告延迟。在链路末端的限制器如果需要承诺避免过冲,则需要真峰值或过采样的阈值。
- 多频段:使用 Linkwitz-Riley 分频器进行分频,以便频段总和恢复平坦;处理每个频段;重新组合。保持分频器相位一致,并报告任何增加的延迟。
- 延迟:分数延迟插值、反馈路径中的阻尼滤波器、速度同步、乒乓、避让(ducking)以及防止失控的反馈限制。
- 混响:反馈延迟网络或 Dattorro 板式拓扑(J. Dattorro,《效果设计第一部分》,《JAES》1997);在尾部衰减高频;轻微调制延迟线以避免金属声染色;暴露前置延迟、衰减、阻尼、大小和混合。
- 领奏声音的常见起始实践,通过听觉测试(R):使用大约 200 到 300 Hz 的高通滤波器将混响返回信号中的低频去除,滚降尾部的高频,并使用适中的混合比例。
- 合唱、合奏和移相器...
[省略了翻译后的更多内容]