同屏连线背后的代码 - SJTU-Art-Center/ACLiveConsole GitHub Wiki

0660 同屏连线开发

行动代号:PIP

0661 变换矩阵

在 WPF 中,一个控件在 2 维空间的变换(RenderTransform)通过一个三阶方阵定义,根据变换类的定义,用代码写就是:

Matrix M = new Matrix(
    M11, M12,
    M21, M22,
    OffsetX, OffsetY
);

可见,你只能更改其中的六个变量,第 3 列是闲置的。初始情况下,控件以几何中心作为渲染原点(RenderOrigin),初始变换矩阵 M_i 为恒等矩阵(I)。后来我们需要将其变换为 M_f 后,横向基向量 i 将会被变换为 [M11,M21],纵向基向量 j 将会被变换为 [M12,M22]。渲染原点将会被平移至 [OffsetX,OffsetY] 的位置。

0662 定比分点

我们可能不希望直接硬切到另一个状态。这时我们就需要插值地算出中间状态。AC Live Console 使用直线形的移动方式(别问我为什么不是贝塞尔曲线,问就是太麻烦了),数学上的定比分点公式在这里也可以套用在矩阵上来计算中间状态:

或许你会认为代码应该这样写:

Matrix M = M_i + p * (M_f - M_i);

p 代表进度,范围是 [0,1]

遗憾的是,在 C# 中矩阵运算是没有定义的,如果需要的话需要重载,但是我比较懒,还是这样写了代码:

// Line 2862
Matrix backMatrix = new Matrix(
backMatrix_init.M11 + (backMatrix_fin.M11 - backMatrix_init.M11) * progress, 0,
0, backMatrix_init.M22 + (backMatrix_fin.M22 - backMatrix_init.M22) * progress,
backMatrix_init.OffsetX + (backMatrix_fin.OffsetX - backMatrix_init.OffsetX) * progress, backMatrix_init.OffsetY + (backMatrix_fin.OffsetY - backMatrix_init.OffsetY) * progress);

然后,应用这样的矩阵变换于空间的 RenderTransform 属性上:

// Line 2866
focaldephov.BackImg.RenderTransform = new MatrixTransform(backMatrix);

0663 进度定义

那么定比 p 如何计算呢?AC Live Console 通过滑动条的值(Value)来计算。在同屏连线模式下,第一次按下按钮,刷新目的地,Value 将从 1 减到 0.5;第二次按下相同的按钮,刷新目的地,Value 将从 0.5 减到 0。我们更希望通过一个连续的函数来定义这个过程(哈哈哈 我后来才发现我傻在了这个地方),就这样定义:

// Line 2861
double progress = 1 - Math.Abs(2 * (ProgressTran.Value - 0.5));

你可以通过手动滑动滑块来体验这个过程。

0664 缓动进度

但是,这种线性的定比分点函数对于动画而言还是太机械了。那么怎么去改进呢?

时间重映射! 我们只要更改滑块移动的快慢(缓动地更改 ProgressTran.Value)就可以产生更加顺滑的动画,这也就用到了缓动函数来产生这种效果。这个道理和弹幕动画是一致的。

0665 控件遮罩

我们看到 AC Live Console 提供了肖像型同屏的模式,该模式下需要将画面两边裁切掉。

这时候我们就使用了 Clip 属性,将 Image 控件的定义如下:

<Image x:Name="BackImg" Width="{Binding ActualWidth, ElementName=grid, Mode=OneWay}" Height="{Binding ActualHeight, ElementName=grid, Mode=OneWay}" RenderTransformOrigin="0.5,0.5" >
    <Image.Clip>
        <RectangleGeometry x:Name="BackClipRect"/>
    </Image.Clip>
</Image>

添加了一个矩形的选区来裁切。

Rect R = new Rect(pointX, pointY,
Width, Height);

这时我们只要在上面的基础上,对矩形的性状进行计算就可以完成裁切的效果:

// Line 2874
Rect backRect = new Rect(
backRect_init.X + (backRect_fin.X - backRect_init.X) * progress,
backRect_init.Y + (backRect_fin.Y - backRect_init.Y) * progress,
backRect_init.Width + (backRect_fin.Width - backRect_init.Width) * progress,
backRect_init.Height + (backRect_fin.Height - backRect_init.Height) * progress
);
focaldephov.BackClipRect.Rect = backRect;

又是没有重载运算符的代码啊

0666 整体框架与切换原理

综合一下,代码框图大致如下:

Function Description
TranEffect 改变同屏连线的状态
TranEffectChanged() 刷新控件状态
DefineStates() 根据样式定义目的地状态
TranEffectSytle() 定比分点计算
selectItem(byte to) 转换到该监视

第二次按下反向定义为恒等是因为进度的第二次是由 100 减少到 0,恰好相反。

在预监(PVW)启用后,TranImg 将会加载BackImg 源,而 BackImg 将会加载预监的源。

  • 如果是标准切换,将会直接地线性变化。
  • 如果是同屏模式下的同模式切换,第一部分是线性到100%的。后面一直保持这个不透明度。
  • 而如果是不同模式切换,将会一直保持100%。

0667 样式定义

0667.A 全比例同屏

0667.B 肖像型同屏(左右、上下)

0667.C 小窗模式

0668 音控台

点击直播监视器右上方的按钮进入音控台。

Item Description
线路编号 对应于监视器左上、右上、左下
线路名称 对应监视器的源名称
输出开关 切换该音道是否开启
音控滑块 音量 0~100
静音 音控滑块变为 0
独奏 切换为只有该音道是开启的
智慧型音控 用 J-cut 或 L-cut 的方式切换声音

0669 智慧型音控台

当启用智慧型音控台后,可以使用两种方式切换音频:

  • J-cut:过渡开始,主监声音退出,预监声音进入。
  • L-cut:过渡结束,主监声音退出,预监声音进入。
  • FADE:线性改变两个源的音量。

这主要对于淡入淡出(快捷键A,S,D,F)的切换模式。同屏模式下两者并存;直切情况下,直接切换。只有在打开音控台并启用智慧型音控台时才会生效。

⚠️ **GitHub.com Fallback** ⚠️