Pedalboard 实时音频、参数自动化与外部乐器插件(VST/AU/MIDI)使用指南
Pedalboard 实时音频、参数自动化与外部乐器插件(VST/AU/MIDI)使用指南
本文以 docs/source/faq.rst 为主线,结合仓库源码(AudioStream.h、ExternalPlugin.h、python_bindings.cpp 与 test_audio_stream.py、test_external_plugins.py)展开,系统回答 Pedalboard 使用中的四个高频问题:能否处理实时音频、能否随时间改变插件参数、能否加载 VST/AU 乐器插件、插件能否接收 MIDI。读完本文,你将掌握 AudioStream 实时音频流的使用方法、分块处理与参数自动化技术,以及用 MIDI 驱动乐器插件渲染音频的完整方案。
一、Pedalboard 能否用于实时(real-time)音频?
可以。 自版本 0.7.0 起(Linux 支持于 0.9.14 加入,read/write 方法于 0.9.12 加入),Pedalboard 提供了 pedalboard.io.AudioStream 类,用于与真实音频设备交互:从输入设备(麦克风、音频接口)实时读取音频流,向输出设备(扬声器、耳机)写入音频,并可在音频流上实时挂载 Pedalboard 效果链。当前仓库版本为 0.9.25(见 version.py)。
1.1 AudioStream 的核心能力与构造参数
从 AudioStream.h 的 Python 绑定可知,AudioStream 构造函数接受以下参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
input_device_name |
None |
输入设备名称;为 None 时输入通道数强制为 0 |
output_device_name |
None |
输出设备名称;为 None 时输出通道数强制为 0 |
plugins |
None |
传入的 Pedalboard/Chain 对象,作为实时处理链 |
sample_rate |
设备默认值 | 采样率,传 0 表示使用设备默认值 |
buffer_size |
512 | 硬件与音频线程之间的缓冲区大小(帧数) |
allow_feedback |
False |
是否允许麦克风→扬声器的可能啸叫配置 |
num_input_channels |
1 | 输入通道数 |
num_output_channels |
2 | 输出通道数 |
构造函数在源码层面做了两处安全校验(AudioStream.h):
- 若输入设备名含 "microphone" 且输出设备名含 "speaker",且未显式传入
allow_feedback=True,会抛出RuntimeError,提示可能产生啸叫; - 输入与输出设备名必须至少提供一个,否则抛出
RuntimeError。
设备信息可通过类属性查询:AudioStream.input_device_names、output_device_names、default_input_device_name、default_output_device_name(分别对应源码中的 getDeviceNames/getDefaultDeviceName 静态方法)。
1.2 三种典型使用模式
① 一次性播放音频(AudioStream.play):
from pedalboard.io import AudioFile, AudioStream
with AudioFile("my_audio_file.mp3") as f:
chunk = f.read(f.samplerate * 10) # 读取 10 秒音频
AudioStream.play(chunk, f.samplerate) # 阻塞直至播放完毕
② 分块流式播放(stream.write):
with AudioStream(output_device="default") as stream:
with AudioFile("my_audio_file.mp3") as f:
while f.tell() < f.frames:
stream.write(f.read(512)) # 每次解码并播放 512 个采样
③ 实时效果处理(同时连接输入与输出设备):
from pedalboard import Pedalboard, Compressor, Gain, Reverb
from pedalboard.io import AudioStream
stream = AudioStream(
input_device_name=AudioStream.default_input_device_name,
output_device_name=AudioStream.default_output_device_name,
num_input_channels=2,
num_output_channels=2,
allow_feedback=True,
buffer_size=128,
sample_rate=44100,
)
stream.plugins = Pedalboard([
Reverb(wet_level=0.2),
Gain(1.0),
Compressor(),
])
stream.run() # 阻塞运行,直到收到 Ctrl-C
上面这段代码与仓库 examples/audio_monitoring_with_effects.py 一致,是一个可直接运行的实时音频监听示例。
1.3 运行时修改插件与同步/异步模式
AudioStream 实现 __enter__/__exit__ 上下文管理器:进入即开始流式传输,退出即停止。在 with 块内可动态增删插件、修改插件属性(源码文档见 AudioStream.h):
with AudioStream(input_device_name, output_device_name) as stream:
reverb = Reverb()
stream.plugins.append(reverb) # 运行时添加插件
reverb.wet_level = 1.0 # 运行时改参数
del stream.plugins[0] # 运行时删除插件
若不用上下文管理器,也可以同步调用 stream.run(),该方法会阻塞当前线程直到收到 KeyboardInterrupt。
底层实现上,AudioStream 采用了双 Pedalboard 加后台观察线程的架构(AudioStream.h):Python 侧修改 plugins 后,changeObserverThread 每 5ms 尝试获取互斥锁,将变更同步到音频线程专用的 livePedalboard;音频回调 audioDeviceIOCallback 则用 SpinLock 尝试加锁后逐插件 process,若锁未被获取(例如同一插件正在离线处理),则跳过该插件以免破坏其状态(AudioStream.h)。这一设计使得在音频线程上安全、实时地修改效果链成为可能。
1.4 录制与缓冲区告警
AudioStream.read(num_samples) 可从输入设备读取音频,不传参时返回缓冲区中全部可用数据;buffered_input_sample_count 属性反映当前缓冲帧数。需要特别注意的是:录音是实时操作,如果代码没有及时调用 read(),音频会被丢弃。此时 read() 默认抛出 RuntimeError(提示丢弃帧数),可通过设置 ignore_dropped_input = True 改为返回最近数据;dropped_input_frame_count 属性始终记录丢弃帧数,不受该设置影响(AudioStream.h)。write(audio, sample_rate) 要求传入采样率与输出设备一致,否则抛错,可用 StreamResampler 先重采样。
仓库的 tests/test_audio_stream.py 覆盖了设备枚举、上下文管理器启停、运行时动态增删插件、写入静音块等场景,可作为 API 用法的补充参考(注意:CI 无真实音频设备时相关测试会跳过)。
二、能否随时间改变插件的参数?
可以,而且这是官方 FAQ 推荐的进阶用法。 Pedalboard 没有内置的"参数包络/自动化"函数,但可以手动分块处理音频,在每块处理前修改插件属性,即可实现任意参数的随时间变化(自动化)。
核心思路来自 docs/source/faq.rst:将音频文件按固定步长分块读取,边处理边写入输出文件,每处理一块就按进度更新目标参数。完整代码:
from pedalboard.io import AudioFile
from pedalboard import Pedalboard, Compressor, Reverb
from tqdm import tqdm
board = Pedalboard([Compressor(), Reverb()])
reverb = board[1] # 取出链中第二个插件(Reverb)
# 步长越小过渡越平滑,但处理速度越慢
step_size_in_samples = 100
with AudioFile("sample.wav") as af:
# 打开输出文件,边处理边写,节省内存:
with AudioFile(
"sample-processed-output.wav", "w", af.samplerate, num_channels=af.num_channels
) as o:
# 实时显示处理进度:
with tqdm(total=af.frames, unit=' samples') as pbar:
for i in range(0, af.frames, step_size_in_samples):
chunk = af.read(step_size_in_samples)
# 让混响 wet 参数等于"已处理进度比例"(0% → 100% 渐变)
percentage_through_track = i / af.frames
reverb.wet_level = percentage_through_track
# 更新进度条:
pbar.update(chunk.shape[1])
# 处理本块音频;reset=False 保证混响尾音不被截断
output = board.process(chunk, af.samplerate, reset=False)
o.write(output)
几个实战要点:
- 步长选择:官方建议取 100 到 1000 之间(在 44.1kHz 采样率下约为 2ms~22ms)。步长足够小则听不出阶梯感/爆音,足够大则不至于显著拖慢处理速度;
reset=False是关键:分块处理同一段音频流时,必须让reset=False,否则每块的混响尾音(或延迟、滤波器的内部状态)会被清除,导致每块之间出现"切口";- 任意参数皆可自动化:不限于
wet_level——增益、压缩比、滤波器截止频率等任何暴露为 Python 属性的参数都可以在循环中赋值; - 内存友好:通过"边处理边写输出文件",长音频文件也不会撑爆内存。
三、能否用 VST 乐器(instruments)替代效果器(effects)?
可以。 自版本 0.7.4 起,Pedalboard 支持加载 VST3 乐器插件与 macOS Audio Unit 乐器插件。相关类为 pedalboard.VST3Plugin 与 pedalboard.AudioUnitPlugin。
3.1 插件格式支持范围
根据 load_plugin 的文档说明:
- VST3®:macOS、Windows、Linux 均支持;
- Audio Units:仅 macOS 支持。
3.2 加载插件的推荐方式:load_plugin
推荐统一通过 pedalboard.load_plugin(path_to_plugin_file, ...) 加载,它会自动尝试所有当前平台可用的插件类:
from pedalboard import load_plugin
plugin = load_plugin("../path-to-my-plugin-file")
assert plugin.is_effect # 或 plugin.is_instrument
load_plugin 的参数(pedalboard/_pedalboard.py):
| 参数 | 默认值 | 说明 |
|---|---|---|
path_to_plugin_file |
必填 | VST3® 或 Audio Unit 插件文件/包路径 |
parameter_values |
{} |
加载后立即设置的参数字典;键需为插件参数名(已规范化为 Python 标识符形式,与 .parameters 字典的键一致),加载时若键不存在会抛 AttributeError |
plugin_name |
None |
从"包含多个插件的包"中选中特定插件;若包内多个插件而未提供该参数,会抛出异常并列出可选名称 |
initialization_timeout |
10.0 |
尝试加载插件的秒数。部分插件启动时会异步加载资源,调大该值可给予更多时间(v0.7.6 起引入) |
加载失败时抛出 ImportError(文件不存在、格式不支持、AU 未安装到系统组件目录等);tests/test_external_plugins.py 中有对缺失路径抛 ImportError 的测试验证。
3.3 效果插件与乐器插件的判别与处理差异
源码通过 acceptsAudioInput()(判断主输入总线通道数是否大于 0)区分两类插件(ExternalPlugin.h),并暴露 is_effect / is_instrument 只读属性:
- 效果插件:接收音频输入,产出处理后的音频。若把音频喂给乐器插件,
process会抛出invalid_argument,提示"该插件不接受音频输入,可能是乐器插件"(ExternalPlugin.h); - 乐器插件:通常没有音频输入总线,只接收 MIDI 并生成音频。
效果插件处理示例(同样见 ExternalPlugin.h 文档):
from pedalboard import load_plugin
from pedalboard.io import AudioFile
plugin = load_plugin("../path-to-my-plugin-file")
assert plugin.is_effect
with AudioFile("input-audio.wav") as f:
output_audio = plugin(f.read(), f.samplerate)
3.4 插件状态管理与重置语义
在 ExternalPlugin.h 中定义了 ExternalPluginReloadType 枚举,用于描述插件调用 reset() 时的行为:
ClearsAudioOnReset:插件能正确清除内部缓冲,reset()只做轻量复位;PersistsAudioOnReset:插件在reset()后仍保留内部音频状态(可视为行为异常的插件),此时 Pedalboard 会整体重新实例化插件并尽量保持其参数;Unknown:行为未知(多为乐器插件),同样采取重新实例化的兜底策略。
detectReloadType() 在实例化时通过"静音测量噪声底 → 注入随机噪声 → 复位后再送静音"的方式探测插件是否真正清空了状态(ExternalPlugin.h)。此外,对乐器插件在启动时会自动发送一个中央 C(note 60)做"预热"(attemptToWarmUp(),见 ExternalPlugin.h),以等待其异步资源加载完成,超时由 initialization_timeout 控制。
四、Pedalboard 插件能否接收 MIDI?
可以,但有限制: 自版本 0.7.4 起,VST3Plugin 与 AudioUnitPlugin 均支持向乐器插件传递 MIDI 消息并渲染出音频;而效果插件目前还不能接收 MIDI(若向效果插件传 MIDI,renderMIDIMessages 会抛出 invalid_argument,见 ExternalPlugin.h)。
4.1 支持的 MIDI 消息格式
将 MIDI 消息传给插件的 process/__call__ 时,需传入一个 Python List,其中每项可以是以下三种之一(ExternalPlugin.h):
- 具有
bytes()方法与time属性的对象(例如mido.Message,mido并非 Pedalboard 自带依赖,需自行安装); - 形如
(midi_bytes: bytes, timestamp_in_seconds: float)的元组; - 形如
(midi_bytes: List[int], timestamp_in_seconds: float)的元组。
这些消息最终由 parseMidiBufferFromPython 借助 pedalboard/midi_utils.py 的 normalize_midi_messages 统一规范化后,写入 JUCE 的 MidiBuffer(ExternalPlugin.h)。tests/test_external_plugins.py 对上述三种格式均有参数化测试验证。
时间戳语义:0 表示缓冲区起点,等于 duration 表示缓冲区终点,大于 duration 的 MIDI 消息会被忽略。输出音频长度为 duration 秒,采样率由 sample_rate 参数指定。
4.2 用 MIDI 驱动乐器插件渲染音频
官方示例(来自 ExternalPlugin.h):
from pedalboard import load_plugin
from pedalboard.io import AudioFile
from mido import Message # 非 Pedalboard 自带,仅作便捷示例
plugin = load_plugin("../path-to-my-plugin-file")
assert plugin.is_instrument
sample_rate = 44100
num_channels = 2
with AudioFile("output-audio.wav", "w", sample_rate, num_channels) as f:
f.write(plugin(
[Message("note_on", note=60), Message("note_off", note=60, time=4)],
sample_rate=sample_rate,
duration=5,
num_channels=num_channels
))
4.3 常见坑与参数校验
duration与sample_rate顺序写反是高频错误。源码做了防御性检测:当duration恰为 48000/44100/22050/11025 且sample_rate小于 8000 时,会抛出提示"这两个参数看起来被颠倒了,请交换顺序"(ExternalPlugin.h),对应测试 tests/test_external_plugins.py;- 不要直接使用
mido.MidiFile的 track 对象:其事件时间戳是 tick 而非秒,Pedalboard 期望的时间戳单位为秒,直接传入会报错(测试见 tests/test_external_plugins.py);应先自行将 tick 转换为秒; buffer_size控制单次返回音频块的大小:调大可提速但会增加内存占用;- 传入的 64 位浮点音频会自动转为 32 位再处理;
- 多次调用
process处理同一 MIDI/音频流时应保持reset=False,避免插件内部状态被清空。
五、总结
围绕官方 FAQ 的四个高频问题,可以得到如下结论与上手路线:
- 实时音频:从 v0.7.0 起用
pedalboard.io.AudioStream连接真实设备,支持实时挂载/修改效果链、播放、录制与缓冲区告警,Linux 支持自 v0.9.14 起可用; - 参数随时间变化:虽然没有内置自动化函数,但通过"固定步长分块 +
reset=False+ 逐块赋值参数"的循环即可平滑实现任意参数的自动化,步长建议 100~1000 个采样; - 乐器插件:从 v0.7.4 起
load_plugin可加载 VST3®(全平台)与 macOS Audio Unit,并通过is_effect/is_instrument判别插件类型; - MIDI:乐器插件支持三种格式的 MIDI 消息输入并渲染音频,效果插件暂不支持;注意
duration/sample_rate参数顺序、时间戳单位(秒)等细节。
若想进一步深入,建议阅读 AudioStream.h、ExternalPlugin.h、midi_utils.py 及对应测试 test_audio_stream.py、test_external_plugins.py,并参考 examples/audio_monitoring_with_effects.py 运行实时效果示例。