From b9b45493e6f3f546f61c5d26034584d83265da03 Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Wed, 17 May 2017 18:02:17 +0800
Subject: [PATCH 01/44] Use generic IComparable together with IEqutable for
 Message.

---
 osu.Game/Online/Chat/Channel.cs |  2 +-
 osu.Game/Online/Chat/Message.cs | 14 +++-----------
 2 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs
index 2925c3ccb4..93fd0a8956 100644
--- a/osu.Game/Online/Chat/Channel.cs
+++ b/osu.Game/Online/Chat/Channel.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Online.Chat
         [JsonProperty(@"channel_id")]
         public int Id;
 
-        public readonly SortedList<Message> Messages = new SortedList<Message>((m1, m2) => m1.Id.CompareTo(m2.Id));
+        public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
 
         //internal bool Joined;
 
diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs
index bf53a68910..8c0cb043c4 100644
--- a/osu.Game/Online/Chat/Message.cs
+++ b/osu.Game/Online/Chat/Message.cs
@@ -8,7 +8,7 @@ using osu.Game.Users;
 
 namespace osu.Game.Online.Chat
 {
-    public class Message
+    public class Message : IComparable<Message>, IEquatable<Message>
     {
         [JsonProperty(@"message_id")]
         public readonly long Id;
@@ -42,17 +42,9 @@ namespace osu.Game.Online.Chat
             Id = id;
         }
 
-        public override bool Equals(object obj)
-        {
-            var objMessage = obj as Message;
+        public int CompareTo(Message other) => Id.CompareTo(other.Id);
 
-            return Id == objMessage?.Id;
-        }
-
-        public override int GetHashCode()
-        {
-            return Id.GetHashCode();
-        }
+        public bool Equals(Message other) => Id == other.Id;
     }
 
     public enum TargetType

From 6c9505fa3a053cc4fc607f366a30d1b94fe3600f Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Wed, 17 May 2017 18:11:38 +0800
Subject: [PATCH 02/44] Handle possible nulls.

---
 osu.Game/Online/Chat/Message.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs
index 8c0cb043c4..ed76ef248e 100644
--- a/osu.Game/Online/Chat/Message.cs
+++ b/osu.Game/Online/Chat/Message.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Online.Chat
 
         public int CompareTo(Message other) => Id.CompareTo(other.Id);
 
-        public bool Equals(Message other) => Id == other.Id;
+        public bool Equals(Message other) => Id == other?.Id;
     }
 
     public enum TargetType

From b5d7211cd6471d58c9bcf6b78d49c9f5ce4e9a77 Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Thu, 18 May 2017 01:30:24 +0800
Subject: [PATCH 03/44] Expire placeholder text.

---
 osu.Game/Overlays/ChatOverlay.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 2836be22ae..c722d46ace 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -229,6 +229,7 @@ namespace osu.Game.Overlays
                 Scheduler.Add(delegate
                 {
                     loading.FadeOut(100);
+                    loading.Expire();
 
                     addChannel(channels.Find(c => c.Name == @"#lazer"));
                     addChannel(channels.Find(c => c.Name == @"#osu"));

From e2b1fcc0887f2ff46c87058261d5c47a1e95c96c Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Thu, 18 May 2017 01:43:11 +0800
Subject: [PATCH 04/44] Use string.Join in GetMessagesRequest.

---
 osu.Game/Online/API/Requests/GetMessagesRequest.cs | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs
index cf52f9ccd3..858015e29b 100644
--- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs
+++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
+using System.Linq;
 using osu.Framework.IO.Network;
 using osu.Game.Online.Chat;
 
@@ -20,10 +21,7 @@ namespace osu.Game.Online.API.Requests
 
         protected override WebRequest CreateWebRequest()
         {
-            string channelString = string.Empty;
-            foreach (Channel c in channels)
-                channelString += c.Id + ",";
-            channelString = channelString.TrimEnd(',');
+            string channelString = string.Join(",", channels.Select(x => x.Id));
 
             var req = base.CreateWebRequest();
             req.AddParameter(@"channels", channelString);

From 23e2d3ef07b2fd99749245c06ad94bd8d4763699 Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Thu, 18 May 2017 02:30:17 +0800
Subject: [PATCH 05/44] Use GroupBy in ChatOverlay.

---
 osu.Game/Overlays/ChatOverlay.cs | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index c722d46ace..686a1d513a 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -321,11 +321,8 @@ namespace osu.Game.Overlays
             fetchReq = new GetMessagesRequest(careChannels, lastMessageId);
             fetchReq.Success += delegate (List<Message> messages)
             {
-                var ids = messages.Where(m => m.TargetType == TargetType.Channel).Select(m => m.TargetId).Distinct();
-
-                //batch messages per channel.
-                foreach (var id in ids)
-                    careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.TargetId == id).ToArray());
+                foreach (var group in messages.Where(m => m.TargetType == TargetType.Channel).GroupBy(m => m.TargetId))
+                    careChannels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
 
                 lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId;
 

From 88f8619e9a655f8ab29d14d3697f29793dcec58c Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Thu, 18 May 2017 02:46:12 +0800
Subject: [PATCH 06/44] More missed exceptions.

---
 osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs | 2 +-
 osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs          | 2 +-
 osu.Game/Online/API/APIAccess.cs                        | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
index 6c39ba40f9..2ff97047c0 100644
--- a/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
+++ b/osu.Game.Rulesets.Mania/Timing/ControlPointContainer.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Timing
             var controlPoint = drawableControlPoints.LastOrDefault(t => t.CanContain(drawable)) ?? drawableControlPoints.FirstOrDefault();
 
             if (controlPoint == null)
-                throw new Exception("Could not find suitable timing section to add object to.");
+                throw new InvalidOperationException("Could not find suitable timing section to add object to.");
 
             controlPoint.Add(drawable);
         }
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
index 986aefb2bd..dccc18fa8b 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
             ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange);
 
             if (firstTimingChange == null)
-                throw new Exception("The Beatmap contains no timing points!");
+                throw new InvalidOperationException("The Beatmap contains no timing points!");
 
             // Generate the timing points, making non-timing changes use the previous timing change
             var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c =>
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 2f344e0bdf..7e3bb44465 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -288,7 +288,7 @@ namespace osu.Game.Online.API
             {
                 APIRequest req;
                 while (oldQueue.TryDequeue(out req))
-                    req.Fail(new Exception(@"Disconnected from server"));
+                    req.Fail(new WebException(@"Disconnected from server"));
             }
         }
 

From ffbab6bfeb55212cb1177b46b168621e551a3996 Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Thu, 18 May 2017 03:26:07 +0800
Subject: [PATCH 07/44] Tidy up DrawableChannel.

---
 osu.Game/Overlays/Chat/DrawableChannel.cs | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs
