Exemple #1
0
        public void Export(string path, ScoreBook book)
        {
            SusArgs args  = CustomArgs;
            var     notes = book.Score.Notes;

            using (var writer = new StreamWriter(path))
            {
                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();

                writer.WriteLine("#REQUEST \"ticks_per_beat {0}\"", book.Score.TicksPerBeat);

                writer.WriteLine();

                int barTick            = book.Score.TicksPerBeat * 4;
                var barIndexCalculator = new BarIndexCalculator(barTick, book.Score.Events.TimeSignatureChangeEvents, args.HasPaddingBar);

                foreach (var item in barIndexCalculator.TimeSignatures)
                {
                    writer.WriteLine("#{0:000}02: {1}", item.StartBarIndex + (args.HasPaddingBar && item.StartBarIndex == 1 ? -1 : 0), 4f * item.TimeSignature.Numerator / item.TimeSignature.Denominator);
                }

                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).Skip(1).Take(bpmlist.Count).ToList();
                foreach (var item in bpmlist)
                {
                    writer.WriteLine("#BPM{0}: {1}", bpmIdentifiers[item.Index], item.Value.BPM);
                }

                if (args.HasPaddingBar)
                {
                    writer.WriteLine("#{0:000}08: {1:x2}", 0, bpmIdentifiers[bpmlist.OrderBy(p => p.Value.Tick).First().Index]);
                }

                foreach (var eventInBar in bpmlist.GroupBy(p => p.BarPosition.BarIndex))
                {
                    var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(eventInBar.Key);
                    int barLength = barTick * sig.Numerator / sig.Denominator;
                    var dic       = eventInBar.ToDictionary(p => p.BarPosition.TickOffset, p => p);
                    int gcd       = eventInBar.Select(p => p.BarPosition.TickOffset).Aggregate(barLength, (p, q) => GetGcd(p, q));
                    writer.Write("#{0:000}08: ", eventInBar.Key);
                    for (int i = 0; i *gcd < barLength; i++)
                    {
                        int tickOffset = i * gcd;
                        writer.Write(dic.ContainsKey(tickOffset) ? bpmIdentifiers[dic[tickOffset].Index] : "00");
                    }
                    writer.WriteLine();
                }

                writer.WriteLine();
                var speeds = book.Score.Events.HighSpeedChangeEvents.Select(p =>
                {
                    var barPos = barIndexCalculator.GetBarPositionFromTick(p.Tick);
                    return(string.Format("{0}'{1}:{2}", args.HasPaddingBar && barPos.BarIndex == 1 && barPos.TickOffset == 0 ? 0 : barPos.BarIndex, barPos.TickOffset, p.SpeedRatio));
                });
                writer.WriteLine("#TIL00: \"{0}\"", string.Join(", ", speeds));
                writer.WriteLine("#HISPEED 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 => new
                {
                    BarPosition = barIndexCalculator.GetBarPositionFromTick(p.Note.Tick),
                    LaneIndex   = p.Note.LaneIndex,
                    Width       = p.Note.Width,
                    Type        = p.Type
                });

                foreach (var notesInBar in shortNotes.GroupBy(p => p.BarPosition.BarIndex))
                {
                    foreach (var notesInLane in notesInBar.GroupBy(p => p.LaneIndex))
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(notesInBar.Key);
                        int barLength = barTick * sig.Numerator / sig.Denominator;

                        var offsetList     = notesInLane.GroupBy(p => p.BarPosition.TickOffset).Select(p => p.ToList());
                        var separatedNotes = Enumerable.Range(0, offsetList.Max(p => p.Count)).Select(p => offsetList.Where(q => q.Count >= p + 1).Select(q => q[p]));

                        foreach (var dic in separatedNotes.Select(p => p.ToDictionary(q => q.BarPosition.TickOffset, q => q)))
                        {
                            int gcd = dic.Values.Select(p => p.BarPosition.TickOffset).Aggregate(barLength, (p, q) => GetGcd(p, q));
                            writer.Write("#{0:000}1{1}:", notesInBar.Key, notesInLane.Key.ToString("x"));
                            for (int i = 0; i *gcd < barLength; i++)
                            {
                                int tickOffset = i * gcd;
                                writer.Write(dic.ContainsKey(tickOffset) ? dic[tickOffset].Type + ToLaneWidthString(dic[tickOffset].Width) : "00");
                            }
                            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(new
                    {
                        BarPosition = barIndexCalculator.GetBarPositionFromTick(p.Tick),
                        LaneIndex = p.LaneIndex,
                        Type = type,
                        Width = p.Width
                    });
                });

                foreach (var airsInBar in airs.GroupBy(p => p.BarPosition.BarIndex))
                {
                    foreach (var airsInLane in airsInBar.GroupBy(p => p.LaneIndex))
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(airsInBar.Key);
                        int barLength = barTick * sig.Numerator / sig.Denominator;

                        var offsetList     = airsInLane.GroupBy(p => p.BarPosition.TickOffset).Select(p => p.ToList());
                        var separatedNotes = Enumerable.Range(0, offsetList.Max(p => p.Count)).Select(p => offsetList.Where(q => q.Count >= p + 1).Select(q => q[p]));
                        foreach (var dic in separatedNotes.Select(p => p.ToDictionary(q => q.BarPosition.TickOffset, q => q)))
                        {
                            int gcd = dic.Values.Select(p => p.BarPosition.TickOffset).Aggregate(barLength, (p, q) => GetGcd(p, q));
                            writer.Write("#{0:000}5{1}:", airsInBar.Key, airsInLane.Key.ToString("x"));
                            for (int i = 0; i *gcd < barLength; i++)
                            {
                                int tickOffset = i * gcd;
                                writer.Write(dic.ContainsKey(tickOffset) ? dic[tickOffset].Type + ToLaneWidthString(dic[tickOffset].Width) : "00");
                            }
                            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
                });

                foreach (var hold in holds)
                {
                    var startBarPosition = barIndexCalculator.GetBarPositionFromTick(hold.StartTick);
                    var endBarPosition   = barIndexCalculator.GetBarPositionFromTick(hold.EndTick);
                    if (startBarPosition.BarIndex == endBarPosition.BarIndex)
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int barLength = barTick * sig.Numerator / sig.Denominator;
                        writer.Write("#{0:000}2{1}{2}:", startBarPosition.BarIndex, hold.LaneIndex.ToString("x"), hold.Identifier);
                        int gcd = GetGcd(GetGcd(startBarPosition.TickOffset, endBarPosition.TickOffset), barLength);
                        for (int i = 0; i *gcd < barLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(hold.Width));
                            }
                            else if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(hold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                    else
                    {
                        var startSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int startBarLength = barTick * startSig.Numerator / startSig.Denominator;
                        writer.Write("#{0:000}2{1}{2}:", startBarPosition.BarIndex, hold.LaneIndex.ToString("x"), hold.Identifier);
                        int gcd = GetGcd(startBarPosition.TickOffset, startBarLength);
                        for (int i = 0; i *gcd < startBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(hold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();

                        var endSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(endBarPosition.BarIndex);
                        int endBarLength = barTick * endSig.Numerator / endSig.Denominator;
                        writer.Write("#{0:000}2{1}{2}:", endBarPosition.BarIndex, hold.LaneIndex.ToString("x"), hold.Identifier);
                        gcd = GetGcd(endBarPosition.TickOffset, endBarLength);
                        for (int i = 0; i *gcd < endBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(hold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                }

                identifier.Clear();

                var slides = notes.Slides
                             .OrderBy(p => p.StartTick)
                             .Select(p => new
                {
                    Identifier = identifier.Allocate(p.StartTick, p.GetDuration()),
                    Note       = p
                });

                foreach (var slide in slides)
                {
                    var start = new[] { new
                                        {
                                            TickOffset  = 0,
                                            BarPosition = barIndexCalculator.GetBarPositionFromTick(slide.Note.StartTick),
                                            LaneIndex   = slide.Note.StartLaneIndex,
                                            Width       = slide.Note.StartWidth,
                                            Type        = "1"
                                        } };
                    var steps = slide.Note.StepNotes.OrderBy(p => p.TickOffset).Select(p => new
                    {
                        TickOffset  = p.TickOffset,
                        BarPosition = barIndexCalculator.GetBarPositionFromTick(p.Tick),
                        LaneIndex   = p.LaneIndex,
                        Width       = p.Width,
                        Type        = p.IsVisible ? "3" : "5"
                    }).Take(slide.Note.StepNotes.Count - 1);
                    var endNote = slide.Note.StepNotes.OrderBy(p => p.TickOffset).Last();
                    var end     = new[] { new
                                          {
                                              TickOffset  = endNote.TickOffset,
                                              BarPosition = barIndexCalculator.GetBarPositionFromTick(endNote.Tick),
                                              LaneIndex   = endNote.LaneIndex,
                                              Width       = endNote.Width,
                                              Type        = "2"
                                          } };
                    var slideNotes = start.Concat(steps).Concat(end);
                    foreach (var notesInBar in slideNotes.GroupBy(p => p.BarPosition.BarIndex))
                    {
                        foreach (var notesInLane in notesInBar.GroupBy(p => p.LaneIndex))
                        {
                            var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(notesInBar.Key);
                            int barLength = barTick * sig.Numerator / sig.Denominator;
                            int gcd       = notesInLane.Select(p => p.BarPosition.TickOffset).Aggregate(barLength, (p, q) => GetGcd(p, q));
                            var dic       = notesInLane.ToDictionary(p => p.BarPosition.TickOffset, p => p);
                            writer.Write("#{0:000}3{1}{2}:", notesInBar.Key, notesInLane.Key.ToString("x"), slide.Identifier);
                            for (int i = 0; i *gcd < barLength; i++)
                            {
                                int tickOffset = i * gcd;
                                writer.Write(dic.ContainsKey(tickOffset) ? dic[tickOffset].Type + ToLaneWidthString(dic[tickOffset].Width) : "00");
                            }
                            writer.WriteLine();
                        }
                    }
                }

                identifier.Clear();

                var airActions = notes.AirActions
                                 .OrderBy(p => p.StartTick)
                                 .Select(p => new
                {
                    Identifier = identifier.Allocate(p.StartTick, p.GetDuration()),
                    Note       = p
                });

                foreach (var airAction in airActions)
                {
                    var start = new[] { new
                                        {
                                            TickOffset  = 0,
                                            BarPosition = barIndexCalculator.GetBarPositionFromTick(airAction.Note.StartTick),
                                            Type        = "1"
                                        } };
                    var actions = airAction.Note.ActionNotes.OrderBy(p => p.Offset).Select(p => new
                    {
                        TickOffset  = p.Offset,
                        BarPosition = barIndexCalculator.GetBarPositionFromTick(p.ParentNote.StartTick + p.Offset),
                        Type        = "3"
                    }).Take(airAction.Note.ActionNotes.Count - 1);
                    var endNote = airAction.Note.ActionNotes.OrderBy(p => p.Offset).Last();
                    var end     = new[] { new
                                          {
                                              TickOffset  = endNote.Offset,
                                              BarPosition = barIndexCalculator.GetBarPositionFromTick(airAction.Note.StartTick + endNote.Offset),
                                              Type        = "2"
                                          } };
                    var actionNotes = start.Concat(actions).Concat(end);
                    foreach (var airActionsInBar in actionNotes.GroupBy(p => p.BarPosition.BarIndex))
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(airActionsInBar.Key);
                        int barLength = barTick * sig.Numerator / sig.Denominator;
                        writer.Write("#{0:000}4{1}{2}:", airActionsInBar.Key, airAction.Note.ParentNote.LaneIndex.ToString("x"), airAction.Identifier);
                        int gcd = airActionsInBar.Select(p => p.BarPosition.TickOffset).Aggregate(barLength, (p, q) => GetGcd(p, q));
                        var dic = airActionsInBar.ToDictionary(p => p.BarPosition.TickOffset, p => p);
                        for (int i = 0; i *gcd < barLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (dic.ContainsKey(tickOffset))
                            {
                                writer.Write("{0}{1}", dic[tickOffset].Type, ToLaneWidthString(airAction.Note.ParentNote.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                }
            }
        }
Exemple #2
0
        public void Export(string path, ScoreBook book)
        {
            // TODO: コンストラクタに移してreadonlyにする
            ScoreBook          = book;
            BarIndexCalculator = new BarIndexCalculator(book.Score.TicksPerBeat, book.Score.Events.TimeSignatureChangeEvents);

            SusArgs args = CustomArgs;

            BarIndexOffset = args.HasPaddingBar ? 1 : 0;
            var notes = book.Score.Notes;

            using (var writer = new StreamWriter(path))
            {
                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).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));
                });
Exemple #3
0
        public void Export(string path, ScoreBook book)
        {
            SusArgs args  = CustomArgs;
            var     notes = book.Score.Notes;

            notes.Taps    = notes.Taps.Distinct().ToList();
            notes.DTaps   = notes.DTaps.Distinct().ToList();
            notes.HTaps   = notes.HTaps.Distinct().ToList();
            notes.LTaps   = notes.LTaps.Distinct().ToList();
            notes.Traces  = notes.Traces.Distinct().ToList();
            notes.DTraces = notes.DTraces.Distinct().ToList();
            notes.HTraces = notes.HTraces.Distinct().ToList();
            notes.LTraces = notes.LTraces.Distinct().ToList();
            notes.Holds   = notes.Holds.Distinct().ToList();
            notes.DHolds  = notes.DHolds.Distinct().ToList();
            notes.HHolds  = notes.HHolds.Distinct().ToList();
            notes.LHolds  = notes.LHolds.Distinct().ToList();

            using (var writer = new StreamWriter(path))
            {
                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("#WAVE \"{0}\"", args.SoundFileName);
                writer.WriteLine("#WAVEOFFSET {0}", args.SoundOffset);

                writer.WriteLine();

                int barTick            = book.Score.TicksPerBeat * 4;
                var barIndexCalculator = new BarIndexCalculator(barTick, book.Score.Events.TimeSignatureChangeEvents, args.HasPaddingBar);

                foreach (var item in barIndexCalculator.TimeSignatures)
                {
                    writer.WriteLine("#{0:000}02: {1}", item.StartBarIndex + (args.HasPaddingBar && item.StartBarIndex == 1 ? -1 : 0), 4f * item.TimeSignature.Numerator / item.TimeSignature.Denominator);
                }

                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).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);
                }

                if (args.HasPaddingBar)
                {
                    writer.WriteLine("#{0:000}08: {1:x2}", 0, bpmIdentifiers[bpmlist.OrderBy(p => p.Value.Tick).First().Index]);
                }

                foreach (var eventInBar in bpmlist.GroupBy(p => p.BarPosition.BarIndex))
                {
                    var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(eventInBar.Key);
                    int barLength = barTick * sig.Numerator / sig.Denominator;
                    var dic       = eventInBar.ToDictionary(p => p.BarPosition.TickOffset, p => p);
                    int gcd       = eventInBar.Select(p => p.BarPosition.TickOffset).Aggregate(barLength, (p, q) => GetGcd(p, q));
                    writer.Write("#{0:000}08: ", eventInBar.Key);
                    for (int i = 0; i *gcd < barLength; i++)
                    {
                        int tickOffset = i * gcd;
                        writer.Write(dic.ContainsKey(tickOffset) ? bpmIdentifiers[dic[tickOffset].Index] : "00");
                    }
                    writer.WriteLine();
                }

                writer.WriteLine();

                foreach (var EventVar in book.Score.Events.HighSpeedChangeEvents)
                {
                    var barPos = barIndexCalculator.GetBarPositionFromTick(EventVar.Tick);
                    writer.Write("#{0:000}07: ", barPos.BarIndex);
                    writer.WriteLine(EventVar.SpeedRatio);
                }

                foreach (var EventVar in book.Score.Events.SplitLaneEvents)
                {
                    var barPos = barIndexCalculator.GetBarPositionFromTick(EventVar.Tick);
                    writer.Write("#{0:000}05: ", barPos.BarIndex);
                    writer.WriteLine(EventVar.ToString().ToCharArray());
                }

                writer.WriteLine();

                var shortNotes = notes.Taps.Cast <TappableBase>().Select(p => new { Type = '1', Note = p })
                                 .Concat(notes.DTaps.Cast <TappableBase>().Select(p => new { Type = '2', Note = p }))
                                 .Concat(notes.HTaps.Cast <TappableBase>().Select(p => new { Type = '3', Note = p }))
                                 .Concat(notes.LTaps.Cast <TappableBase>().Select(p => new { Type = '4', Note = p }))
                                 .Concat(notes.Traces.Cast <TappableBase>().Select(p => new { Type = '5', Note = p }))
                                 .Concat(notes.DTraces.Cast <TappableBase>().Select(p => new { Type = '6', Note = p }))
                                 .Concat(notes.HTraces.Cast <TappableBase>().Select(p => new { Type = '7', Note = p }))
                                 .Concat(notes.LTraces.Cast <TappableBase>().Select(p => new { Type = '8', Note = p }))
                                 .Concat(notes.Flicks.Cast <TappableBase>().Select(p => new { Type = '9', Note = p }))
                                 .Concat(notes.Damages.Cast <TappableBase>().Select(p => new { Type = 'A', Note = p }))
                                 .Select(p => new
                {
                    BarPosition = barIndexCalculator.GetBarPositionFromTick(p.Note.Tick),
                    LaneIndex   = p.Note.LaneIndex,
                    Width       = p.Note.Width,
                    Type        = p.Type
                });

                foreach (var notesInBar in shortNotes.GroupBy(p => p.BarPosition.BarIndex))
                {
                    foreach (var notesInLane in notesInBar.GroupBy(p => p.LaneIndex))
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(notesInBar.Key);
                        int barLength = barTick * sig.Numerator / sig.Denominator;

                        var offsetList     = notesInLane.GroupBy(p => p.BarPosition.TickOffset).Select(p => p.ToList());
                        var separatedNotes = Enumerable.Range(0, offsetList.Max(p => p.Count)).Select(p => offsetList.Where(q => q.Count >= p + 1).Select(q => q[p]));

                        foreach (var dic in separatedNotes.Select(p => p.ToDictionary(q => q.BarPosition.TickOffset, q => q)))
                        {
                            int gcd = dic.Values.Select(p => p.BarPosition.TickOffset).Aggregate(barLength, (p, q) => GetGcd(p, q));
                            writer.Write("#{0:000}1{1}:", notesInBar.Key, notesInLane.Key.ToString("x"));
                            for (int i = 0; i *gcd < barLength; i++)
                            {
                                int tickOffset = i * gcd;
                                writer.Write(dic.ContainsKey(tickOffset) ? dic[tickOffset].Type + ToLaneWidthString(dic[tickOffset].Width) : "00");
                            }
                            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
                });

                foreach (var hold in holds)
                {
                    var startBarPosition = barIndexCalculator.GetBarPositionFromTick(hold.StartTick);
                    var endBarPosition   = barIndexCalculator.GetBarPositionFromTick(hold.EndTick);
                    if (startBarPosition.BarIndex == endBarPosition.BarIndex)
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int barLength = barTick * sig.Numerator / sig.Denominator;
                        writer.Write("#{0:000}2{1}{2}:", startBarPosition.BarIndex, hold.LaneIndex.ToString("x"), hold.Identifier);
                        int gcd = GetGcd(GetGcd(startBarPosition.TickOffset, endBarPosition.TickOffset), barLength);
                        for (int i = 0; i *gcd < barLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(hold.Width));
                            }
                            else if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(hold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                    else
                    {
                        var startSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int startBarLength = barTick * startSig.Numerator / startSig.Denominator;
                        writer.Write("#{0:000}2{1}{2}:", startBarPosition.BarIndex, hold.LaneIndex.ToString("x"), hold.Identifier);
                        int gcd = GetGcd(startBarPosition.TickOffset, startBarLength);
                        for (int i = 0; i *gcd < startBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(hold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();

                        var endSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(endBarPosition.BarIndex);
                        int endBarLength = barTick * endSig.Numerator / endSig.Denominator;
                        writer.Write("#{0:000}2{1}{2}:", endBarPosition.BarIndex, hold.LaneIndex.ToString("x"), hold.Identifier);
                        gcd = GetGcd(endBarPosition.TickOffset, endBarLength);
                        for (int i = 0; i *gcd < endBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(hold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                }

                var identifier2 = new IdentifierAllocationManager();

                var dholds = book.Score.Notes.DHolds
                             .OrderBy(p => p.StartTick)
                             .Select(p => new
                {
                    Identifier2 = identifier2.Allocate(p.StartTick, p.Duration),
                    StartTick   = p.StartTick,
                    EndTick     = p.StartTick + p.Duration,
                    Width       = p.Width,
                    LaneIndex   = p.LaneIndex
                });
                foreach (var dhold in dholds)
                {
                    var startBarPosition = barIndexCalculator.GetBarPositionFromTick(dhold.StartTick);
                    var endBarPosition   = barIndexCalculator.GetBarPositionFromTick(dhold.EndTick);
                    if (startBarPosition.BarIndex == endBarPosition.BarIndex)
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int barLength = barTick * sig.Numerator / sig.Denominator;
                        writer.Write("#{0:000}3{1}{2}:", startBarPosition.BarIndex, dhold.LaneIndex.ToString("x"), dhold.Identifier2);
                        int gcd = GetGcd(GetGcd(startBarPosition.TickOffset, endBarPosition.TickOffset), barLength);
                        for (int i = 0; i *gcd < barLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(dhold.Width));
                            }
                            else if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(dhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                    else
                    {
                        var startSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int startBarLength = barTick * startSig.Numerator / startSig.Denominator;
                        writer.Write("#{0:000}3{1}{2}:", startBarPosition.BarIndex, dhold.LaneIndex.ToString("x"), dhold.Identifier2);
                        int gcd = GetGcd(startBarPosition.TickOffset, startBarLength);
                        for (int i = 0; i *gcd < startBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(dhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();

                        var endSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(endBarPosition.BarIndex);
                        int endBarLength = barTick * endSig.Numerator / endSig.Denominator;
                        writer.Write("#{0:000}3{1}{2}:", endBarPosition.BarIndex, dhold.LaneIndex.ToString("x"), dhold.Identifier2);
                        gcd = GetGcd(endBarPosition.TickOffset, endBarLength);
                        for (int i = 0; i *gcd < endBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(dhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                }

                var identifier3 = new IdentifierAllocationManager();

                var hholds = book.Score.Notes.DHolds
                             .OrderBy(p => p.StartTick)
                             .Select(p => new
                {
                    Identifier3 = identifier3.Allocate(p.StartTick, p.Duration),
                    StartTick   = p.StartTick,
                    EndTick     = p.StartTick + p.Duration,
                    Width       = p.Width,
                    LaneIndex   = p.LaneIndex
                });
                foreach (var hhold in hholds)
                {
                    var startBarPosition = barIndexCalculator.GetBarPositionFromTick(hhold.StartTick);
                    var endBarPosition   = barIndexCalculator.GetBarPositionFromTick(hhold.EndTick);
                    if (startBarPosition.BarIndex == endBarPosition.BarIndex)
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int barLength = barTick * sig.Numerator / sig.Denominator;
                        writer.Write("#{0:000}4{1}{2}:", startBarPosition.BarIndex, hhold.LaneIndex.ToString("x"), hhold.Identifier3);
                        int gcd = GetGcd(GetGcd(startBarPosition.TickOffset, endBarPosition.TickOffset), barLength);
                        for (int i = 0; i *gcd < barLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(hhold.Width));
                            }
                            else if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(hhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                    else
                    {
                        var startSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int startBarLength = barTick * startSig.Numerator / startSig.Denominator;
                        writer.Write("#{0:000}4{1}{2}:", startBarPosition.BarIndex, hhold.LaneIndex.ToString("x"), hhold.Identifier3);
                        int gcd = GetGcd(startBarPosition.TickOffset, startBarLength);
                        for (int i = 0; i *gcd < startBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(hhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();

                        var endSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(endBarPosition.BarIndex);
                        int endBarLength = barTick * endSig.Numerator / endSig.Denominator;
                        writer.Write("#{0:000}4{1}{2}:", endBarPosition.BarIndex, hhold.LaneIndex.ToString("x"), hhold.Identifier3);
                        gcd = GetGcd(endBarPosition.TickOffset, endBarLength);
                        for (int i = 0; i *gcd < endBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(hhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }

                    identifier.Clear();
                }

                var identifier4 = new IdentifierAllocationManager();

                var lholds = book.Score.Notes.DHolds
                             .OrderBy(p => p.StartTick)
                             .Select(p => new
                {
                    Identifier4 = identifier4.Allocate(p.StartTick, p.Duration),
                    StartTick   = p.StartTick,
                    EndTick     = p.StartTick + p.Duration,
                    Width       = p.Width,
                    LaneIndex   = p.LaneIndex
                });
                foreach (var lhold in lholds)
                {
                    var startBarPosition = barIndexCalculator.GetBarPositionFromTick(lhold.StartTick);
                    var endBarPosition   = barIndexCalculator.GetBarPositionFromTick(lhold.EndTick);
                    if (startBarPosition.BarIndex == endBarPosition.BarIndex)
                    {
                        var sig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int barLength = barTick * sig.Numerator / sig.Denominator;
                        writer.Write("#{0:000}5{1}{2}:", startBarPosition.BarIndex, lhold.LaneIndex.ToString("x"), lhold.Identifier4);
                        int gcd = GetGcd(GetGcd(startBarPosition.TickOffset, endBarPosition.TickOffset), barLength);
                        for (int i = 0; i *gcd < barLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(lhold.Width));
                            }
                            else if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(lhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                    else
                    {
                        var startSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(startBarPosition.BarIndex);
                        int startBarLength = barTick * startSig.Numerator / startSig.Denominator;
                        writer.Write("#{0:000}5{1}{2}:", startBarPosition.BarIndex, lhold.LaneIndex.ToString("x"), lhold.Identifier4);
                        int gcd = GetGcd(startBarPosition.TickOffset, startBarLength);
                        for (int i = 0; i *gcd < startBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (startBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("1" + ToLaneWidthString(lhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();

                        var endSig       = barIndexCalculator.GetTimeSignatureFromBarIndex(endBarPosition.BarIndex);
                        int endBarLength = barTick * endSig.Numerator / endSig.Denominator;
                        writer.Write("#{0:000}5{1}{2}:", endBarPosition.BarIndex, lhold.LaneIndex.ToString("x"), lhold.Identifier4);
                        gcd = GetGcd(endBarPosition.TickOffset, endBarLength);
                        for (int i = 0; i *gcd < endBarLength; i++)
                        {
                            int tickOffset = i * gcd;
                            if (endBarPosition.TickOffset == tickOffset)
                            {
                                writer.Write("2" + ToLaneWidthString(lhold.Width));
                            }
                            else
                            {
                                writer.Write("00");
                            }
                        }
                        writer.WriteLine();
                    }
                }
                identifier.Clear();
                identifier2.Clear();
                identifier3.Clear();
                identifier4.Clear();
            }
        }