static void Main(string[] args) { string command = ""; bool filesInFolder = false; bool recursive = false; bool fileIsValid = false; List <string> filenames = new List <string>(); if (args.Length == 0 || args[0].Contains("--?") || args[0].Contains("/?") || args[0].Contains("--help")) { Console.WriteLine(); Console.WriteLine("Welcome to the SUS <-> DR2 Converter! (Version 0.1)"); Console.WriteLine(); Console.WriteLine("For the time being, only SUS->DR2 conversion is possible."); Console.WriteLine(); Console.WriteLine("To convert a single file, type the path here now (include file extension), or run the .exe with the path as the first and only argument."); Console.WriteLine("To convert all files in this folder, type \"folder\", or run the .exe with the argument --f"); Console.WriteLine("To convert all files in this folder and every sub-folder recursively, type \"recursive\", or run the .exe with the argument --r"); Console.WriteLine(); Console.WriteLine("**WARNING** This program will overwrite existing conversions. Back them up if you do not wish to lose them."); Console.WriteLine(); Console.WriteLine("Otherwise, press Enter to close."); command = Console.ReadLine(); if (string.IsNullOrEmpty(command)) { Environment.Exit(0); } } if (command.ToLower() == "folder" || (args.Length > 0 && args[0].ToLower() == "--f")) { filesInFolder = true; } else if (command.ToLower() == "recursive" || (args.Length > 0 && args[0].ToLower() == "--r")) { recursive = true; } else { if (string.IsNullOrEmpty(command) && args.Length > 0) { command = args[0]; } if (!File.Exists(command)) { Console.WriteLine("The specified file {0} cannot be found. Press Enter to close.", command); Console.ReadLine(); Environment.Exit(0); } filenames.Add(command); fileIsValid = true; } if (recursive) { var filesHere = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory); foreach (var file in filesHere) { if (file.EndsWith(".sus")) { filenames.Add(file); } } SearchForFile(ref filenames, AppDomain.CurrentDomain.BaseDirectory, ".sus"); } if (filesInFolder) { var files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory); foreach (var file in files) { if (file.EndsWith(".sus")) { filenames.Add(file); } } } foreach (var file in filenames) { Console.WriteLine("Parsing {0}...", file); // Check which direction we're going if (file.EndsWith(".sus")) { Dr2Metadata meta; var notes = ParseSus(file, out meta); string exportName = file.Replace(".sus", ".dr2"); ExportDr2(exportName, notes, meta); Globals.ResetFlags(); NoteIncrementer.Reset(); } else if (file.EndsWith(".dr2")) { Console.WriteLine("DR2->SUS is not yet supported."); } Console.WriteLine("Parse is complete."); Console.WriteLine(""); } Console.WriteLine("Press Enter to close."); Console.ReadLine(); }
static List <Dr2Note> ParseSus(string filename, out Dr2Metadata metadata) { List <Dr2Note> dr2Notes = new List <Dr2Note>(); metadata = new Dr2Metadata(); Dictionary <int, Dr2BpmChange> bpmChanges = new Dictionary <int, Dr2BpmChange>(); string[] bpmSplit = new string[] { "#BPM", ":" }; Dictionary <int, int> holdIDlist = new Dictionary <int, int>(); List <Tuple <NoteParse, int> > unmatchedAirs = new List <Tuple <NoteParse, int> >(); List <NoteParse> unmatchedAirLane = new List <NoteParse>(); Dictionary <int, SlideCollection> tempSlideDict = new Dictionary <int, SlideCollection>(); List <SlideCollection> slideNotes = new List <SlideCollection>(); List <NoteParse> failedNotes = new List <NoteParse>(); int ticksPerBeat = 192; // Default value using (StreamReader sr = new StreamReader(filename)) { while (!sr.EndOfStream) { string line = sr.ReadLine(); // Eventually need to parse metadata if (line.StartsWith("#DESIGNER")) { metadata.Designer = line.Replace("#DESIGNER ", "").Replace("\"", ""); } else if (line.StartsWith("#WAVEOFFSET")) { metadata.Offset = Convert.ToDouble(line.Replace("#WAVEOFFSET ", "")); } else if (Regex.Match(line, "(#BPM)").Success) { string[] split = line.Split(bpmSplit, StringSplitOptions.RemoveEmptyEntries); int index = ParseAlphanumeric(split[0].Substring(1)); if (!bpmChanges.ContainsKey(index)) { bpmChanges.Add(index, new Dr2BpmChange() { Bpm = Convert.ToDouble(split[1]), BpmStart = -1 }); } else { Globals.MultipleBPMLinesPresent = true; } } else if (line.StartsWith("#DIFFICULTY")) { string[] split = line.Split(new string[] { "#DIFFICULTY", ":", "\"" }, StringSplitOptions.RemoveEmptyEntries); foreach (var numStr in split) { int num; if (int.TryParse(numStr, out num)) { metadata.Difficulty = num; } } } else if (line.StartsWith("#REQUEST")) { if (line.Contains("ticks_per_beat")) { string[] split = line.Split(new string[] { "#REQUEST", "\"", " " }, StringSplitOptions.RemoveEmptyEntries); if (split[0] == "ticks_per_beat") { ticksPerBeat = Convert.ToInt32(split[1]); } else { Globals.ErrorParsingTicksPerBeat = true; } } } // Regex match for Single Notes if (Regex.Match(line, "[#][0-9]{3}[1]").Success) { var parsed = ParseLine(line); double noteSub = 1.0 / parsed.Notes.Count; for (int i = 0; i < parsed.Notes.Count; i++) { switch (parsed.Notes[i].Item1) { case 1: // Tap case 2: // ExTap case 3: // Flick dr2Notes.Add(new Dr2Note() { ID = NoteIncrementer.GetNextID(), NoteType = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1), Measure = (parsed.Measure + i * noteSub), LaneIndex = parsed.LaneIndex, Width = parsed.Notes[i].Item2 }); break; case 4: // Hell Tap break; default: // Rest notes / spacers (0) are ignored break; } } } // Regex match for Hold, Slide, and Air-Action Lane else if (Regex.IsMatch(line, "[#][0-9]{3}[2-4]")) { var parsed = ParseLine(line); if (parsed.NoteClass == 4) { unmatchedAirLane.Add(parsed); } else if (parsed.NoteClass == 3) { double noteSub = 1.0 / parsed.Notes.Count; for (int i = 0; i < parsed.Notes.Count; i++) { switch (parsed.Notes[i].Item1) { case 1: // Start Note case 2: // End Note // Check if a collection for this already exists if (tempSlideDict.ContainsKey(parsed.NoteIdentifier)) { // Check to see if it already has a start and end if (tempSlideDict[parsed.NoteIdentifier].containsStart && tempSlideDict[parsed.NoteIdentifier].containsEnd) { // Then commit this and start a new one slideNotes.Add(tempSlideDict[parsed.NoteIdentifier]); tempSlideDict[parsed.NoteIdentifier] = new SlideCollection(); } } else { // Add a new one tempSlideDict.Add(parsed.NoteIdentifier, new SlideCollection()); } // Add this note to it tempSlideDict[parsed.NoteIdentifier].Notes.Add(new Tuple <NoteParse, int>(parsed, i)); break; case 3: // Bend Note Step case 5: // Bend Note Invisible if (!tempSlideDict.ContainsKey(parsed.NoteIdentifier)) { tempSlideDict.Add(parsed.NoteIdentifier, new SlideCollection()); // Add a new one } // Add this note to it tempSlideDict[parsed.NoteIdentifier].Notes.Add(new Tuple <NoteParse, int>(parsed, i)); break; case 4: // Bezier notes // DR2 doesn't support bezier notes if (!tempSlideDict.ContainsKey(parsed.NoteIdentifier)) { tempSlideDict.Add(parsed.NoteIdentifier, new SlideCollection()); // Add a new one } // Add this note to it tempSlideDict[parsed.NoteIdentifier].Notes.Add(new Tuple <NoteParse, int>(parsed, i)); break; default: break; } } } else // This is only hold notes { double noteSub = 1.0 / parsed.Notes.Count; for (int i = 0; i < parsed.Notes.Count; i++) { switch (parsed.Notes[i].Item1) { case 1: // Start a new note dr2Notes.Add(new Dr2Note() { ID = NoteIncrementer.GetNextID(), NoteType = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1), Measure = (parsed.Measure + i * noteSub), LaneIndex = parsed.LaneIndex, Width = parsed.Notes[i].Item2 }); holdIDlist.Remove(parsed.NoteIdentifier); holdIDlist[parsed.NoteIdentifier] = dr2Notes.Last().ID; break; case 3: // Bend Point Step case 5: // Bend Point (Invisible) dr2Notes.Add(new Dr2Note() { ID = NoteIncrementer.GetNextID(), NoteType = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1), Measure = (parsed.Measure + i * noteSub), LaneIndex = parsed.LaneIndex, Width = parsed.Notes[i].Item2, ParentID = holdIDlist[parsed.NoteIdentifier] }); holdIDlist[parsed.NoteIdentifier] = dr2Notes.Last().ID; break; case 2: // End a note dr2Notes.Add(new Dr2Note() { ID = NoteIncrementer.GetNextID(), NoteType = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1), Measure = (parsed.Measure + i * noteSub), LaneIndex = parsed.LaneIndex, Width = parsed.Notes[i].Item2, ParentID = holdIDlist[parsed.NoteIdentifier] }); holdIDlist[parsed.NoteIdentifier] = dr2Notes.Last().ID; break; default: // Rest notes / spacers (0) are ignored break; } } } } // Regex match for Air Motions else if (Regex.IsMatch(line, "[#][0-9]{3}[5]")) { // Air Motions are weird, because they must be paired with an existing note var parsed = ParseLine(line); double noteSub = 1.0 / parsed.Notes.Count; for (int i = 0; i < parsed.Notes.Count; i++) { if (parsed.Notes[i].Item1 > 0) { int laneIndex = parsed.LaneIndex; int width = parsed.Notes[i].Item2; double measure = parsed.Measure + i * noteSub; var noteIndex = dr2Notes.FindIndex(x => x.Measure == measure && x.LaneIndex == laneIndex && x.Width == width); if (noteIndex >= 0) { dr2Notes[noteIndex].AirDirection = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1); } else { unmatchedAirs.Add(new Tuple <NoteParse, int>(parsed, i)); } } } } // Parse BPM changes else if (Regex.IsMatch(line, "[#][0-9]{3}(08)")) { var parsed = ParseLine(line); double noteSub = 1.0 / parsed.Notes.Count; for (int i = 0; i < parsed.Notes.Count; i++) { if (parsed.Notes[i].Item2 > 0) { // Then there's a BPM change somewhere here int bpmID = Convert.ToInt32(parsed.Notes[i].Item1.ToString() + parsed.Notes[i].Item2.ToString()); bpmChanges[bpmID].BpmStart = parsed.Measure + i * noteSub; } } } // Parse Time Signature // ** DR2 does not support dynamic time signature changes, so we just grab the first one ** else if (Regex.IsMatch(line, "[#][0-9]{3}(02)")) { var parsed = ParseLine(line, true); if (metadata.Beat == -1) { metadata.Beat = parsed.Values[0] / 4; } } // Parse Speed Changes else if (Regex.IsMatch(line, "(#TIL)")) { if (line.StartsWith("#TIL00")) { string[] split = line.Split(new string[] { "#TIL00", ":", ",", "\"", "'", " " }, StringSplitOptions.RemoveEmptyEntries); if (split.Length == 0) { metadata.SpeedChanges.Add(new Dr2SpeedChange() { SpeedChange = 1.0, SpeedChangeIndex = 0.0 }); } else if (split.Length % 3 != 0) { Globals.ErrorParsingSpeedChange = true; } else { for (int i = 0; i < split.Length; i += 3) { double measure = Convert.ToDouble(split[i]) + Convert.ToDouble(split[i + 1]) / (ticksPerBeat * (metadata.Beat * 4)); double speed = Convert.ToDouble(split[i + 2]); metadata.SpeedChanges.Add(new Dr2SpeedChange() { SpeedChange = speed, SpeedChangeIndex = measure }); } } } else { Globals.ComplexSpeedChangesPresent = true; } } // Attributes tag is not supported else if (Regex.IsMatch(line, "(#ATR)") || Regex.IsMatch(line, "(#ATTR)")) { Globals.AttributesTagPresent = true; } } } // Generate slide notes // Commit leftover notes foreach (var item in tempSlideDict) { slideNotes.Add(item.Value); } foreach (var list in slideNotes) { list.Notes.Sort((x, y) => (x.Item1.Measure + x.Item2 * (1.0 / x.Item1.Notes.Count)).CompareTo(y.Item1.Measure + y.Item2 * (1.0 / y.Item1.Notes.Count))); foreach (var tuple in list.Notes) { var parsed = tuple.Item1; double noteSub = 1.0 / parsed.Notes.Count; var i = tuple.Item2; bool isStart = parsed.Notes[i].Item1 == 1; dr2Notes.Add(new Dr2Note() { ID = NoteIncrementer.GetNextID(), NoteType = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1), Measure = parsed.Measure + i * noteSub, LaneIndex = parsed.LaneIndex, Width = parsed.Notes[i].Item2, ParentID = isStart ? 0 : dr2Notes.Count - 1 }); } } // Try to match air notes again foreach (var item in unmatchedAirs) { var parsed = item.Item1; int i = item.Item2; double noteSub = 1.0 / parsed.Notes.Count; int laneIndex = parsed.LaneIndex; int width = parsed.Notes[i].Item2; double measure = parsed.Measure + i * noteSub; var noteIndex = dr2Notes.FindIndex(x => x.Measure == measure && x.LaneIndex == laneIndex && x.Width == width); if (noteIndex >= 0) { dr2Notes[noteIndex].AirDirection = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1); } else { failedNotes.Add(parsed); } } // Try to match air lanes foreach (var parsed in unmatchedAirLane) { double noteSub = 1.0 / parsed.Notes.Count; for (int i = 0; i < parsed.Notes.Count; i++) { if (parsed.Notes[i].Item1 == 1) // For start, we need to find the parent ID { int laneIndex = parsed.LaneIndex; int width = parsed.Notes[i].Item2; double measure = parsed.Measure + i * noteSub; var noteIndex = dr2Notes.FindIndex(x => x.Measure == measure && x.LaneIndex == laneIndex && x.Width == width); if (noteIndex >= 0) { holdIDlist[parsed.NoteIdentifier] = dr2Notes[noteIndex].ID; } else { holdIDlist.Remove(parsed.NoteIdentifier); } } if (parsed.Notes[i].Item1 == 2) // End point references the ID we found earlier { if (holdIDlist.ContainsKey(parsed.NoteIdentifier)) { dr2Notes.Add(new Dr2Note() { ID = NoteIncrementer.GetNextID(), NoteType = SusToDr2Note(parsed.NoteClass, parsed.Notes[i].Item1), Measure = (parsed.Measure + i * noteSub), LaneIndex = parsed.LaneIndex, Width = parsed.Notes[i].Item2, ParentID = holdIDlist[parsed.NoteIdentifier] }); } else { failedNotes.Add(parsed); } } } } // Re-sort notes by measure dr2Notes.Sort((x, y) => x.Measure.CompareTo(y.Measure)); // Add BPM changes into metadata foreach (var item in bpmChanges) { metadata.BpmChanges.Add(item.Value); } if (Globals.BezierNotesPresent) { Console.WriteLine("WARNING: Bezier notes were found in this chart. This is not supported by *.dr2 currently and will appear as straight slides."); } if (Globals.ComplexSpeedChangesPresent) { Console.WriteLine("ERROR: Complex speed changes were found in this chart and this cannot be parsed at this time. It is not recommended to play this conversion."); } if (Globals.MultipleBPMLinesPresent) { Console.WriteLine("WARNING: Multiple BPM commands with the same keys were found. The first one was used and the rest were ignored."); } if (Globals.AttributesTagPresent) { Console.WriteLine("ERROR: Attribute tags were found in this chart and this cannot be parsed at this time. It is not recommended to play this conversion."); } if (Globals.ErrorParsingTicksPerBeat) { Console.WriteLine("ERROR: Could not parse ticks_per_beat, which makes speed changes incorrect. It is not recommended to play this conversion."); } if (Globals.ErrorParsingSpeedChange) { Console.WriteLine("ERROR: Speed change could not be parsed, unexpected number of arguments. Check original file"); } return(dr2Notes); }