index 355aeed134..50c849f00e 100644
--- a/osu.Game/Overlays/Chat/DrawableChannel.cs
+++ b/osu.Game/Overlays/Chat/DrawableChannel.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Chat
     public class DrawableChannel : Container
     {
         public readonly Channel Channel;
-        private readonly FillFlowContainer flow;
+        private readonly FillFlowContainer<ChatLine> flow;
         private readonly ScrollContainer scroll;
 
         public DrawableChannel(Channel channel)
@@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Chat
                     RelativeSizeAxes = Axes.Both,
                     Children = new Drawable[]
                     {
-                        flow = new FillFlowContainer
+                        flow = new FillFlowContainer<ChatLine>
                         {
                             Direction = FillDirection.Vertical,
                             RelativeSizeAxes = Axes.X,
@@ -63,19 +63,18 @@ namespace osu.Game.Overlays.Chat
 
             var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY));
 
+            //up to last Channel.MAX_HISTORY messages
+            flow.Add(displayMessages.Select(m => new ChatLine(m)));
+
             if (scroll.IsScrolledToEnd(10) || !flow.Children.Any())
                 scrollToEnd();
 
-            //up to last Channel.MAX_HISTORY messages
-            foreach (Message m in displayMessages)
-            {
-                var d = new ChatLine(m);
-                flow.Add(d);
-            }
+            var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray();
+            int count = staleMessages.Length - Channel.MAX_HISTORY;
 
-            while (flow.Children.Count(c => c.LifetimeEnd == double.MaxValue) > Channel.MAX_HISTORY)
+            for (int i = 0; i < count; i++)
             {
-                var d = flow.Children.First(c => c.LifetimeEnd == double.MaxValue);
+                var d = staleMessages[i];
                 if (!scroll.IsScrolledToEnd(10))
                     scroll.OffsetScrollPosition(-d.DrawHeight);
                 d.Expire();

From cd065b8ff31ce97931cd6e560bf27763290fdccc Mon Sep 17 00:00:00 2001
From: Huo Yaoyuan <huoyaoyuan@hotmail.com>
Date: Thu, 18 May 2017 05:27:20 +0800
Subject: [PATCH 08/44] Add back GetHashCode.

---
 osu.Game/Online/Chat/Message.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs
index ed76ef248e..4c7e099647 100644
--- a/osu.Game/Online/Chat/Message.cs
+++ b/osu.Game/Online/Chat/Message.cs
@@ -45,6 +45,8 @@ namespace osu.Game.Online.Chat
         public int CompareTo(Message other) => Id.CompareTo(other.Id);
 
         public bool Equals(Message other) => Id == other?.Id;
+
+        public override int GetHashCode() => Id.GetHashCode();
     }
 
     public enum TargetType

From 2af025e630256e510c72070df391ea322971b087 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 18:09:13 +0900
Subject: [PATCH 09/44] Add IHasDistance object pattern generation.

---
 .../Legacy/DistanceObjectPatternGenerator.cs  | 463 ++++++++++++++++++
 .../osu.Game.Rulesets.Mania.csproj            |   1 +
 2 files changed, 464 insertions(+)
 create mode 100644 osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
new file mode 100644
index 0000000000..03240a2be4
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -0,0 +1,463 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Linq;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Mania.MathUtils;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+
+namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
+{
+    /// <summary>
+    /// A pattern generator for IHasDistance hit objects.
+    /// </summary>
+    internal class DistanceObjectPatternGenerator : PatternGenerator
+    {
+        /// <summary>
+        /// Base osu! slider scoring distance.
+        /// </summary>
+        private const float osu_base_scoring_distance = 100;
+
+        private readonly double endTime;
+        private readonly int repeatCount;
+
+        private PatternType convertType;
+
+        public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
+            : base(random, hitObject, beatmap, previousPattern)
+        {
+            ControlPoint overridePoint;
+            ControlPoint controlPoint = Beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint);
+
+            convertType = PatternType.None;
+            if ((overridePoint ?? controlPoint)?.KiaiMode == false)
+                convertType = PatternType.LowProbability;
+
+            var distanceData = hitObject as IHasDistance;
+            var repeatsData = hitObject as IHasRepeats;
+
+            repeatCount = repeatsData?.RepeatCount ?? 1;
+
+            double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime);
+            double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment;
+
+            // The true distance, accounting for any repeats. This ends up being the drum roll distance later
+            double distance = distanceData.Distance * repeatCount;
+
+            // The velocity of the osu! hit object - calculated as the velocity of a slider
+            double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength;
+            // The duration of the osu! hit object
+            double osuDuration = distance / osuVelocity;
+
+            endTime = hitObject.StartTime + osuDuration;
+        }
+
+        public override Pattern Generate()
+        {
+            double segmentDuration = endTime / repeatCount;
+
+            if (repeatCount > 1)
+            {
+                if (segmentDuration <= 90)
+                    return generateRandomHoldNotes(HitObject.StartTime, endTime, 1);
+
+                if (segmentDuration <= 120)
+                {
+                    convertType |= PatternType.ForceNotStack;
+                    return generateRandomNotes(HitObject.StartTime, segmentDuration, repeatCount);
+                }
+
+                if (segmentDuration <= 160)
+                    return generateStair(HitObject.StartTime, segmentDuration);
+
+                if (segmentDuration <= 200 && ConversionDifficulty > 3)
+                    return generateRandomMultipleNotes(HitObject.StartTime, segmentDuration, repeatCount);
+
+                double duration = endTime - HitObject.StartTime;
+                if (duration >= 4000)
+                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.23, 0, 0);
+
+                if (segmentDuration > 400 && duration < 4000 && repeatCount < AvailableColumns - 1 - RandomStart)
+                    return generateTiledHoldNotes(HitObject.StartTime, segmentDuration, repeatCount);
+
+                return generateHoldAndNormalNotes(HitObject.StartTime, segmentDuration);
+            }
+
+            if (segmentDuration <= 110)
+            {
+                if (PreviousPattern.ColumnsFilled < AvailableColumns)
+                    convertType |= PatternType.ForceNotStack;
+                else
+                    convertType &= ~PatternType.ForceNotStack;
+                return generateRandomNotes(HitObject.StartTime, segmentDuration, segmentDuration < 80 ? 0 : 1);
+            }
+
+            if (ConversionDifficulty > 6.5)
+            {
+                if ((convertType & PatternType.LowProbability) > 0)
+                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.78, 0.3, 0);
+                return generateNRandomNotes(HitObject.StartTime, endTime, 0.85, 0.36, 0.03);
+            }
+
+            if (ConversionDifficulty > 4)
+            {
+                if ((convertType & PatternType.LowProbability) > 0)
+                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.43, 0.08, 0);
+                return generateNRandomNotes(HitObject.StartTime, endTime, 0.56, 0.18, 0);
+            }
+
+            if (ConversionDifficulty > 2.5)
+            {
+                if ((convertType & PatternType.LowProbability) > 0)
+                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.3, 0, 0);
+                return generateNRandomNotes(HitObject.StartTime, endTime, 0.37, 0.08, 0);
+            }
+
+            if ((convertType & PatternType.LowProbability) > 0)
+                return generateNRandomNotes(HitObject.StartTime, endTime, 0.17, 0, 0);
+            return generateNRandomNotes(HitObject.StartTime, endTime, 0.27, 0, 0);
+        }
+
+        /// <summary>
+        /// Generates random hold notes that start at an span the same amount of rows.
+        /// </summary>
+        /// <param name="startTime">Start time of each hold note.</param>
+        /// <param name="endTime">End time of the hold notes.</param>
+        /// <param name="noteCount">Number of hold notes.</param>
+        /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
+        private Pattern generateRandomHoldNotes(double startTime, double endTime, int noteCount)
+        {
+            // - - - -
+            // ■ - ■ ■
+            // □ - □ □
+            // ■ - ■ ■
+
+            var pattern = new Pattern();
+
+            int usableColumns = AvailableColumns - RandomStart - PreviousPattern.ColumnsFilled;
+            int nextColumn = Random.Next(RandomStart, AvailableColumns);
+            for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
+            {
+                while (pattern.IsFilled(nextColumn) || PreviousPattern.IsFilled(nextColumn))  //find available column
+                    nextColumn = Random.Next(RandomStart, AvailableColumns);
+                AddToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+            }
+
+            // This is can't be combined with the above loop due to RNG
+            for (int i = 0; i < noteCount - usableColumns; i++)
+            {
+                while (pattern.IsFilled(nextColumn))
+                    nextColumn = Random.Next(RandomStart, AvailableColumns);
+                AddToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+            }
+
+            return pattern;
+        }
+
+        /// <summary>
+        /// Generates random notes, with one note per row and no stacking.
+        /// </summary>
+        /// <param name="startTime">The start time.</param>
+        /// <param name="separationTime">The separation of notes between rows.</param>
+        /// <param name="repeatCount">The number of rows.</param>
+        /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
+        private Pattern generateRandomNotes(double startTime, double separationTime, int repeatCount)
+        {
+            // - - - -
+            // x - - -
+            // - - x -
+            // - - - x
+            // x - - -
+
+            var pattern = new Pattern();
+
+            int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
+            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnsFilled < AvailableColumns)
+            {
+                while (PreviousPattern.IsFilled(nextColumn))
+                    nextColumn = Random.Next(RandomStart, AvailableColumns);
+            }
+
+            int lastColumn = nextColumn;
+            for (int i = 0; i <= repeatCount; i++)
+            {
+                AddToPattern(pattern, HitObject, nextColumn, startTime, startTime);
+                while (nextColumn == lastColumn)
+                    nextColumn = Random.Next(RandomStart, AvailableColumns);
+
+                lastColumn = nextColumn;
+                startTime += separationTime;
+            }
+
+            return pattern;
+        }
+
+        /// <summary>
+        /// Generates a stair of notes, with one note per row.
+        /// </summary>
+        /// <param name="startTime">The start time.</param>
+        /// <param name="separationTime">The separation of notes between rows.</param>
+        /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
+        private Pattern generateStair(double startTime, double separationTime)
+        {
+            // - - - -
+            // x - - -
+            // - x - -
+            // - - x -
+            // - - - x
+            // - - x -
+            // - x - -
+            // x - - -
+
+            var pattern = new Pattern();
+
+            int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
+            bool increasing = Random.NextDouble() > 0.5;
+
+            for (int i = 0; i <= repeatCount; i++)
+            {
+                AddToPattern(pattern, HitObject, column, startTime, startTime);
+                startTime += separationTime;
+                
+                // Check if we're at the borders of the stage, and invert the pattern if so
+                if (increasing)
+                {
+                    if (column >= AvailableColumns - 1)
+                    {
+                        increasing = false;
+                        column--;
+                    }
+                    else
+                        column++;
+                }
+                else
+                {
+                    if (column <= RandomStart)
+                    {
+                        increasing = true;
+                        column++;
+                    }
+                    else
+                        column--;
+                }
+            }
+
+            return pattern;
+        }
+
+        /// <summary>
+        /// Generates random notes with 1-2 notes per row and no stacking.
+        /// </summary>
+        /// <param name="startTime">The start time.</param>
+        /// <param name="separationTime">The separation of notes between rows.</param>
+        /// <param name="repeatCount">The number of rows.</param>
+        /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
+        private Pattern generateRandomMultipleNotes(double startTime, double separationTime, int repeatCount)
+        {
+            // - - - -
+            // x - - 
+            // - x x -
+            // - - - x
+            // x - x -
+
+            var pattern = new Pattern();
+
+            bool legacy = AvailableColumns >= 4 && AvailableColumns <= 8;
+            int interval = Random.Next(1, AvailableColumns - (legacy ? 1 : 0));
+
+            int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
+            for (int i = 0; i <= repeatCount; i++)
+            {
+                AddToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
+
+                nextColumn += interval;
+                if (nextColumn >= AvailableColumns - RandomStart)
+                    nextColumn = nextColumn - AvailableColumns - RandomStart + (legacy ? 1 : 0);
+                nextColumn += RandomStart;
+
+                // If we're in 2K, let's not add many consecutive doubles
+                if (AvailableColumns > 2)
+                    AddToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
+
+                nextColumn = Random.Next(RandomStart, AvailableColumns);
+                startTime += separationTime;
+            }
+
+            return pattern;
+        }
+
+        /// <summary>
+        /// Generates random hold notes. The amount of hold notes generated is determined by probabilities.
+        /// </summary>
+        /// <param name="startTime">The hold note start time.</param>
+        /// <param name="endTime">The hold note end time.</param>
+        /// <param name="p2">The probability required for 2 hold notes to be generated.</param>
+        /// <param name="p3">The probability required for 3 hold notes to be generated.</param>
+        /// <param name="p4">The probability required for 4 hold notes to be generated.</param>
+        /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
+        private Pattern generateNRandomNotes(double startTime, double endTime, double p2, double p3, double p4)
+        {
+            // - - - -
+            // ■ - ■ ■
+            // □ - □ □
+            // ■ - ■ ■
+
+            switch (AvailableColumns)
+            {
+                case 2:
+                    p2 = 0;
+                    p3 = 0;
+                    p4 = 0;
+                    break;
+                case 3:
+                    p2 = Math.Max(p2, 0.1);
+                    p3 = 0;
+                    p4 = 0;
+                    break;
+                case 4:
+                    p2 = Math.Max(p2, 0.3);
+                    p3 = Math.Max(p3, 0.04);
+                    p4 = 0;
+                    break;
+                case 5:
+                    p2 = Math.Max(p2, 0.34);
+                    p3 = Math.Max(p3, 0.1);
+                    p4 = Math.Max(p4, 0.03);
+                    break;
+            }
+
+            Func<SampleInfo, bool> isDoubleSample = sample => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
+
+            bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
+            canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
+
+            if (canGenerateTwoNotes)
+                p2 = 1;
+
+            return generateRandomHoldNotes(startTime, endTime, GetRandomNoteCount(p2, p3, p4));
+        }
+
+        /// <summary>
+        /// Generates tiled hold notes. You can think of this as a stair of hold notes.
+        /// </summary>
+        /// <param name="startTime">The first hold note start time.</param>
+        /// <param name="separationTime">The separation time between hold notes.</param>
+        /// <param name="noteCount">The amount of hold notes.</param>
+        /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
+        private Pattern generateTiledHoldNotes(double startTime, double separationTime, int noteCount)
+        {
+            // - - - -
+            // ■ ■ ■ ■
+            // □ □ □ □
+            // □ □ □ □
+            // □ □ □ ■
+            // □ □ ■ -
+            // □ ■ - -
+            // ■ - - -
+
+            var pattern = new Pattern();
+
+            int columnRepeat = Math.Min(noteCount, AvailableColumns);
+
+            int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
+            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnsFilled < AvailableColumns)
+            {
+                while (PreviousPattern.IsFilled(nextColumn))
+                    nextColumn = Random.Next(RandomStart, AvailableColumns);
+            }
+
+            for (int i = 0; i < columnRepeat; i++)
+            {
+                while (pattern.IsFilled(nextColumn))
+                    nextColumn = Random.Next(RandomStart, AvailableColumns);
+
+                AddToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+                startTime += separationTime;
+            }
+
+            return pattern;
+        }
+
+        /// <summary>
+        /// Generates a hold note alongside normal notes.
+        /// </summary>
+        /// <param name="startTime">The start time of notes.</param>
+        /// <param name="separationTime">The separation time between notes.</param>
+        /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
+        private Pattern generateHoldAndNormalNotes(double startTime, double separationTime)
+        {
+            // - - - -
+            // ■ x x -
+            // ■ - x x
+            // ■ x - x
+            // ■ - x x
+
+            var pattern = new Pattern();
+
+            int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
+            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnsFilled < AvailableColumns)
+            {
+                while (PreviousPattern.IsFilled(holdColumn))
+                    holdColumn = Random.Next(RandomStart, AvailableColumns);
+            }
+
+            // Create the hold note
+            AddToPattern(pattern, HitObject, holdColumn, startTime, separationTime * repeatCount);
+
+            int noteCount = 1;
+            if (ConversionDifficulty > 6.5)
+                noteCount = GetRandomNoteCount(0.63, 0);
+            else if (ConversionDifficulty > 4)
+                noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0.12 : 0.45, 0);
+            else if (ConversionDifficulty > 2.5)
+                noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0 : 0.24, 0);
+            noteCount = Math.Min(AvailableColumns - 1, noteCount);
+
+            bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP);
+            int nextColumn = Random.Next(RandomStart, AvailableColumns);
+
+            var rowPattern = new Pattern();
+            for (int i = 0; i <= repeatCount; i++)
+            {
+                if (!(ignoreHead && startTime == HitObject.StartTime))
+                {
+                    for (int j = 0; j < noteCount; j++)
+                    {
+                        while (rowPattern.IsFilled(nextColumn) || nextColumn == holdColumn)
+                            nextColumn = Random.Next(RandomStart, AvailableColumns);
+                        AddToPattern(rowPattern, HitObject, nextColumn, startTime, startTime, noteCount + 1);
+                    }
+                }
+
+                pattern.Add(rowPattern);
+                rowPattern.Clear();
+
+                startTime += separationTime;
+            }
+
+            return pattern;
+        }
+
+        /// <summary>
+        /// Retrieves the sample info list at a point in time.
+        /// </summary>
+        /// <param name="time">The time to retrieve the sample info list from.</param>
+        /// <returns></returns>
+        private SampleInfoList sampleInfoListAt(double time)
+        {
+            var curveData = HitObject as IHasCurve;
+
+            if (curveData == null)
+                return HitObject.Samples;
+
+            double segmentTime = (curveData.EndTime - HitObject.StartTime) / repeatCount;
+
+            int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
+            return curveData.RepeatSamples[index];
+        }
+    }
+}
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index ea65588a81..9de9cd703f 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -47,6 +47,7 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Beatmaps\Patterns\Legacy\DistanceObjectPatternGenerator.cs" />
     <Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" />
     <Compile Include="Beatmaps\Patterns\PatternGenerator.cs" />
     <Compile Include="Beatmaps\LegacyBeatmapConverter.cs" />

