public void Export(Stream stream) { var book = ScoreBook; SusArgs args = CustomArgs; var notes = book.Score.Notes; using (var writer = new StreamWriter(stream)) { writer.WriteLine("This file was generated by Ched {0}.", System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString()); writer.WriteLine("#TITLE \"{0}\"", book.Title); writer.WriteLine("#ARTIST \"{0}\"", book.ArtistName); writer.WriteLine("#DESIGNER \"{0}\"", book.NotesDesignerName); writer.WriteLine("#DIFFICULTY {0}", (int)args.PlayDifficulty + (string.IsNullOrEmpty(args.ExtendedDifficulty) ? "" : ":" + args.ExtendedDifficulty)); writer.WriteLine("#PLAYLEVEL {0}", args.PlayLevel); writer.WriteLine("#SONGID \"{0}\"", args.SongId); writer.WriteLine("#WAVE \"{0}\"", args.SoundFileName); writer.WriteLine("#WAVEOFFSET {0}", args.SoundOffset); writer.WriteLine("#JACKET \"{0}\"", args.JacketFilePath); writer.WriteLine(); if (!string.IsNullOrEmpty(args.AdditionalData)) { writer.WriteLine(args.AdditionalData); writer.WriteLine(); } writer.WriteLine("#REQUEST \"ticks_per_beat {0}\"", book.Score.TicksPerBeat); writer.WriteLine(); var timeSignatures = BarIndexCalculator.TimeSignatures.Select(p => new SusDataLine(p.StartBarIndex, barIndex => string.Format("#{0:000}02: {1}", barIndex, 4f * p.TimeSignature.Numerator / p.TimeSignature.Denominator), p.StartBarIndex == 0)); WriteLinesWithOffset(writer, timeSignatures); writer.WriteLine(); var bpmlist = book.Score.Events.BpmChangeEvents .GroupBy(p => p.Bpm) .SelectMany((p, i) => p.Select(q => new { Index = i, Value = q, BarPosition = BarIndexCalculator.GetBarPositionFromTick(q.Tick) })) .ToList(); if (bpmlist.Count >= 36 * 36) { throw new ArgumentException("BPM定義数が上限を超えました。"); } var bpmIdentifiers = EnumerateIdentifiers(2, NumChars.Concat(AlphaChars)).Skip(1).Take(bpmlist.Count).ToList(); foreach (var item in bpmlist.GroupBy(p => p.Index).Select(p => p.First())) { writer.WriteLine("#BPM{0}: {1}", bpmIdentifiers[item.Index], item.Value.Bpm); } // 小節オフセット追加用に初期BPM定義だけ1行に分離 var bpmChanges = bpmlist.GroupBy(p => p.Value.Tick == 0).SelectMany(p => p.GroupBy(q => q.BarPosition.BarIndex).Select(eventInBar => { var sig = BarIndexCalculator.GetTimeSignatureFromBarIndex(eventInBar.Key); int barLength = StandardBarTick * sig.Numerator / sig.Denominator; var items = eventInBar.Select(q => (q.BarPosition.TickOffset, bpmIdentifiers[q.Index])); return(new SusDataLine(eventInBar.Key, barIndex => string.Format("#{0:000}08: {1}", barIndex, GenerateLineData(barLength, items)), p.Key)); })); WriteLinesWithOffset(writer, bpmChanges); writer.WriteLine(); var speeds = book.Score.Events.HighSpeedChangeEvents.Select(p => { var barPos = BarIndexCalculator.GetBarPositionFromTick(p.Tick); return(string.Format("{0}'{1}:{2}", barPos.BarIndex + (p.Tick == 0 ? 0 : BarIndexOffset), barPos.TickOffset, p.SpeedRatio)); }); writer.WriteLine("#TIL00: \"{0}\"", string.Join(", ", speeds)); writer.WriteLine("#HISPEED 00"); writer.WriteLine("#MEASUREHS 00"); writer.WriteLine(); var shortNotes = notes.Taps.Cast <TappableBase>().Select(p => new { Type = '1', Note = p }) .Concat(notes.ExTaps.Cast <TappableBase>().Select(p => new { Type = '2', Note = p })) .Concat(notes.Flicks.Cast <TappableBase>().Select(p => new { Type = '3', Note = p })) .Concat(notes.Damages.Cast <TappableBase>().Select(p => new { Type = '4', Note = p })) .Select(p => (p.Note.Tick, p.Note.LaneIndex, p.Type + ToLaneWidthString(p.Note.Width))); WriteLinesWithOffset(writer, GetShortNoteLines("1", shortNotes)); writer.WriteLine(); var airs = notes.Airs.Select(p => { string type = ""; switch (p.HorizontalDirection) { case HorizontalAirDirection.Center: type = p.VerticalDirection == VerticalAirDirection.Up ? "1" : "2"; break; case HorizontalAirDirection.Left: type = p.VerticalDirection == VerticalAirDirection.Up ? "3" : "5"; break; case HorizontalAirDirection.Right: type = p.VerticalDirection == VerticalAirDirection.Up ? "4" : "6"; break; } return(p.Tick, p.LaneIndex, type + ToLaneWidthString(p.Width)); }); WriteLinesWithOffset(writer, GetShortNoteLines("5", airs)); writer.WriteLine(); var identifier = new IdentifierAllocationManager(); var holds = book.Score.Notes.Holds .OrderBy(p => p.StartTick) .Select(p => new { Identifier = identifier.Allocate(p.StartTick, p.Duration), StartTick = p.StartTick, EndTick = p.StartTick + p.Duration, Width = p.Width, LaneIndex = p.LaneIndex }) .SelectMany(hold => { var items = new[] { (hold.StartTick, hold.LaneIndex, "1" + ToLaneWidthString(hold.Width)), (hold.EndTick, hold.LaneIndex, "2" + ToLaneWidthString(hold.Width)) }; return(GetLongNoteLines("2", hold.Identifier.ToString(), items)); });
public SusExporter(ScoreBook book, SusArgs susArgs) { ScoreBook = book; CustomArgs = susArgs; BarIndexCalculator = new BarIndexCalculator(book.Score.TicksPerBeat, book.Score.Events.TimeSignatureChangeEvents); }