diff --git a/osu-framework b/osu-framework
index 3c76bce457..870ae62a35 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 3c76bce45762765b7268f8036da681a788f756b9
+Subproject commit 870ae62a35fc0296009bf6d711922da5b9b5612f
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseBeatSyncedContainer.cs b/osu.Desktop.VisualTests/Tests/TestCaseBeatSyncedContainer.cs
index 067bf47521..01d7d4ff01 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseBeatSyncedContainer.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseBeatSyncedContainer.cs
@@ -58,6 +58,8 @@ namespace osu.Desktop.VisualTests.Tests
             private readonly InfoString currentBeat;
             private readonly InfoString beatsPerMinute;
             private readonly InfoString adjustedBeatLength;
+            private readonly InfoString timeUntilNextBeat;
+            private readonly InfoString timeSinceLastBeat;
 
             private readonly Box flashLayer;
 
@@ -95,6 +97,8 @@ namespace osu.Desktop.VisualTests.Tests
                                     currentBeat = new InfoString(@"Current beat"),
                                     beatsPerMinute = new InfoString(@"BPM"),
                                     adjustedBeatLength = new InfoString(@"Adjusted beat length"),
+                                    timeUntilNextBeat = new InfoString(@"Time until next beat"),
+                                    timeSinceLastBeat = new InfoString(@"Time since last beat"),
                                 }
                             }
                         }
@@ -131,6 +135,8 @@ namespace osu.Desktop.VisualTests.Tests
                     currentBeat.Value = 0;
                     beatsPerMinute.Value = 0;
                     adjustedBeatLength.Value = 0;
+                    timeUntilNextBeat.Value = 0;
+                    timeSinceLastBeat.Value = 0;
                 };
             }
 
@@ -151,6 +157,13 @@ namespace osu.Desktop.VisualTests.Tests
                 return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
             }
 
+            protected override void Update()
+            {
+                base.Update();
+                timeUntilNextBeat.Value = TimeUntilNextBeat;
+                timeSinceLastBeat.Value = TimeSinceLastBeat;
+            }
+
             protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
             {
                 base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseContextMenu.cs b/osu.Desktop.VisualTests/Tests/TestCaseContextMenu.cs
index 6c0da885ac..f9dc424153 100644
--- a/osu.Desktop.VisualTests/Tests/TestCaseContextMenu.cs
+++ b/osu.Desktop.VisualTests/Tests/TestCaseContextMenu.cs
@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Cursor;
 using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Transforms;
 using osu.Framework.Graphics.UserInterface;
 using osu.Framework.Testing;
 using osu.Game.Graphics.UserInterface;
@@ -21,10 +20,10 @@ namespace osu.Desktop.VisualTests.Tests
         private const int start_time = 0;
         private const int duration = 1000;
 
+        private readonly Container container;
+
         public TestCaseContextMenu()
         {
-            MyContextMenuContainer container;
-
             Add(container = new MyContextMenuContainer
             {
                 Size = new Vector2(200),
@@ -54,43 +53,26 @@ namespace osu.Desktop.VisualTests.Tests
                     }
                 }
             });
+        }
 