From eae4f8b4127cb98a1717cd590ab4ac2dd2f83c4a Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 18:11:09 +0900
Subject: [PATCH 10/44] Fix compilation due to previously-removed function.

---
 .../Legacy/DistanceObjectPatternGenerator.cs  | 58 ++++++++++++++++---
 1 file changed, 49 insertions(+), 9 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 03240a2be4..304a32a51b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -9,6 +9,7 @@ using osu.Game.Beatmaps.Timing;
 using osu.Game.Rulesets.Mania.MathUtils;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Mania.Objects;
 
 namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 {
@@ -144,7 +145,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             {
                 while (pattern.IsFilled(nextColumn) || PreviousPattern.IsFilled(nextColumn))  //find available column
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
-                AddToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
             }
 
             // This is can't be combined with the above loop due to RNG
@@ -152,7 +153,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             {
                 while (pattern.IsFilled(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
-                AddToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
             }
 
             return pattern;
@@ -185,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             int lastColumn = nextColumn;
             for (int i = 0; i <= repeatCount; i++)
             {
-                AddToPattern(pattern, HitObject, nextColumn, startTime, startTime);
+                addToPattern(pattern, HitObject, nextColumn, startTime, startTime);
                 while (nextColumn == lastColumn)
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
@@ -220,7 +221,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
             for (int i = 0; i <= repeatCount; i++)
             {
-                AddToPattern(pattern, HitObject, column, startTime, startTime);
+                addToPattern(pattern, HitObject, column, startTime, startTime);
                 startTime += separationTime;
                 
                 // Check if we're at the borders of the stage, and invert the pattern if so
@@ -272,7 +273,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
             for (int i = 0; i <= repeatCount; i++)
             {
-                AddToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
+                addToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
 
                 nextColumn += interval;
                 if (nextColumn >= AvailableColumns - RandomStart)
@@ -281,7 +282,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
                 // If we're in 2K, let's not add many consecutive doubles
                 if (AvailableColumns > 2)
-                    AddToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
+                    addToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
 
                 nextColumn = Random.Next(RandomStart, AvailableColumns);
                 startTime += separationTime;
@@ -375,7 +376,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 while (pattern.IsFilled(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
-                AddToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
                 startTime += separationTime;
             }
 
@@ -406,7 +407,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             }
 
             // Create the hold note
-            AddToPattern(pattern, HitObject, holdColumn, startTime, separationTime * repeatCount);
+            addToPattern(pattern, HitObject, holdColumn, startTime, separationTime * repeatCount);
 
             int noteCount = 1;
             if (ConversionDifficulty > 6.5)
@@ -429,7 +430,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     {
                         while (rowPattern.IsFilled(nextColumn) || nextColumn == holdColumn)
                             nextColumn = Random.Next(RandomStart, AvailableColumns);
-                        AddToPattern(rowPattern, HitObject, nextColumn, startTime, startTime, noteCount + 1);
+                        addToPattern(rowPattern, HitObject, nextColumn, startTime, startTime, noteCount + 1);
                     }
                 }
 
@@ -459,5 +460,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
             return curveData.RepeatSamples[index];
         }
+
+
+        /// <summary>
+        /// Constructs and adds a note to a pattern.
+        /// </summary>
+        /// <param name="pattern">The pattern to add to.</param>
+        /// <param name="originalObject">The original hit object (used for samples).</param>
+        /// <param name="column">The column to add the note to.</param>
+        /// <param name="startTime">The start time of the note.</param>
+        /// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
+        /// <param name="siblings">The number of children alongside this note (these will not be generated, but are used for volume calculations).</param>
+        private void addToPattern(Pattern pattern, HitObject originalObject, int column, double startTime, double endTime, int siblings = 1)
+        {
+            ManiaHitObject newObject;
+
+            if (startTime == endTime)
+            {
+                newObject = new Note
+                {
+                    StartTime = startTime,
+                    Samples = originalObject.Samples,
+                    Column = column
+                };
+            }
+            else
+            {
+                newObject = new HoldNote
+                {
+                    StartTime = startTime,
+                    Samples = originalObject.Samples,
+                    Column = column,
+                    Duration = endTime - startTime
+                };
+            }
+
+            // Todo: Consider siblings and write sample volumes (probably at ManiaHitObject level)
+
+            pattern.Add(newObject);
+        }
     }
 }

From 53a2f34f8b891e4e084b5351eb5fef1b629cc989 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 18:15:09 +0900
Subject: [PATCH 11/44] Set siblings, removing warning.

---
 .../Patterns/Legacy/DistanceObjectPatternGenerator.cs     | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 304a32a51b..adb0966b81 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -481,7 +481,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 {
                     StartTime = startTime,
                     Samples = originalObject.Samples,
-                    Column = column
+                    Column = column,
+                    Siblings = siblings
                 };
             }
             else
@@ -491,12 +492,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     StartTime = startTime,
                     Samples = originalObject.Samples,
                     Column = column,
-                    Duration = endTime - startTime
+                    Duration = endTime - startTime,
+                    Siblings = siblings
                 };
             }
 
-            // Todo: Consider siblings and write sample volumes (probably at ManiaHitObject level)
-
             pattern.Add(newObject);
         }
     }

From a30e49d21bf925586fdc19a43d760e631046c5c1 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 18:19:29 +0900
Subject: [PATCH 12/44] Add hold end samples.

---
 .../Patterns/Legacy/DistanceObjectPatternGenerator.cs       | 5 +++--
 osu.Game.Rulesets.Mania/Objects/HoldNote.cs                 | 6 ++++++
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index adb0966b81..b2a9de4b54 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -480,7 +480,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 newObject = new Note
                 {
                     StartTime = startTime,
-                    Samples = originalObject.Samples,
+                    Samples = sampleInfoListAt(startTime),
                     Column = column,
                     Siblings = siblings
                 };
@@ -490,7 +490,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 newObject = new HoldNote
                 {
                     StartTime = startTime,
-                    Samples = originalObject.Samples,
+                    Samples = sampleInfoListAt(startTime),
+                    EndSamples = sampleInfoListAt(endTime),
                     Column = column,
                     Duration = endTime - startTime,
                     Siblings = siblings
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index a25b8fbf2a..701947c381 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using osu.Game.Audio;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Mania.Judgements;
@@ -22,6 +23,11 @@ namespace osu.Game.Rulesets.Mania.Objects
         public double Duration { get; set; }
         public double EndTime => StartTime + Duration;
 
+        /// <summary>
+        /// The samples to be played when this hold note is released.
+        /// </summary>
+        public SampleInfoList EndSamples = new SampleInfoList();
+
         /// <summary>
         /// The key-release hit windows for this hold note.
         /// </summary>

From 35bd608a4ac5c6a74fe466bfa96f865b06c6976f Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 18:41:44 +0900
Subject: [PATCH 13/44] Simplify method parameters, make conversion work again.

---
 .../Beatmaps/LegacyBeatmapConverter.cs        |  4 +-
 .../Legacy/DistanceObjectPatternGenerator.cs  | 83 +++++++++----------
 2 files changed, 40 insertions(+), 47 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
index 3aa88af62d..a69145ba75 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
 using osu.Game.Rulesets.Mania.MathUtils;
+using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
 
 namespace osu.Game.Rulesets.Mania.Beatmaps
 {
@@ -74,10 +75,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
             var distanceData = original as IHasDistance;
             var positionData = original as IHasPosition;
 
-            PatternGenerator conversion = null;
+            Patterns.PatternGenerator conversion = null;
 
             if (distanceData != null)
             {
+                conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern);
                 // Slider
             }
             else if (endTimeData != null)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index b2a9de4b54..238e25f0c4 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         private const float osu_base_scoring_distance = 100;
 
         private readonly double endTime;
+        private readonly double segmentDuration;
         private readonly int repeatCount;
 
         private PatternType convertType;
@@ -55,37 +56,36 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             double osuDuration = distance / osuVelocity;
 
             endTime = hitObject.StartTime + osuDuration;
+            segmentDuration = (endTime - HitObject.StartTime) / repeatCount;
         }
 
         public override Pattern Generate()
         {
-            double segmentDuration = endTime / repeatCount;
-
             if (repeatCount > 1)
             {
                 if (segmentDuration <= 90)
-                    return generateRandomHoldNotes(HitObject.StartTime, endTime, 1);
+                    return generateRandomHoldNotes(HitObject.StartTime, 1);
 
                 if (segmentDuration <= 120)
                 {
                     convertType |= PatternType.ForceNotStack;
-                    return generateRandomNotes(HitObject.StartTime, segmentDuration, repeatCount);
+                    return generateRandomNotes(HitObject.StartTime, repeatCount + 1);
                 }
 
                 if (segmentDuration <= 160)
-                    return generateStair(HitObject.StartTime, segmentDuration);
+                    return generateStair(HitObject.StartTime);
 
                 if (segmentDuration <= 200 && ConversionDifficulty > 3)
-                    return generateRandomMultipleNotes(HitObject.StartTime, segmentDuration, repeatCount);
+                    return generateRandomMultipleNotes(HitObject.StartTime);
 
                 double duration = endTime - HitObject.StartTime;
                 if (duration >= 4000)
-                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.23, 0, 0);
+                    return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
 
                 if (segmentDuration > 400 && duration < 4000 && repeatCount < AvailableColumns - 1 - RandomStart)
-                    return generateTiledHoldNotes(HitObject.StartTime, segmentDuration, repeatCount);
+                    return generateTiledHoldNotes(HitObject.StartTime);
 
-                return generateHoldAndNormalNotes(HitObject.StartTime, segmentDuration);
+                return generateHoldAndNormalNotes(HitObject.StartTime);
             }
 
             if (segmentDuration <= 110)
@@ -94,43 +94,42 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     convertType |= PatternType.ForceNotStack;
                 else
                     convertType &= ~PatternType.ForceNotStack;
-                return generateRandomNotes(HitObject.StartTime, segmentDuration, segmentDuration < 80 ? 0 : 1);
+                return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2);
             }
 
             if (ConversionDifficulty > 6.5)
             {
                 if ((convertType & PatternType.LowProbability) > 0)
-                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.78, 0.3, 0);
-                return generateNRandomNotes(HitObject.StartTime, endTime, 0.85, 0.36, 0.03);
+                    return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
+                return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
             }
 
             if (ConversionDifficulty > 4)
             {
                 if ((convertType & PatternType.LowProbability) > 0)
-                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.43, 0.08, 0);
-                return generateNRandomNotes(HitObject.StartTime, endTime, 0.56, 0.18, 0);
+                    return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
+                return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
             }
 
             if (ConversionDifficulty > 2.5)
             {
                 if ((convertType & PatternType.LowProbability) > 0)
-                    return generateNRandomNotes(HitObject.StartTime, endTime, 0.3, 0, 0);
-                return generateNRandomNotes(HitObject.StartTime, endTime, 0.37, 0.08, 0);
+                    return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
+                return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
             }
 
             if ((convertType & PatternType.LowProbability) > 0)
