2023-07-17 10:20:00 +02:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Linq;
|
2023-07-17 12:24:28 +02:00
|
|
|
|
using System.Reflection;
|
2023-07-17 10:20:00 +02:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using TaikoSoundEditor.Extensions;
|
2023-07-19 07:10:25 +02:00
|
|
|
|
using TaikoSoundEditor.Utils;
|
2023-07-17 10:20:00 +02:00
|
|
|
|
using static TaikoSoundEditor.TJA;
|
|
|
|
|
|
|
|
|
|
namespace TaikoSoundEditor
|
|
|
|
|
{
|
|
|
|
|
internal class TJA
|
|
|
|
|
{
|
|
|
|
|
public TJA(string[] content)
|
|
|
|
|
{
|
|
|
|
|
Parse(content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Line
|
|
|
|
|
{
|
|
|
|
|
public string Type { get; set; }
|
|
|
|
|
public string Scope { get; set; }
|
|
|
|
|
public string Name { get; set; }
|
|
|
|
|
public string Value { get; set; }
|
|
|
|
|
|
|
|
|
|
public Line(string type, string scope, string name, string value)
|
|
|
|
|
{
|
|
|
|
|
Type = type;
|
|
|
|
|
Scope = scope;
|
|
|
|
|
Name = name;
|
|
|
|
|
Value = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static readonly string[] HEADER_GLOBAL = new string[]
|
|
|
|
|
{
|
2023-07-17 12:24:28 +02:00
|
|
|
|
"TITLE", "TITLEJA", "SUBTITLE", "BPM", "WAVE", "OFFSET", "DEMOSTART","GENRE",
|
2023-07-17 10:20:00 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static readonly string[] HEADER_COURSE = new string[]
|
|
|
|
|
{
|
|
|
|
|
"COURSE", "LEVEL", "BALLOON", "SCOREINIT", "SCOREDIFF", "TTROWBEAT",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static readonly string[] COMMAND = new string[]
|
|
|
|
|
{
|
|
|
|
|
"START","END","GOGOSTART","GOGOEND","MEASURE","SCROLL","BPMCHANGE","DELAY","BRANCHSTART","BRANCHEND","SECTION","N","E","M","LEVELHOLD","BMSCROLL","HBSCROLL","BARLINEOFF","BARLINEON","TTBREAK",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public Line ParseLine(string line)
|
|
|
|
|
{
|
|
|
|
|
Match match = null;
|
|
|
|
|
|
2023-07-22 17:18:18 +02:00
|
|
|
|
Logger.Info($"Parsing line : {line}");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
|
|
|
|
|
if ((match = line.Match("\\/\\/.*")) != null)
|
|
|
|
|
{
|
|
|
|
|
line = line.Substring(0, match.Index).Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((match = line.Match("^([A-Z]+):(.+)", "i")) != null)
|
|
|
|
|
{
|
|
|
|
|
var nameUpper = match.Groups[1].Value.ToUpper();
|
|
|
|
|
var value = match.Groups[2].Value;
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Match = {nameUpper}, {value}");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
|
|
|
|
|
if (HEADER_GLOBAL.Contains(nameUpper))
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Detected header");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
return new Line("header", "global", nameUpper, value.Trim());
|
|
|
|
|
}
|
|
|
|
|
else if (HEADER_COURSE.Contains(nameUpper))
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Detected course");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
return new Line("header", "course", nameUpper, value.Trim());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ((match = line.Match("^#([A-Z]+)(?:\\s+(.+))?", "i")) != null)
|
|
|
|
|
{
|
|
|
|
|
var nameUpper = match.Groups[1].Value.ToUpper();
|
|
|
|
|
var value = match.Groups[2].Value ?? "";
|
|
|
|
|
|
|
|
|
|
if (COMMAND.Contains(nameUpper))
|
2023-07-19 07:10:25 +02:00
|
|
|
|
{
|
|
|
|
|
Logger.Info($"Detected command");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
return new Line("command", null, nameUpper, value.Trim());
|
2023-07-19 07:10:25 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
Logger.Warning($"Unknown command : {nameUpper} with value {value.Trim()}");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
else if ((match = line.Match("^(([0-9]|A|B|C|F|G)*,?)$")) != null)
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Detected command");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
var data = match.Groups[1].Value;
|
|
|
|
|
return new Line("data", null, null, data);
|
|
|
|
|
}
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Warning($"Unknown line : {line}");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
return new Line("unknwon", null, null, line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Course GetCourse(Header tjaHeaders, Line[] lines)
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Getting course from {lines.Length} lines");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
var headers = new CourseHeader();
|
|
|
|
|
|
|
|
|
|
var measures = new List<Measure>();
|
|
|
|
|
var measureDividend = 4;
|
|
|
|
|
var measureDivisor = 4;
|
|
|
|
|
var measureProperties = new Dictionary<string, bool>();
|
|
|
|
|
var measureData = "";
|
|
|
|
|
var measureEvents = new List<MeasureEvent>();
|
|
|
|
|
var currentBranch = "N";
|
|
|
|
|
var targetBranch = "N";
|
|
|
|
|
var flagLevelhold = false;
|
2023-07-22 10:28:52 +02:00
|
|
|
|
bool hasBranches = false;
|
2023-07-17 10:20:00 +02:00
|
|
|
|
|
|
|
|
|
foreach(var line in lines)
|
|
|
|
|
{
|
|
|
|
|
if(line.Type=="header")
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"header {line.Name} {line.Value}");
|
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (line.Name == "COURSE")
|
|
|
|
|
headers.Course = line.Value;
|
|
|
|
|
else if (line.Name == "LEVEL")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.Level = Number.ParseInt(line.Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
else if (line.Name == "BALLOON")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.Balloon = new Regex("[^0-9]").Split(line.Value).Where(_ => _ != "").Select(Number.ParseInt).ToArray();
|
2023-07-17 10:20:00 +02:00
|
|
|
|
else if (line.Name == "SCOREINIT")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.ScoreInit = Number.ParseInt(line.Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
else if (line.Name == "SCOREDIFF")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.ScoreDiff = Number.ParseInt(line.Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
else if (line.Name == "TTROWBEAT")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.TTRowBeat = Number.ParseInt(line.Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
else if(line.Type=="command")
|
|
|
|
|
{
|
2023-07-22 17:18:18 +02:00
|
|
|
|
Logger.Info($"Command Name={line.Name} Val={line.Value}");
|
2023-07-19 07:10:25 +02:00
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (line.Name == "BRANCHSTART")
|
|
|
|
|
{
|
2023-07-22 10:28:52 +02:00
|
|
|
|
hasBranches = true;
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (!flagLevelhold)
|
|
|
|
|
{
|
|
|
|
|
var values = line.Value.Split(',');
|
|
|
|
|
if (values[0] == "r")
|
|
|
|
|
{
|
|
|
|
|
if (values.Length >= 3) targetBranch = "M";
|
|
|
|
|
else if (values.Length == 2) targetBranch = "E";
|
|
|
|
|
else targetBranch = "N";
|
|
|
|
|
}
|
|
|
|
|
else if (values[0] == "p")
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
if (values.Length >= 3 && Number.ParseFloat(values[2]) <= 100) targetBranch = "M";
|
|
|
|
|
else if (values.Length >= 2 && Number.ParseFloat(values[1]) <= 100) targetBranch = "E";
|
2023-07-17 10:20:00 +02:00
|
|
|
|
else targetBranch = "N";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (line.Name == "BRANCHEND")
|
|
|
|
|
currentBranch = targetBranch;
|
|
|
|
|
else if (line.Name == "N" || line.Name == "E" || line.Name == "M")
|
|
|
|
|
currentBranch = line.Name;
|
|
|
|
|
else if(line.Name=="START" || line.Name == "END")
|
|
|
|
|
{
|
|
|
|
|
currentBranch = targetBranch = "N";
|
|
|
|
|
flagLevelhold = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (currentBranch == targetBranch)
|
|
|
|
|
{
|
|
|
|
|
if (line.Name == "MEASURE")
|
|
|
|
|
{
|
|
|
|
|
var matchMeasure = line.Value.Match("(\\d+)\\/(\\d+)");
|
|
|
|
|
if (matchMeasure != null)
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
measureDividend = Number.ParseInt(matchMeasure.Groups[1].Value);
|
|
|
|
|
measureDivisor = Number.ParseInt(matchMeasure.Groups[2].Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (line.Name == "GOGOSTART")
|
|
|
|
|
measureEvents.Add(new MeasureEvent("gogoStart", measureData.Length));
|
|
|
|
|
else if (line.Name == "GOGOEND")
|
|
|
|
|
measureEvents.Add(new MeasureEvent("gogoEnd", measureData.Length));
|
|
|
|
|
else if (line.Name == "SCROLL")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
measureEvents.Add(new MeasureEvent("scroll", measureData.Length, Number.ParseFloat(line.Value)));
|
2023-07-17 10:20:00 +02:00
|
|
|
|
else if (line.Name == "BPMCHANGE")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
measureEvents.Add(new MeasureEvent("bpm", measureData.Length, Number.ParseFloat(line.Value)));
|
2023-07-17 10:20:00 +02:00
|
|
|
|
else if (line.Name == "TTBREAK")
|
|
|
|
|
measureProperties["ttBreak"] = true;
|
|
|
|
|
else if (line.Name == "LEVELHOLD")
|
|
|
|
|
flagLevelhold = true;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if(line.Type=="data" && currentBranch==targetBranch)
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Data {line.Value}");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
var data = line.Value;
|
|
|
|
|
if (data.EndsWith(","))
|
|
|
|
|
{
|
|
|
|
|
measureData += data.Substring(0, data.Length - 1); //data.slice(0, -1);
|
|
|
|
|
var measure = new Measure(new int[] { measureDividend, measureDivisor }, measureProperties, measureData, measureEvents.ToList());
|
|
|
|
|
measures.Add(measure);
|
|
|
|
|
measureData = "";
|
|
|
|
|
measureEvents.Clear();
|
|
|
|
|
measureProperties.Clear();
|
|
|
|
|
}
|
|
|
|
|
else measureData += data;
|
|
|
|
|
}
|
|
|
|
|
} // foreach
|
|
|
|
|
|
|
|
|
|
if(measures.Count>0)
|
|
|
|
|
{
|
|
|
|
|
// Make first BPM event
|
|
|
|
|
var firstBPMEventFound = false;
|
|
|
|
|
for (var i = 0; i < measures[0].Events.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var evt = measures[0].Events[i];
|
|
|
|
|
|
|
|
|
|
if (evt.Name == "bpm" && evt.Position == 0)
|
|
|
|
|
{
|
|
|
|
|
firstBPMEventFound = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!firstBPMEventFound)
|
|
|
|
|
{
|
|
|
|
|
measures[0].Events = measures[0].Events.Prepend(new MeasureEvent("bmp", 0, tjaHeaders.Bpm)).ToList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper values
|
|
|
|
|
var course = 0;
|
|
|
|
|
var courseValue = headers.Course.ToLower();
|
|
|
|
|
|
|
|
|
|
if (courseValue == "easy" || courseValue == "0")
|
|
|
|
|
course = 0;
|
|
|
|
|
else if (courseValue == "normal" || courseValue == "1")
|
|
|
|
|
course = 1;
|
|
|
|
|
else if (courseValue == "hard" || courseValue == "2")
|
|
|
|
|
course = 2;
|
|
|
|
|
else if(courseValue=="oni" || courseValue=="3")
|
|
|
|
|
course = 3;
|
|
|
|
|
else if (courseValue == "edit" || courseValue == "ura"|| courseValue == "4")
|
|
|
|
|
course = 4;
|
|
|
|
|
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Course difficulty = {course}");
|
|
|
|
|
|
|
|
|
|
if (measureData!="" || measureData!=null)
|
2023-07-17 10:20:00 +02:00
|
|
|
|
{
|
|
|
|
|
measures.Add(new Measure(new int[] { measureDividend, measureDivisor }, measureProperties, measureData, measureEvents));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
foreach(var ev in measureEvents)
|
|
|
|
|
{
|
|
|
|
|
ev.Position = measures[measures.Count - 1].MeasureData.Length;
|
|
|
|
|
measures[measures.Count - 1].Events.Add(ev);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-22 10:28:52 +02:00
|
|
|
|
var c = new Course(course, headers, measures) { HasBranches = hasBranches };
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Course created : {c}");
|
|
|
|
|
return c;
|
2023-07-17 10:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Parse(string[] lines)
|
2023-07-19 07:10:25 +02:00
|
|
|
|
{
|
|
|
|
|
Logger.Info($"Parse start");
|
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
var headers = new Header();
|
|
|
|
|
var courses = new Dictionary<int, Course>();
|
|
|
|
|
|
|
|
|
|
int idx;
|
|
|
|
|
var courseLines = new List<Line>();
|
|
|
|
|
|
|
|
|
|
for(idx=0;idx<lines.Length;idx++)
|
|
|
|
|
{
|
|
|
|
|
var line = lines[idx];
|
|
|
|
|
if (line == "") continue;
|
|
|
|
|
var parsed = ParseLine(line);
|
|
|
|
|
|
|
|
|
|
if(parsed.Type=="header" && parsed.Scope=="global")
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Header global {parsed.Name} = {parsed.Value}");
|
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (parsed.Name == "TITLE")
|
|
|
|
|
headers.Title = parsed.Value;
|
2023-07-17 12:24:28 +02:00
|
|
|
|
if (parsed.Name == "TITLEJA")
|
|
|
|
|
headers.TitleJa = parsed.Value;
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (parsed.Name == "SUBTITLE")
|
2023-07-17 12:35:25 +02:00
|
|
|
|
headers.Subtitle = parsed.Value.StartsWith("--") ? parsed.Value.Substring(2) : parsed.Value;
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (parsed.Name == "BPM")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.Bpm = Number.ParseFloat(parsed.Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (parsed.Name == "WAVE")
|
|
|
|
|
headers.Wave = parsed.Value;
|
|
|
|
|
if (parsed.Name == "OFFSET")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.Offset = Number.ParseFloat(parsed.Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (parsed.Name == "DEMOSTART")
|
2023-07-19 07:10:25 +02:00
|
|
|
|
headers.DemoStart = Number.ParseFloat(parsed.Value);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (parsed.Name == "GENRE")
|
|
|
|
|
headers.Genre = parsed.Value;
|
|
|
|
|
}
|
|
|
|
|
else if (parsed.Type == "header" && parsed.Scope == "course")
|
|
|
|
|
{
|
|
|
|
|
if (parsed.Name == "COURSE")
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"Course found : {parsed.Value}");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (courseLines.Count>0)
|
|
|
|
|
{
|
|
|
|
|
var course = GetCourse(headers, courseLines.ToArray());
|
|
|
|
|
courses[course.CourseN] = course;
|
|
|
|
|
courseLines.Clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
courseLines.Add(parsed);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
courseLines.Add(parsed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(courseLines.Count>0)
|
|
|
|
|
{
|
|
|
|
|
var course = GetCourse(headers, courseLines.ToArray());
|
|
|
|
|
courses[course.CourseN] = course;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Headers = headers;
|
|
|
|
|
Courses = courses;
|
2023-07-19 07:10:25 +02:00
|
|
|
|
|
|
|
|
|
Logger.Info($"Parse end");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Header Headers;
|
|
|
|
|
public Dictionary<int, Course> Courses;
|
|
|
|
|
|
|
|
|
|
public class Course
|
|
|
|
|
{
|
|
|
|
|
public int CourseN { get; set; }
|
|
|
|
|
public CourseHeader Headers { get; set; }
|
|
|
|
|
public List<Measure> Measures { get; set; }
|
|
|
|
|
|
2023-07-22 10:28:52 +02:00
|
|
|
|
public bool HasBranches { get; set; }
|
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
public Course(int courseN, CourseHeader headers, List<Measure> measures)
|
|
|
|
|
{
|
|
|
|
|
CourseN = courseN;
|
|
|
|
|
Headers = headers;
|
|
|
|
|
Measures = measures;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string ToString()
|
|
|
|
|
=> $"C({CourseN},{Headers},{string.Join("|",Measures)})";
|
|
|
|
|
|
2023-07-17 13:59:33 +02:00
|
|
|
|
public ConvertedCourse Converted => ConvertToTimed(this);
|
|
|
|
|
|
|
|
|
|
private static readonly List<string> typeNote = new List<string> { "don", "kat", "donBig", "katBig" };
|
|
|
|
|
|
|
|
|
|
public int NotesCount
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
Debug.WriteLine(string.Join("", Measures.Select(_ => _.MeasureData)));
|
|
|
|
|
return string.Join("", Measures.Select(_ => _.MeasureData)).Where(c => "1234".Contains(c)).Count();
|
|
|
|
|
}
|
|
|
|
|
//get => Converted.Notes.Where(n => typeNote.Contains(n.Type)).Count();
|
|
|
|
|
}
|
2023-07-17 10:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Measure
|
|
|
|
|
{
|
|
|
|
|
public int[] Length { get; set; }
|
|
|
|
|
public Dictionary<string, bool> Properties { get; set; }
|
|
|
|
|
public string MeasureData { get; set; }
|
|
|
|
|
public List<MeasureEvent> Events { get; set; }
|
|
|
|
|
|
|
|
|
|
public Measure(int[] length, Dictionary<string, bool> properties, string measureData, List<MeasureEvent> events)
|
|
|
|
|
{
|
|
|
|
|
Length = length;
|
|
|
|
|
Properties = properties;
|
|
|
|
|
MeasureData = measureData;
|
|
|
|
|
Events = events;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string ToString() =>
|
|
|
|
|
$"M({string.Join(" ", Length)},{string.Join(";", Properties.Select(kv => kv.Key + "=" + kv.Value))},{MeasureData},{string.Join(";", Events)})";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class MeasureEvent
|
|
|
|
|
{
|
|
|
|
|
public string Name { get; set; }
|
|
|
|
|
public int Position { get; set; }
|
|
|
|
|
public float Value { get; set; }
|
|
|
|
|
|
|
|
|
|
public MeasureEvent(string name, int position, float value = 0)
|
|
|
|
|
{
|
|
|
|
|
Name = name;
|
|
|
|
|
Position = position;
|
|
|
|
|
Value = value;
|
|
|
|
|
}
|
|
|
|
|
public override string ToString() => $"ME({Name},{Position},{Value}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class Header
|
|
|
|
|
{
|
|
|
|
|
public string Title { get; set; } = "";
|
|
|
|
|
public string Subtitle { get; set; } = "";
|
2023-07-17 12:24:28 +02:00
|
|
|
|
public string TitleJa { get; set; } = "";
|
2023-07-17 10:20:00 +02:00
|
|
|
|
public float Bpm { get; set; } = 120;
|
|
|
|
|
public string Wave { get; set; } = "";
|
|
|
|
|
public float Offset { get; set; } = 0;
|
|
|
|
|
public float DemoStart { get; set; } = 0;
|
|
|
|
|
public string Genre { get; set; } = "";
|
|
|
|
|
public override string ToString() => $"H({Title},{Subtitle},{Bpm},{Wave},{Offset},{DemoStart},{Genre})";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class CourseHeader
|
|
|
|
|
{
|
|
|
|
|
public string Course { get; set; } = "Oni";
|
|
|
|
|
public int Level { get; set; } = 0;
|
|
|
|
|
public int[] Balloon { get; set; } = new int[0];
|
|
|
|
|
public int ScoreInit { get; set; } = 100;
|
|
|
|
|
public int ScoreDiff { get; set; } = 100;
|
|
|
|
|
public int TTRowBeat { get; set; } = 16;
|
|
|
|
|
|
|
|
|
|
public override string ToString() => $"CH({Course}, {Level}, {string.Join(";",Balloon)}, {ScoreInit}, {ScoreDiff}, {TTRowBeat})";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static List<byte[]> RunTja2Fumen(string sourcePath)
|
|
|
|
|
{
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info("Running tja2fumen");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
sourcePath = Path.GetFullPath(sourcePath);
|
2023-07-19 07:10:25 +02:00
|
|
|
|
Logger.Info($"source = {sourcePath}");
|
2023-07-17 10:20:00 +02:00
|
|
|
|
|
|
|
|
|
var dir = Path.GetDirectoryName(sourcePath);
|
|
|
|
|
var fname = Path.GetFileNameWithoutExtension(sourcePath);
|
|
|
|
|
|
|
|
|
|
var p = new Process();
|
|
|
|
|
p.StartInfo.FileName = Path.GetFullPath(@"Tools\tja2fumen.exe");
|
|
|
|
|
p.StartInfo.ArgumentList.Add(sourcePath);
|
|
|
|
|
p.StartInfo.UseShellExecute = false;
|
|
|
|
|
p.StartInfo.RedirectStandardError = true;
|
|
|
|
|
p.StartInfo.RedirectStandardOutput = true;
|
|
|
|
|
|
|
|
|
|
p.Start();
|
|
|
|
|
p.WaitForExit();
|
|
|
|
|
int exitCode = p.ExitCode;
|
|
|
|
|
string stdout = p.StandardOutput.ReadToEnd();
|
|
|
|
|
string stderr = p.StandardError.ReadToEnd();
|
|
|
|
|
|
|
|
|
|
if (exitCode != 0)
|
|
|
|
|
throw new Exception($"Process tja2fumen failed with exit code {exitCode}:\n" + stderr);
|
|
|
|
|
|
|
|
|
|
var result = new List<byte[]>
|
|
|
|
|
{
|
|
|
|
|
File.ReadAllBytes(Path.Combine(dir, fname + "_e.bin")),
|
|
|
|
|
File.ReadAllBytes(Path.Combine(dir, fname + "_h.bin")),
|
|
|
|
|
File.ReadAllBytes(Path.Combine(dir, fname + "_m.bin")),
|
2023-07-22 10:28:52 +02:00
|
|
|
|
File.ReadAllBytes(Path.Combine(dir, fname + "_n.bin"))
|
2023-07-17 10:20:00 +02:00
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
File.Delete(Path.Combine(dir, fname + "_e.bin"));
|
|
|
|
|
File.Delete(Path.Combine(dir, fname + "_h.bin"));
|
|
|
|
|
File.Delete(Path.Combine(dir, fname + "_m.bin"));
|
|
|
|
|
File.Delete(Path.Combine(dir, fname + "_n.bin"));
|
2023-07-22 10:28:52 +02:00
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
if (File.Exists(Path.Combine(dir, fname + "_x.bin")))
|
2023-07-22 10:28:52 +02:00
|
|
|
|
{
|
2023-07-17 10:20:00 +02:00
|
|
|
|
result.Add(File.ReadAllBytes(Path.Combine(dir, fname + "_x.bin")));
|
|
|
|
|
File.Delete(Path.Combine(dir, fname + "_x.bin"));
|
|
|
|
|
}
|
2023-07-22 10:28:52 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
result.Add(new byte[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= 2; i++)
|
|
|
|
|
{
|
|
|
|
|
int k = 0;
|
|
|
|
|
foreach (var d in "ehmnx")
|
|
|
|
|
{
|
|
|
|
|
var path = Path.Combine(dir, $"{fname}_{d}_{i}.bin");
|
|
|
|
|
if(File.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
result.Add(File.ReadAllBytes(path));
|
|
|
|
|
File.Delete(path);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
result.Add(result[k].ToArray());
|
|
|
|
|
}
|
|
|
|
|
k++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-17 10:20:00 +02:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string ToString() => $"{Headers}\n{string.Join("\n", Courses)}";
|
|
|
|
|
|
2023-07-17 13:59:33 +02:00
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
|
2023-07-17 13:59:33 +02:00
|
|
|
|
public static ConvertedCourse ConvertToTimed(Course course)
|
2023-07-17 10:20:00 +02:00
|
|
|
|
{
|
|
|
|
|
List<TimedEvent> events=new List<TimedEvent>();
|
|
|
|
|
List<Note> notes = new List<Note>();
|
|
|
|
|
float beat = 0;
|
|
|
|
|
int balloon=0;
|
|
|
|
|
bool imo = false;
|
|
|
|
|
|
2023-07-17 13:59:33 +02:00
|
|
|
|
//Debug.WriteLine("-----------------------------------------");
|
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
for (int m = 0; m < course.Measures.Count; m++)
|
|
|
|
|
{
|
|
|
|
|
var measure = course.Measures[m];
|
|
|
|
|
float length = measure.Length[0] / measure.Length[1] * 4;
|
|
|
|
|
for (int e = 0; e < measure.Events.Count; e++)
|
|
|
|
|
{
|
|
|
|
|
var evt = measure.Events[e];
|
|
|
|
|
float eBeat = length / (measure.MeasureData.Length == 0 ? 1 : measure.MeasureData.Length) * evt.Position;
|
|
|
|
|
if (evt.Name == "bpm")
|
|
|
|
|
events.Add(new TimedEvent("bmp", evt.Value, beat + eBeat));
|
|
|
|
|
else if (evt.Name == "gogoStart" || evt.Name == "gogoEnd")
|
|
|
|
|
events.Add(new TimedEvent(evt.Name, 0, beat + eBeat));
|
|
|
|
|
}
|
2023-07-17 13:59:33 +02:00
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
for(int d=0;d<measure.MeasureData.Length;d++)
|
|
|
|
|
{
|
2023-07-17 13:59:33 +02:00
|
|
|
|
//Debug.WriteLine(measure.MeasureData[d]);
|
2023-07-17 10:20:00 +02:00
|
|
|
|
var ch = measure.MeasureData[d];
|
|
|
|
|
var nBeat = length / measure.MeasureData.Length * d;
|
|
|
|
|
|
|
|
|
|
var note = new Note("", beat + nBeat);
|
|
|
|
|
switch(ch)
|
|
|
|
|
{
|
|
|
|
|
case '1':
|
|
|
|
|
note.Type = "don";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '2':
|
|
|
|
|
note.Type = "kat";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '3':
|
|
|
|
|
case 'A':
|
|
|
|
|
note.Type = "donBig";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '4':
|
|
|
|
|
case 'B':
|
|
|
|
|
note.Type = "katBig";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '5':
|
|
|
|
|
note.Type = "renda";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '6':
|
|
|
|
|
note.Type = "rendaBig";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '7':
|
|
|
|
|
note.Type = "balloon";
|
|
|
|
|
note.Count = course.Headers.Balloon[balloon++];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '8':
|
|
|
|
|
note.Type = "end";
|
|
|
|
|
if (imo) imo = false;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '9':
|
|
|
|
|
if (imo == false)
|
|
|
|
|
{
|
|
|
|
|
note.Type = "balloon";
|
|
|
|
|
note.Count = course.Headers.Balloon[balloon++];
|
|
|
|
|
imo = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
if (note.Type != "") notes.Add(note);
|
|
|
|
|
}
|
|
|
|
|
beat += length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var times = PulseToTime(events, notes.Select(n => n.Beat).ToArray());
|
|
|
|
|
|
|
|
|
|
for(int i=0;i<times.Length;i++)
|
|
|
|
|
{
|
|
|
|
|
notes[i].Time = times[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new ConvertedCourse(course.Headers, events.ToArray(), notes.ToArray());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class ConvertedCourse
|
|
|
|
|
{
|
|
|
|
|
public CourseHeader Headers { get; set; }
|
|
|
|
|
public TimedEvent[] Events { get; set; }
|
|
|
|
|
public Note[] Notes { get; set; }
|
|
|
|
|
|
|
|
|
|
public ConvertedCourse(CourseHeader headers, TimedEvent[] events, Note[] notes)
|
|
|
|
|
{
|
|
|
|
|
Headers = headers;
|
|
|
|
|
Events = events;
|
|
|
|
|
Notes = notes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static float[] PulseToTime(List<TimedEvent> events, float[] objects)
|
|
|
|
|
{
|
|
|
|
|
float bpm = 120;
|
|
|
|
|
float passedBeat = 0;
|
|
|
|
|
float passedTime = 0;
|
|
|
|
|
int eidx = 0, oidx = 0;
|
|
|
|
|
|
|
|
|
|
var times = new List<float>();
|
|
|
|
|
while(oidx<objects.Length)
|
|
|
|
|
{
|
|
|
|
|
var evt = eidx < events.Count ? events[eidx] : null;
|
|
|
|
|
var objBeat = objects[oidx];
|
|
|
|
|
|
|
|
|
|
while (evt != null && evt.Beat <= objBeat)
|
|
|
|
|
{
|
|
|
|
|
if (evt.Type == "bpm")
|
|
|
|
|
{
|
|
|
|
|
var _beat = evt.Beat - passedBeat;
|
|
|
|
|
var _time = 60 / bpm * _beat;
|
|
|
|
|
|
|
|
|
|
passedBeat += _beat;
|
|
|
|
|
passedTime += _time;
|
|
|
|
|
bpm = evt.Value;
|
|
|
|
|
}
|
|
|
|
|
eidx++;
|
|
|
|
|
evt = eidx < events.Count ? events[eidx] : null;
|
|
|
|
|
}
|
|
|
|
|
var beat = objBeat - passedBeat;
|
|
|
|
|
var time = 60 / bpm * beat;
|
|
|
|
|
times.Add(passedTime + time);
|
|
|
|
|
|
|
|
|
|
passedBeat += beat;
|
|
|
|
|
passedTime += time;
|
|
|
|
|
oidx++;
|
|
|
|
|
}
|
|
|
|
|
return times.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class TimedEvent
|
|
|
|
|
{
|
|
|
|
|
public string Type { get; set; }
|
|
|
|
|
public float Value { get; set; }
|
|
|
|
|
public float Beat { get; set; }
|
|
|
|
|
|
|
|
|
|
public TimedEvent(string type, float value, float beat)
|
|
|
|
|
{
|
|
|
|
|
Type = type;
|
|
|
|
|
Value = value;
|
|
|
|
|
Beat = beat;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class Note
|
|
|
|
|
{
|
|
|
|
|
public string Type { get; set; }
|
|
|
|
|
public float Beat { get; set; }
|
|
|
|
|
public int Count { get; set; }
|
|
|
|
|
public float Time { get; set; }
|
|
|
|
|
|
|
|
|
|
public Note(string type, float beat)
|
|
|
|
|
{
|
|
|
|
|
Type = type;
|
|
|
|
|
Beat = beat;
|
|
|
|
|
}
|
2023-07-21 19:02:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-22 08:07:15 +02:00
|
|
|
|
public static TJA ReadAsUTF8(string path)
|
2023-07-21 19:02:02 +02:00
|
|
|
|
{
|
|
|
|
|
var lines = File.ReadAllLines(path);
|
|
|
|
|
return new TJA(lines);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static TJA ReadAsShiftJIS(string path)
|
|
|
|
|
{
|
|
|
|
|
var lines = File.ReadAllLines(path, Encoding.GetEncoding("shift_jis"));
|
|
|
|
|
return new TJA(lines);
|
|
|
|
|
}
|
2023-07-22 08:07:15 +02:00
|
|
|
|
|
|
|
|
|
public static TJA ReadDefault(string path)
|
|
|
|
|
{
|
|
|
|
|
var bytes = File.ReadAllBytes(path).Take(3).ToArray();
|
|
|
|
|
if (bytes[0]==0xEF && bytes[1]==0xBB && bytes[2]==0xBF) // BOM
|
|
|
|
|
{
|
|
|
|
|
return ReadAsUTF8(path);
|
|
|
|
|
}
|
|
|
|
|
return ReadAsShiftJIS(path);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-17 10:20:00 +02:00
|
|
|
|
}
|
|
|
|
|
}
|