-            container.Transforms.Add(new TransformPosition
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            using (container.BeginLoopedSequence())
             {
-                StartValue = Vector2.Zero,
-                EndValue = new Vector2(0, 100),
-                StartTime = start_time,
-                EndTime = start_time + duration,
-                LoopCount = -1,
-                LoopDelay = duration * 3
-            });
-            container.Transforms.Add(new TransformPosition
-            {
-                StartValue = new Vector2(0, 100),
-                EndValue = new Vector2(100, 100),
-                StartTime = start_time + duration,
-                EndTime = start_time + duration * 2,
-                LoopCount = -1,
-                LoopDelay = duration * 3
-            });
-            container.Transforms.Add(new TransformPosition
-            {
-                StartValue = new Vector2(100, 100),
-                EndValue = new Vector2(100, 0),
-                StartTime = start_time + duration * 2,
-                EndTime = start_time + duration * 3,
-                LoopCount = -1,
-                LoopDelay = duration * 3
-            });
-            container.Transforms.Add(new TransformPosition
-            {
-                StartValue = new Vector2(100, 0),
-                EndValue = Vector2.Zero,
-                StartTime = start_time + duration * 3,
-                EndTime = start_time + duration * 4,
-                LoopCount = -1,
-                LoopDelay = duration * 3
-            });
+                container.MoveTo(new Vector2(0, 100), duration);
+                using (container.BeginDelayedSequence(duration))
+                {
+                    container.MoveTo(new Vector2(100, 100), duration);
+                    using (container.BeginDelayedSequence(duration))
+                    {
+                        container.MoveTo(new Vector2(100, 0), duration);
+                        using (container.BeginDelayedSequence(duration))
+                            container.MoveTo(Vector2.Zero, duration);
+                    }
+                }
+            }
         }
 
         private class MyContextMenuContainer : Container, IHasContextMenu
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index ae6585bdb2..3e7d9bd6d7 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -5,18 +5,17 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.Textures;
-using osu.Framework.Graphics.Transforms;
 using OpenTK;
 
 namespace osu.Game.Rulesets.Catch.Objects.Drawable
 {
     internal class DrawableFruit : Sprite
     {
-        private readonly CatchBaseHit h;
+        //private readonly CatchBaseHit h;
 
         public DrawableFruit(CatchBaseHit h)
         {
-            this.h = h;
+            //this.h = h;
 
             Origin = Anchor.Centre;
             Scale = new Vector2(0.1f);
@@ -29,10 +28,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
         {
             Texture = textures.Get(@"Menu/logo");
 
-            const double duration = 0;
-
-            Transforms.Add(new TransformPosition { StartTime = h.StartTime - 200, EndTime = h.StartTime, StartValue = new Vector2(h.Position, -0.1f), EndValue = new Vector2(h.Position, 0.9f) });
-            Transforms.Add(new TransformAlpha { StartTime = h.StartTime + duration + 200, EndTime = h.StartTime + duration + 400, StartValue = 1, EndValue = 0 });
+            //Transforms.Add(new TransformPosition { StartTime = h.StartTime - 200, EndTime = h.StartTime, StartValue = new Vector2(h.Position, -0.1f), EndValue = new Vector2(h.Position, 0.9f) });
+            //Transforms.Add(new TransformAlpha { StartTime = h.StartTime + duration + 200, EndTime = h.StartTime + duration + 400, StartValue = 1, EndValue = 0 });
             Expire(true);
         }
     }
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index a0c683143c..3feb448752 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.UI
 
         private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, EasingTypes easing = EasingTypes.None)
         {
-            TransformTo(() => visibleTimeRange.Value, newTimeRange, duration, easing, new TransformTimeSpan());
+            TransformTo(newTimeRange, duration, easing, new TransformTimeSpan());
         }
 
         protected override void Update()
@@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.UI
 
         private class TransformTimeSpan : Transform<double, Drawable>
         {
-            public override double CurrentValue
+            public double CurrentValue
             {
                 get
                 {
@@ -259,13 +259,8 @@ namespace osu.Game.Rulesets.Mania.UI
                 }
             }
 
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-
-                var p = (ManiaPlayfield)d;
-                p.visibleTimeRange.Value = (float)CurrentValue;
-            }
+            public override void Apply(Drawable d) => ((ManiaPlayfield)d).visibleTimeRange.Value = (float)CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = ((ManiaPlayfield)d).visibleTimeRange.Value;
         }
     }
 }
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 09bfffeefe..75b2dc0a32 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -89,11 +89,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
         {
             base.UpdateInitialState();
 
-            //sane defaults
-            ring.Alpha = circle.Alpha = number.Alpha = glow.Alpha = 1;
-            ApproachCircle.Alpha = 0;
-            ApproachCircle.Scale = new Vector2(4);
-            explode.Alpha = 0;
+            // sane defaults
+            ring.Show();
+            circle.Show();
+            number.Show();
+            glow.Show();
+
+            ApproachCircle.Hide();
+            ApproachCircle.ScaleTo(new Vector2(4));
+            explode.Hide();
         }
 
         protected override void UpdatePreemptState()
