· 5 分钟阅读 · 2004 字

游戏音频交叉淡化:等功率曲线为什么仍会变响或静音

两段环境音在场景交界处交叉淡化,改用正弦、余弦曲线后,中间不再凹下去。但把同一段循环音复制成两路、从同一个采样点播放,淡化中点反而高出约 3.01 dB;再把第二路反相,中点几乎完全静音。三种结果可以来自同一组增益,“等功率”这个名称没有约束两路素材之间的关系。

本篇用独立 C# 工程 FadeLab 保留这三个反例。它处理归一化单声道离散样本,不调用音频设备;功率指一个固定样本块的均方值,不等同于电声输出功率或主观响度。这个范围足以回答混音器应该提供一条默认曲线,还是需要把素材关系写进切换合同。

摄影纸面上的两种淡化增益曲线,精确曲线由代码绘制

两路各自的能量之外,还有交叉项

设输出 y=a*x+b*z,a、b 是本次采样采用的两路增益,x、z 是对齐的输入样本。将平方展开后在同一个窗口取平均:

Pout = a² Px + b² Pz + 2ab C
Px = mean(x²), Pz = mean(z²), C = mean(xz)

这里使用原始二阶矩,不减去均值;只有零均值信号时才能直接把 C 解释为通常意义的协方差。若两路均方值相等、都为 P,且 C 为零,那么 a²+b²=1 才能推出 Pout=P。常用的等功率曲线取 a=cos(pi*t/2)、b=sin(pi*t/2),满足这条平方和关系。

线性曲线则取 a=1-t、b=t。对互不相关且功率相同的输入,中点平方和为 0.5,输出均方值减半,对应约 -3.01 dB。它没有凭空丢失数据,只是两个衰减后的、互不相关的分量按平方相加。

固定输入 x=[1,0,-1,0],均方值为 0.5。第二路分别取正交的 [0,1,0,-1]、相同的 x,以及反相的 -x。它们各自功率相等,但 C 分别为 0、0.5、-0.5。中点结果如下,表内接近零的浮点残差按零展示:

第二路 线性输出均方值 等功率输出均方值 等功率输出峰值
正交 0.25 0.5 0.707107
同相同样本 0.5 1 1.414214
反相同样本 0 约 0 约 0

同相时 y=(a+b)x,所以保持原信号幅度需要的是 a+b=1,线性淡化恰好成立。反相时 y=(a-b)x,只要连续地从“仅第一路”过渡到“仅第二路”,系数就会经过零;换一条连续的实数增益曲线,不能凭空消除这个过零点。应当先处理素材极性、相位或重复播放关系。

曲线只负责增益,混音结果保留峰值

Crossfade.cs 中 Crossfade.At 接受归一化进度 t,而非秒数;端点明确返回 (1,0) 与 (0,1),保证另一条流精确静音。Mix 校验输入块,再生成完整输出,调用链为 Program → Mix → At,结果交给 Measure 检查均方值与峰值。

namespace FadeLab;

public enum Curve { Linear, EqualPower }
public readonly record struct Gains(double A, double B);
public readonly record struct Stats(double Power, double Peak);

public static class Crossfade
{
    public static Gains At(double t, Curve curve)
    {
        if (!double.IsFinite(t) || t < 0 || t > 1)
            throw new ArgumentOutOfRangeException(nameof(t));
        if (curve != Curve.Linear && curve != Curve.EqualPower)
            throw new ArgumentOutOfRangeException(nameof(curve));
        if (t == 0) return new(1, 0);
        if (t == 1) return new(0, 1);
        return curve == Curve.Linear
            ? new(1 - t, t)
            : new(Math.Cos(Math.PI * t / 2), Math.Sin(Math.PI * t / 2));
    }

    public static double[] Mix(double[] a, double[] b, double t, Curve curve)
    {
        if (a.Length == 0 || a.Length != b.Length)
            throw new ArgumentException("Expected equal nonempty blocks.");
        var gains = At(t, curve);
        var output = new double[a.Length];
        for (int i = 0; i < a.Length; i++)
        {
            if (!double.IsFinite(a[i]) || Math.Abs(a[i]) > 1 ||
                !double.IsFinite(b[i]) || Math.Abs(b[i]) > 1)
                throw new ArgumentException("Expected finite normalized samples.");
            // 保留超出单位幅度的结果,交给后续总线处理余量。
            output[i] = gains.A * a[i] + gains.B * b[i];
        }
        return output;
    }