-                return generateNRandomNotes(HitObject.StartTime, endTime, 0.17, 0, 0);
-            return generateNRandomNotes(HitObject.StartTime, endTime, 0.27, 0, 0);
+                return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
+            return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
         }
 
         /// <summary>
         /// Generates random hold notes that start at an span the same amount of rows.
         /// </summary>
         /// <param name="startTime">Start time of each hold note.</param>
-        /// <param name="endTime">End time of the hold notes.</param>
         /// <param name="noteCount">Number of hold notes.</param>
         /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
-        private Pattern generateRandomHoldNotes(double startTime, double endTime, int noteCount)
+        private Pattern generateRandomHoldNotes(double startTime, int noteCount)
         {
             // - - - -
             // ■ - ■ ■
@@ -163,10 +162,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// Generates random notes, with one note per row and no stacking.
         /// </summary>
         /// <param name="startTime">The start time.</param>
-        /// <param name="separationTime">The separation of notes between rows.</param>
-        /// <param name="repeatCount">The number of rows.</param>
+        /// <param name="noteCount">The number of notes.</param>
         /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
-        private Pattern generateRandomNotes(double startTime, double separationTime, int repeatCount)
+        private Pattern generateRandomNotes(double startTime, int noteCount)
         {
             // - - - -
             // x - - -
@@ -184,14 +182,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             }
 
             int lastColumn = nextColumn;
-            for (int i = 0; i <= repeatCount; i++)
+            for (int i = 0; i < noteCount; i++)
             {
                 addToPattern(pattern, HitObject, nextColumn, startTime, startTime);
                 while (nextColumn == lastColumn)
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
                 lastColumn = nextColumn;
-                startTime += separationTime;
+                startTime += segmentDuration;
             }
 
             return pattern;
@@ -201,9 +199,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// Generates a stair of notes, with one note per row.
         /// </summary>
         /// <param name="startTime">The start time.</param>
-        /// <param name="separationTime">The separation of notes between rows.</param>
         /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
-        private Pattern generateStair(double startTime, double separationTime)
+        private Pattern generateStair(double startTime)
         {
             // - - - -
             // x - - -
@@ -222,7 +219,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             for (int i = 0; i <= repeatCount; i++)
             {
                 addToPattern(pattern, HitObject, column, startTime, startTime);
-                startTime += separationTime;
+                startTime += segmentDuration;
                 
                 // Check if we're at the borders of the stage, and invert the pattern if so
                 if (increasing)
@@ -254,10 +251,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// Generates random notes with 1-2 notes per row and no stacking.
         /// </summary>
         /// <param name="startTime">The start time.</param>
-        /// <param name="separationTime">The separation of notes between rows.</param>
-        /// <param name="repeatCount">The number of rows.</param>
         /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
-        private Pattern generateRandomMultipleNotes(double startTime, double separationTime, int repeatCount)
+        private Pattern generateRandomMultipleNotes(double startTime)
         {
             // - - - -
             // x - - 
@@ -285,7 +280,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     addToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
 
                 nextColumn = Random.Next(RandomStart, AvailableColumns);
-                startTime += separationTime;
+                startTime += segmentDuration;
             }
 
             return pattern;
@@ -295,12 +290,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// Generates random hold notes. The amount of hold notes generated is determined by probabilities.
         /// </summary>
         /// <param name="startTime">The hold note start time.</param>
-        /// <param name="endTime">The hold note end time.</param>
         /// <param name="p2">The probability required for 2 hold notes to be generated.</param>
         /// <param name="p3">The probability required for 3 hold notes to be generated.</param>
         /// <param name="p4">The probability required for 4 hold notes to be generated.</param>
         /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
-        private Pattern generateNRandomNotes(double startTime, double endTime, double p2, double p3, double p4)
+        private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4)
         {
             // - - - -
             // ■ - ■ ■
@@ -339,17 +333,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             if (canGenerateTwoNotes)
                 p2 = 1;
 
-            return generateRandomHoldNotes(startTime, endTime, GetRandomNoteCount(p2, p3, p4));
+            return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4));
         }
 
         /// <summary>
         /// Generates tiled hold notes. You can think of this as a stair of hold notes.
         /// </summary>
         /// <param name="startTime">The first hold note start time.</param>
-        /// <param name="separationTime">The separation time between hold notes.</param>
-        /// <param name="noteCount">The amount of hold notes.</param>
         /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
-        private Pattern generateTiledHoldNotes(double startTime, double separationTime, int noteCount)
+        private Pattern generateTiledHoldNotes(double startTime)
         {
             // - - - -
             // ■ ■ ■ ■
@@ -362,7 +354,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
             var pattern = new Pattern();
 
-            int columnRepeat = Math.Min(noteCount, AvailableColumns);
+            int columnRepeat = Math.Min(repeatCount, AvailableColumns);
 
             int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
             if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnsFilled < AvailableColumns)
@@ -376,8 +368,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 while (pattern.IsFilled(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
-                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
-                startTime += separationTime;
+                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, repeatCount);
+                startTime += segmentDuration;
             }
 
             return pattern;
@@ -387,9 +379,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// Generates a hold note alongside normal notes.
         /// </summary>
         /// <param name="startTime">The start time of notes.</param>
-        /// <param name="separationTime">The separation time between notes.</param>
         /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
-        private Pattern generateHoldAndNormalNotes(double startTime, double separationTime)
+        private Pattern generateHoldAndNormalNotes(double startTime)
         {
             // - - - -
             // ■ x x -
@@ -407,7 +398,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             }
 
             // Create the hold note
-            addToPattern(pattern, HitObject, holdColumn, startTime, separationTime * repeatCount);
+            addToPattern(pattern, HitObject, holdColumn, startTime, endTime);
 
             int noteCount = 1;
             if (ConversionDifficulty > 6.5)
@@ -437,7 +428,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 pattern.Add(rowPattern);
                 rowPattern.Clear();
 
-                startTime += separationTime;
+                startTime += segmentDuration;
             }
 
             return pattern;
@@ -455,7 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             if (curveData == null)
                 return HitObject.Samples;
 
-            double segmentTime = (curveData.EndTime - HitObject.StartTime) / repeatCount;
+            double segmentTime = (endTime - HitObject.StartTime) / repeatCount;
 
             int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
             return curveData.RepeatSamples[index];

From a239411808b14df9a90895bc331e798010c28861 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 19:09:02 +0900
Subject: [PATCH 14/44] Fix warnings/errors.

---
 .../Beatmaps/LegacyBeatmapConverter.cs        | 10 ++++----
 .../Legacy/DistanceObjectPatternGenerator.cs  | 23 +++++++++----------
 2 files changed, 16 insertions(+), 17 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
index 17552c05ef..de826063b0 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
             // Following lines currently commented out to appease resharper
 
-            //PatternGenerator conversion = null;
+            Patterns.PatternGenerator conversion = null;
 
             if (distanceData != null)
             {
@@ -93,13 +93,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
                 // Circle
             }
 
-            //if (conversion == null)
+            if (conversion == null)
                 return null;
 
-            //Pattern newPattern = conversion.Generate();
-            //lastPattern = newPattern;
+            Pattern newPattern = conversion.Generate();
+            lastPattern = newPattern;
 
-            //return newPattern.HitObjects;
+            return newPattern.HitObjects;
         }
 
         /// <summary>
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 238e25f0c4..04e2b30816 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment;
 
             // The true distance, accounting for any repeats. This ends up being the drum roll distance later
-            double distance = distanceData.Distance * repeatCount;
+            double distance = (distanceData?.Distance ?? 0) * repeatCount;
 
             // The velocity of the osu! hit object - calculated as the velocity of a slider
             double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength;
@@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             {
                 while (pattern.IsFilled(nextColumn) || PreviousPattern.IsFilled(nextColumn))  //find available column
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
-                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+                addToPattern(pattern, nextColumn, startTime, endTime, noteCount);
             }
 
             // This is can't be combined with the above loop due to RNG
