效果图
想法
假设我们开发了一个表达式来对一个特定的属性进行动画。我们设置了它,使动画从时间 0 开始,并在计算中使用全局对象时间。我们想要做的是,当音频水平越过某个阈值时,触发这个表达式。例如,假设我们有以下表达式,当应用到一个图层的缩放属性时,它会产生衰减的抖动:
amp = 25;
freq = 5;
decay = 4.0;
angle = freq * 2 * Math.PI * time;
wobble = 1 + amp * Math.sin(angle) / Math.exp(decay * time) / 100;
[value[0] * wobble, value[1] / wobble]
如上的表达式,从时间 0 开始,将导致层抖动。我们真正想要的是只在音频水平越过阈值时才执行抖动表达式。
上:wobble 示意图
下:1./wobble 示意图
设计
我们在这里使用的技术适用于基于全局对象时间(表示当前的计算时间)计算结果的表达式(如上面的表达式)。
我们的基本计划在概念上将相当简单(但细节将有点棘手)。首先,我们需要在表达式中添加一个“节拍检测器”。为了做到这一点,我们将添加一些从当前帧开始并在时间上向后 '<-' 移动的代码,逐帧检查音频振幅,寻找振幅从低于阈值到高于阈值的最近过渡。一旦我们找到了这个过渡,我们就将变量 t 设置为自过渡发生以来所经过的时间(以秒为单位)。
然后,我们所要做的就是修改原始的 wobble 表达式代码,以便任何对 time 的引用都被新变量 t 的引用所替换。这应该会导致 wobble 代码在任何发生转换的时候开始执行。
表达式代码
threshold = 10.0;
audioLev = thisComp.layer("Audio Amplitude").effect("Both Channels")("Slider");
/// @note 寻找最近的(从低阈值到高阈值)过渡
above = false;
frame = Math.round(time / thisComp.frameDuration);
while (true) {
t = frame * thisComp.frameDuration;
if (above) {
/// @note 如果找到小于阈值的音频水平
if (audioLev.valueAtTime(t) < threshold) {
frame++; ///< 向前 '->' 一帧,跳出循环
break;
}
} else if (audioLev.valueAtTime(t) >= threshold) {
above = true; ///< 如果找到大于阈值的音频水平
}
if (frame == 0) {
break;
}
frame-- ///< 逐帧向后 '<-' 寻找
}
if (!above) {
t = 0;
} else {
t = time - frame * thisComp.frameDuration;
}
amp = 25;
freq = 5;
decay = 4.0;
angle = freq * 2 * Math.PI * t;
wobble = 1 + amp * Math.sin(angle) / Math.exp(decay * t) / 100;
[value[0] * wobble, value[1] / wobble]
(完)