源码来自工程『Sincerely』
主体思路是在一个由字符串组成的平面上通过读取TimeLine中的信息生成动画,最后将这个平面映射到聊天栏,并生成tellraw命令。 (注释已比较详细了,不作太多解释。)
using System;
using Audio2Minecraft;
using Newtonsoft.Json;
namespace WpfApplication1
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
InheritExpression.SetCompareLists(System.Windows.Forms.Application.StartupPath + "\\test");
//生成TimeLine(时间序列)
var a = new TimeLine().Serialize(MidiPath.Text, WavePath.Text, 72);
//var a = new AudioStreamMidi().Serialize(MidiPath.Text, new TimeLine()); Console.Write(new List()[0]);
//所有音轨的发生发声延长3tick
a.Sound_ExtraDelay(3);
a.Sound_StopSound(false); //禁用/stopsound
a.EnableWave(false); //禁用Wave
a.Sound_InheritExpression("%p"); //设置子表达式
//设定音轨及其音色
for (var i = 1; i <= 8; i++) { a.Sound_SoundName("1", i.ToString());} //钢琴
a.Sound_SoundName("74c", "74"); a.Sound_StopSound(true, "74"); a.Sound_PercVolume(90, "45");
a.Sound_SoundName("45c", "45"); a.Sound_StopSound(true, "45"); a.Sound_PercVolume(175, "45"); a.Sound_ExtraDelay(4);
a.Sound_SoundName("96c", "96"); a.Sound_StopSound(true, "96"); a.Sound_PercVolume(160, "45"); a.Sound_ExtraDelay(4);
a.Sound_SoundName("52c", "52"); a.Sound_StopSound(true, "52"); a.Sound_PercVolume(160, "45"); a.Sound_ExtraDelay(4); //弦乐
a.Sound_SoundName("0.86", "beat", "Drum 86");
a.Sound_SoundName("0.40", "beat", "Electric Snare");
a.Sound_SoundName("0.43", "beat", "High Floor Tom");
a.Sound_SoundName("0.41", "beat", "Low Floor Tom");
a.Sound_SoundName("0.69", "beat", "Cabasa");
a.Sound_SoundName("0.44", "beat", "Pedal Hi-Hat");
a.Sound_SoundName("0.67", "beat", "High Agogo");
a.Sound_SoundName("0.45", "beat", "Low Tom");
a.Sound_SoundName("0.57", "beat", "Crash Cymbal 2"); //设定音色
a.EnableMidi(false); //禁用Midi
a.EnableMidi(true, "", "", -1, "PlaySound"); //只启用Midi的/playsound
//生成CommandLine(命令序列)
var b = new CommandLine().Serialize(a);
b.Start.Clear(); b.End.Clear();
//生成聊天栏文本(命令序列)
var text = textShow(a);
for (int i = 0; i < 51; i++) b.Keyframe.Insert(0, new Command()); //插入18个tick用于'琴键'的下落
b = b.Combine(b, text); //读取Text嵌入命令序列
//每一刻都增加"tp @p ~0.2 ~ ~"的命令
for (int i = 0; i < b.Keyframe.Count; i ++) b.Keyframe[i].Commands.Add("tp @p ~0.2 ~ ~");
//生成schematic文件
new Schematic().ExportSchematic(b, new ExportSetting() { AlwaysActive = true, AlwaysLoadEntities = false, Direction = 0, Width = 5 }, "E:\\time.schematic");
}
private void MidiSelect(object sender, MouseButtonEventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Filter = "Midi|*.mid";
fileDialog.FilterIndex = 1;
if (fileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { MidiPath.Text = fileDialog.FileName; }
}
private void WaveSelect(object sender, MouseButtonEventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Filter = "Wave|*.wav";
fileDialog.FilterIndex = 1;
if (fileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { WavePath.Text = fileDialog.FileName; }
}
private void LrcSelect(object sender, MouseButtonEventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Filter = "Lrc|*.lrc;*.amlrc;*.txt";
fileDialog.FilterIndex = 1;
if (fileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { LrcPath.Text = fileDialog.FileName; }
}
private CommandLine textShow(TimeLine a) //根据时间序列生成聊天栏文本
{
var text = new CommandLine(); //实例化聊天栏文本(命令序列)
var wav = new AudioStreamWave().Serialize(WavePath.Text, new TimeLine(), 60, 60); //实例化Wav(时间序列)
var timeD = new List(); //实例化文本的二维平面
//获取频率&音色的特征信息(平均值&最大值)
/*var highest_fre = wav.TickNodes.Max(v => v.WaveNodesLeft.Max(h => h.Param["FrequencyPerTick"].Max(t => t.Value)));
var highest_vol = wav.TickNodes.Max(v => v.WaveNodesLeft.Max(h => h.Param["VolumePerTick"].Max(t => t.Value)));
var av_highest_fre = wav.TickNodes.Average(v => v.WaveNodesLeft.Max(h => h.Param["FrequencyPerTick"].Max(t => t.Value)));
var av_highest_vol = wav.TickNodes.Average(v => v.WaveNodesLeft.Max(h => h.Param["VolumePerTick"].Max(t => t.Value)));
var av_fre = (double)wav.TickNodes.Sum(v => v.WaveNodesLeft.Sum(h => h.Param["FrequencyPerTick"].Sum(t => t.Value))) / wav.TickNodes.Count / 160;
var av_vol = (double)wav.TickNodes.Sum(v => v.WaveNodesLeft.Sum(h => h.Param["VolumePerTick"].Sum(t => t.Value))) / wav.TickNodes.Count / 160;
=> average: 77 - level: 6*/
for (int i = 0; i < wav.TickNodes.Count; i++) //创建文本平面的像素点(64*18)
{
timeD.Add(new Pixel[64, 18]);
for (int x = 0; x < 63; x++)
for (int y = 0; y < 18; y++)
{
timeD[i][x, y] = new Pixel();
}
}
for (int i = 0; i < wav.TickNodes.Count; i++) //节奏可视化
{
if (a.TickNodes[i].MidiTracks.Count > 0)
{
if (a.TickNodes[i].MidiTracks.ContainsKey("beat")) //遍历beat音轨下的乐器
{
if (a.TickNodes[i].MidiTracks["beat"].ContainsKey("Crash Cymbal 2")) //单面钹
{
for (int j = 0; j < 64; j++)
{
var delT = Math.Sqrt(64 * 64 - (j + 0.5 - 32) * (j + 0.5 - 32)); //延时
timeD = crash(timeD, i + (int)delT - 64, j, "dark_gray"); //生成动画
}
}
if (a.TickNodes[i].MidiTracks["beat"].ContainsKey("Low Tom") || a.TickNodes[i].MidiTracks["beat"].ContainsKey("Low Floor Tom") || a.TickNodes[i].MidiTracks["beat"].ContainsKey("High Floor Tom")) //低音鼓&低音落地鼓&高音落地鼓
{
timeD = beat(timeD, i, 0, "dark_aqua", "aqua"); //在x=0和x=63生成动画
timeD = beat(timeD, i, 63, "dark_aqua", "aqua");
}
if (a.TickNodes[i].MidiTracks["beat"].ContainsKey("Electric Snare")) //电子鼓
{
timeD = beat(timeD, i, 1, "dark_green", "green"); //在x=1和x=62生成动画
timeD = beat(timeD, i, 62, "dark_green", "green");
}
}
}
}
for (int i = 0; i < wav.TickNodes.Count; i++) //波形可视化
{
var display = timeD[i];
foreach (var node in wav.TickNodes[i].WaveNodesLeft)
{
for (int j = 0; j < 64; j++)
{
if (node.Param["VolumePerTick"].Count == 60 && j > 1 && j < 62) //遍历x=2到x=61
{
var h = node.Param["VolumePerTick"][j - 2].Value / 18; //获取音量
if (h > 18) h = 18;
if (i == 0) h = h / 2;
else
{
var mh = 0;
for (int d = 0; d < 18; d++) if (timeD[i - 1][j, d].Color != "black") mh++;
h = (h + mh) / 2;
} //获取上一刻该处的波形高度
var p = (double)(60 - Math.Abs(j - 30)) / 60 * 100;
h = (int)(h * p / 100); //按照x坐标得到该处的波形高度(中间高两边低)
for (int k = 0; k < h; k++)
{
var color = "dark_purple";
if (k == h - 1)
color = "blue"; //最上层为蓝色
display[j, k] = new Pixel() { Color = color }; //设置像素
}
}
}
}
}
for (int i = 0; i < 54; i++) timeD.Insert(0, new Pixel[64, 18]); //插入18个tick用于'琴键'的下落
//最高音: 97 , 最低音: 25
for (int i = 0; i < a.TickNodes.Count; i++) //琴键可视化
{
if (a.TickNodes[i].MidiTracks.Count > 0)
{
foreach (var track in a.TickNodes[i].MidiTracks.Keys)
{
var color = "white";
if (track == "2") color = "yellow";
if (track == "3") color = "gray";
if (track == "4") color = "yellow";
if (track == "5") color = "yellow";
if (track == "6") color = "red";
if (track == "7") color = "red";
if (track == "8") color = "red"; //设置各音轨的琴键颜色
foreach (string instrument in a.TickNodes[i].MidiTracks[track].Keys)
{
if (instrument == "Acoustic Grand") //遍历所有Acoustic Grand
{
var nodes = a.TickNodes[i].MidiTracks[track][instrument];
foreach (var node in nodes)
{
var pitch = node.Param["Pitch"].Value; //获取音高
var x = pitch - 31; if (x > 63 || x < 0) continue; //删除出现在平面外的琴键
for (int s = 0; s < 3; s++) //琴键高度=3
{
for (int y = 0; y < 60; y++) //遍历x
{
var h = y / 3 + s - 3;
if (h == 0 && s > 0)
timeD[i + 60 - y][x, h] = new Pixel() { Char = "◙", Color = color }; //表示正在消失的琴键样式
else if (h == 0 && s == 0)
timeD[i + 60 - y][x, h] = new Pixel() { Char = "◘", Color = color }; //表示刚刚敲击的琴键样式
else if (h < 18 && h >= 0)
timeD[i + 60 - y][x, h] = new Pixel() { Color = color };
}
}
}
}
}
}
}
}
var lrc = new Lrc().Serialize(LrcPath.Text); //获取.lrc的内容
var Lrcs = textLrc(lrc, timeD.Count, 54); //.lrc -> Tellraw
for (int i = 0; i < timeD.Count; i++) //像素点 -> Tellraw
{
var display = timeD[i];
text.Keyframe.Add(new Command());
var tlw = new Tellraw();
for (int y = 17; y > -1; y--)
{
var sumT = new Tellraw.Text();
var lastT = new Tellraw.Text();
for (int x = 0; x < 64; x++)
{
var t = new Tellraw.Text();
if (display[x, y] == null) t = new Tellraw.Text() { text = "▌", color = "black" };
else t = new Tellraw.Text() { text = display[x, y].Char, color = display[x, y].Color };
if (x == 0) lastT = t;
if (lastT.text == t.text && lastT.color == t.color)
{
sumT.text += t.text;
sumT.color = t.color;
if (x == 63) tlw.texts.Add(sumT);
}
else
{
tlw.texts.Add(sumT);
lastT = t;
sumT = t;
if (x == 63) tlw.texts.Add(t);
}
}
tlw.texts[tlw.texts.Count - 1].text += "\n";
}
if (Lrcs[i] != null) tlw.texts.AddRange(Lrcs[i].texts);
else tlw.texts.Add(new Tellraw.Text() { text = "\n" });
//播放进度
var proBar = new Tellraw.Text[3];
proBar[0] = new Tellraw.Text() { color = "dark_purple" };
proBar[1] = new Tellraw.Text() { text = "▪", color = "gold" };
proBar[2] = new Tellraw.Text() { color = "white" };
var did = (int)((double)i / timeD.Count * 94) + 1;
for (int t = 0; t < did - 1; t++) proBar[0].text += "▪";
for (int t = did; t < 94; t++) proBar[2].text += "▫";
tlw.texts.Add(new Tellraw.Text() { text = " ▎▎ ", color = "white" });
tlw.texts.AddRange(proBar);
var m = (i / 1200 > 9) ? (i / 1200).ToString() : "0" + (i / 1200).ToString();
var s = (i % 1200 / 20 > 9) ? (i % 1200 / 20).ToString() : "0" + (i % 1200 / 20).ToString();
tlw.texts.Add(new Tellraw.Text() { text = " " + m + ":" + s, color = "white" });
text.Keyframe[i].Commands.Add("tellraw @p " + JsonConvert.SerializeObject(tlw.texts)); //生成tellraw命令
}
return text;
}
private List beat(List timeD, int i, int j, string c1, string c2)
{
for (int m = 0; m < 1; m++) timeD[i - 3][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 6; m++) timeD[i - 2][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 11; m++) timeD[i - 1][j, m] = new Pixel() { Color = c2 };
for (int m = 0; m < 16; m++) timeD[i][j, m] = new Pixel() { Color = c2 };
for (int m = 0; m < 13; m++) timeD[i + 1][j, m] = new Pixel() { Color = c2 };
for (int m = 0; m < 11; m++) timeD[i + 2][j, m] = new Pixel() { Color = c2 };
for (int m = 0; m < 10; m++) timeD[i + 3][j, m] = new Pixel() { Color = c2 };
for (int m = 0; m < 9; m++) timeD[i + 4][j, m] = new Pixel() { Color = c2 };
for (int m = 0; m < 8; m++) timeD[i + 5][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 7; m++) timeD[i + 6][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 6; m++) timeD[i + 7][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 5; m++) timeD[i + 8][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 4; m++) timeD[i + 9][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 3; m++) timeD[i + 10][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 2; m++) timeD[i + 11][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 1; m++) timeD[i + 12][j, m] = new Pixel() { Color = c1 };
return timeD;
} //打击乐动画
private List crash(List timeD, int i, int j, string c1)
{
for (int m = 0; m < 5; m++) timeD[i - 3][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 10; m++) timeD[i - 2][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 18; m++) timeD[i - 1][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 18; m++) timeD[i][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 18; m++) timeD[i + 1][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 16; m++) timeD[i + 2][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 14; m++) timeD[i + 3][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 13; m++) timeD[i + 4][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 12; m++) timeD[i + 5][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 11; m++) timeD[i + 6][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 10; m++) timeD[i + 7][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 9; m++) timeD[i + 8][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 8; m++) timeD[i + 9][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 7; m++) timeD[i + 10][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 6; m++) timeD[i + 11][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 5; m++) timeD[i + 12][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 4; m++) timeD[i + 13][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 3; m++) timeD[i + 14][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 2; m++) timeD[i + 15][j, m] = new Pixel() { Color = c1 };
for (int m = 0; m < 1; m++) timeD[i + 16][j, m] = new Pixel() { Color = c1 };
return timeD;
} //单面钹动画
private Tellraw[] textLrc(Lrc lrc, int sum = 0, int delay = 0) //.lrc -> Tellraw
{
var textLrc = new Tellraw[sum];
foreach(var l in lrc.Lrcs)
{
var start = l.Start;
var duration = l.Duration;
var length = l.Content.Length; if (length == 0) continue;
var pTick = (double)duration / length;
for (int i = 0; i < duration; i++)
{
var index = start + i + delay;
var playedChar = (int)((i + 1) / pTick) + 1;
if (playedChar >= length) playedChar = length;
var hltext = l.Content.Substring(0, playedChar);
var ntext = l.Content.Substring(playedChar);
var tlw = new Tellraw();
tlw.texts = new List()
{
new Tellraw.Text() { text = " -- 「", color = "dark_gray"},
new Tellraw.Text() { text = hltext.Replace("\u3000",@" "), color = "yellow"},
new Tellraw.Text() { text = ntext.Replace("\u3000",@" "), color = "gray"},
new Tellraw.Text() { text = "」\n", color = "dark_gray"}
};
textLrc[index] = tlw;
}
}
return textLrc;
}
}
public class Pixel //像素点
{
public string Char = "▌";
public string Color = "black";
}
public class Tellraw //Tellraw
{
public List texts = new List();
public class Text
{
public string text = "";
public string color = "white";
}
}
}
因为不需要在游戏中调用元素,所以这项工程没有结合计分板,