@@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             {
                 while (pattern.IsFilled(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
-                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, noteCount);
+                addToPattern(pattern, nextColumn, startTime, endTime, noteCount);
             }
 
             return pattern;
@@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             int lastColumn = nextColumn;
             for (int i = 0; i < noteCount; i++)
             {
-                addToPattern(pattern, HitObject, nextColumn, startTime, startTime);
+                addToPattern(pattern, nextColumn, startTime, startTime);
                 while (nextColumn == lastColumn)
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
@@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
             for (int i = 0; i <= repeatCount; i++)
             {
-                addToPattern(pattern, HitObject, column, startTime, startTime);
+                addToPattern(pattern, column, startTime, startTime);
                 startTime += segmentDuration;
                 
                 // Check if we're at the borders of the stage, and invert the pattern if so
@@ -268,7 +268,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
             for (int i = 0; i <= repeatCount; i++)
             {
-                addToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
+                addToPattern(pattern, nextColumn, startTime, startTime, 2);
 
                 nextColumn += interval;
                 if (nextColumn >= AvailableColumns - RandomStart)
@@ -277,7 +277,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
                 // If we're in 2K, let's not add many consecutive doubles
                 if (AvailableColumns > 2)
-                    addToPattern(pattern, HitObject, nextColumn, startTime, startTime, 2);
+                    addToPattern(pattern, nextColumn, startTime, startTime, 2);
 
                 nextColumn = Random.Next(RandomStart, AvailableColumns);
                 startTime += segmentDuration;
@@ -368,7 +368,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 while (pattern.IsFilled(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
-                addToPattern(pattern, HitObject, nextColumn, startTime, endTime, repeatCount);
+                addToPattern(pattern, nextColumn, startTime, endTime, repeatCount);
                 startTime += segmentDuration;
             }
 
@@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             }
 
             // Create the hold note
-            addToPattern(pattern, HitObject, holdColumn, startTime, endTime);
+            addToPattern(pattern, holdColumn, startTime, endTime);
 
             int noteCount = 1;
             if (ConversionDifficulty > 6.5)
@@ -421,7 +421,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     {
                         while (rowPattern.IsFilled(nextColumn) || nextColumn == holdColumn)
                             nextColumn = Random.Next(RandomStart, AvailableColumns);
-                        addToPattern(rowPattern, HitObject, nextColumn, startTime, startTime, noteCount + 1);
+                        addToPattern(rowPattern, nextColumn, startTime, startTime, noteCount + 1);
                     }
                 }
 
@@ -457,12 +457,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// Constructs and adds a note to a pattern.
         /// </summary>
         /// <param name="pattern">The pattern to add to.</param>
-        /// <param name="originalObject">The original hit object (used for samples).</param>
         /// <param name="column">The column to add the note to.</param>
         /// <param name="startTime">The start time of the note.</param>
         /// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
         /// <param name="siblings">The number of children alongside this note (these will not be generated, but are used for volume calculations).</param>
-        private void addToPattern(Pattern pattern, HitObject originalObject, int column, double startTime, double endTime, int siblings = 1)
+        private void addToPattern(Pattern pattern, int column, double startTime, double endTime, int siblings = 1)
         {
             ManiaHitObject newObject;
 

From 1f6939f57bc7443abbb73d9c775e6dfab311bd01 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 17:39:03 +0900
Subject: [PATCH 15/44] Add IHasEndTime object pattern generation.

---
 .../Beatmaps/LegacyBeatmapConverter.cs        |  14 +--
 .../Legacy/EndTimeObjectPatternGenerator.cs   | 100 ++++++++++++++++++
 osu.Game.Rulesets.Mania/Objects/HoldNote.cs   |   6 ++
 .../osu.Game.Rulesets.Mania.csproj            |   1 +
 4 files changed, 114 insertions(+), 7 deletions(-)
 create mode 100644 osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
index e3045df185..3fd5b3900b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/LegacyBeatmapConverter.cs
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
 using osu.Game.Rulesets.Mania.MathUtils;
+using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
 
 namespace osu.Game.Rulesets.Mania.Beatmaps
 {
@@ -74,9 +75,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
             var distanceData = original as IHasDistance;
             var positionData = original as IHasPosition;
 
-            // Following lines currently commented out to appease resharper
-
-            //PatternGenerator conversion = null;
+            Patterns.PatternGenerator conversion = null;
 
             if (distanceData != null)
             {
@@ -84,6 +83,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
             }
             else if (endTimeData != null)
             {
+                conversion = new EndTimeObjectPatternGenerator(random, original, beatmap);
                 // Spinner
             }
             else if (positionData != null)
@@ -91,13 +91,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
                 // Circle
             }
 
-            //if (conversion == null)
+            if (conversion == null)
                 return null;
 
-            //Pattern newPattern = conversion.Generate();
-            //lastPattern = newPattern;
+            Pattern newPattern = conversion.Generate();
+            lastPattern = newPattern;
 
-            //return newPattern.HitObjects;
+            return newPattern.HitObjects;
         }
 
         /// <summary>
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
new file mode 100644
index 0000000000..cd04418ac5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -0,0 +1,100 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.MathUtils;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using System.Linq;
+using osu.Game.Audio;
+using osu.Game.Rulesets.Mania.Objects;
+
+namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
+{
+    internal class EndTimeObjectPatternGenerator : PatternGenerator
+    {
+        private readonly double endTime;
+
+        public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap)
+            : base(random, hitObject, beatmap, new Pattern())
+        {
+            var endtimeData = HitObject as IHasEndTime;
+
+            endTime = endtimeData?.EndTime ?? 0;
+        }
+
+        public override Pattern Generate()
+        {
+            var pattern = new Pattern();
+
+            bool generateHold = endTime - HitObject.StartTime >= 100;
+
+            if (AvailableColumns == 8)
+            {
+                if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000)
+                    addToPattern(pattern, 0, generateHold);
+                else
+                    addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
+            }
+            else
+                addToPattern(pattern, getNextRandomColumn(0), generateHold);
+
+            return pattern;
+        }
+
+        /// <summary>
+        /// Picks a random column after a column.
+        /// </summary>
+        /// <param name="start">The starting column.</param>
+        /// <returns>A random column after <paramref name="start"/>.</returns>
+        private int getNextRandomColumn(int start)
+        {
+            int nextColumn = Random.Next(start, AvailableColumns);
+
+            while (PreviousPattern.IsFilled(nextColumn))
+                nextColumn = Random.Next(start, AvailableColumns);
+
+            return nextColumn;
+        }
+
+        /// <summary>
+        /// Constructs and adds a note to a pattern.
+        /// </summary>
+        /// <param name="pattern">The pattern to add to.</param>
+        /// <param name="column">The column to add the note to.</param>
+        /// <param name="holdNote">Whether to add a hold note.</param>
+        private void addToPattern(Pattern pattern, int column, bool holdNote)
+        {
+            ManiaHitObject newObject;
+
+            if (holdNote)
+            {
+                newObject = new HoldNote
+                {
+                    StartTime = HitObject.StartTime,
+                    EndSamples = HitObject.Samples,
+                    Column = column,
+                    Duration = endTime - HitObject.StartTime,
+                    Siblings = 1
+                };
+
+                newObject.Samples.Add(new SampleInfo
+                {
+                    Name = SampleInfo.HIT_NORMAL
+                });
+            }
+            else
+            {
+                newObject = new Note
+                {
+                    StartTime = HitObject.StartTime,
+                    Samples = HitObject.Samples,
+                    Column = column,
+                    Siblings = 1
+                };
+            }
+
+            pattern.Add(newObject);
+        }
+    }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index a25b8fbf2a..701947c381 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using osu.Game.Audio;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Mania.Judgements;
@@ -22,6 +23,11 @@ namespace osu.Game.Rulesets.Mania.Objects
         public double Duration { get; set; }
         public double EndTime => StartTime + Duration;
 
+        /// <summary>
+        /// The samples to be played when this hold note is released.
+        /// </summary>
+        public SampleInfoList EndSamples = new SampleInfoList();
+
         /// <summary>
         /// The key-release hit windows for this hold note.
         /// </summary>
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index ea65588a81..9e8d300328 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -47,6 +47,7 @@
     <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Beatmaps\Patterns\Legacy\EndTimeObjectPatternGenerator.cs" />
     <Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" />
     <Compile Include="Beatmaps\Patterns\PatternGenerator.cs" />
     <Compile Include="Beatmaps\LegacyBeatmapConverter.cs" />

From b5674c8f300720c8bdd60072e0011cf60ba2b580 Mon Sep 17 00:00:00 2001
From: Dan Balasescu <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 19:33:02 +0900
Subject: [PATCH 16/44] Update DistanceObjectPatternGenerator.cs

---
 .../Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 04e2b30816..d1cc638fe2 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             {
                 addToPattern(pattern, column, startTime, startTime);
                 startTime += segmentDuration;
-                
+
                 // Check if we're at the borders of the stage, and invert the pattern if so
                 if (increasing)
                 {

From e52b87bc586512735493f1b76ce8b2e74e400ec5 Mon Sep 17 00:00:00 2001
From: Dan Balasescu <smoogipooo@gmail.com>
Date: Thu, 18 May 2017 19:47:16 +0900
Subject: [PATCH 17/44] Fix incorrect comment.

---
 .../Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index d1cc638fe2..9ea4353317 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         private Pattern generateRandomMultipleNotes(double startTime)
         {
             // - - - -
-            // x - - 
+            // x - - -
             // - x x -
             // - - - x
             // x - x -

From 90270ac5869d5ddb8212c67d603cacbc061b45c0 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Fri, 19 May 2017 17:48:23 +0900
Subject: [PATCH 18/44] Update to match base branch.

---
 .../Beatmaps/ManiaBeatmapConverter.cs           | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 933fe0787c..46ca90e02b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
 using osu.Game.Rulesets.Mania.MathUtils;
 using osu.Game.Database;
+using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
 
 namespace osu.Game.Rulesets.Mania.Beatmaps
 {
@@ -83,12 +84,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
             // Following lines currently commented out to appease resharper
 
-            //Patterns.PatternGenerator conversion = null;
+            Patterns.PatternGenerator conversion = null;
 
             if (distanceData != null)
-            {
-                // Slider
-            }
+                conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern);
             else if (endTimeData != null)
             {
                 // Spinner
@@ -98,13 +97,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
                 // Circle
             }
 
-            //if (conversion == null)
-            return null;
+            if (conversion == null)
+                return null;
 
-            //Pattern newPattern = conversion.Generate();
-            //lastPattern = newPattern;
+            Pattern newPattern = conversion.Generate();
+            lastPattern = newPattern;
 
-            //return newPattern.HitObjects;
+            return newPattern.HitObjects;
         }
 
         /// <summary>

From e76cb4cc3153bdc4e716f60a2a68c81bdf804bb4 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Fri, 19 May 2017 17:51:59 +0900
Subject: [PATCH 19/44] Update to match base branch.

---
 .../Beatmaps/ManiaBeatmapConverter.cs           | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 933fe0787c..9a93815ce3 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
 using osu.Game.Rulesets.Mania.MathUtils;
 using osu.Game.Database;
+using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
 
 namespace osu.Game.Rulesets.Mania.Beatmaps
 {
@@ -83,28 +84,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
             // Following lines currently commented out to appease resharper
 
-            //Patterns.PatternGenerator conversion = null;
+            Patterns.PatternGenerator conversion = null;
 
             if (distanceData != null)
             {
                 // Slider
             }
             else if (endTimeData != null)
-            {
-                // Spinner
-            }
+                conversion = new EndTimeObjectPatternGenerator(random, original, beatmap);
             else if (positionData != null)
             {
                 // Circle
             }
 
-            //if (conversion == null)
-            return null;
+            if (conversion == null)
+                return null;
 
-            //Pattern newPattern = conversion.Generate();
-            //lastPattern = newPattern;
+            Pattern newPattern = conversion.Generate();
+            lastPattern = newPattern;
 
-            //return newPattern.HitObjects;
+            return newPattern.HitObjects;
         }
 
         /// <summary>

From bfedd42ca442d5a855e2bc300922b6464ef032de Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 19 May 2017 23:56:48 -0300
Subject: [PATCH 20/44] Add mod tooltips

---
 osu.Game/Overlays/Mods/ModButton.cs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index fe1b3b0192..9bbd793934 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.UI;
 using System;
 using System.Linq;
+using osu.Game.Graphics;
 
 namespace osu.Game.Overlays.Mods
 {
@@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Mods
     /// <summary>
     /// Represents a clickable button which can cycle through one of more mods.
     /// </summary>
-    public class ModButton : ModButtonEmpty
+    public class ModButton : ModButtonEmpty, IHasTooltip
     {
         private ModIcon foregroundIcon;
         private readonly SpriteText text;
@@ -32,6 +33,8 @@ namespace osu.Game.Overlays.Mods
 
         public Action<Mod> Action; // Passed the selected mod or null if none
 
+        public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? @"";
+
         private int _selectedIndex = -1;
         private int selectedIndex
         {

From dcc3dbf5e2e8cd7cbc6c428d1e733298a854e3c2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 01:11:20 -0300
Subject: [PATCH 21/44] Make PlaylistItem use Paragraphs

---
 osu.Game/Overlays/Music/PlaylistItem.cs | 71 ++++++++++++++++---------
 1 file changed, 47 insertions(+), 24 deletions(-)

diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 16978903a7..bde043d0d4 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -11,6 +11,9 @@ using osu.Game.Graphics.Sprites;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Localisation;
+using osu.Framework.Graphics.Sprites;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace osu.Game.Overlays.Music
 {
@@ -19,9 +22,13 @@ namespace osu.Game.Overlays.Music
         private const float fade_duration = 100;
 
         private Color4 hoverColour;
+        private Color4 artistColour;
 
         private TextAwesome handle;
-        private OsuSpriteText title;
+        private Paragraph text;
+        private IEnumerable<SpriteText> titleSprites;
+        private UnicodeBindableString titleBind;
+        private UnicodeBindableString artistBind;
 
         public readonly BeatmapSetInfo BeatmapSetInfo;
 
@@ -37,7 +44,8 @@ namespace osu.Game.Overlays.Music
                 selected = value;
 
                 Flush(true);
-                title.FadeColour(Selected ? hoverColour : Color4.White, fade_duration);
+                foreach (SpriteText s in titleSprites)
+                    s.FadeColour(Selected ? hoverColour : Color4.White, fade_duration);
             }
         }
 
@@ -53,8 +61,10 @@ namespace osu.Game.Overlays.Music
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, LocalisationEngine localisation)
         {
-            BeatmapMetadata metadata = BeatmapSetInfo.Metadata;
-
+            hoverColour = colours.Yellow;
+            artistColour = colours.Gray9;
+            
+            var metadata = BeatmapSetInfo.Metadata;
             FilterTerms = metadata.SearchableTerms;
 
             Children = new Drawable[]
@@ -70,33 +80,46 @@ namespace osu.Game.Overlays.Music
                     Margin = new MarginPadding { Left = 5 },
                     Padding = new MarginPadding { Top = 2 },
                 },
-                new FillFlowContainer<OsuSpriteText>
+                text = new Paragraph
                 {
                     RelativeSizeAxes = Axes.X,
                     AutoSizeAxes = Axes.Y,
                     Padding = new MarginPadding { Left = 20 },
-                    Spacing = new Vector2(5f, 0f),
-                    Children = new []
-                    {
-                        title = new OsuSpriteText
-                        {
-                            TextSize = 16,
-                            Font = @"Exo2.0-Regular",
-                            Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title),
-                        },
-                        new OsuSpriteText
-                        {
-                            TextSize = 14,
-                            Font = @"Exo2.0-Bold",
-                            Colour = colours.Gray9,
-                            Padding = new MarginPadding { Top = 1 },
-                            Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist),
-                        }
-                    }
+                    ContentIndent = 10f,
                 },
             };
 
-            hoverColour = colours.Yellow;
+            titleBind = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title);
+            artistBind = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist);
+
+            titleBind.ValueChanged += newText => recreateText();
+            titleBind.TriggerChange();
+        }
+
+        private void recreateText()
+        {
+            text.Clear();
+
+            var metadata = BeatmapSetInfo.Metadata;
+            var t = new List<SpriteText>();
+
+            //space after the title to put a space between the title and artist
+            text.AddText(titleBind.Value + @"  ", sprite =>
+            {
+                sprite.TextSize = 16;
+                sprite.Font = @"Exo2.0-Regular";
+                t.Add(sprite);
+            });
+
+            titleSprites = t;
+
+            text.AddText(artistBind.Value, sprite =>
+            {
+                sprite.TextSize = 14;
+                sprite.Font = @"Exo2.0-Bold";
+                sprite.Colour = artistColour;
+                sprite.Padding = new MarginPadding { Top = 1 };
+            });
         }
 
         protected override bool OnHover(Framework.Input.InputState state)

