引言
本文将持续研究隐藏在电音合成软件背后的数学问题:线性代数、信号与系统、和声学基础。以上的数学知识与游戏开发、计算机音频、计算机图像联系紧密。正因为拥有强大的数学基础,计算机模拟的音像世界才可以像今天这样逼真。数学在音频编辑器的开发中占有重要的位置。对此数学单独进行研究,由此显得非常重要。本文将对声音和合成和显示进行探究,进而使用这一些专业知识编写音频编辑器或图形程序。
一.线性代数
1.点乘
我们知道点乘在数学中,又称数量积。只需要记住,向量点乘就是对应分量乘积的和,其结果是一个标量。在平面中,两个向量的点乘可表示为:
设
则
一般来说,点乘的结果描述了两个向量的相似程度,点乘结果越大,两向量越接近。点乘等于向量的大小与向量夹角cos值的积。其公式为:
实际上,我们在计算中,更重要的是求出两个向量的之间的夹角。对上述的公式(1)稍作变换,可得:
值得注意的是,此公式十分重要。在本文中被用于Unity仿真中的向量夹角计算。
2.线性空间变换
方阵能描述任意的线性变换。其中,线性变换,从感性上讲,指的是对图片或函数图像等其所在空间的,拉伸、旋转、仿射等变换。
想象任意一个2维空间的向量,比如说:。那么,向量a其实可以表示为:。由于向量可视为空间中的一个点,向量表示从原点向走3个单位后,再向走个单位。更进一步地,对于任意一个向量:,可以表示为:从原点出发,向轴走个单位后,再向轴方向走个单位,即到达向量的所在地。那么,任意一个二维空间中的向量,可以表示为基向量的线性组合。
一个任意的向量,被描述为了两个基向量:的线性组合。但实际上,基向量不一定要相互垂直(讲人话:x轴和y轴不一定要相互垂直)。只要两个向量不在一条直线上,就可以构成一组基向量来描述二维空间中的任意一个点。
以下是几个不同基向量所张成的空间。为了方便观察,我在其中放置一样的函数。可以观察到,随着选取的基向量的不同,空间可以发生放大、缩小、切变等线性变换。存在于里面的函数图像,虽然位置没有发生改变,但是由于函数所处空间的改变,函数“被迫”发生了改变。
设基向量,张成了一个二维平面空间。使用基向量X,Y构成一个的矩阵:
用一个向量乘以该矩阵,则称为对向量的一次“变换”。
3. 旋转
问题的提出:
将一个函数图像逆时针旋转
具体求解步骤:
随机生成一个函数序列
构造方阵
函数序列每一点的坐标,乘以方阵
显示该函数图像
函数序列生成:
使用matlab自带的函数生成随机离散序列,使用函数对进行数据平滑。
其代码如下:
clc;
clear;
%%
%%随机生成一个起点和终点都在0附近,均值为0的函数波形
n = 300;
x = 0.5*rand(1,n);
x = x - mean(x);
x(1:10) = 0;
x(n-10:n) = 0;
for i = 1:10
x = smooth(x,10);
end
x(1) = 0;
x(length(x)) = 0;
X = zeros(n,2);
X(:,1) = linspace(0,1,n);
X(:,2) = x;
plot(X(:,1),X(:,2),'.')
构造方阵:
在笛卡尔坐标系中,基向量,.使得向量向逆时针旋转,可得:
则,
结果演示:
根据上文求解步骤编写代码,实现函数旋转算法。代码运行结果如下所示。
稳定性测试:
使用Matlab,随机生成一个函数序列,使用开发的旋转算法持续旋转函数图像,并将结果打印出来,观察算法的稳定性。由结果可见,代码对函数图像的旋转稳定可靠。
核心代码:
for angle = 1:10:360
%目标向量
pos1 = [cosd(angle);sind(angle)];
pos2 = [0;0];
pos3 = pos1 - pos2;
pos0 = [1;0];
%%
if(pos3(2)>=0)
alpha = acos(sum(pos3.*pos0)/(norm(pos3)*norm(pos0)));
else
alpha = -1*acos(sum(pos3.*pos0)/(norm(pos3)*norm(pos0)));
end
%(alpha/pi)*180
temp = [cos(alpha) sin(alpha);...
cos(alpha + pi/2) sin(alpha+ pi/2);];
A = norm(pos3)/1;
result = X*(A*temp) + pos2';
plot(result(:,1),result(:,2),'.');
hold on
end
其结果如下:
模型的改进:
令人兴奋的是,将图片放置在空间中,使用方阵M对空间进行线性变换,可以很方便实现图片的放大、缩小、旋转操作。只需写一点点matlab代码。便可得到以下结果:
Unity仿真
在matlab完成算法探究以后,在游戏开发引擎Unity上实现该算法,完成算法的落地。
具体求解步骤:
使用Unity搭建参加场景,创建地面(Plane)、圆柱(Cylinder)等物体
编写脚本,绑定组件。脚本逻辑是:当用户按下鼠标时,摄像头坐标系中发出的射线与地面相互碰撞。根据此来确定基向量以及方阵。
根据方阵计算组件线段渲染器(LineRender)应该所处的位置
显示该函数图像
稳定性测试:
多次地、随机地点击游戏画面,测试算法的稳定性和性能。实验结果表明,算法稳定可靠地运行,完成了对算法的落地。.
代码:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LineTest : MonoBehaviour
{
public float length = 0;
public float height = 0;//线段的高度
public float w = 0; //角频率
public float A = 0; //震动幅度
private Vector3 Vec; //用于存储每一点的坐标
private LineRenderer lineRenderer;
// Start is called before the first frame update
void Start()
{
Vec = new Vector3();
//设置长度
GetComponent<LineRenderer>().positionCount = (int)length;
}
public void Run(float x,float y){
float l = (float)Math.Sqrt(x*x + y*y);//模长,假设0为原点
float alpha = 0; //用于求向量夹角
for (int i=0;i<length;i++){
float posX = (float)(i/length);
float posY = A*(float)Math.Sin(w*posX);
alpha = 0;
if(y>=0){
alpha = (float)Math.Acos(x/l);
}
else{
alpha = -1*(float)Math.Acos(x/l);
}
Vec.x = 0.1f*(float)(posX*l*Math.Cos(alpha) + posY*l*Math.Cos(alpha + Math.PI/2));
Vec.y = height;
Vec.z = 0.1f*(float)(posX*l*Math.Sin(alpha) + posY*l*Math.Sin(alpha + Math.PI/2));
GetComponent<LineRenderer>().SetPosition(i,Vec);
}
}
// Update is called once per frame
void Update()
{
}
}