using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; public class TrackInfo { public string Title { get; private set; } public string Artist { get; private set; } public string Genre { get; private set; } public float BPM { get; private set; } public int Level { get; private set; } public AudioSource BGM { get; private set; } public List TrackList { get; private set; } public List Notes { get; private set; } public TrackInfo(string path): this(new FileInfo(path)) { } public TrackInfo(FileInfo file) { MultiDictionary parseResult = ParseBPE(file); ExtractTrackHeader(parseResult); TrackNotes notes = new TrackNotes(); notes.ExtractNotes(parseResult, BPM); Notes = notes.Notes; } MultiDictionary ParseBPE(FileInfo file) { MultiDictionary result = new MultiDictionary(); using (StreamReader reader = new StreamReader(new BufferedStream(file.OpenRead()))) { string line; while ((line = reader.ReadLine()) != null) { if (line == "" || line[0] != '#') continue; string[] token = line.Split(new char[] { ' ' }, 2); result[token[0]] = new List { token[1].Trim() }; } } return result; } private void ExtractTrackHeader(MultiDictionary parseResult) { TrackHeader header = new TrackHeader(); header.ExtractHeader(parseResult); Title = header.Title; Artist = header.Artist; Genre = header.Genre; BPM = header.BPM; Level = header.Level; TrackList = header.TrackList; BGM = header.BGM; } } internal class MultiDictionary : IEnumerable>> { private Dictionary> dictionary = new Dictionary>(); public List this[TKey key] { get { List list; if (!dictionary.TryGetValue(key, out list)) { list = new List(); dictionary[key] = list; } return list; } set { List list; if (!dictionary.TryGetValue(key, out list)) dictionary[key] = value; else dictionary[key] = list.Concat(value).ToList(); } } public bool TryGetValue(TKey key, out List value) { return dictionary.TryGetValue(key, out value); } public IEnumerator>> GetEnumerator() { return dictionary.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public bool Remove(TKey key) { return dictionary.Remove(key); } } internal class TrackHeader { public string Title { get; private set; } public string Artist { get; private set; } public string Genre { get; private set; } public float BPM { get; private set; } public int Level { get; private set; } public AudioSource BGM { get; private set; } public List TrackList { get; private set; } public void ExtractHeader(MultiDictionary parseResult) { Title = ExtractMetaString(parseResult, "#TITLE"); Artist = ExtractMetaString(parseResult, "#ARTIST"); Genre = ExtractMetaString(parseResult, "#GENRE"); BPM = ExtractBPM(parseResult); Level = ExtractLevel(parseResult); TrackList = ExtractMetaList(parseResult, "#TRACKLIST"); BGM = Resources.Load(ExtractMetaString(parseResult, "#WAV")) as AudioSource; } private List ExtractMetaList(MultiDictionary parseResult, string key) { List list; if (parseResult.TryGetValue(key, out list)) { parseResult.Remove(key); return list; } return null; } private string ExtractMetaString(MultiDictionary parseResult, string key) { List list = ExtractMetaList(parseResult, key); return list == null ? null : list[0]; } private float ExtractBPM(MultiDictionary parseResult) { string str = ExtractMetaString(parseResult, "#BPM"); float outBPM; if (float.TryParse(str, out outBPM)) return outBPM; else return 0; } private int ExtractLevel(MultiDictionary parseResult) { string str = ExtractMetaString(parseResult, "#PLAYLEVEL"); int outLevel; if (int.TryParse(str, out outLevel)) return outLevel; else return 0; } } internal class TrackNotes { private List notes = new List(); public List Notes { get { return notes; } } public void ExtractNotes(MultiDictionary parseResult, float BPM) { var validSigns = ExtractValidSigns(parseResult); CreateNotes(validSigns, BPM); CreateBeatLines(ExtractEnd(validSigns), BPM); } IEnumerable>> ExtractValidSigns(MultiDictionary parseResult) { return parseResult.Where(x => IsValidSignType(x.Key)); } bool IsValidSignType(string signType) { int measure; return signType.Length == 7 && int.TryParse(signType.Substring(1, 3), out measure) && Enumerable.Contains(new List { "SBT", "LBT", "SMO", "LMO" }, signType.Substring(4, 3)); } int ExtractEnd(IEnumerable>> validSigns) { return validSigns.Select(x => int.Parse(x.Key.Substring(1, 3))).Max(); } void CreateBeatLines(int end, float BPM) { Notes.Concat(Enumerable.Range(1, (end + 1) * 4) .Select(x => new Note(NoteType.BeatLine, (x / 4.0f) * (4 * 60 * 1000f / BPM)))); } void CreateNotes(IEnumerable>> validSigns, float BPM) { notes = new List(); validSigns.ToList().ForEach(x => AddNoteSign(x, 4 * 60 * 1000f / BPM, new NoteMaker())); } void AddNoteSign(KeyValuePair> parsedItem, float miliSeconds, NoteMaker noteMaker) { parsedItem.Value.ForEach(x => Notes.Concat(NoteSignGenerator.SignToNotes(x, parsedItem.Key, miliSeconds, noteMaker))); } } internal class NoteSignGenerator { public static IEnumerable SignToNotes(string sign, string signType, float miliSeconds, NoteMaker noteMaker) { return GenerateNoteSequence(Enumerable.Range(0, sign.Length / 2), x => CurrentCode(x, sign), x => ComputeTiming(int.Parse(signType.Substring(1, 3)), x, sign.Length / 2, miliSeconds), (code, timing) => MakeNote(noteMaker, code, signType.Substring(4, 3), timing)); } static string CurrentCode(int location, string sign) { return sign.Substring(location * 2, 2); } static float ComputeTiming(float measure, int location, int sequenceSize, float miliSeconds) { return (measure + (float)location / sequenceSize) * miliSeconds; } static Note MakeNote(NoteMaker noteMaker, string code, string type, float timing) { return noteMaker.Make(code, type, timing); } static IEnumerable GenerateNoteSequence(IEnumerable range, Func currentCode, Func calcTiming, Func concreteNote) { return DiscreteRange(range, x => currentCode(x)) .Select(x => concreteNote(currentCode(x), calcTiming(x))) .Where(x => x != null); } static IEnumerable DiscreteRange(IEnumerable range, Func currentCode) { return range.Where(x => currentCode(x).Equals("00")); } } internal abstract class NoteMakerBase { protected float? Timing = null; public Note Make(string code, string type, float timing) { if (NotValidType(type)) return null; if (IsShort(type)) return MakeShort(code, type, timing); if (IsLongEnd(type)) return MakeLong(code, type, timing); RegisterTiming(timing); return null; } bool IsShort(string type) { return type[0] == 'S'; } bool IsLongEnd(string type) { return type[0] == 'L' && Timing != null; } void RegisterTiming(float timing) { Timing = timing; } abstract protected bool NotValidType(string type); abstract protected Note MakeShort(string code, string type, float timing); abstract protected Note MakeLong(string code, string type, float timing); } internal class ButtonNoteMaker : NoteMakerBase { override protected bool NotValidType(string type) { return type.Substring(1) != "BT"; } override protected Note MakeShort(string code, string type, float timing) { return new Note(code, type, timing); } override protected Note MakeLong(string code, string type, float timing) { float start = Timing.Value; Timing = null; return new Note(code, type, start, timing); } } internal class MotionNoteMaker : NoteMakerBase { override protected bool NotValidType(string type) { Type motionType; return type.Substring(1) != "MO" || !MotionNote.keymap.TryGetValue(type, out motionType); } override protected Note MakeShort(string code, string type, float timing) { return (MotionNote)Activator.CreateInstance (MotionNote.keymap[type], code, timing); } override protected Note MakeLong(string code, string type, float timing) { float start = Timing.Value; Timing = null; return (MotionNote)Activator.CreateInstance (MotionNote.keymap[type], code, start, timing); } } internal class NoteMaker { ButtonNoteMaker buttonNoteMaker = new ButtonNoteMaker(); MotionNoteMaker motionNoteMaker = new MotionNoteMaker(); public Note Make(string code, string type, float timing) { return motionNoteMaker.Make(code, type, timing) ?? buttonNoteMaker.Make(code, type, timing); } }