From 615082d75734e3e96143b4177cbd95b9680d6a7b Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 01:17:04 -0300
Subject: [PATCH 22/44] Remove whitespace

---
 osu.Game/Overlays/Music/PlaylistItem.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index bde043d0d4..269b160672 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Overlays.Music
         {
             hoverColour = colours.Yellow;
             artistColour = colours.Gray9;
-            
+
             var metadata = BeatmapSetInfo.Metadata;
             FilterTerms = metadata.SearchableTerms;
 

From 48417beb81dce79200a375698e13aa067695793f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 01:22:33 -0300
Subject: [PATCH 23/44] Unused using directive

---
 osu.Game/Overlays/Music/PlaylistItem.cs | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 269b160672..9f3ce6a3d4 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -7,13 +7,10 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Database;
 using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Localisation;
 using osu.Framework.Graphics.Sprites;
 using System.Collections.Generic;
-using System.Linq;
 
 namespace osu.Game.Overlays.Music
 {
@@ -100,7 +97,6 @@ namespace osu.Game.Overlays.Music
         {
             text.Clear();
 
-            var metadata = BeatmapSetInfo.Metadata;
             var t = new List<SpriteText>();
 
             //space after the title to put a space between the title and artist

From 43bd0f686b083b25da7f90f9f0eea3863f4764e9 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 01:25:42 -0300
Subject: [PATCH 24/44] Fix artist being the opposite value

---
 osu.Game/Overlays/Music/PlaylistItem.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 9f3ce6a3d4..0f8dc621df 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -90,6 +90,7 @@ namespace osu.Game.Overlays.Music
             artistBind = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist);
 
             titleBind.ValueChanged += newText => recreateText();
+            artistBind.ValueChanged += newText => recreateText();
             titleBind.TriggerChange();
         }
 

From 139b03a1ceabab51dc3651eea3c7e90ce2c28c5e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 02:05:13 -0300
Subject: [PATCH 25/44] Only recreate text once per unicode option change

---
 osu.Game/Overlays/Music/PlaylistItem.cs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 0f8dc621df..3a3be53569 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -89,9 +89,8 @@ namespace osu.Game.Overlays.Music
             titleBind = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title);
             artistBind = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist);
 
-            titleBind.ValueChanged += newText => recreateText();
             artistBind.ValueChanged += newText => recreateText();
-            titleBind.TriggerChange();
+            artistBind.TriggerChange();
         }
 
         private void recreateText()

From 7485a78128a7b25bef1f75f3d9c6f549ae1f90fc Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 03:14:22 -0300
Subject: [PATCH 26/44] Update framework

---
 osu-framework                           | 2 +-
 osu.Game/Overlays/Music/PlaylistItem.cs | 9 ++-------
 2 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/osu-framework b/osu-framework
index 4c76571784..d00a7df902 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 4c765717846fef9a141800782a5a968c2bc3a278
+Subproject commit d00a7df902074d0b3f1479904b7f322db9d39c1f
diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 3a3be53569..9b72cfce42 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -97,18 +97,13 @@ namespace osu.Game.Overlays.Music
         {
             text.Clear();
 
-            var t = new List<SpriteText>();
-
-            //space after the title to put a space between the title and artist
-            text.AddText(titleBind.Value + @"  ", sprite =>
+            //space after the title to put a space between the title and artist
+            titleSprites = text.AddText(titleBind.Value + @"  ", sprite =>
             {
                 sprite.TextSize = 16;
                 sprite.Font = @"Exo2.0-Regular";
-                t.Add(sprite);
             });
 
-            titleSprites = t;
-
             text.AddText(artistBind.Value, sprite =>
             {
                 sprite.TextSize = 14;

From 995a573b9c8d0828170738736283b7323866afe1 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 20 May 2017 03:22:37 -0300
Subject: [PATCH 27/44] @"" -> string.Empty

---
 osu.Game/Overlays/Mods/ModButton.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 9bbd793934..831b9082bd 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Mods
 
         public Action<Mod> Action; // Passed the selected mod or null if none
 
-        public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? @"";
+        public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty;
 
         private int _selectedIndex = -1;
         private int selectedIndex

From 57b9ed0f547fdcccd23aa66b5fdbe2f1e0aebd47 Mon Sep 17 00:00:00 2001
From: MrTheMake <marc.stephan96@hotmail.de>
Date: Sat, 20 May 2017 18:56:50 +0200
Subject: [PATCH 28/44] Do not pause a running replay if the window is not
 focused.

---
 osu.Game/Screens/Play/Player.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index f9307595d2..3efc85d743 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -150,7 +150,7 @@ namespace osu.Game.Screens.Play
                     FramedClock = offsetClock,
                     OnRetry = Restart,
                     OnQuit = Exit,
-                    CheckCanPause = () => ValidForResume && !HasFailed,
+                    CheckCanPause = () => ValidForResume && !HasFailed && !HitRenderer.HasReplayLoaded,
                     Retries = RestartCount,
                     OnPause = () => {
                         hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;

From 18a7271dc6ad3d5a487fb8d0e4436221c6508c3c Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Sun, 21 May 2017 17:07:16 +0900
Subject: [PATCH 29/44] Update framework

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index d00a7df902..60e0a343d2 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit d00a7df902074d0b3f1479904b7f322db9d39c1f
+Subproject commit 60e0a343d2bf590f736782e2bb2a01c132e6cac0

From 95498fe6df783eb5fd15fb55fd1d14a7e6f088e8 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Sun, 21 May 2017 17:21:49 +0900
Subject: [PATCH 30/44] Adjust spinner colours making use of EdgeEffect.Hollow

---
 osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs   | 2 +-
 .../Objects/Drawables/Pieces/SpinnerBackground.cs            | 5 +++--
 .../Objects/Drawables/Pieces/SpinnerDisc.cs                  | 2 +-
 .../Objects/Drawables/Pieces/SpinnerTicks.cs                 | 4 ++--
 4 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 854a9b5f49..cc5086f442 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
 
             background.AccentColour = normalColour;
 
-            completeColour = colours.YellowLight.Opacity(0.6f);
+            completeColour = colours.YellowLight.Opacity(0.75f);
 
             disc.AccentColour = colours.SpinnerFill;
             circle.Colour = colours.BlueDark;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
index 1c54f9f893..aa8dea50a4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
@@ -28,9 +28,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
 
                 EdgeEffect = new EdgeEffect
                 {
+                    Hollow = true,
                     Type = EdgeEffectType.Glow,
-                    Radius = 14,
-                    Colour = value.Opacity(0.3f),
+                    Radius = 40,
+                    Colour = value,
                 };
             }
         }
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index 4e4d4e30b9..29d6d1f147 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
             if (Complete && updateCompleteTick())
             {
                 background.Flush(flushType: typeof(TransformAlpha));
-                background.FadeTo(tracking_alpha + 0.4f, 60, EasingTypes.OutExpo);
+                background.FadeTo(tracking_alpha + 0.2f, 60, EasingTypes.OutExpo);
                 background.Delay(60);
                 background.FadeTo(tracking_alpha, 250, EasingTypes.OutQuint);
             }
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
index dc3d18d40a..c0e16288be 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            glowColour = colours.BlueDarker.Opacity(0.4f);
+            glowColour = Color4.Gray.Opacity(0.2f);
             layout();
         }
 
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
                     EdgeEffect = new EdgeEffect
                     {
                         Type = EdgeEffectType.Glow,
-                        Radius = 20,
+                        Radius = 10,
                         Colour = glowColour,
                     },
                     RelativePositionAxes = Axes.Both,

From 8f3ab79918585b5322fc351a6305cb41f94fff3f Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Sun, 21 May 2017 17:29:00 +0900
Subject: [PATCH 31/44] Simplify SpinnerTicks & CI fixes

---
 .../Drawables/Pieces/SpinnerBackground.cs        |  1 -
 .../Objects/Drawables/Pieces/SpinnerTicks.cs     | 16 +---------------
 2 files changed, 1 insertion(+), 16 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
index aa8dea50a4..66cf7758b9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
index c0e16288be..4dbb6bd4d6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs
@@ -2,12 +2,10 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
-using osu.Framework.Allocation;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
-using osu.Game.Graphics;
 using OpenTK;
 using OpenTK.Graphics;
 
@@ -15,24 +13,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
 {
     public class SpinnerTicks : Container
     {
-        private Color4 glowColour;
-
         public SpinnerTicks()
         {
             Origin = Anchor.Centre;
             Anchor = Anchor.Centre;
             RelativeSizeAxes = Axes.Both;
-        }
 
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colours)
-        {
-            glowColour = Color4.Gray.Opacity(0.2f);
-            layout();
-        }
-
-        private void layout()
-        {
             const int count = 18;
 
             for (int i = 0; i < count; i++)
@@ -45,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
                     {
                         Type = EdgeEffectType.Glow,
                         Radius = 10,
-                        Colour = glowColour,
+                        Colour = Color4.Gray.Opacity(0.2f),
                     },
                     RelativePositionAxes = Axes.Both,
                     Masking = true,

From 5b244367ee321bd454a4704e4a64209df6aaea4c Mon Sep 17 00:00:00 2001
From: Jorolf <jorolf@gmx.de>
Date: Sun, 21 May 2017 20:29:05 +0200
Subject: [PATCH 32/44] allow for solutions were the osu repository isn't at
 the solution level

---
 osu.Game/osu.Game.csproj | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ee906caa9b..acc6fd4abe 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -430,11 +430,11 @@
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj">
+    <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
       <Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
       <Name>osu.Framework</Name>
     </ProjectReference>
-    <ProjectReference Include="$(SolutionDir)\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
+    <ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
       <Project>{d9a367c9-4c1a-489f-9b05-a0cea2b53b58}</Project>
       <Name>osu.Game.Resources</Name>
     </ProjectReference>

From ecf81fa8d2bc802631212b24c44907e85244798f Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 08:25:48 +0900
Subject: [PATCH 33/44] Cleanup.

---
 .../Patterns/Legacy/DistanceObjectPatternGenerator.cs        | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 9ea4353317..4b063be222 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -47,9 +47,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime);
             double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment;
 
-            // The true distance, accounting for any repeats. This ends up being the drum roll distance later
+            // The true distance, accounting for any repeats
             double distance = (distanceData?.Distance ?? 0) * repeatCount;
-
             // The velocity of the osu! hit object - calculated as the velocity of a slider
             double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength;
             // The duration of the osu! hit object
@@ -82,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 if (duration >= 4000)
                     return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
 
-                if (segmentDuration > 400 && duration < 4000 && repeatCount < AvailableColumns - 1 - RandomStart)
+                if (segmentDuration > 400 && repeatCount < AvailableColumns - 1 - RandomStart)
                     return generateTiledHoldNotes(HitObject.StartTime);
 
                 return generateHoldAndNormalNotes(HitObject.StartTime);

From e58ffbd87dc20424268d5e201e4d88a82183b43f Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 09:46:34 +0900
Subject: [PATCH 34/44] Remove Note Siblings.

---
 osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index f6eb4aea2c..93aaa94f45 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -9,11 +9,5 @@ namespace osu.Game.Rulesets.Mania.Objects
     public abstract class ManiaHitObject : HitObject, IHasColumn
     {
         public int Column { get; set; }
-
-        /// <summary>
-        /// The number of other <see cref="ManiaHitObject"/> that start at
-        /// the same time as this hit object.
-        /// </summary>
-        public int Siblings { get; set; }
     }
 }

From 3041b55aacd53b8ae4fc227f2d60f2373b856694 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 09:50:36 +0900
Subject: [PATCH 35/44] Remove siblings.

---
 .../Legacy/DistanceObjectPatternGenerator.cs  | 21 ++++++++-----------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 4b063be222..ece278e64b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             {
                 while (pattern.IsFilled(nextColumn) || PreviousPattern.IsFilled(nextColumn))  //find available column
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
-                addToPattern(pattern, nextColumn, startTime, endTime, noteCount);
+                addToPattern(pattern, nextColumn, startTime, endTime);
             }
 
             // This is can't be combined with the above loop due to RNG
@@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             {
                 while (pattern.IsFilled(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
-                addToPattern(pattern, nextColumn, startTime, endTime, noteCount);
+                addToPattern(pattern, nextColumn, startTime, endTime);
             }
 
             return pattern;
@@ -267,7 +267,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
             for (int i = 0; i <= repeatCount; i++)
             {
-                addToPattern(pattern, nextColumn, startTime, startTime, 2);
+                addToPattern(pattern, nextColumn, startTime, startTime);
 
                 nextColumn += interval;
                 if (nextColumn >= AvailableColumns - RandomStart)
@@ -276,7 +276,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
                 // If we're in 2K, let's not add many consecutive doubles
                 if (AvailableColumns > 2)
-                    addToPattern(pattern, nextColumn, startTime, startTime, 2);
+                    addToPattern(pattern, nextColumn, startTime, startTime);
 
                 nextColumn = Random.Next(RandomStart, AvailableColumns);
                 startTime += segmentDuration;
@@ -367,7 +367,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 while (pattern.IsFilled(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
-                addToPattern(pattern, nextColumn, startTime, endTime, repeatCount);
+                addToPattern(pattern, nextColumn, startTime, endTime);
                 startTime += segmentDuration;
             }
 
@@ -420,7 +420,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     {
                         while (rowPattern.IsFilled(nextColumn) || nextColumn == holdColumn)
                             nextColumn = Random.Next(RandomStart, AvailableColumns);
-                        addToPattern(rowPattern, nextColumn, startTime, startTime, noteCount + 1);
+                        addToPattern(rowPattern, nextColumn, startTime, startTime);
                     }
                 }
 
@@ -459,8 +459,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// <param name="column">The column to add the note to.</param>
         /// <param name="startTime">The start time of the note.</param>
         /// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
-        /// <param name="siblings">The number of children alongside this note (these will not be generated, but are used for volume calculations).</param>
-        private void addToPattern(Pattern pattern, int column, double startTime, double endTime, int siblings = 1)
+        private void addToPattern(Pattern pattern, int column, double startTime, double endTime)
         {
             ManiaHitObject newObject;
 
@@ -470,8 +469,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 {
                     StartTime = startTime,
                     Samples = sampleInfoListAt(startTime),
-                    Column = column,
-                    Siblings = siblings
+                    Column = column
                 };
             }
             else
@@ -482,8 +480,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     Samples = sampleInfoListAt(startTime),
                     EndSamples = sampleInfoListAt(endTime),
                     Column = column,
-                    Duration = endTime - startTime,
-                    Siblings = siblings
+                    Duration = endTime - startTime
                 };
             }
 

From 82cf94bbff70f42a10d35d06c1b7ef07aad7a89d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 22 May 2017 09:50:45 +0900
Subject: [PATCH 36/44] Move spinner colours to local definition for now

We don't want to start polluting the OsuColours namespace with non-UI colours.
---
 osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 7 +++++--
 osu.Game/Graphics/OsuColour.cs                             | 3 ---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index cc5086f442..3722d13ffc 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
 
         private readonly TextAwesome symbol;
 
+        private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c");
+        private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c");
+
         private Color4 normalColour;
         private Color4 completeColour;
 
@@ -154,13 +157,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            normalColour = colours.SpinnerBase;
+            normalColour = baseColour;
 
             background.AccentColour = normalColour;
 
             completeColour = colours.YellowLight.Opacity(0.75f);
 
-            disc.AccentColour = colours.SpinnerFill;
+            disc.AccentColour = fillColour;
             circle.Colour = colours.BlueDark;
             glow.Colour = colours.BlueDark;
         }
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index 697f8f4629..3d83668d07 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -87,8 +87,5 @@ namespace osu.Game.Graphics
         public readonly Color4 RedDarker = FromHex(@"870000");
 
         public readonly Color4 ChatBlue = FromHex(@"17292e");
-
-        public readonly Color4 SpinnerBase = FromHex(@"002c3c");
-        public readonly Color4 SpinnerFill = FromHex(@"005b7c");
     }
 }

