如何在 SuperCollider 中暂停调制位置,并在下一次触发时反向继续
要暂停调制位置并反向继续,请让相位生成器保持运行,同时将增量设为零;然后在下一次触发时将增量切换为负值。不要将相位重置输入连接到这些触发信号,以便运动从保持的位置继续。
本教程将在 SuperCollider 中构建这种行为:音高先上升,第一次触发时暂停,第二次触发时沿原路返回。它适合想要通过连续事件控制可重复调制动作的声音设计师。同一个相位信号之后还可以驱动另一个参数。
暂停期间必须保持什么?
将调制视为两个相连的操作:一个位置在周期内移动,映射则将该位置转换为有用的参数值。在这里,位置从零向一移动,映射将其转换为振荡器频率。
SuperCollider 的 Phasor 用来表示位置。它的 rate 参数指定每个采样的增量;而它的触发输入会跳到 resetPos。这是两个独立的控制,因此用于改变方向的触发信号不应同时重置位置。Phasor 文档
该实现会保留内部电平,并将速率加到该电平上。由此可知,速率为零时电平保持不变,速率为负时电平向下移动。下面的补丁直接应用了这一点,在暂停期间保留生成器的状态。SuperCollider 3.14.0 TriggerUGens.cpp 实现
每次连续触发应执行什么操作?
连接声音之前,先定义事件序列。此补丁一开始便向前移动。每次后续触发都会推进一个状态:
| 收到的触发次数 | 状态 | 方向乘数 |
|---|---|---|
| 0 | 向前移动 | +1 |
| 1 | 保持当前位置 | 0 |
| 2 | 反向继续 | −1 |
| 3 | 保持当前位置 | 0 |
| 4 | 向前继续 | +1 |
这个四状态循环将两种保持状态分开:一种之后进入反向运动,另一种之后进入正向运动。这样无需额外的方向按钮,也能让重复动作保持可预测。
当输入从非正值变为正值时,PulseCount 会递增。对计数取模四即可得到重复的索引 0–3。随后 Select 从 [1, 0, -1, 0] 中选取相应乘数。这就是 PulseCount 和 Select 文档中说明的用途。
如何构建完整补丁?
使用音频输出正常工作的本地 SuperCollider 安装;此示例依据 3.14.0 文档编写。它使用内置单元发生器并自行生成声音,因此不需要采样文件或扩展。完整求值下面的代码块一次,然后等待就绪消息。
如果服务器尚未启动,waitForBoot 会启动它。创建合成器之前会先发送定义,而 s.sync 会等待尚未完成的服务器操作。请参阅 Server 文档和 SynthDef 文档。
(
s = Server.default;
s.waitForBoot({
SynthDef(\pauseReverseMod, {
|out = 0, t_step = 0, cycleSeconds = 8, amp = 0.08|
var state, direction, increment, phase, frequency, sound;
state = PulseCount.kr(t_step) % 4;
direction = Select.kr(state, [1, 0, -1, 0]);
// A full 0..1 traversal takes cycleSeconds seconds.
increment = direction * SampleDur.ir
/ cycleSeconds.max(0.01);
// State-changing triggers never reset this phase.
phase = Phasor.ar(
trig: 0,
rate: increment,
start: 0,
end: 1,
resetPos: 0
);
frequency = LinExp.ar(phase, 0, 1, 220, 880);
sound = SinOsc.ar(frequency, 0, amp);
Out.ar(out, sound ! 2);
Poll.kr(Impulse.kr(2), state, "state");
Poll.kr(Impulse.kr(2), A2K.kr(phase), "phase");
}).add;
s.sync;
~pauseReverseSynth = Synth(\pauseReverseMod);
"Ready: evaluate ~pauseReverseSynth.set(\\t_step, 1);".postln;
});
)
增量等于方向乘以采样时长,再除以周期时长。SampleDur 返回服务器采样率的倒数,因此八秒的设置不依赖特定的采样率。SampleDur 文档
音高映射范围为 220–880 Hz。LinExp 将线性输入范围转换为指数输出范围;其目标端点必须非零且符号相同。此处的两个端点都满足这一要求。LinExp 文档
我根据检索到的文档检查了 SuperCollider 示例。研究环境中没有 SuperCollider 运行时,因此我没有运行或试听此补丁。另行进行的确定性算术检查验证了方向序列和增量为零时的保持行为。
如何暂停并反向继续?
收到就绪消息后,让音高上升约两秒。然后求值一次此交互命令:
~pauseReverseSynth.set(\t_step, 1);
状态应变为 1,打印出的相位也应稳定在一个数值上。稍等片刻,再次求值同一命令。状态应变为 2,相位则应从保持的位置开始下降。第三次求值会保持下降中的扫描;第四次则会再次向前移动。
t_ 前缀会使 t_step 成为触发控制。设置它会产生一个控制速率脉冲,因此在间隔适当的常规求值中,无需手动单独发送零值,也能重复使用同一命令。SynthDef 触发控制文档
举例来说,八秒周期每秒会移动整个范围的 0.125。若在两秒后暂停,位置大约会停在 0.25。无论暂停多久,反向移动一秒都会使其到达约 0.125。这些是计算所得的预期值;实际停止位置取决于手动输入命令的时机。
结束时,求值清理命令:
~pauseReverseSynth.free;
如何确认位置确实保持不变?
将打印出的相位作为主要诊断依据。正向运动时它应当增加;处于任一保持状态时应当不变;反向运动时应当减少,直到到达周期边界。
此补丁每秒打印两次,因此显示的是行为采样结果,而非每个采样点的值。暂停时观察连续几次读数,然后比较恢复运动后的前几次下降读数。Poll 用于调试单元发生器的值;将此定义改作常规用途时,请删除两行轮询代码。Poll 文档
这项测试比单靠聆听更有参考价值。声音稳定说明目标参数停止变化,而稳定的相位读数则能确认底层位置也停止了。整个测试期间保持映射不变,结果会更容易解读。
为什么补丁可能会重新开始、跳变或漏掉触发?
恢复时重新开始。 检查该事件是否也传到了 Phasor 的触发输入。该输入会明确重置位置。在此补丁中,该输入保持为零,事件只传给状态计数器。Phasor 文档
到达端点后发生跳变。 此示例采用循环模式。实现会将相位回绕到配置范围内,负向移动时也一样。反向移动越过零后,相位会回到接近上边界的位置;使用此斜坡映射时,音高也会随之跳变。请先在远离该边界的位置测试暂停和反转。Phasor 实现
多次快速输入命令却没有产生多个状态。 计数器需要彼此独立的上升沿。触发控制按控制速率运行,因此不要假定同一个控制块内收到的多个命令会变成多个独立事件。先尝试间隔明确的命令;对于需要精确定时的序列,应围绕独立脉冲设计事件源。PulseCount 文档,SynthDef 文档
保持相位时声音仍在变化。 检查目标映射或其他声音参数是否也在变化。进行此测试时,请保持音高端点不变,并通过打印出的相位单独观察调制传输。
完整动作依次为:向前、保持、反向、保持,然后再次向前。确认这一序列正常后,可保留状态计数器和相位生成器作为可重复使用的调制源,并根据所制作的声音调整目标映射。
来源
- Phasor — SuperCollider 3.14.0 帮助文档
- TriggerUGens.cpp — SuperCollider,Version-3.14.0
- PulseCount — SuperCollider 3.14.0 帮助文档
- Select — SuperCollider 3.14.0 帮助文档
- Server — SuperCollider 3.14.0 帮助文档
- SynthDef — SuperCollider 3.14.0 帮助文档
- SampleDur — SuperCollider 3.14.0 帮助文档
- LinExp — SuperCollider 3.14.0 帮助文档
- Poll — SuperCollider 3.14.0 帮助文档
