AEJoy —— 表达式之音频触发动画【JS】

263 阅读1分钟

效果图

2.gif

想法

假设我们开发了一个表达式来对一个特定的属性进行动画。我们设置了它,使动画从时间 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 示意图

image.png

设计

我们在这里使用的技术适用于基于全局对象时间(表示当前的计算时间)计算结果的表达式(如上面的表达式)。

我们的基本计划在概念上将相当简单(但细节将有点棘手)。首先,我们需要在表达式中添加一个“节拍检测器”。为了做到这一点,我们将添加一些从当前帧开始并在时间上向后 '<-' 移动的代码,逐帧检查音频振幅,寻找振幅从低于阈值到高于阈值的最近过渡。一旦我们找到了这个过渡,我们就将变量 t 设置为自过渡发生以来所经过的时间(以秒为单位)。

image.png

然后,我们所要做的就是修改原始的 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]

(完)