Add Bestdori chart converter.
This commit is contained in:
52
Assets/Cryville/Common/Math/FractionUtils.cs
Normal file
52
Assets/Cryville/Common/Math/FractionUtils.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Cryville.Common.Math {
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a set of <see langword="static" /> methods related to fractions.
|
||||||
|
/// </summary>
|
||||||
|
public static class FractionUtils {
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a <see cref="double" /> decimal to a fraction.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The decimal.</param>
|
||||||
|
/// <param name="error">The error.</param>
|
||||||
|
/// <param name="n">The numerator.</param>
|
||||||
|
/// <param name="d">The denominator.</param>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value" /> is less than 0 or <paramref name="error" /> is not greater than 0 or not less than 1.</exception>
|
||||||
|
public static void ToFraction(double value, double error, out int n, out int d) {
|
||||||
|
if (value < 0.0)
|
||||||
|
throw new ArgumentOutOfRangeException("value", "Must be >= 0.");
|
||||||
|
if (error <= 0.0 || error >= 1.0)
|
||||||
|
throw new ArgumentOutOfRangeException("accuracy", "Must be > 0 and < 1.");
|
||||||
|
|
||||||
|
int num = (int)System.Math.Floor(value);
|
||||||
|
value -= num;
|
||||||
|
|
||||||
|
if (value < error) { n = num; d = 1; return; }
|
||||||
|
if (1 - error < value) { n = num + 1; d = 1; return; }
|
||||||
|
|
||||||
|
int lower_n = 0;
|
||||||
|
int lower_d = 1;
|
||||||
|
int upper_n = 1;
|
||||||
|
int upper_d = 1;
|
||||||
|
while (true) {
|
||||||
|
int middle_n = lower_n + upper_n;
|
||||||
|
int middle_d = lower_d + upper_d;
|
||||||
|
|
||||||
|
if (middle_d * (value + error) < middle_n) {
|
||||||
|
upper_n = middle_n;
|
||||||
|
upper_d = middle_d;
|
||||||
|
}
|
||||||
|
else if (middle_n < (value - error) * middle_d) {
|
||||||
|
lower_n = middle_n;
|
||||||
|
lower_d = middle_d;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
n = num * middle_d + middle_n;
|
||||||
|
d = middle_d;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Cryville/Common/Math/FractionUtils.cs.meta
Normal file
11
Assets/Cryville/Common/Math/FractionUtils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6829ada596979a545a935785eeea2972
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Assets/Cryville/Crtr/Extensions/Bestdori.meta
Normal file
8
Assets/Cryville/Crtr/Extensions/Bestdori.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b9bd9e24d7c553341a2a12391843542f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@@ -0,0 +1,183 @@
|
|||||||
|
using Cryville.Common;
|
||||||
|
using Cryville.Common.Math;
|
||||||
|
using Cryville.Crtr.Browsing;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Cryville.Crtr.Extensions.Bestdori {
|
||||||
|
public class BestdoriChartConverter : ResourceConverter {
|
||||||
|
static readonly string[] SUPPORTED_FORMATS = { ".json" };
|
||||||
|
public override string[] GetSupportedFormats() {
|
||||||
|
return SUPPORTED_FORMATS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<Resource> ConvertFrom(FileInfo file) {
|
||||||
|
List<Resource> result = new List<Resource>();
|
||||||
|
List<BestdoriChartEvent> src;
|
||||||
|
using (var reader = new StreamReader(file.FullName)) {
|
||||||
|
src = JsonConvert.DeserializeObject<List<BestdoriChartEvent>>(reader.ReadToEnd());
|
||||||
|
}
|
||||||
|
var group = new Chart.Group() {
|
||||||
|
tracks = new List<Chart.Track>(),
|
||||||
|
notes = new List<Chart.Note>(),
|
||||||
|
motions = new List<Chart.Motion>(),
|
||||||
|
};
|
||||||
|
Chart chart = new Chart {
|
||||||
|
format = 2,
|
||||||
|
time = new BeatTime(0, 0, 1),
|
||||||
|
ruleset = "bang_dream_girls_band_party",
|
||||||
|
sigs = new List<Chart.Signature>(),
|
||||||
|
sounds = new List<Chart.Sound>(),
|
||||||
|
motions = new List<Chart.Motion>(),
|
||||||
|
groups = new List<Chart.Group> { group },
|
||||||
|
};
|
||||||
|
string bgm = null;
|
||||||
|
double? cbpm = null;
|
||||||
|
double pbeat = 0, ctime = 0;
|
||||||
|
double endbeat = 0;
|
||||||
|
foreach (var ev in src) {
|
||||||
|
double cbeat = ev.StartBeat;
|
||||||
|
ctime += cbpm == null ? 0 : (cbeat - pbeat) / cbpm.Value * 60;
|
||||||
|
pbeat = cbeat;
|
||||||
|
if (cbeat > endbeat) endbeat = cbeat;
|
||||||
|
if (ev is BestdoriChartEvent.System) {
|
||||||
|
if (bgm != null) continue;
|
||||||
|
var tev = (BestdoriChartEvent.System)ev;
|
||||||
|
bgm = StringUtils.TrimExt(tev.data);
|
||||||
|
var name = "bang_dream_girls_band_party__" + bgm;
|
||||||
|
result.Add(new SongResource(name, new FileInfo(Path.Combine(file.Directory.FullName, tev.data))));
|
||||||
|
chart.sounds.Add(new Chart.Sound { time = ToBeatTime(tev.beat), id = name });
|
||||||
|
}
|
||||||
|
else if (ev is BestdoriChartEvent.BPM) {
|
||||||
|
var tev = (BestdoriChartEvent.BPM)ev;
|
||||||
|
cbpm = tev.bpm;
|
||||||
|
chart.sigs.Add(new Chart.Signature { time = ToBeatTime(tev.beat), tempo = (float)tev.bpm });
|
||||||
|
}
|
||||||
|
else if (ev is BestdoriChartEvent.Single) {
|
||||||
|
var tev = (BestdoriChartEvent.Single)ev;
|
||||||
|
group.notes.Add(new Chart.Note {
|
||||||
|
time = ToBeatTime(tev.beat),
|
||||||
|
judges = new List<Chart.Judge> { new Chart.Judge { name = tev.flick ? "single_flick" : "single" } },
|
||||||
|
motions = new List<Chart.Motion> { new Chart.Motion { motion = "track:" + tev.lane.ToString() } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (ev is BestdoriChartEvent.Long) {
|
||||||
|
var tev = (BestdoriChartEvent.Long)ev;
|
||||||
|
var c1 = tev.connections[tev.connections.Count - 1];
|
||||||
|
var note = new Chart.Note {
|
||||||
|
time = ToBeatTime(tev.connections[0].beat),
|
||||||
|
endtime = ToBeatTime(c1.beat),
|
||||||
|
judges = new List<Chart.Judge>(),
|
||||||
|
};
|
||||||
|
for (int i = 0; i < tev.connections.Count; i++) {
|
||||||
|
BestdoriChartEvent.Connection c = tev.connections[i];
|
||||||
|
note.motions.Add(new Chart.Motion { motion = "track:" + c.lane.ToString() });
|
||||||
|
if (i == 0)
|
||||||
|
note.judges.Add(new Chart.Judge { name = "single" });
|
||||||
|
else if (i == tev.connections.Count - 1)
|
||||||
|
note.judges.Add(new Chart.Judge { time = ToBeatTime(c.beat), name = c.flick ? "longend_flick" : "longend" });
|
||||||
|
else if (!c.hidden)
|
||||||
|
note.judges.Add(new Chart.Judge { time = ToBeatTime(c.beat), name = "longnode" });
|
||||||
|
}
|
||||||
|
if (c1.beat > endbeat) endbeat = c1.beat;
|
||||||
|
group.notes.Add(note);
|
||||||
|
}
|
||||||
|
else throw new NotImplementedException("Unsupported event: " + ev.type);
|
||||||
|
}
|
||||||
|
if (bgm == null) throw new FormatException("Chart contains no song");
|
||||||
|
chart.endtime = ToBeatTime(endbeat);
|
||||||
|
result.Add(new RawChartResource(string.Format("bang_dream_girls_band_party__{0}__{1}", bgm, StringUtils.TrimExt(file.Name)), chart, new ChartMeta {
|
||||||
|
name = string.Format("Bandori {0} {1}", bgm, StringUtils.TrimExt(file.Name)),
|
||||||
|
author = "©BanG Dream! Project ©Craft Egg Inc. ©bushiroad",
|
||||||
|
ruleset = "bang_dream_girls_band_party",
|
||||||
|
note_count = group.notes.Count,
|
||||||
|
length = (float)ctime,
|
||||||
|
song = new SongMetaInfo {
|
||||||
|
name = bgm,
|
||||||
|
author = "©BanG Dream! Project ©Craft Egg Inc. ©bushiroad",
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeatTime ToBeatTime(double beat, double error = 1e-4) {
|
||||||
|
int i, n, d;
|
||||||
|
FractionUtils.ToFraction(beat, error, out n, out d);
|
||||||
|
i = n / d; n %= d;
|
||||||
|
return new BeatTime(i, n, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006
|
||||||
|
[JsonConverter(typeof(BestdoriChartEventCreator))]
|
||||||
|
abstract class BestdoriChartEvent {
|
||||||
|
public abstract string type { get; }
|
||||||
|
public abstract double StartBeat { get; }
|
||||||
|
public abstract class InstantEvent : BestdoriChartEvent {
|
||||||
|
public double beat;
|
||||||
|
public override double StartBeat { get { return beat; } }
|
||||||
|
}
|
||||||
|
public class BPM : InstantEvent {
|
||||||
|
public override string type { get { return "BPM"; } }
|
||||||
|
public double bpm;
|
||||||
|
}
|
||||||
|
public class System : InstantEvent {
|
||||||
|
public override string type { get { return "System"; } }
|
||||||
|
public string data;
|
||||||
|
}
|
||||||
|
public abstract class SingleBase : InstantEvent {
|
||||||
|
public double lane;
|
||||||
|
public bool skill;
|
||||||
|
public bool flick;
|
||||||
|
}
|
||||||
|
public class Single : SingleBase {
|
||||||
|
public override string type { get { return "Single"; } }
|
||||||
|
}
|
||||||
|
public class Directional : SingleBase {
|
||||||
|
public override string type { get { return "Directional"; } }
|
||||||
|
public string direction;
|
||||||
|
public int width;
|
||||||
|
}
|
||||||
|
public class Connection : SingleBase {
|
||||||
|
public override string type { get { return null; } }
|
||||||
|
public bool hidden;
|
||||||
|
}
|
||||||
|
public class Long : BestdoriChartEvent {
|
||||||
|
public override string type { get { return "Long"; } }
|
||||||
|
public List<Connection> connections;
|
||||||
|
public override double StartBeat { get { return connections[0].beat; } }
|
||||||
|
}
|
||||||
|
public class Slide : Long {
|
||||||
|
public override string type { get { return "Slide"; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore IDE1006
|
||||||
|
class BestdoriChartEventCreator : CustomCreationConverter<BestdoriChartEvent> {
|
||||||
|
string _currentType;
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
|
||||||
|
var obj = JObject.ReadFrom(reader);
|
||||||
|
var type = obj["type"];
|
||||||
|
if (type == null) _currentType = null;
|
||||||
|
else _currentType = obj["type"].ToObject<string>();
|
||||||
|
return base.ReadJson(obj.CreateReader(), objectType, existingValue, serializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BestdoriChartEvent Create(Type objectType) {
|
||||||
|
switch (_currentType) {
|
||||||
|
case "BPM": return new BestdoriChartEvent.BPM();
|
||||||
|
case "System": return new BestdoriChartEvent.System();
|
||||||
|
case "Single": return new BestdoriChartEvent.Single();
|
||||||
|
case "Directional": return new BestdoriChartEvent.Directional();
|
||||||
|
case null: return new BestdoriChartEvent.Connection();
|
||||||
|
case "Long": return new BestdoriChartEvent.Long();
|
||||||
|
case "Slide": return new BestdoriChartEvent.Slide();
|
||||||
|
default: throw new ArgumentException("Unknown event type: " + _currentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e3c5a8bf05d5e284ba498e91cb0dd35e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
Reference in New Issue
Block a user