UWP简单示例(一):快速合成音乐MV
说明
本文发布较早,查看最新动态,请关注 TypeScript 版本。(2020 年 1 月 注)

在线演示: 音频可视化(TypeScript)
准备
IDE:Visual Studio
Nuget包:SharpDx.XAudio2
Nuget包:Win2D.UWP
了解学习:Win2D 官方博客
了解学习:Win2D 官方示例
第一节 波形
获取实时时域数据。
Imports SharpDX.Multimedia
Imports SharpDX.XAudio2
Public Class AudioPlayer
Public Event WavePlaying(e As WavePlayingEventArgs)
Public Property Device As XAudio2
Public Property Voice As SourceVoice
Private CurrentFormat As WaveFormat
Private CurrentBuffer As AudioBuffer
Private PacketsInfo As UInteger()
Public Sub New()
Device = New XAudio2()
Device.StartEngine()
Dim mv As New MasteringVoice(Device)
End Sub
Public Async Function LoadFile(fileName As String) As Task(Of Boolean)
Try
Voice = Await CreateVoiceFromFile(Device, fileName)
LoadBuffer()
Return True
Catch
Return False
End Try
End Function
Public Sub Play(Optional volume As Single = .0F)
Voice?.SetVolume(volume)
Voice?.Start()
ReadBuffer()
End Sub
Public Sub [Stop]()
Voice?.Stop()
End Sub
Protected Async Function CreateVoiceFromFile(device As XAudio2, fileName As String) As Task(Of SourceVoice)
Dim file = Await Package.Current.InstalledLocation.GetFileAsync(fileName)
Dim streamWithContentType = Await file.OpenReadAsync()
Dim st = streamWithContentType.AsStreamForRead()
Using stream = New SoundStream(st)
CurrentFormat = stream.Format
CurrentBuffer = New AudioBuffer() With {
.Stream = stream.ToDataStream(),
.AudioBytes = CInt(stream.Length),
.Flags = BufferFlags.EndOfStream
}
PacketsInfo = stream.DecodedPacketsInfo
End Using
Dim sourceVoice = New SourceVoice(device, CurrentFormat, True)
Return sourceVoice
End Function
Protected Sub LoadBuffer()
Voice?.FlushSourceBuffers()
Voice?.SubmitSourceBuffer(CurrentBuffer, PacketsInfo)
End Sub
''' <summary>
''' 从流中读取当前播放的数据
''' </summary>
Private Async Sub ReadBuffer()
Try
Dim count As Integer = CurrentFormat?.AverageBytesPerSecond /
While Voice.State.BuffersQueued >
If Voice.State.SamplesPlayed * CurrentFormat.BlockAlign > CurrentBuffer.Stream.Position + count Then
Dim byteArr(count - ) As Byte
Await CurrentBuffer.Stream.ReadAsync(byteArr, , count)
RaiseEvent WavePlaying(New WavePlayingEventArgs(byteArr, CurrentFormat))
Else
Await Task.Delay()
End If
End While
Catch
Return
End Try
End Sub
Public Function Position() As Integer
Return Voice.State.SamplesPlayed / CurrentFormat.SampleRate
End Function
Protected Overrides Sub Finalize()
Try
Dispose(False)
Finally
MyBase.Finalize()
End Try
End Sub
Public Sub Dispose()
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Private Sub Dispose(isDisposing As Boolean)
If Not isDisposing Then
Return
End If
Voice.DestroyVoice()
Voice.Dispose()
CurrentBuffer.Stream.Dispose()
End Sub
End Class
VB.NET
//由于在线转换工具对异步代码段支持不好,该处代码请参考VB.
C#

图1-1 实时波谱
第二节 频谱
通过快速傅里叶变换将时域信号转换为频域信号。
''' <summary>
''' 快速傅里叶变换
''' </summary>
''' <param name="data">指定的数据</param>
''' <param name="count"></param>
''' <returns></returns>
Public Shared Function FFT(ByVal data() As Single, ByVal count As Integer) As Single() '快速傅里叶变换
Dim fftCount As Integer = count
Dim j As Integer
Dim k As Integer
Dim NM1 As Integer
Dim ND2 As Integer
Dim M, L, LE, LE2, IP, JM1 As Integer
Dim TR, TI, SR, SI, UR, UI As Double
Dim IMX(fftCount - ) As Double
Dim REX(fftCount - ) As Double
Dim n As Integer = fftCount
Array.Copy(data, REX, fftCount)
NM1 = n -
ND2 = n /
M = CInt(Math.Log(n) / Math.Log())
j = ND2 For i = To n -
If i < j Then
TR = REX(j)
TI = IMX(j)
REX(j) = REX(i)
IMX(j) = IMX(i)
REX(i) = TR
IMX(i) = TI
End If
k = ND2
While (k <= j)
j = j - k
k = k /
End While
j = j + k
Next i For L = To M
LE = CInt( ^ L)
LE2 = LE /
UR =
UI =
SR = Math.Cos(Math.PI / LE2)
SI = -Math.Sin(Math.PI / LE2)
For j = To LE2
JM1 = j -
For i = JM1 To NM1 Step LE
IP = i + LE2
TR = REX(IP) * UR - IMX(IP) * UI
TI = REX(IP) * UI + IMX(IP) * UR
REX(IP) = REX(i) - TR
IMX(IP) = IMX(i) - TI
REX(i) = REX(i) + TR
IMX(i) = IMX(i) + TI
Next i
TR = UR
UR = TR * SR - UI * SI
UI = TR * SI + UI * SR
Next j
Next L
'
Dim w() As Single
ReDim w(fftCount / ) '取有效值
''公式:F=Kf/N F=频率;k=位置;f=取样频率;N=样本数;有效数据(N/2+1)个.
For i = To fftCount /
w(i) = Math.Sqrt(REX(i) * REX(i) + IMX(i) * IMX(i))
Next
Return w
End Function
VB.NET
/// <summary>
/// 快速傅里叶变换
/// </summary>
/// <param name="data">指定的数据</param>
/// <param name="count"></param>
/// <returns></returns>
public static float[] FFT(float[] data, int count)
{
//快速傅里叶变换
int fftCount = count;
int j = ;
int k = ;
int NM1 = ;
int ND2 = ;
int M = ;
int L = ;
int LE = ;
int LE2 = ;
int IP = ;
int JM1 = ;
double TR = ;
double TI = ;
double SR = ;
double SI = ;
double UR = ;
double UI = ;
double[] IMX = new double[fftCount];
double[] REX = new double[fftCount];
int n = fftCount;
Array.Copy(data, REX, fftCount);
NM1 = n - ;
ND2 = n / ;
M = Convert.ToInt32(Math.Log(n) / Math.Log());
j = ND2; for (i = ; i <= n - ; i++) {
if (i < j) {
TR = REX[j];
TI = IMX[j];
REX[j] = REX[i];
IMX[j] = IMX[i];
REX[i] = TR;
IMX[i] = TI;
}
k = ND2;
while ((k <= j)) {
j = j - k;
k = k / ;
}
j = j + k;
} for (L = ; L <= M; L++) {
LE = Convert.ToInt32(Math.Pow(, L));
LE2 = LE / ;
UR = ;
UI = ;
SR = Math.Cos(Math.PI / LE2);
SI = -Math.Sin(Math.PI / LE2);
for (j = ; j <= LE2; j++) {
JM1 = j - ;
for (i = JM1; i <= NM1; i += LE) {
IP = i + LE2;
TR = REX[IP] * UR - IMX[IP] * UI;
TI = REX[IP] * UI + IMX[IP] * UR;
REX[IP] = REX[i] - TR;
IMX[IP] = IMX[i] - TI;
REX[i] = REX[i] + TR;
IMX[i] = IMX[i] + TI;
}
TR = UR;
UR = TR * SR - UI * SI;
UI = TR * SI + UI * SR;
}
}
//
float[] w = null;
w = new float[fftCount / + ];
//取有效值
//'公式:F=Kf/N F=频率;k=位置;f=取样频率;N=样本数;有效数据(N/2+1)个.
for (i = ; i <= fftCount / ; i++) {
w[i] = Math.Sqrt(REX[i] * REX[i] + IMX[i] * IMX[i]);
}
return w;
}
C#

图2-1 实时频谱
第三节 简化
简化频域数据,将相邻的若干组值相加后取平均值。
频谱也可以绘制成圆环状。
Public DataWave As ConcurrentQueue(Of Single)
Public DataFFT() As Single
Public DataFFTExtra() As Single
Private Sub CalcFFT()
Dim count As Integer = '最小时域数据的长度
If DataWave.Count < count Then Return
Dim tempD(count - ) As Single
For i = To count -
tempD(i) = DataWave(DataWave.Count - count + i)
Next
DataFFT = SignalMath.FFT(tempD, count) '原始频谱 Dim extraCount As Integer = '简化的长度
Dim TempList As New List(Of Single)
Dim sCount As Integer = DataFFT.Count / extraCount
TempList.Add()
For i = To extraCount -
Dim TempSingle As Single =
For j = To sCount -
TempSingle += DataFFT(i * sCount + j)
Next
TempSingle = TempSingle / sCount /
TempList.Add(TempSingle)
Next
DataFFTExtra = TempList.ToArray '简化后的频谱
WaveSignal.Energy = DataFFTExtra.ToList.IndexOf(DataFFTExtra.Max) '不精确的基音位置
End Sub
VB.NET
public ConcurrentQueue<float> DataWave;
public float[] DataFFT;
public float[] DataFFTExtra;
private void CalcFFT()
{
int count = ;
//最小时域数据的长度
if (DataWave.Count < count)
return;
float[] tempD = new float[count];
for (i = ; i <= count - ; i++) {
tempD[i] = DataWave(DataWave.Count - count + i);
}
DataFFT = SignalMath.FFT(tempD, count);
//原始频谱 int extraCount = ;
//简化的长度
List<float> TempList = new List<float>();
int sCount = DataFFT.Count / extraCount;
TempList.Add();
for (i = ; i <= extraCount - ; i++) {
float TempSingle = ;
for (j = ; j <= sCount - ; j++) {
TempSingle += DataFFT[i * sCount + j];
}
TempSingle = TempSingle / sCount / ;
TempList.Add(TempSingle);
}
DataFFTExtra = TempList.ToArray();//简化后的频谱
}
C#

图3-1 频谱变形
第四节 场景
粒子
加入飞舞的粒子。粒子运动速度与实时音频的基音大小相关。
若用贴图取代圆点,可生成效果逼真的烟雾或者飞雪等场景。
Imports System.Numerics
Imports Windows.UI
''' <summary>
''' 粒子类,表示一个拥有加速度、加速度和位置矢量的抽象粒子
''' </summary>
Public Class Partical
Public Property Location As Vector2 '位置矢量
Public Property Velocity As Vector2 '速度
Public Property Acceleration As Vector2 '加速度
Public Property Mass As Single = 10.0 '质量大小
Public Property Age As Single = '生命周期
Public Property Alpha As Single =
Public Property Size As Single =
Public Property Radius As Single =
Public Property RadiusX As Single =
Public Property RadiusY As Single =
Public Property ImageSize As Single = '粒子图像的大小
Public Property Color As Color '粒子颜色
Public Shared Rnd As New Random
''' <summary>
''' 初始化一个粒子
''' </summary>
Public Sub New(loc As Vector2)
Location = loc
Velocity = New Vector2(, )
Acceleration = New Vector2(, )
End Sub
''' <summary>
''' 指定的力作用于当前对象
''' </summary>
''' <param name="forceVec">指定的力</param>
Public Sub ApplyForce(forceVec As Vector2)
Acceleration = Acceleration + forceVec / Mass
End Sub
''' <summary>
''' 更新粒子位置,重绘每帧图像前调用该方法
''' </summary>
Public Sub Move()
Velocity += Acceleration * WaveSignal.Energy
'Velocity.LimitMag(20)
Location += Velocity '更新位置
Acceleration = Vector2.Zero
End Sub
Public Sub StartNew(Loc As Vector2)
Location = Loc
Velocity.SetMag()
End Sub
End Class
VB.NET
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Numerics;
using Windows.UI;
/// <summary>
/// 粒子类,表示一个拥有加速度、加速度和位置矢量的抽象粒子
/// </summary>
public class Partical
{
public Vector2 Location { get; set; }
//位置矢量
public Vector2 Velocity { get; set; }
//速度
public Vector2 Acceleration { get; set; }
//加速度
public float Mass { get; set; }
//质量大小
public float Age { get; set; }
//生命周期
public float Alpha { get; set; }
public float Size { get; set; }
public float Radius { get; set; }
public float RadiusX { get; set; }
public float RadiusY { get; set; }
public float ImageSize { get; set; }
//粒子图像的大小
public Color Color { get; set; }
//粒子颜色
public static Random Rnd = new Random();
/// <summary>
/// 初始化一个粒子
/// </summary>
public Partical(Vector2 loc)
{
Location = loc;
Velocity = new Vector2(, );
Acceleration = new Vector2(, );
}
/// <summary>
/// 指定的力作用于当前对象
/// </summary>
/// <param name="forceVec">指定的力</param>
public void ApplyForce(Vector2 forceVec)
{
Acceleration = Acceleration + forceVec / Mass;
}
/// <summary>
/// 更新粒子位置,重绘每帧图像前调用该方法
/// </summary>
public void Move()
{
Velocity += Acceleration * WaveSignal.Energy;//与基音位置相关
//Velocity.LimitMag(20)
Location += Velocity;
//更新位置
Acceleration = Vector2.Zero;
}
public void StartNew(Vector2 Loc)
{
Location = Loc;
Velocity.SetMag();
}
}
C#
背景
加入背景图片。背景高斯模糊与实时音频的基音大小相关。
也可以让背景随机抖动或者旋转。
Private Sub DrawBackGround(DrawingSession As CanvasDrawingSession)
Using cmdList = New CanvasCommandList(DrawingSession)
Using dl = cmdList.CreateDrawingSession
dl.DrawImage(ImageManager.GetResource(ImageResourceID.back1))
End Using
Using blur1 = New Effects.GaussianBlurEffect With {.Source = cmdList, .BlurAmount = + WaveSignal.Energy / }
DrawingSession.DrawImage(blur1)
End Using
End Using
End Sub
VB.NET
private void DrawBackGround(CanvasDrawingSession DrawingSession)
{
using (cmdList == new CanvasCommandList(DrawingSession)) {
using (dl == cmdList.CreateDrawingSession) {
dl.DrawImage(ImageManager.GetResource(ImageResourceID.back1));
}
using (blur1 == new Effects.GaussianBlurEffect {Source = cmdList,BlurAmount = + WaveSignal.Energy / }) {
DrawingSession.DrawImage(blur1);
}
}
}
C#
文字
加入歌曲名称信息。
具体的文字效果由你设置,请参考 Win2D 的 Microsoft.Graphics.Canvas.Effects 文档。
Imports Microsoft.Graphics.Canvas
Imports Windows.UI
Public Class StaticStringView
Inherits TypedGameView(Of StaticString)
Public Sub New(Target As StaticString)
MyBase.New(Target)
End Sub
Public Overrides Sub Draw(DrawingSession As CanvasDrawingSession)
Using cmdList = New CanvasCommandList(DrawingSession)
Using dl = cmdList.CreateDrawingSession
DrawText(dl)
End Using
Using blur1 = New Effects.GaussianBlurEffect With {.Source = cmdList, .BlurAmount = }
DrawingSession.DrawImage(blur1)
DrawingSession.DrawImage(cmdList)
End Using
End Using
End Sub
Dim TextFormat = New Text.CanvasTextFormat() With {.FontFamily = "微软雅黑",
.FontSize = ,
.HorizontalAlignment = Microsoft.Graphics.Canvas.Text.CanvasHorizontalAlignment.Center,
.VerticalAlignment = Microsoft.Graphics.Canvas.Text.CanvasVerticalAlignment.Center}
Public Sub DrawText(Dl As CanvasDrawingSession)
Dl.DrawText(Target.Str, Target.Location, Colors.White, TextFormat)
End Sub
End Class
VB.NET
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using Microsoft.Graphics.Canvas;
using Windows.UI;
public class StaticStringView : TypedGameView<StaticString>
{
public StaticStringView(StaticString Target) : base(Target)
{
}
public override void Draw(CanvasDrawingSession DrawingSession)
{
using (cmdList == new CanvasCommandList(DrawingSession)) {
using (dl == cmdList.CreateDrawingSession) {
DrawText(dl);
}
using (blur1 == new Effects.GaussianBlurEffect {Source = cmdList,BlurAmount = }) {
DrawingSession.DrawImage(blur1);
DrawingSession.DrawImage(cmdList);
}
}
}
TextFormat = new Text.CanvasTextFormat {
FontFamily = "微软雅黑",
FontSize = ,
HorizontalAlignment = Microsoft.Graphics.Canvas.Text.CanvasHorizontalAlignment.Center,
VerticalAlignment = Microsoft.Graphics.Canvas.Text.CanvasVerticalAlignment.Center
};
public void DrawText(CanvasDrawingSession Dl)
{
Dl.DrawText(Target.Str, Target.Location, Colors.White, TextFormat);
}
}
C#