@@ -106,43 +110,45 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
 
         protected override void UpdateCurrentState(ArmedState state)
         {
-            ApproachCircle.FadeOut();
+            double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime;
 
-            double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
-            double duration = endTime - HitObject.StartTime;
-
-            glow.Delay(duration);
-            glow.FadeOut(400);
+            using (glow.BeginDelayedSequence(duration))
+                glow.FadeOut(400);
 
             switch (state)
             {
                 case ArmedState.Idle:
-                    Delay(duration + TIME_PREEMPT);
-                    FadeOut(TIME_FADEOUT);
+                    using (BeginDelayedSequence(duration + TIME_PREEMPT))
+                        FadeOut(TIME_FADEOUT);
                     Expire(true);
                     break;
                 case ArmedState.Miss:
+                    ApproachCircle.FadeOut(50);
                     FadeOut(TIME_FADEOUT / 5);
                     Expire();
                     break;
                 case ArmedState.Hit:
+                    ApproachCircle.FadeOut(50);
+
                     const double flash_in = 40;
 
                     flash.FadeTo(0.8f, flash_in);
-                    flash.Delay(flash_in);
-                    flash.FadeOut(100);
+                    using (flash.BeginDelayedSequence(flash_in))
+                        flash.FadeOut(100);
 
                     explode.FadeIn(flash_in);
 
-                    Delay(flash_in, true);
+                    using (BeginDelayedSequence(flash_in, true))
+                    {
+                        //after the flash, we can hide some elements that were behind it
+                        ring.FadeOut();
+                        circle.FadeOut();
+                        number.FadeOut();
 
-                    //after the flash, we can hide some elements that were behind it
-                    ring.FadeOut();
-                    circle.FadeOut();
-                    number.FadeOut();
+                        FadeOut(800);
+                        ScaleTo(Scale * 1.5f, 400, EasingTypes.OutQuad);
+                    }
 
-                    FadeOut(800);
-                    ScaleTo(Scale * 1.5f, 400, EasingTypes.OutQuad);
                     Expire();
                     break;
             }
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 57a9804330..2711ec1a62 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
             : base(hitObject)
         {
             AccentColour = HitObject.ComboColour;
+            Alpha = 0;
         }
 
         protected override OsuJudgement CreateJudgement() => new OsuJudgement { MaxScore = OsuScoreResult.Hit300 };
@@ -25,10 +26,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
         {
             Flush();
 
-            UpdateInitialState();
-
             using (BeginAbsoluteSequence(HitObject.StartTime - TIME_PREEMPT, true))
             {
+                UpdateInitialState();
+
                 UpdatePreemptState();
 
                 using (BeginDelayedSequence(TIME_PREEMPT + Judgement.TimeOffset, true))
@@ -36,8 +37,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
             }
         }
 
-        protected virtual void UpdateCurrentState(ArmedState state)
+        protected virtual void UpdateInitialState()
         {
+            Hide();
         }
 
         protected virtual void UpdatePreemptState()
@@ -45,9 +47,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
             FadeIn(TIME_FADEIN);
         }
 
-        protected virtual void UpdateInitialState()
+        protected virtual void UpdateCurrentState(ArmedState state)
         {
-            Alpha = 0;
         }
     }
 
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBouncer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBouncer.cs
index 65679dd7d3..a34ff30a43 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBouncer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBouncer.cs
@@ -37,8 +37,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
         protected override void LoadComplete()
         {
             base.LoadComplete();
-            icon.RotateTo(360, 1000);
-            icon.Loop();
+            using (icon.BeginLoopedSequence())
+                icon.RotateTo(360, 1000);
         }
 
         public void UpdateProgress(double progress, int repeat)
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 4a6b972f4a..910918297f 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Tests.Beatmaps.IO
             return osu;
         }
 
