diff --git a/BAKKA-Editor/Chart.cs b/BAKKA-Editor/Chart.cs index 402e2dd..0e3c89a 100644 --- a/BAKKA-Editor/Chart.cs +++ b/BAKKA-Editor/Chart.cs @@ -1,300 +1,302 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace BAKKA_Editor { - internal class Chart - { - public List Notes { get; set; } - public List Gimmicks { get; set; } - /// - /// Offset in seconds. - /// - public double Offset { get; set; } - /// - /// Movie offset in seconds - /// - public double MovieOffset { get; set; } - String SongFileName { get; set; } - List TimeEvents { get; set; } - public bool HasInitEvents + internal class Chart { - get - { - return TimeEvents != null && - TimeEvents.Count > 0 && - Gimmicks.Count(x => x.Measure == 0 && x.GimmickType == GimmickType.BpmChange) >= 1 && - Gimmicks.Count(x => x.Measure == 0 && x.GimmickType == GimmickType.TimeSignatureChange) >= 1; - } - } - public bool IsSaved { get; set; } - - public Chart() - { - Notes = new(); - Gimmicks = new(); - Offset = 0; - MovieOffset = 0; - SongFileName = ""; - IsSaved = true; - } - - public bool ParseFile(string filename) - { - var file = File.ReadAllLines(filename); - - int index = 0; - - do - { - var line = file[index]; - - var path = Utils.GetTag(line, "#MUSIC_FILE_PATH "); - if (path != null) - SongFileName = path; - - var offset = Utils.GetTag(line, "#OFFSET"); - if (offset != null) - Offset = Convert.ToDouble(offset); - - offset = Utils.GetTag(line, "#MOVIEOFFSET"); - if (offset != null) - MovieOffset = Convert.ToDouble(offset); - - - if (line.Contains("#BODY")) + private static readonly CultureInfo _defaultParsingCulture = CultureInfo.InvariantCulture; + public List Notes { get; set; } + public List Gimmicks { get; set; } + /// + /// Offset in seconds. + /// + public double Offset { get; set; } + /// + /// Movie offset in seconds + /// + public double MovieOffset { get; set; } + String SongFileName { get; set; } + List TimeEvents { get; set; } + public bool HasInitEvents { - index++; - break; - } - - } while(++index < file.Length); - - int lineNum; - Gimmick gimmickTemp; - Note noteTemp; - Dictionary notesByLine = new(); - Dictionary refByLine = new(); - for (int i = index; i < file.Length; i++) - { - if (String.IsNullOrWhiteSpace(file[i])) - continue; - - var parsed = file[i].Split(new string[] {" "}, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - NoteBase temp = new(); - temp.BeatInfo = new BeatInfo(Convert.ToInt32(parsed[0]), Convert.ToInt32(parsed[1])); - temp.GimmickType = (GimmickType)Convert.ToInt32(parsed[2]); - - switch (temp.GimmickType) - { - case GimmickType.NoGimmick: - noteTemp = new Note(temp.BeatInfo); - noteTemp.NoteType = (NoteType)Convert.ToInt32(parsed[3]); - lineNum = Convert.ToInt32(parsed[4]); - noteTemp.Position = Convert.ToInt32(parsed[5]); - noteTemp.Size = Convert.ToInt32(parsed[6]); - noteTemp.HoldChange = Convert.ToBoolean(Convert.ToInt32(parsed[7])); - if (noteTemp.NoteType == NoteType.MaskAdd || noteTemp.NoteType == NoteType.MaskRemove) + get { - noteTemp.MaskFill = (MaskType)Convert.ToInt32(parsed[8]); + return TimeEvents != null && + TimeEvents.Count > 0 && + Gimmicks.Count(x => x.Measure == 0 && x.GimmickType == GimmickType.BpmChange) >= 1 && + Gimmicks.Count(x => x.Measure == 0 && x.GimmickType == GimmickType.TimeSignatureChange) >= 1; } - else if (noteTemp.NoteType == NoteType.HoldStartNoBonus || - noteTemp.NoteType == NoteType.HoldJoint || - noteTemp.NoteType == NoteType.HoldStartBonusFlair) + } + public bool IsSaved { get; set; } + + public Chart() + { + Notes = new(); + Gimmicks = new(); + Offset = 0; + MovieOffset = 0; + SongFileName = ""; + IsSaved = true; + } + + public bool ParseFile(string filename) + { + var file = File.ReadAllLines(filename); + + int index = 0; + + do { - refByLine[lineNum] = Convert.ToInt32(parsed[8]); + var line = file[index]; + + var path = Utils.GetTag(line, "#MUSIC_FILE_PATH "); + if (path != null) + SongFileName = path; + var offset = Utils.GetTag(line, "#OFFSET"); + if (offset != null) + Offset = Convert.ToDouble(offset, _defaultParsingCulture); + + offset = Utils.GetTag(line, "#MOVIEOFFSET"); + if (offset != null) + MovieOffset = Convert.ToDouble(offset, _defaultParsingCulture); + + + if (line.Contains("#BODY")) + { + index++; + break; + } + + } while (++index < file.Length); + + int lineNum; + Gimmick gimmickTemp; + Note noteTemp; + Dictionary notesByLine = new(); + Dictionary refByLine = new(); + for (int i = index; i < file.Length; i++) + { + if (String.IsNullOrWhiteSpace(file[i])) + continue; + + var parsed = file[i].Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + NoteBase temp = new(); + temp.BeatInfo = new BeatInfo(Convert.ToInt32(parsed[0], _defaultParsingCulture), Convert.ToInt32(parsed[1], _defaultParsingCulture)); + temp.GimmickType = (GimmickType)Convert.ToInt32(parsed[2], _defaultParsingCulture); + + switch (temp.GimmickType) + { + case GimmickType.NoGimmick: + noteTemp = new Note(temp.BeatInfo); + noteTemp.NoteType = (NoteType)Convert.ToInt32(parsed[3], _defaultParsingCulture); + lineNum = Convert.ToInt32(parsed[4], _defaultParsingCulture); + noteTemp.Position = Convert.ToInt32(parsed[5], _defaultParsingCulture); + noteTemp.Size = Convert.ToInt32(parsed[6], _defaultParsingCulture); + noteTemp.HoldChange = Convert.ToBoolean(Convert.ToInt32(parsed[7], _defaultParsingCulture)); + if (noteTemp.NoteType == NoteType.MaskAdd || noteTemp.NoteType == NoteType.MaskRemove) + { + noteTemp.MaskFill = (MaskType)Convert.ToInt32(parsed[8], _defaultParsingCulture); + } + else if (noteTemp.NoteType == NoteType.HoldStartNoBonus || + noteTemp.NoteType == NoteType.HoldJoint || + noteTemp.NoteType == NoteType.HoldStartBonusFlair) + { + refByLine[lineNum] = Convert.ToInt32(parsed[8], _defaultParsingCulture); + } + Notes.Add(noteTemp); + notesByLine[lineNum] = Notes.Last(); + break; + case GimmickType.BpmChange: + gimmickTemp = new Gimmick(temp.BeatInfo, temp.GimmickType); + gimmickTemp.BPM = Convert.ToDouble(parsed[3], _defaultParsingCulture); + Gimmicks.Add(gimmickTemp); + break; + case GimmickType.TimeSignatureChange: + gimmickTemp = new Gimmick(temp.BeatInfo, temp.GimmickType); + gimmickTemp.TimeSig = new TimeSignature() { Upper = Convert.ToInt32(parsed[3], _defaultParsingCulture), Lower = parsed.Length == 5 ? Convert.ToInt32(parsed[4], _defaultParsingCulture) : 4 }; + Gimmicks.Add(gimmickTemp); + break; + case GimmickType.HiSpeedChange: + gimmickTemp = new Gimmick(temp.BeatInfo, temp.GimmickType); + gimmickTemp.HiSpeed = Convert.ToDouble(parsed[3], _defaultParsingCulture); + Gimmicks.Add(gimmickTemp); + break; + case GimmickType.ReverseStart: + case GimmickType.ReverseMiddle: + case GimmickType.ReverseEnd: + case GimmickType.StopStart: + case GimmickType.StopEnd: + default: + Gimmicks.Add(new Gimmick(temp.BeatInfo, temp.GimmickType)); + break; + } + } - Notes.Add(noteTemp); - notesByLine[lineNum] = Notes.Last(); - break; - case GimmickType.BpmChange: - gimmickTemp = new Gimmick(temp.BeatInfo, temp.GimmickType); - gimmickTemp.BPM = Convert.ToDouble(parsed[3]); - Gimmicks.Add(gimmickTemp); - break; - case GimmickType.TimeSignatureChange: - gimmickTemp = new Gimmick(temp.BeatInfo, temp.GimmickType); - gimmickTemp.TimeSig = new TimeSignature() { Upper = Convert.ToInt32(parsed[3]), Lower = parsed.Length == 5 ? Convert.ToInt32(parsed[4]) : 4 }; - Gimmicks.Add(gimmickTemp); - break; - case GimmickType.HiSpeedChange: - gimmickTemp = new Gimmick(temp.BeatInfo, temp.GimmickType); - gimmickTemp.HiSpeed = Convert.ToDouble(parsed[3]); - Gimmicks.Add(gimmickTemp); - break; - case GimmickType.ReverseStart: - case GimmickType.ReverseMiddle: - case GimmickType.ReverseEnd: - case GimmickType.StopStart: - case GimmickType.StopEnd: - default: - Gimmicks.Add(new Gimmick(temp.BeatInfo, temp.GimmickType)); - break; - } - } - - // Generate hold references - for (int i = 0; i < Notes.Count; i++) - { - if (refByLine.ContainsKey(i)) - { - Notes[i].NextNote = notesByLine[refByLine[i]]; - Notes[i].NextNote.PrevNote = Notes[i]; - } - } - - RecalcTime(); - - IsSaved = true; - return true; - } - - public bool WriteFile(string filename, bool setSave = true) - { - using (StreamWriter sw = new StreamWriter(new FileStream(filename, FileMode.Create), new UTF8Encoding(false))) - { - // LF line ending - sw.NewLine = "\n"; - - sw.WriteLine("#MUSIC_SCORE_ID 0"); - sw.WriteLine("#MUSIC_SCORE_VERSION 0"); - sw.WriteLine("#GAME_VERSION "); - sw.WriteLine($"#MUSIC_FILE_PATH {SongFileName}"); - sw.WriteLine($"#OFFSET {Offset:F6}"); - sw.WriteLine($"#MOVIEOFFSET {MovieOffset:F6}"); - sw.WriteLine("#BODY"); - - foreach (var gimmick in Gimmicks) - { - sw.Write($"{gimmick.BeatInfo.Measure,4:F0}{gimmick.BeatInfo.Beat,5:F0}{((int)gimmick.GimmickType),5:F0}"); - switch (gimmick.GimmickType) - { - case GimmickType.BpmChange: - sw.WriteLine($" {gimmick.BPM:F6}"); - break; - case GimmickType.TimeSignatureChange: - sw.WriteLine($"{gimmick.TimeSig.Upper,5:F0}{gimmick.TimeSig.Lower,5:F0}"); - break; - case GimmickType.HiSpeedChange: - sw.WriteLine($" {gimmick.HiSpeed:F6}"); - break; - default: - sw.WriteLine(""); - break; - } - } - - foreach (var note in Notes) - { - sw.Write($"{note.BeatInfo.Measure,4:F0}{note.BeatInfo.Beat,5:F0}{((int)note.GimmickType),5:F0}{(int)note.NoteType,5:F0}"); - sw.Write($"{Notes.IndexOf(note),5:F0}{note.Position,5:F0}{note.Size,5:F0}{Convert.ToInt32(note.HoldChange),5:F0}"); - if (note.IsMask) - sw.Write($"{(int)note.MaskFill,5:F0}"); - if (note.NextNote != null) - sw.Write($"{Notes.IndexOf(note.NextNote),5:F0}"); - sw.WriteLine(""); - } - } - - IsSaved = setSave; - return true; - } - - public void RecalcTime() - { - Gimmicks = Gimmicks.OrderBy(x => x.Measure).ToList(); - var timeSig = Gimmicks.FirstOrDefault(x => x.GimmickType == GimmickType.TimeSignatureChange && x.Measure == 0.0f); - var bpm = Gimmicks.FirstOrDefault(x => x.GimmickType == GimmickType.BpmChange && x.Measure == 0.0f); - if (timeSig == null || bpm == null) - return; // Cannot calculate times without either starting value - - TimeEvents = new(); - for (int i = 0; i < Gimmicks.Count; i++) - { - var evt = TimeEvents.FirstOrDefault(x => x.BeatInfo.MeasureDecimal == Gimmicks[i].BeatInfo.MeasureDecimal); - - if (Gimmicks[i].GimmickType == GimmickType.BpmChange) - { - if (evt == null) - { - TimeEvents.Add(new Gimmick() + // Generate hold references + for (int i = 0; i < Notes.Count; i++) { - BeatInfo = new BeatInfo(Gimmicks[i].BeatInfo), - BPM = Gimmicks[i].BPM, - TimeSig = new TimeSignature(timeSig.TimeSig) - }); - } - else - { - evt.BPM = Gimmicks[i].BPM; - } - bpm = Gimmicks[i]; + if (refByLine.ContainsKey(i)) + { + Notes[i].NextNote = notesByLine[refByLine[i]]; + Notes[i].NextNote.PrevNote = Notes[i]; + } + } + + RecalcTime(); + + IsSaved = true; + return true; } - if (Gimmicks[i].GimmickType == GimmickType.TimeSignatureChange) + + public bool WriteFile(string filename, bool setSave = true) { - if (evt == null) - { - TimeEvents.Add(new Gimmick() + using (StreamWriter sw = new StreamWriter(new FileStream(filename, FileMode.Create), new UTF8Encoding(false))) { - BeatInfo = new BeatInfo(Gimmicks[i].BeatInfo), - BPM = bpm.BPM, - TimeSig = new TimeSignature(Gimmicks[i].TimeSig) - }); - } - else - { - evt.TimeSig = new TimeSignature(Gimmicks[i].TimeSig); - } - timeSig = Gimmicks[i]; + // LF line ending + sw.NewLine = "\n"; + + sw.WriteLine("#MUSIC_SCORE_ID 0"); + sw.WriteLine("#MUSIC_SCORE_VERSION 0"); + sw.WriteLine("#GAME_VERSION "); + sw.WriteLine($"#MUSIC_FILE_PATH {SongFileName}"); + sw.WriteLine($"#OFFSET {Offset:F6}"); + sw.WriteLine($"#MOVIEOFFSET {MovieOffset:F6}"); + sw.WriteLine("#BODY"); + + foreach (var gimmick in Gimmicks) + { + sw.Write($"{gimmick.BeatInfo.Measure,4:F0}{gimmick.BeatInfo.Beat,5:F0}{((int)gimmick.GimmickType),5:F0}"); + switch (gimmick.GimmickType) + { + case GimmickType.BpmChange: + sw.WriteLine($" {gimmick.BPM:F6}"); + break; + case GimmickType.TimeSignatureChange: + sw.WriteLine($"{gimmick.TimeSig.Upper,5:F0}{gimmick.TimeSig.Lower,5:F0}"); + break; + case GimmickType.HiSpeedChange: + sw.WriteLine($" {gimmick.HiSpeed:F6}"); + break; + default: + sw.WriteLine(""); + break; + } + } + + foreach (var note in Notes) + { + sw.Write($"{note.BeatInfo.Measure,4:F0}{note.BeatInfo.Beat,5:F0}{((int)note.GimmickType),5:F0}{(int)note.NoteType,5:F0}"); + sw.Write($"{Notes.IndexOf(note),5:F0}{note.Position,5:F0}{note.Size,5:F0}{Convert.ToInt32(note.HoldChange, _defaultParsingCulture),5:F0}"); + if (note.IsMask) + sw.Write($"{(int)note.MaskFill,5:F0}"); + if (note.NextNote != null) + sw.Write($"{Notes.IndexOf(note.NextNote),5:F0}"); + sw.WriteLine(""); + } + } + + IsSaved = setSave; + return true; } - } - // Run through all time events and generate valid start times - TimeEvents[0].StartTime = Offset * 1000.0; - for (int i = 1; i < TimeEvents.Count; i++) - { - TimeEvents[i].StartTime = ((TimeEvents[i].Measure - TimeEvents[i - 1].Measure) * (4.0f * TimeEvents[i - 1].TimeSig.Ratio * (60000.0 / TimeEvents[i - 1].BPM))) + TimeEvents[i - 1].StartTime; - } + public void RecalcTime() + { + Gimmicks = Gimmicks.OrderBy(x => x.Measure).ToList(); + var timeSig = Gimmicks.FirstOrDefault(x => x.GimmickType == GimmickType.TimeSignatureChange && x.Measure == 0.0f); + var bpm = Gimmicks.FirstOrDefault(x => x.GimmickType == GimmickType.BpmChange && x.Measure == 0.0f); + if (timeSig == null || bpm == null) + return; // Cannot calculate times without either starting value + + TimeEvents = new(); + for (int i = 0; i < Gimmicks.Count; i++) + { + var evt = TimeEvents.FirstOrDefault(x => x.BeatInfo.MeasureDecimal == Gimmicks[i].BeatInfo.MeasureDecimal); + + if (Gimmicks[i].GimmickType == GimmickType.BpmChange) + { + if (evt == null) + { + TimeEvents.Add(new Gimmick() + { + BeatInfo = new BeatInfo(Gimmicks[i].BeatInfo), + BPM = Gimmicks[i].BPM, + TimeSig = new TimeSignature(timeSig.TimeSig) + }); + } + else + { + evt.BPM = Gimmicks[i].BPM; + } + bpm = Gimmicks[i]; + } + if (Gimmicks[i].GimmickType == GimmickType.TimeSignatureChange) + { + if (evt == null) + { + TimeEvents.Add(new Gimmick() + { + BeatInfo = new BeatInfo(Gimmicks[i].BeatInfo), + BPM = bpm.BPM, + TimeSig = new TimeSignature(Gimmicks[i].TimeSig) + }); + } + else + { + evt.TimeSig = new TimeSignature(Gimmicks[i].TimeSig); + } + timeSig = Gimmicks[i]; + } + } + + // Run through all time events and generate valid start times + TimeEvents[0].StartTime = Offset * 1000.0; + for (int i = 1; i < TimeEvents.Count; i++) + { + TimeEvents[i].StartTime = ((TimeEvents[i].Measure - TimeEvents[i - 1].Measure) * (4.0f * TimeEvents[i - 1].TimeSig.Ratio * (60000.0 / TimeEvents[i - 1].BPM))) + TimeEvents[i - 1].StartTime; + } + } + /* + ((60000.0 / evt.BPM) * 4.0 * evt.TimeSig.Ratio) * measure = time + time / ((60000.0 / evt.BPM) * 4.0 * evt.TimeSig.Ratio) = measure + */ + + /// + /// Translate clock time to beats + /// + /// Current timestamp in ms + /// + public BeatInfo GetBeat(float time) + { + if (TimeEvents == null || TimeEvents.Count == 0) + return new BeatInfo(-1, 0); + + var evt = TimeEvents.Where(x => time >= x.StartTime).LastOrDefault(); + if (evt == null) + evt = TimeEvents[0]; + return new BeatInfo((float)((time - evt.StartTime) / ((60000.0 / evt.BPM) * 4.0f * evt.TimeSig.Ratio) + evt.Measure)); + } + + /// + /// Translate measures into clock time + /// + /// + /// + public int GetTime(BeatInfo beat) + { + if (TimeEvents == null || TimeEvents.Count == 0) + return 0; + + var evt = TimeEvents.Where(x => beat.MeasureDecimal >= x.Measure).LastOrDefault(); + if (evt == null) + evt = TimeEvents[0]; + return (int)(((60000.0 / evt.BPM) * 4.0f * evt.TimeSig.Ratio) * (beat.MeasureDecimal - evt.Measure) + evt.StartTime); + } } - /* - ((60000.0 / evt.BPM) * 4.0 * evt.TimeSig.Ratio) * measure = time - time / ((60000.0 / evt.BPM) * 4.0 * evt.TimeSig.Ratio) = measure - */ - - /// - /// Translate clock time to beats - /// - /// Current timestamp in ms - /// - public BeatInfo GetBeat(float time) - { - if (TimeEvents == null || TimeEvents.Count == 0) - return new BeatInfo(-1, 0); - - var evt = TimeEvents.Where(x => time >= x.StartTime).LastOrDefault(); - if (evt == null) - evt = TimeEvents[0]; - return new BeatInfo((float)((time - evt.StartTime) / ((60000.0 / evt.BPM) * 4.0f * evt.TimeSig.Ratio) + evt.Measure)); - } - - /// - /// Translate measures into clock time - /// - /// - /// - public int GetTime(BeatInfo beat) - { - if (TimeEvents == null || TimeEvents.Count == 0) - return 0; - - var evt = TimeEvents.Where(x => beat.MeasureDecimal >= x.Measure).LastOrDefault(); - if (evt == null) - evt = TimeEvents[0]; - return (int)(((60000.0 / evt.BPM) * 4.0f * evt.TimeSig.Ratio) * (beat.MeasureDecimal - evt.Measure) + evt.StartTime); - } - } } \ No newline at end of file