图4-1 最终效果
附录
开源:MusicVideoGenerator (已失效)
相同类型的MV:
UWP简单示例(一):快速合成音乐MV的更多相关文章
- UWP简单示例(一):快速合成音乐MV
说明 本文发布时间较早,内容可能已过时.最新动态请关注 TypeScript 版本.(2019 年 3 月 注) 在线演示: 音频可视化(TypeScript) 准备 IDE:Visual Studi ...
- UWP简单示例(三):快速开发2D游戏引擎
准备 IDE:VisualStudio 2015 Language:VB.NET/C# 图形API:Win2D MSDN教程:UWP游戏开发 游戏开发涉及哪些技术? 游戏开发是一门复杂的艺术,编码方面 ...
- UWP简单示例(二):快速开始你的3D编程
准备 IDE:Visual Studio 2015 了解并学习:SharpDx官方GitHub 推荐Demo:SharpDX_D3D12HelloWorld 第一节 世界 世界坐标系是一个特殊的坐标系 ...
- UWP简单示例(三):快速开发2D游戏引擎
准备 IDE:Visual Studio 图形 API:Win2D MSDN 教程:UWP游戏开发 游戏开发涉及哪些技术? 游戏开发是一门复杂的艺术,编码方面你需要考虑图形.输入和网络 以及相对独立的 ...
- UWP简单示例(二):快速开始你的3D编程
准备 IDE:Visual Studio 开源库:GitHub.SharpDx 入门示例:SharpDX_D3D12HelloWorld 为什么选择 SharpDx? SharpDx 库与 UWP 兼 ...
- Redis 安装与简单示例
Redis 安装与简单示例 一.Redis的安装 Redis下载地址如下:https://github.com/dmajkic/redis/downloads 解压后根据自己机器的实际情况选择32位或 ...
- springMVC源码分析--异常处理机制HandlerExceptionResolver简单示例(一)
springMVC对Controller执行过程中出现的异常提供了统一的处理机制,其实这种处理机制也简单,只要抛出的异常在DispatcherServlet中都会进行捕获,这样就可以统一的对异常进行处 ...
- Optaplanner规划引擎的工作原理及简单示例(2)
开篇 在前面一篇关于规划引擎Optapalnner的文章里(Optaplanner规划引擎的工作原理及简单示例(1)),老农介绍了应用Optaplanner过程中需要掌握的一些基本概念,这些概念有且于 ...
- AMQP消息队列之RabbitMQ简单示例
前面一篇文章讲了如何快速搭建一个ActiveMQ的示例程序,ActiveMQ是JMS的实现,那这篇文章就再看下另外一种消息队列AMQP的代表实现RabbitMQ的简单示例吧.在具体讲解之前,先通过一个 ...
随机推荐
- 红黑树——算法导论(15)
1. 什么是红黑树 (1) 简介 上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...
- Oracle安装部署,版本升级,应用补丁快速参考
一.Oracle安装部署 1.1 单机环境 1.2 Oracle RAC环境 1.3 Oracle DataGuard环境 1.4 主机双机 1.5 客户端部署 二.Oracle版本升级 2.1 单机 ...
- winform异步加载数据到界面
做一个学习记录. 有两个需求: 1.点击按钮,异步加载数据,不卡顿UI. 2.把获取的数据加载到gridview上面. 对于需求1,2,代码如下: public delegate void ShowD ...
- Oracle 数据库语句大全
Oracle数据库语句大全 ORACLE支持五种类型的完整性约束 NOT NULL (非空)--防止NULL值进入指定的列,在单列基础上定义,默认情况下,ORACLE允许在任何列中有NULL值. CH ...
- 防线修建 bzoj 2300
防线修建(1s 512MB)defense [问题描述] 近来A国和B国的矛盾激化,为了预防不测,A国准备修建一条长长的防线,当然修建防线的话,肯定要把需要保护的城市修在防线内部了.可是A国上层现在还 ...
- javascript高性能编程-算法和流程控制
代码整体结构是执行速度的决定因素之一. 代码量少不一定运行速度快, 代码量多也不一定运行速度慢. 性能损失与代码组织方式和具体问题解决办法直接相关. 倒序循环可以提高性能,如: ...
- ubuntu下配置vimtab空格数
vim ~/.vimrc 没有就创建 set tabstop=4 //4就是4个空格
- Membership三步曲之进阶篇 - 深入剖析Provider Model
Membership 三步曲之进阶篇 - 深入剖析Provider Model 本文的目标是让每一个人都知道Provider Model 是什么,并且能灵活的在自己的项目中使用它. Membershi ...
- Lesson 14 Do you speak English?
Text I had an amusing experience last year. After I had left a small village in the south of France. ...
- 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制
参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...