-        private void ensureLoaded(GameHost host, int timeout = 10000)
+        private void ensureLoaded(GameHost host, int timeout = 60000)
         {
             IEnumerable<BeatmapSetInfo> resultSets = null;
 
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index c0defceac0..5d8a5753b0 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -23,6 +23,16 @@ namespace osu.Game.Graphics.Containers
         /// </summary>
         protected double EarlyActivationMilliseconds;
 
+        /// <summary>
+        /// The time in milliseconds until the next beat.
+        /// </summary>
+        public double TimeUntilNextBeat { get; private set; }
+
+        /// <summary>
+        /// The time in milliseconds since the last beat
+        /// </summary>
+        public double TimeSinceLastBeat { get; private set; }
+
         protected override void Update()
         {
             if (Beatmap.Value?.Track == null)
@@ -42,12 +52,16 @@ namespace osu.Game.Graphics.Containers
             if (currentTrackTime < timingPoint.Time)
                 beatIndex--;
 
+            TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
+            if (TimeUntilNextBeat < 0)
+                TimeUntilNextBeat += timingPoint.BeatLength;
+
+            TimeSinceLastBeat = timingPoint.BeatLength - TimeUntilNextBeat;
+
             if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
                 return;
 
-            double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
-
-            using (BeginDelayedSequence(offsetFromBeat, true))
+            using (BeginDelayedSequence(-TimeSinceLastBeat, true))
                 OnNewBeat(beatIndex, timingPoint, effectPoint, Beatmap.Value.Track.CurrentAmplitudes);
 
             lastBeat = beatIndex;
diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs
index 672c59a935..9eb66d8fac 100644
--- a/osu.Game/Graphics/IHasAccentColour.cs
+++ b/osu.Game/Graphics/IHasAccentColour.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Graphics
         public static void FadeAccent<T>(this T accentedDrawable, Color4 newColour, double duration = 0, EasingTypes easing = EasingTypes.None)
             where T : Transformable<Drawable>, IHasAccentColour
         {
-            accentedDrawable.TransformTo(() => accentedDrawable.AccentColour, newColour, duration, easing, new TransformAccent());
+            accentedDrawable.TransformTo(newColour, duration, easing, new TransformAccent());
         }
     }
 }
diff --git a/osu.Game/Graphics/Transforms/TransformAccent.cs b/osu.Game/Graphics/Transforms/TransformAccent.cs
index d49f969c20..53a452ad8a 100644
--- a/osu.Game/Graphics/Transforms/TransformAccent.cs
+++ b/osu.Game/Graphics/Transforms/TransformAccent.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Graphics.Transforms
         /// <summary>
         /// Current value of the transformed colour in linear colour space.
         /// </summary>
-        public override Color4 CurrentValue
+        public virtual Color4 CurrentValue
         {
             get
             {
@@ -25,13 +25,7 @@ namespace osu.Game.Graphics.Transforms
             }
         }
 
-        public override void Apply(Drawable d)
-        {
-            base.Apply(d);
-
-            var accented = d as IHasAccentColour;
-            if (accented != null)
-                accented.AccentColour = CurrentValue;
-        }
+        public override void Apply(Drawable d) => ((IHasAccentColour)d).AccentColour = CurrentValue;
+        public override void ReadIntoStartValue(Drawable d) => StartValue = ((IHasAccentColour)d).AccentColour;
     }
 }
diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs
index 27a888f0b5..eed5061abd 100644
--- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs
+++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs
@@ -34,9 +34,8 @@ namespace osu.Game.Graphics.UserInterface
         {
             base.LoadComplete();
 
-            spinner.RotateTo(360, 2000);
-            using (spinner.BeginDelayedSequence(2000))
-                spinner.Loop();
+            using (spinner.BeginLoopedSequence())
+                spinner.RotateTo(360, 2000);
         }
 
         private const float transition_duration = 500;
diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs
index 79cdc9effe..b51dd2287b 100644
--- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs
+++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Graphics.UserInterface
 
         protected class TransformAccuracy : Transform<double, Drawable>
         {
-            public override double CurrentValue
+            public virtual double CurrentValue
             {
                 get
                 {
@@ -59,11 +59,8 @@ namespace osu.Game.Graphics.UserInterface
                 }
             }
 
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-                ((PercentageCounter)d).DisplayedCount = CurrentValue;
-            }
+            public override void Apply(Drawable d) => ((PercentageCounter)d).DisplayedCount = CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = ((PercentageCounter)d).DisplayedCount;
         }
     }
 }
diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs
index 4338dd23eb..db6e6ff44f 100644
--- a/osu.Game/Graphics/UserInterface/RollingCounter.cs
+++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs
@@ -210,7 +210,6 @@ namespace osu.Game.Graphics.UserInterface
 
             transform.StartTime = TransformStartTime;
             transform.EndTime = TransformStartTime + rollingTotalDuration;