    public static Stats Measure(double[] samples)
    {
        if (samples.Length == 0) throw new ArgumentException("Empty block.");
        double sum = 0, peak = 0;
        foreach (double value in samples)
        {
            if (!double.IsFinite(value)) throw new ArgumentException("Nonfinite sample.");
            sum += value * value;
            peak = Math.Max(peak, Math.Abs(value));
        }
        return new(sum / samples.Length, peak);
    }
}

代码没有在混合后把结果夹到 [-1,1]。当前同相中点已经给出峰值 1.414214,直接夹断会改变波形,掩盖总线余量不足。对两路幅度各不超过 1 的输入,非负增益满足 |y|<=a+b;这组等功率曲线的上界为根号二,同相样本确实达到了它。若要对任意允许输入保证此处不超出单位幅度,可以预留约 3.01 dB 幅度余量;这会降低端点电平,必须成为明确的混音政策,后续叠加其他声部还需另算预算。

这份实现分配数组、逐块使用固定 t,适合作为数值探针,不能直接当作实时音频回调。产品接入时,应让音频线程持有采样进度、逐样本推进增益,并在预分配缓冲区里混合;主线程提供切换目标和时长。若只在画面帧边界跳变音量,块内公式正确也不能证明没有控制阶跃。这里没有测量设备延迟、爆音或听感。

同一等功率曲线在正交、同相、反相输入下的功率比及峰值

验证曲线时,把素材关系一起固定

Program.cs 对三组样本的中点分别断言两条曲线的均方值,检查精确端点及同相峰值超限,再覆盖空块、长度不匹配、非有限样本、越界幅度、非法进度和未知枚举。关键失败回归直接保留了“等功率仍然超幅”的结果:

Check(Crossfade.Measure(Crossfade.Mix(a, a, .5, Curve.EqualPower)).Peak > 1, "headroom needed");

另外按 0.001 间隔遍历 [0,1] 的 1001 个位置,组合两条曲线和三种素材关系,得到 6006 个输出块、24024 次逐样本峰值约束检查。每块实测均方值都与含交叉项的展开式对比;同时验证正交输入的等功率结果恒定、同相输入的线性结果恒定。命令在研究工程目录执行:

dotnet build FadeLab.csproj -c Release --nologo
dotnet run --project FadeLab.csproj -c Release --no-build

2026-10-17 的 .NET 9 Release 构建退出码 0,零警告、零错误,耗时 16.04 秒;运行退出码 0,24 项通过、0 失败、0 跳过,功能检查计时 75.6019 毫秒。关键输出保留如下,耗时不作为实时混音吞吐基准:

passed=24 failed=0 skipped=0
orthogonal equalPower=0.5 peak=0.7071067811865476
same equalPower=0.9999999999999998 peak=1.414213562373095
inverted equalPower=6.162975822039155E-33 peak=1.1102230246251565E-16
timeSteps=1001 blocks=6006 sampleChecks=24024
maxPowerError=3.3306690738754696E-16

因此,场景环境音、同一音乐的两份播放实例、共享相位的音乐分轨,不应无条件走同一条淡化政策。等功率可作为等电平、低相关素材的候选;同一信号的冗余切换优先保证时间对齐并采用幅度互补;明显反相关的内容应回到素材与播放关系处理。两路本身电平不一致时,即便 C 为零,平方和恒定也无法令输出恒定,入场前的电平匹配仍有独立责任。

实际素材的相关性会随窗口、频段、播放偏移和声道改变。短窗自动归一化若试图补回反相抵消,可能在接近零的能量上要求极高增益;不能把它当成无条件修复。工程上应先明确切换类别,保留可观察的峰值与总线余量,再对真实素材做多声道、采样率和听感验收。当前探针给出的决策依据是:增益曲线控制两路权重,交叉项决定它们叠加之后是否仍符合电平预期。