Files
crtr/Assets/Cryville/Crtr/Extensions/Malody/MalodyChartConverter.cs
2022-11-23 12:02:25 +08:00

233 lines
7.3 KiB
C#

using Cryville.Crtr.Browsing;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
namespace Cryville.Crtr.Extensions.Malody {
public class MalodyChartConverter : ResourceConverter {
static readonly string[] SUPPORTED_FORMATS = { ".mc", ".mcz" };
static readonly string[] MODES = { "key", "step", "dj", "catch", "pad", "taiko", "ring", "slide", "live" };
public override string[] GetSupportedFormats() {
return SUPPORTED_FORMATS;
}
public override IEnumerable<Resource> ConvertFrom(FileInfo file) {
List<Resource> result = new List<Resource>();
MalodyChart src;
if (file.Extension != ".mc") throw new NotImplementedException("mcz file is not supported");
using (var reader = new StreamReader(file.FullName)) {
src = JsonConvert.DeserializeObject<MalodyChart>(reader.ReadToEnd());
}
if (src.meta.mode != 0) throw new NotImplementedException("The chart mode is not supported");
if (src.meta.mode_ext.column != 4) throw new NotImplementedException("The key count is not supported");
var ruleset = "malody!" + MODES[src.meta.mode];
if (src.meta.mode == 0) {
ruleset += "." + src.meta.mode_ext.column.ToString(CultureInfo.InvariantCulture) + "k";
}
ChartMeta meta = new ChartMeta() {
name = src.meta.version,
author = src.meta.creator,
song = new SongMetaInfo() {
name = src.meta.song.titleorg != null ? src.meta.song.titleorg : src.meta.song.title,
author = src.meta.song.artistorg != null ? src.meta.song.artistorg : src.meta.song.artist,
},
ruleset = ruleset,
};
Chart chart = new Chart {
format = 2,
time = new BeatTime(-4, 0, 1),
ruleset = ruleset,
sigs = new List<Chart.Signature>(),
sounds = new List<Chart.Sound>(),
motions = new List<Chart.Motion>(),
groups = new List<Chart.Group>(),
};
var group = new Chart.Group() {
tracks = new List<Chart.Track>(),
notes = new List<Chart.Note>(),
motions = new List<Chart.Motion>(),
};
chart.groups.Add(group);
int col = src.meta.mode_ext.column;
IEnumerable<MalodyChart.IEvent> events = src.time.Cast<MalodyChart.IEvent>();
if (src.effect != null) events = events.Concat(src.effect.Cast<MalodyChart.IEvent>());
events = events.Concat(src.note.Cast<MalodyChart.IEvent>());
List<MalodyChart.IEvent> endEvents = new List<MalodyChart.IEvent>();
foreach (var ev in events)
if (ev.endbeat != null)
endEvents.Add(new MalodyChart.EndEvent {
beat = ev.endbeat,
StartEvent = ev,
});
events = events.Concat(endEvents)
.OrderBy(e => e.beat[0] + (float)e.beat[1] / e.beat[2])
.ThenBy(e => e.Priority);
Dictionary<MalodyChart.IEvent, StartEventState> longEvents
= new Dictionary<MalodyChart.IEvent, StartEventState>();
float? baseBpm = null, cbpm = null;
float pbeat = 0f, ctime = 0f;
int[] endbeat = new int[] { 0, 0, 1 };
foreach (var ev in events) {
float cbeat = ConvertBeat(ev.beat);
ctime += cbpm == null ? 0 : (cbeat - pbeat) / cbpm.Value * 60f;
pbeat = cbeat;
if (ev is MalodyChart.Time) {
var tev = (MalodyChart.Time)ev;
if (baseBpm == null) baseBpm = tev.bpm;
cbpm = tev.bpm;
chart.sigs.Add(new Chart.Signature {
time = new BeatTime(ev.beat[0], ev.beat[1], ev.beat[2]),
tempo = tev.bpm,
});
chart.motions.Add(new Chart.Motion {
time = new BeatTime(ev.beat[0], ev.beat[1], ev.beat[2]),
motion = "svm:" + (tev.bpm / baseBpm.Value).ToString(CultureInfo.InvariantCulture)
});
}
else if (ev is MalodyChart.Effect) {
var tev = (MalodyChart.Effect)ev;
if (tev.scroll != null) group.motions.Add(new Chart.Motion {
time = new BeatTime(ev.beat[0], ev.beat[1], ev.beat[2]),
motion = "svm:" + tev.scroll.Value.ToString(CultureInfo.InvariantCulture)
});
}
else if (ev is MalodyChart.Note) {
var tev = (MalodyChart.Note)ev;
if (tev.type == 1) {
if (tev.beat[0] == 0 && tev.beat[1] == 0) {
var res = new SongResource(meta.song.name, new FileInfo(file.DirectoryName + "/" + tev.sound));
result.Add(res);
chart.sounds.Add(new Chart.Sound {
time = new BeatTime(0, 0, 1),
id = res.Name,
offset = -tev.offset / 1000f,
});
}
else throw new NotImplementedException();
}
else {
if (ConvertBeat(tev.beat) > ConvertBeat(endbeat)) endbeat = tev.beat;
var rn = new Chart.Note() {
time = new BeatTime(tev.beat[0], tev.beat[1], tev.beat[2]),
motions = new List<Chart.Motion> {
new Chart.Motion() { motion = "track:" + tev.column.ToString(CultureInfo.InvariantCulture) }
},
};
if (tev.endbeat != null) {
if (ConvertBeat(tev.endbeat) > ConvertBeat(endbeat)) endbeat = tev.endbeat;
rn.endtime = new BeatTime(tev.endbeat[0], tev.endbeat[1], tev.endbeat[2]);
longEvents.Add(ev, new StartEventState {
Destination = rn,
Time = ctime,
});
}
group.notes.Add(rn);
}
}
else if (ev is MalodyChart.EndEvent) {
var tev = (MalodyChart.EndEvent)ev;
if (tev.StartEvent is MalodyChart.Note) {
var sev = tev.StartEvent;
longEvents.Remove(sev);
}
else throw new NotSupportedException();
}
else throw new NotSupportedException();
}
chart.endtime = new BeatTime(endbeat[0] + 4, endbeat[1], endbeat[2]);
meta.length = ctime;
meta.note_count = group.notes.Count;
string chartName = string.Format("{0} - {1}", meta.song.name, meta.name);
if (src.meta.background != null) {
meta.cover = src.meta.background;
}
result.Add(new RawChartResource(chartName, chart, meta));
return result;
}
struct StartEventState {
public float Time { get; set; }
public ChartEvent Destination { get; set; }
}
float ConvertBeat(int[] beat) {
return beat[0] + (float)beat[1] / beat[2];
}
}
#pragma warning disable IDE1006
struct MalodyChart {
public interface IEvent {
int[] beat { get; set; }
int[] endbeat { get; set; }
int Priority { get; }
}
public struct EndEvent : IEvent {
public int[] beat { get; set; }
public int[] endbeat { get; set; }
public IEvent StartEvent { get; set; }
public int Priority { get { return StartEvent.Priority - 1; } }
}
public Meta meta;
public struct Meta {
public SongInfo song;
public struct SongInfo {
public string title;
public string artist;
public string titleorg;
public string artistorg;
}
public string background;
public string creator;
public string version;
public int mode;
public ModeExt mode_ext;
public struct ModeExt {
public int column;
}
}
public List<Time> time;
public struct Time : IEvent {
public int[] beat { get; set; }
public int[] endbeat { get; set; }
public float bpm;
public int Priority { get { return -2; } }
}
public List<Effect> effect;
public struct Effect : IEvent {
public int[] beat { get; set; }
public int[] endbeat { get; set; }
public float? scroll;
public int Priority { get { return 0; } }
}
public List<Note> note;
public struct Note : IEvent {
public int[] beat { get; set; }
public int[] endbeat { get; set; }
public int column;
public string sound;
public int offset;
public int type;
public int Priority { get { return 0; } }
}
}
#pragma warning restore IDE1006
}