-            transform.StartValue = currentValue;
             transform.EndValue = newValue;
             transform.Easing = RollingEasing;
 
diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs
index f98e84852a..6fe43e1fcc 100644
--- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs
+++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Graphics.UserInterface
 
         protected class TransformScore : Transform<double, Drawable>
         {
-            public override double CurrentValue
+            public virtual double CurrentValue
             {
                 get
                 {
@@ -70,11 +70,8 @@ namespace osu.Game.Graphics.UserInterface
                 }
             }
 
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-                ((ScoreCounter)d).DisplayedCount = CurrentValue;
-            }
+            public override void Apply(Drawable d) => ((ScoreCounter)d).DisplayedCount = CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = ((ScoreCounter)d).DisplayedCount;
         }
     }
 }
diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs
index bee1a71894..7664eeee40 100644
--- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs
+++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Graphics.UserInterface
 
         private class TransformCounterCount : Transform<int, Drawable>
         {
-            public override int CurrentValue
+            public int CurrentValue
             {
                 get
                 {
@@ -51,11 +51,8 @@ namespace osu.Game.Graphics.UserInterface
                 }
             }
 
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-                ((SimpleComboCounter)d).DisplayedCount = CurrentValue;
-            }
+            public override void Apply(Drawable d) => ((SimpleComboCounter)d).DisplayedCount = CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = ((SimpleComboCounter)d).DisplayedCount;
         }
     }
 }
\ No newline at end of file
diff --git a/osu.Game/Overlays/DragBar.cs b/osu.Game/Overlays/DragBar.cs
index 07e0c76396..89bb81c70b 100644
--- a/osu.Game/Overlays/DragBar.cs
+++ b/osu.Game/Overlays/DragBar.cs
@@ -75,7 +75,7 @@ namespace osu.Game.Overlays
         private void updatePosition(float position, bool easing = true)
         {
             position = MathHelper.Clamp(position, 0, 1);
-            Fill.TransformTo(() => Fill.Width, position, easing ? 200 : 0, EasingTypes.OutQuint, new TransformSeek());
+            Fill.TransformTo(position, easing ? 200 : 0, EasingTypes.OutQuint, new TransformSeek());
         }
 
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
@@ -100,11 +100,8 @@ namespace osu.Game.Overlays
 
         private class TransformSeek : TransformFloat<Drawable>
         {
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-                d.Width = CurrentValue;
-            }
+            public override void Apply(Drawable d) => d.Width = CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = d.Width;
         }
     }
 }
diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs
index 3380c61385..74cced5ee9 100644
--- a/osu.Game/Overlays/Notifications/Notification.cs
+++ b/osu.Game/Overlays/Notifications/Notification.cs
@@ -7,7 +7,6 @@ using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Colour;
 using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Transforms;
 using osu.Framework.Input;
 using osu.Game.Graphics;
 using OpenTK;
@@ -203,6 +202,8 @@ namespace osu.Game.Overlays.Notifications
                 get { return pulsate; }
                 set
                 {
+                    if (pulsate == value) return;
+
                     pulsate = value;
 
                     pulsateLayer.ClearTransforms();
@@ -211,25 +212,12 @@ namespace osu.Game.Overlays.Notifications
                     if (pulsate)
                     {
                         const float length = 1000;
-                        pulsateLayer.Transforms.Add(new TransformAlpha
+                        using (pulsateLayer.BeginLoopedSequence(length / 2))
                         {
-                            StartTime = Time.Current,
-                            EndTime = Time.Current + length,
-                            StartValue = 1,
-                            EndValue = 0.4f,
-                            Easing = EasingTypes.In
-                        });
-                        pulsateLayer.Transforms.Add(new TransformAlpha
-                        {
-                            StartTime = Time.Current + length,
-                            EndTime = Time.Current + length * 2,
-                            StartValue = 0.4f,
-                            EndValue = 1,
-                            Easing = EasingTypes.Out
-                        });
-
-                        //todo: figure why we can't add arbitrary delays at the end of loop.
-                        pulsateLayer.Loop(length * 2);
+                            pulsateLayer.FadeTo(0.4f, length, EasingTypes.In);
+                            using (pulsateLayer.BeginDelayedSequence(length))
+                                pulsateLayer.FadeTo(1, length, EasingTypes.Out);
+                        }
                     }
                 }
             }
diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs
index c8739f8894..8dad83bd0e 100644
--- a/osu.Game/Screens/Menu/Button.cs
+++ b/osu.Game/Screens/Menu/Button.cs
@@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Transforms;
 using osu.Framework.Input;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
@@ -17,6 +16,9 @@ using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Input;
 using osu.Framework.Extensions.Color4Extensions;
+using osu.Game.Graphics.Containers;
+using osu.Framework.Audio.Track;
+using osu.Game.Beatmaps.ControlPoints;
 
 namespace osu.Game.Screens.Menu
 {
@@ -24,7 +26,7 @@ namespace osu.Game.Screens.Menu
     /// Button designed specifically for the osu!next main menu.
     /// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape).
     /// </summary>
-    public class Button : Container, IStateful<ButtonState>
+    public class Button : BeatSyncedContainer, IStateful<ButtonState>
     {
         private readonly Container iconText;
         private readonly Container box;
@@ -117,6 +119,27 @@ namespace osu.Game.Screens.Menu
             };
         }
 
+        protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+        {
+            base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
+
+            if (!IsHovered) return;
+
+            bool rightward = beatIndex % 2 == 1;
+            double duration = timingPoint.BeatLength / 2;
+
+            icon.RotateTo(rightward ? 10 : -10, duration * 2, EasingTypes.InOutSine);
+
+            icon.MoveToY(-10, duration, EasingTypes.Out);
+            icon.ScaleTo(Vector2.One, duration, EasingTypes.Out);
+
+            using (icon.BeginDelayedSequence(duration))
+            {
+                icon.MoveToY(0, duration, EasingTypes.In);
+                icon.ScaleTo(new Vector2(1, 0.9f), duration, EasingTypes.In);
+            }
+        }
+
         protected override bool OnHover(InputState state)
         {
             if (State != ButtonState.Expanded) return true;
@@ -125,85 +148,11 @@ namespace osu.Game.Screens.Menu
 
             box.ScaleTo(new Vector2(1.5f, 1), 500, EasingTypes.OutElastic);
 
-            int duration = 0; //(int)(Game.Audio.BeatLength / 2);
-            if (duration == 0) duration = 250;
+            double duration = TimeUntilNextBeat;
 
             icon.ClearTransforms();
-
-            icon.ScaleTo(1, 500, EasingTypes.OutElasticHalf);
-
-            const double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration;
-            double startTime = Time.Current + offset;
-
-            icon.RotateTo(10, offset, EasingTypes.InOutSine);
-            icon.ScaleTo(new Vector2(1, 0.9f), offset, EasingTypes.Out);
-
-            icon.Transforms.Add(new TransformRotation
-            {
-                StartValue = -10,
-                EndValue = 10,
-                StartTime = startTime,
-                EndTime = startTime + duration * 2,
-                Easing = EasingTypes.InOutSine,
-                LoopCount = -1,
-                LoopDelay = duration * 2
-            });
-
-            icon.Transforms.Add(new TransformPosition
-            {
-                StartValue = Vector2.Zero,
-                EndValue = new Vector2(0, -10),
-                StartTime = startTime,
-                EndTime = startTime + duration,
-                Easing = EasingTypes.Out,
-                LoopCount = -1,
-                LoopDelay = duration
-            });
-
-            icon.Transforms.Add(new TransformScale
-            {
-                StartValue = new Vector2(1, 0.9f),
-                EndValue = Vector2.One,
-                StartTime = startTime,
-                EndTime = startTime + duration,
-                Easing = EasingTypes.Out,
-                LoopCount = -1,
-                LoopDelay = duration
-            });
-
-            icon.Transforms.Add(new TransformPosition
-            {
-                StartValue = new Vector2(0, -10),
-                EndValue = Vector2.Zero,
-                StartTime = startTime + duration,
-                EndTime = startTime + duration * 2,
-                Easing = EasingTypes.In,
-                LoopCount = -1,
-                LoopDelay = duration
-            });
-
-            icon.Transforms.Add(new TransformScale
-            {
-                StartValue = Vector2.One,
-                EndValue = new Vector2(1, 0.9f),
-                StartTime = startTime + duration,
-                EndTime = startTime + duration * 2,
-                Easing = EasingTypes.In,
-                LoopCount = -1,
-                LoopDelay = duration
-            });
-
-            icon.Transforms.Add(new TransformRotation
-            {
-                StartValue = 10,
-                EndValue = -10,
-                StartTime = startTime + duration * 2,
-                EndTime = startTime + duration * 4,
-                Easing = EasingTypes.InOutSine,
-                LoopCount = -1,
-                LoopDelay = duration * 2
-            });
-
+            icon.RotateTo(10, duration, EasingTypes.InOutSine);
+            icon.ScaleTo(new Vector2(1, 0.9f), duration, EasingTypes.Out);
             return true;
         }
 