From 9f2ca1acbf87c53aff1088c81d196395e5b23440 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 09:57:03 +0900
Subject: [PATCH 37/44] Remove siblings.

---
 .../Patterns/Legacy/EndTimeObjectPatternGenerator.cs        | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index cd04418ac5..e84802617c 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -74,8 +74,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                     StartTime = HitObject.StartTime,
                     EndSamples = HitObject.Samples,
                     Column = column,
-                    Duration = endTime - HitObject.StartTime,
-                    Siblings = 1
+                    Duration = endTime - HitObject.StartTime
                 };
 
                 newObject.Samples.Add(new SampleInfo
@@ -89,8 +88,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 {
                     StartTime = HitObject.StartTime,
                     Samples = HitObject.Samples,
-                    Column = column,
-                    Siblings = 1
+                    Column = column
                 };
             }
 

From 9de18d45a5bd4e464ce644717a1c89b0622f5839 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 10:00:11 +0900
Subject: [PATCH 38/44] Use bitshifted notation.

---
 .../Beatmaps/Patterns/Legacy/PatternType.cs   | 26 +++++++++----------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
index d4957d41a9..d645882511 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
@@ -15,51 +15,51 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// <summary>
         /// Keep the same as last row.
         /// </summary>
-        ForceStack = 1,
+        ForceStack = 1 << 0,
         /// <summary>
         /// Keep different from last row.
         /// </summary>
-        ForceNotStack = 2,
+        ForceNotStack = 1 << 1,
         /// <summary>
         /// Keep as single note at its original position.
         /// </summary>
-        KeepSingle = 4,
+        KeepSingle = 1 << 2,
         /// <summary>
         /// Use a lower random value.
         /// </summary>
-        LowProbability = 8,
+        LowProbability = 1 << 3,
         /// <summary>
         /// Reserved.
         /// </summary>
-        Alternate = 16,
+        Alternate = 1 << 4,
         /// <summary>
         /// Ignore the repeat count.
         /// </summary>
-        ForceSigSlider = 32,
+        ForceSigSlider = 1 << 5,
         /// <summary>
         /// Convert slider to circle.
         /// </summary>
-        ForceNotSlider = 64,
+        ForceNotSlider = 1 << 6,
         /// <summary>
         /// Notes gathered together.
         /// </summary>
-        Gathered = 128,
-        Mirror = 256,
+        Gathered = 1 << 7,
+        Mirror = 1 << 8,
         /// <summary>
         /// Change 0 -> 6.
         /// </summary>
-        Reverse = 512,
+        Reverse = 1 << 9,
         /// <summary>
         /// 1 -> 5 -> 1 -> 5 like reverse.
         /// </summary>
-        Cycle = 1024,
+        Cycle = 1 << 10,
         /// <summary>
         /// Next note will be at column + 1.
         /// </summary>
-        Stair = 2048,
+        Stair = 1 << 11,
         /// <summary>
         /// Next note will be at column - 1.
         /// </summary>
-        ReverseStair = 4096
+        ReverseStair = 1 << 12
     }
 }

From 8c260e3364be8034d8babfb33185b0fe35d3551f Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 10:04:25 +0900
Subject: [PATCH 39/44] Renamings + refactorings to Pattern.

---
 .../Beatmaps/Patterns/Pattern.cs                    | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs
index cbde1f0f53..331b92f8a4 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs
@@ -21,16 +21,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
         public IEnumerable<ManiaHitObject> HitObjects => hitObjects;
 
         /// <summary>
-        /// Whether this pattern already contains a hit object in a code.
+        /// Check whether a column of this patterns contains a hit object.
         /// </summary>
         /// <param name="column">The column index.</param>
-        /// <returns>Whether this pattern already contains a hit object in <paramref name="column"/></returns>
-        public bool IsFilled(int column) => hitObjects.Exists(h => h.Column == column);
+        /// <returns>Whether the column with index <paramref name="column"/> contains a hit object.</returns>
+        public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column);
 
         /// <summary>
         /// Amount of columns taken up by hit objects in this pattern.
         /// </summary>
-        public int ColumnsFilled => HitObjects.GroupBy(h => h.Column).Count();
+        public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count();
 
         /// <summary>
         /// Adds a hit object to this pattern.
@@ -42,10 +42,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
         /// Copies hit object from another pattern to this one.
         /// </summary>
         /// <param name="other">The other pattern.</param>
-        public void Add(Pattern other)
-        {
-            other.HitObjects.ForEach(Add);
-        }
+        public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects);
 
         /// <summary>
         /// Clears this pattern, removing all hit objects.

From 0a0139adedb34bbc3ad1e0f4015f06f65fe3f8e0 Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 10:12:33 +0900
Subject: [PATCH 40/44] Remove the concept of beatmap events, rename BreakEvent
 -> BreakPeriod.

---
 .../Patterns/Legacy/PatternGenerator.cs       |  2 +-
 osu.Game/Beatmaps/Beatmap.cs                  | 11 +++++--
 osu.Game/Beatmaps/Events/BackgroundEvent.cs   | 13 --------
 osu.Game/Beatmaps/Events/Event.cs             | 13 --------
 osu.Game/Beatmaps/Events/EventInfo.cs         | 33 -------------------
 osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 11 ++-----
 .../BreakEvent.cs => Timing/BreakPeriod.cs}   | 13 +++++---
 osu.Game/osu.Game.csproj                      |  5 +--
 8 files changed, 21 insertions(+), 80 deletions(-)
 delete mode 100644 osu.Game/Beatmaps/Events/BackgroundEvent.cs
 delete mode 100644 osu.Game/Beatmaps/Events/Event.cs
 delete mode 100644 osu.Game/Beatmaps/Events/EventInfo.cs
 rename osu.Game/Beatmaps/{Events/BreakEvent.cs => Timing/BreakPeriod.cs} (70%)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index ad07c03b96..e6e3f1d07f 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 HitObject firstObject = Beatmap.HitObjects.FirstOrDefault();
 
                 double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
-                drainTime -= Beatmap.EventInfo.TotalBreakTime;
+                drainTime -= Beatmap.TotalBreakTime;
 
                 if (drainTime == 0)
                     drainTime = 10000;
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index a64002e0b0..608b2fcd19 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -2,11 +2,11 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using OpenTK.Graphics;
-using osu.Game.Beatmaps.Events;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Database;
 using osu.Game.Rulesets.Objects;
 using System.Collections.Generic;
+using System.Linq;
 
 namespace osu.Game.Beatmaps
 {
@@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps
     {
         public BeatmapInfo BeatmapInfo;
         public TimingInfo TimingInfo = new TimingInfo();
-        public EventInfo EventInfo = new EventInfo();
+        public List<BreakPeriod> Breaks = new List<BreakPeriod>();
         public readonly List<Color4> ComboColors = new List<Color4>
         {
             new Color4(17, 136, 170, 255),
@@ -34,6 +34,11 @@ namespace osu.Game.Beatmaps
         /// </summary>
         public List<T> HitObjects;
 
+        /// <summary>
+        /// Total amount of break time in the beatmap.
+        /// </summary>
+        public double TotalBreakTime => Breaks.Sum(b => b.Duration);
+
         /// <summary>
         /// Constructs a new beatmap.
         /// </summary>
@@ -42,7 +47,7 @@ namespace osu.Game.Beatmaps
         {
             BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
             TimingInfo = original?.TimingInfo ?? TimingInfo;
-            EventInfo = original?.EventInfo ?? EventInfo;
+            Breaks = original?.Breaks ?? Breaks;
             ComboColors = original?.ComboColors ?? ComboColors;
         }
     }
diff --git a/osu.Game/Beatmaps/Events/BackgroundEvent.cs b/osu.Game/Beatmaps/Events/BackgroundEvent.cs
deleted file mode 100644
index 215373bd3b..0000000000
--- a/osu.Game/Beatmaps/Events/BackgroundEvent.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-namespace osu.Game.Beatmaps.Events
-{
-    public class BackgroundEvent : Event
-    {
-        /// <summary>
-        /// The file name.
-        /// </summary>
-        public string Filename;
-    }
-}
diff --git a/osu.Game/Beatmaps/Events/Event.cs b/osu.Game/Beatmaps/Events/Event.cs
deleted file mode 100644
index 3af3909462..0000000000
--- a/osu.Game/Beatmaps/Events/Event.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-namespace osu.Game.Beatmaps.Events
-{
-    public abstract class Event
-    {
-        /// <summary>
-        /// The event start time.
-        /// </summary>
-        public double StartTime;
-    }
-}
diff --git a/osu.Game/Beatmaps/Events/EventInfo.cs b/osu.Game/Beatmaps/Events/EventInfo.cs
deleted file mode 100644
index 3ba3d5ba03..0000000000
--- a/osu.Game/Beatmaps/Events/EventInfo.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System.Collections.Generic;
-using System.Linq;
-
-namespace osu.Game.Beatmaps.Events
-{
-    public class EventInfo
-    {
-        /// <summary>
-        /// All the background events.
-        /// </summary>
-        public readonly List<BackgroundEvent> Backgrounds = new List<BackgroundEvent>();
-
-        /// <summary>
-        /// All the break events.
-        /// </summary>
-        public readonly List<BreakEvent> Breaks = new List<BreakEvent>();
-
-        /// <summary>
-        /// Total duration of all breaks.
-        /// </summary>
-        public double TotalBreakTime => Breaks.Sum(b => b.Duration);
-
-        /// <summary>
-        /// Retrieves the active background at a time.
-        /// </summary>
-        /// <param name="time">The time to retrieve the background at.</param>
-        /// <returns>The background.</returns>
-        public BackgroundEvent BackgroundAt(double time) => Backgrounds.FirstOrDefault(b => b.StartTime <= time);
-    }
-}
diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
index 772c0e9c07..04208337c7 100644
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
@@ -5,7 +5,6 @@ using System;
 using System.Globalization;
 using System.IO;
 using OpenTK.Graphics;
-using osu.Game.Beatmaps.Events;
 using osu.Game.Beatmaps.Timing;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Objects.Legacy;
@@ -217,18 +216,12 @@ namespace osu.Game.Beatmaps.Formats
                 case EventType.Background:
                     string filename = split[2].Trim('"');
 
-                    beatmap.EventInfo.Backgrounds.Add(new BackgroundEvent
-                    {
-                        StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
-                        Filename = filename
-                    });
-
                     if (type == EventType.Background)
                         beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
 
                     break;
                 case EventType.Break:
-                    var breakEvent = new BreakEvent
+                    var breakEvent = new BreakPeriod
                     {
                         StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
                         EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
@@ -237,7 +230,7 @@ namespace osu.Game.Beatmaps.Formats
                     if (!breakEvent.HasEffect)
                         return;
 
-                    beatmap.EventInfo.Breaks.Add(breakEvent);
+                    beatmap.Breaks.Add(breakEvent);
                     break;
             }
         }
diff --git a/osu.Game/Beatmaps/Events/BreakEvent.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs
similarity index 70%
rename from osu.Game/Beatmaps/Events/BreakEvent.cs
rename to osu.Game/Beatmaps/Timing/BreakPeriod.cs
index 78e33f2fbb..fb307b7144 100644
--- a/osu.Game/Beatmaps/Events/BreakEvent.cs
+++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs
@@ -1,27 +1,32 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-namespace osu.Game.Beatmaps.Events
+namespace osu.Game.Beatmaps.Timing
 {
-    public class BreakEvent : Event
+    public class BreakPeriod
     {
         /// <summary>
         /// The minimum duration required for a break to have any effect.
         /// </summary>
         private const double min_break_duration = 650;
 
+        /// <summary>
+        /// The break start time.
+        /// </summary>
+        public double StartTime;
+
         /// <summary>
         /// The break end time.
         /// </summary>
         public double EndTime;
 
         /// <summary>
-        /// The duration of the break.
+        /// The break duration.
         /// </summary>
         public double Duration => EndTime - StartTime;
 
         /// <summary>
-        /// Whether the break has any effect. Breaks that are too short are culled before they reach the EventInfo.
+        /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap.
         /// </summary>
         public bool HasEffect => Duration >= min_break_duration;
     }
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index acc6fd4abe..1631311ef6 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -74,10 +74,6 @@
     <Compile Include="Audio\SampleInfoList.cs" />
     <Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
     <Compile Include="Beatmaps\DifficultyCalculator.cs" />
-    <Compile Include="Beatmaps\Events\BackgroundEvent.cs" />
-    <Compile Include="Beatmaps\Events\BreakEvent.cs" />
-    <Compile Include="Beatmaps\Events\Event.cs" />
-    <Compile Include="Beatmaps\Events\EventInfo.cs" />
     <Compile Include="Online\API\Requests\PostMessageRequest.cs" />
     <Compile Include="Online\Chat\ErrorMessage.cs" />
     <Compile Include="Overlays\Chat\ChatTabControl.cs" />
@@ -91,6 +87,7 @@
     <Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
     <Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
     <Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" />
+    <Compile Include="Beatmaps\Timing\BreakPeriod.cs" />
     <Compile Include="Beatmaps\Timing\TimeSignatures.cs" />
     <Compile Include="Beatmaps\Timing\TimingInfo.cs" />
     <Compile Include="Database\BeatmapMetrics.cs" />

From cd66e2af140763698f50af017052564c4d22bece Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 10:17:08 +0900
Subject: [PATCH 41/44] Update with pattern changes.

---
 .../Legacy/DistanceObjectPatternGenerator.cs  | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index ece278e64b..0cad23304e 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
             if (segmentDuration <= 110)
             {
-                if (PreviousPattern.ColumnsFilled < AvailableColumns)
+                if (PreviousPattern.ColumnWithObjects < AvailableColumns)
                     convertType |= PatternType.ForceNotStack;
                 else
                     convertType &= ~PatternType.ForceNotStack;
@@ -137,11 +137,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
             var pattern = new Pattern();
 
-            int usableColumns = AvailableColumns - RandomStart - PreviousPattern.ColumnsFilled;
+            int usableColumns = AvailableColumns - RandomStart - PreviousPattern.ColumnWithObjects;
             int nextColumn = Random.Next(RandomStart, AvailableColumns);
             for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
             {
-                while (pattern.IsFilled(nextColumn) || PreviousPattern.IsFilled(nextColumn))  //find available column
+                while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn))  //find available column
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
                 addToPattern(pattern, nextColumn, startTime, endTime);
             }
@@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             // This is can't be combined with the above loop due to RNG
             for (int i = 0; i < noteCount - usableColumns; i++)
             {
-                while (pattern.IsFilled(nextColumn))
+                while (pattern.ColumnHasObject(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
                 addToPattern(pattern, nextColumn, startTime, endTime);
             }
@@ -174,9 +174,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             var pattern = new Pattern();
 
             int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
-            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnsFilled < AvailableColumns)
+            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
             {
-                while (PreviousPattern.IsFilled(nextColumn))
+                while (PreviousPattern.ColumnHasObject(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
             }
 
@@ -356,15 +356,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             int columnRepeat = Math.Min(repeatCount, AvailableColumns);
 
             int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
-            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnsFilled < AvailableColumns)
+            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
             {
-                while (PreviousPattern.IsFilled(nextColumn))
+                while (PreviousPattern.ColumnHasObject(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
             }
 
             for (int i = 0; i < columnRepeat; i++)
             {
-                while (pattern.IsFilled(nextColumn))
+                while (pattern.ColumnHasObject(nextColumn))
                     nextColumn = Random.Next(RandomStart, AvailableColumns);
 
                 addToPattern(pattern, nextColumn, startTime, endTime);
@@ -390,9 +390,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             var pattern = new Pattern();
 
             int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
-            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnsFilled < AvailableColumns)
+            if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
             {
-                while (PreviousPattern.IsFilled(holdColumn))
+                while (PreviousPattern.ColumnHasObject(holdColumn))
                     holdColumn = Random.Next(RandomStart, AvailableColumns);
             }
 
@@ -418,7 +418,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 {
                     for (int j = 0; j < noteCount; j++)
                     {
-                        while (rowPattern.IsFilled(nextColumn) || nextColumn == holdColumn)
+                        while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn)
                             nextColumn = Random.Next(RandomStart, AvailableColumns);
                         addToPattern(rowPattern, nextColumn, startTime, startTime);
                     }

From 4ce3a7806690b7672080253bc13caaf7e9fe3813 Mon Sep 17 00:00:00 2001
From: Dan Balasescu <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 10:18:42 +0900
Subject: [PATCH 42/44] Update Pattern.cs

---
 osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs
index 331b92f8a4..15d31406e9 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs
@@ -3,7 +3,6 @@
 
 using System.Collections.Generic;
 using System.Linq;
-using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Game.Rulesets.Mania.Objects;
 
 namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns

From 2d11a8bdf0588a9fece729f38c8b20b59eb5159f Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 10:20:42 +0900
Subject: [PATCH 43/44] Update with pattern changes.

---
 .../Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index e84802617c..1956361f5c 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         {
             int nextColumn = Random.Next(start, AvailableColumns);
 
-            while (PreviousPattern.IsFilled(nextColumn))
+            while (PreviousPattern.ColumnHasObject(nextColumn))
                 nextColumn = Random.Next(start, AvailableColumns);
 
             return nextColumn;

From 6714a244e83f6a43c462dc47ad7f1373b16323af Mon Sep 17 00:00:00 2001
From: smoogipooo <smoogipooo@gmail.com>
Date: Mon, 22 May 2017 10:23:08 +0900
Subject: [PATCH 44/44] Add check before possibly going into endless loop.

---
 .../Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 1956361f5c..8f438f9ff4 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 else
                     addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
             }
-            else
+            else if (AvailableColumns > 0)
                 addToPattern(pattern, getNextRandomColumn(0), generateHold);
 
             return pattern;