@@ -212,7 +161,6 @@ namespace osu.Game.Screens.Menu
             icon.ClearTransforms();
             icon.RotateTo(0, 500, EasingTypes.Out);
             icon.MoveTo(Vector2.Zero, 500, EasingTypes.Out);
-            icon.ScaleTo(0.7f, 500, EasingTypes.OutElasticHalf);
             icon.ScaleTo(Vector2.One, 200, EasingTypes.Out);
 
             if (State == ButtonState.Expanded)
diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs
index 3793181ecd..f527eaacaf 100644
--- a/osu.Game/Screens/Play/HUD/ComboCounter.cs
+++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs
@@ -198,7 +198,6 @@ namespace osu.Game.Screens.Play.HUD
 
             transform.StartTime = Time.Current;
             transform.EndTime = Time.Current + getProportionalDuration(currentValue, newValue);
-            transform.StartValue = currentValue;
             transform.EndValue = newValue;
             transform.Easing = RollingEasing;
 
@@ -207,7 +206,7 @@ namespace osu.Game.Screens.Play.HUD
 
         protected class TransformComboRoll : Transform<int, Drawable>
         {
-            public override int CurrentValue
+            public virtual int CurrentValue
             {
                 get
                 {
@@ -219,11 +218,8 @@ namespace osu.Game.Screens.Play.HUD
                 }
             }
 
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-                ((ComboCounter)d).DisplayedCount = CurrentValue;
-            }
+            public override void Apply(Drawable d) => ((ComboCounter)d).DisplayedCount = CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = ((ComboCounter)d).DisplayedCount;
         }
 
         protected abstract void OnDisplayedCountRolling(int currentValue, int newValue);
diff --git a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs
index a4a20c1fd0..c280702390 100644
--- a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs
+++ b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play.HUD
 
         protected class TransformComboResult : Transform<ulong, Drawable>
         {
-            public override ulong CurrentValue
+            public virtual ulong CurrentValue
             {
                 get
                 {
@@ -48,11 +48,8 @@ namespace osu.Game.Screens.Play.HUD
                 }
             }
 
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-                ((ComboResultCounter)d).DisplayedCount = CurrentValue;
-            }
+            public override void Apply(Drawable d) => ((ComboResultCounter)d).DisplayedCount = CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = ((ComboResultCounter)d).DisplayedCount;
         }
     }
 }
diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs
index a7019f1e35..30f109e598 100644
--- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs
+++ b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs
@@ -297,7 +297,7 @@ namespace osu.Game.Screens.Tournament
             }
         }
 
-        private void speedTo(float value, double duration = 0, EasingTypes easing = EasingTypes.None) => TransformTo(() => speed, value, duration, easing, new TransformScrollSpeed());
+        private void speedTo(float value, double duration = 0, EasingTypes easing = EasingTypes.None) => TransformTo(value, duration, easing, new TransformScrollSpeed());
 
         private enum ScrollState
         {
@@ -310,11 +310,8 @@ namespace osu.Game.Screens.Tournament
 
         public class TransformScrollSpeed : TransformFloat<Drawable>
         {
-            public override void Apply(Drawable d)
-            {
-                base.Apply(d);
-                ((ScrollingTeamContainer)d).speed = CurrentValue;
-            }
+            public override void Apply(Drawable d) => ((ScrollingTeamContainer)d).speed = CurrentValue;
+            public override void ReadIntoStartValue(Drawable d) => StartValue = ((ScrollingTeamContainer)d).speed;
         }
 
         public class ScrollingTeam : Container