diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx
index 4cf245aaf..034477b60 100644
--- a/source/funkin/Conductor.hx
+++ b/source/funkin/Conductor.hx
@@ -316,7 +316,8 @@ class Conductor
         }
       }
 
-      resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepLengthMs);
+      var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / stepLengthMs;
+      resultStep += resultFractionalStep; // Math.floor();
 
       return resultStep;
     }
diff --git a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx
index 501c28c3c..aa0b97270 100644
--- a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx
@@ -34,16 +34,6 @@ class ChartEditorNoteSprite extends FlxSprite
    */
   public var noteStyle(get, null):String;
 
-  /**
-   * This note is the previous sprite in a sustain chain.
-   */
-  public var parentNoteSprite(default, set):ChartEditorNoteSprite = null;
-
-  /**
-   * This note is the next sprite in a sustain chain.
-   */
-  public var childNoteSprite(default, set):ChartEditorNoteSprite = null;
-
   public function new(parent:ChartEditorState)
   {
     super();
@@ -124,14 +114,6 @@ class ChartEditorNoteSprite extends FlxSprite
 
     if (this.noteData == null)
     {
-      // Disown parent.
-      this.parentNoteSprite = null;
-      if (this.childNoteSprite != null)
-      {
-        // Kill all children and disown them.
-        this.childNoteSprite.noteData = null;
-        this.childNoteSprite = null;
-      }
       this.kill();
       return this.noteData;
     }
@@ -170,71 +152,22 @@ class ChartEditorNoteSprite extends FlxSprite
       }
     }
 
-    if (parentNoteSprite == null)
+    this.x = cursorColumn * ChartEditorState.GRID_SIZE;
+
+    // Notes far in the song will start far down, but the group they belong to will have a high negative offset.
+    if (this.noteData.stepTime >= 0)
     {
-      this.x = cursorColumn * ChartEditorState.GRID_SIZE;
-
-      // Notes far in the song will start far down, but the group they belong to will have a high negative offset.
-      if (this.noteData.stepTime >= 0)
-      {
-        // noteData.stepTime is a calculated value which accounts for BPM changes
-        this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
-      }
-
-      if (origin != null)
-      {
-        this.x += origin.x;
-        this.y += origin.y;
-      }
-    }
-    else
-    {
-      // If this is a hold note, we need to adjust the position to be centered.
-      if (parentNoteSprite.parentNoteSprite == null)
-      {
-        this.x = parentNoteSprite.x;
-        this.x += (ChartEditorState.GRID_SIZE / 2);
-        this.x -= this.width / 2;
-      }
-      else
-      {
-        this.x = parentNoteSprite.x;
-      }
-
-      this.y = parentNoteSprite.y;
-      if (parentNoteSprite.parentNoteSprite == null)
-      {
-        this.y += parentNoteSprite.height / 2;
-      }
-      else
-      {
-        this.y += parentNoteSprite.height - 1;
-      }
-    }
-  }
-
-  function set_parentNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
-  {
-    this.parentNoteSprite = value;
-
-    if (this.parentNoteSprite != null)
-    {
-      this.noteData = this.parentNoteSprite.noteData;
+      // noteData.stepTime is a calculated value which accounts for BPM changes
+      var stepTime:Float = this.noteData.stepTime;
+      var roundedStepTime:Float = Math.floor(stepTime + 0.01); // Add epsilon to fix rounding issues
+      this.y = roundedStepTime * ChartEditorState.GRID_SIZE;
     }
 
-    return this.parentNoteSprite;
-  }
-
-  function set_childNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
-  {
-    this.childNoteSprite = value;
-
-    if (this.parentNoteSprite != null)
+    if (origin != null)
     {
-      this.noteData = this.parentNoteSprite.noteData;
+      this.x += origin.x;
+      this.y += origin.y;
     }
-
-    return this.childNoteSprite;
   }
 
   function get_noteStyle():String
@@ -247,7 +180,6 @@ class ChartEditorNoteSprite extends FlxSprite
   {
     // Decide whether to display a note or a sustain.
     var baseAnimationName:String = 'tap';
-    if (this.parentNoteSprite != null) baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd';
 
     // Play the appropriate animation for the type, direction, and skin.
     var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle}';
@@ -260,17 +192,6 @@ class ChartEditorNoteSprite extends FlxSprite
     {
       case 'tap':
         this.setGraphicSize(0, ChartEditorState.GRID_SIZE);
-      case 'hold':
-        if (parentNoteSprite.parentNoteSprite == null)
-        {
-          this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
-        }
-        else
-        {
-          this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), ChartEditorState.GRID_SIZE);
-        }
-      case 'holdEnd':
-        this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
     }
     this.updateHitbox();
 
@@ -294,11 +215,4 @@ class ChartEditorNoteSprite extends FlxSprite
 
     return false;
   }
-
-  public function getBaseNoteSprite()
-  {
-    if (this.parentNoteSprite == null) return this;
-    else
-      return this.parentNoteSprite;
-  }
 }
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index 4a04c0bfb..c73e5fff4 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -1,5 +1,6 @@
 package funkin.ui.debug.charting;
 
+import funkin.graphics.rendering.SustainTrail;
 import funkin.util.SortUtil;
 import funkin.ui.debug.charting.ChartEditorCommand;
 import flixel.input.keyboard.FlxKey;
@@ -944,6 +945,8 @@ class ChartEditorState extends HaxeUIState
    */
   var renderedNotes:FlxTypedSpriteGroup<ChartEditorNoteSprite>;
 
+  var renderedHoldNotes:FlxTypedSpriteGroup<SustainTrail>;
+
   /**
    * The sprite group containing the song events.
    * Only displays a subset of the data from `currentSongChartEventData`,
@@ -1706,7 +1709,6 @@ class ChartEditorState extends HaxeUIState
       var cursorFractionalStep:Float = cursorY / GRID_SIZE / (16 / noteSnapQuant);
       var cursorStep:Int = Std.int(Math.floor(cursorFractionalStep));
       var cursorMs:Float = Conductor.getStepTimeInMs(cursorStep);
-      trace('${cursorFractionalStep} ${cursorStep} ${cursorMs}');
       // The direction value for the column at the cursor.
       var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE);
       if (cursorColumn < 0) cursorColumn = 0;
@@ -1858,8 +1860,7 @@ class ChartEditorState extends HaxeUIState
             {
               if (highlightedNote != null)
               {
-                // Handle the case of clicking on a sustain piece.
-                highlightedNote = highlightedNote.getBaseNoteSprite();
+                // TODO: Handle the case of clicking on a sustain piece.
                 // Control click to select/deselect an individual note.
                 if (isNoteSelected(highlightedNote.noteData))
                 {
@@ -2055,8 +2056,7 @@ class ChartEditorState extends HaxeUIState
 
           if (highlightedNote != null)
           {
-            // Handle the case of clicking on a sustain piece.
-            highlightedNote = highlightedNote.getBaseNoteSprite();
+            // TODO: Handle the case of clicking on a sustain piece.
             // Remove the note.
             performCommand(new RemoveNotesCommand([highlightedNote.noteData]));
           }
@@ -2167,18 +2167,18 @@ class ChartEditorState extends HaxeUIState
           // Kill the note sprite and recycle it.
           noteSprite.noteData = null;
         }
-        else if (noteSprite.noteData.length > 0 && (noteSprite.parentNoteSprite == null && noteSprite.childNoteSprite == null))
-        {
-          // Note was extended.
-          // Kill the note sprite and recycle it.
-          noteSprite.noteData = null;
-        }
-        else if (noteSprite.noteData.length == 0 && (noteSprite.parentNoteSprite != null || noteSprite.childNoteSprite != null))
-        {
-          // Note was shortened.
-          // Kill the note sprite and recycle it.
-          noteSprite.noteData = null;
-        }
+          // else if (noteSprite.noteData.length > 0 && (noteSprite.parentNoteSprite == null && noteSprite.childNoteSprite == null))
+          // {
+          //   // Note was extended.
+          //   // Kill the note sprite and recycle it.
+          //   noteSprite.noteData = null;
+          // }
+          // else if (noteSprite.noteData.length == 0 && (noteSprite.parentNoteSprite != null || noteSprite.childNoteSprite != null))
+          // {
+          //   // Note was shortened.
+          //   // Kill the note sprite and recycle it.
+          //   noteSprite.noteData = null;
+        // }
         else
         {
           // Note is already displayed and should remain displayed.
@@ -2252,30 +2252,34 @@ class ChartEditorState extends HaxeUIState
         // TODO: Replace this with SustainTrail.
         if (noteSprite.noteData.length > 0)
         {
+          var holdNoteSprite:SustainTrail = renderedHoldNotes.recycle(() -> new SustainTrail(this));
+
+          var noteLengthPixels:Float = noteSprite.noteData.stepLength * GRID_SIZE;
+
           // If the note is a hold, we need to make sure it's long enough.
-          var noteLengthSteps:Float = noteSprite.noteData.stepLength;
-          var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
-
-          while (noteLengthSteps > 0)
-          {
-            if (noteLengthSteps <= 1.0)
-            {
-              // Last note in the hold.
-              // TODO: We may need to make it shorter and clip it visually.
-            }
-
-            var nextNoteSprite:ChartEditorNoteSprite = renderedNotes.recycle(ChartEditorNoteSprite);
-            nextNoteSprite.parentState = this;
-            nextNoteSprite.parentNoteSprite = lastNoteSprite;
-            lastNoteSprite.childNoteSprite = nextNoteSprite;
-
-            lastNoteSprite = nextNoteSprite;
-
-            noteLengthSteps -= 1;
-          }
-
-          // Make sure the last note sprite shows the end cap properly.
-          lastNoteSprite.childNoteSprite = null;
+          // var noteLengthSteps:Float = ;
+          // var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
+          //
+          // while (noteLengthSteps > 0)
+          // {
+          //   if (noteLengthSteps <= 1.0)
+          //   {
+          //     // Last note in the hold.
+          //     // TODO: We may need to make it shorter and clip it visually.
+          //   }
+          //
+          //   var nextNoteSprite:ChartEditorNoteSprite = renderedNotes.recycle(ChartEditorNoteSprite);
+          //   nextNoteSprite.parentState = this;
+          //   nextNoteSprite.parentNoteSprite = lastNoteSprite;
+          //   lastNoteSprite.childNoteSprite = nextNoteSprite;
+          //
+          //   lastNoteSprite = nextNoteSprite;
+          //
+          //   noteLengthSteps -= 1;
+          // }
+          //
+          // // Make sure the last note sprite shows the end cap properly.
+          // lastNoteSprite.childNoteSprite = null;
 
           // var noteLengthPixels:Float = (noteLengthMs / Conductor.stepLengthMs + 1) * GRID_SIZE;
           // add(new FlxSprite(noteSprite.x, noteSprite.y - renderedNotes.y + noteLengthPixels).makeGraphic(40, 2, 0xFFFF0000));
@@ -2326,7 +2330,8 @@ class ChartEditorState extends HaxeUIState
       // Recycle selection squares if possible.
       for (noteSprite in renderedNotes.members)
       {
-        if (isNoteSelected(noteSprite.noteData) && noteSprite.parentNoteSprite == null)
+        // TODO: Handle selection of hold notes.
+        if (isNoteSelected(noteSprite.noteData))
         {
           var selectionSquare:FlxSprite = renderedSelectionSquares.recycle(buildSelectionSquare);
 
@@ -2833,11 +2838,13 @@ class ChartEditorState extends HaxeUIState
     // Assume notes are sorted by time.
     for (noteData in currentSongChartNoteData)
     {
+      // Check for notes between the old and new song positions.
+
       if (noteData.time < oldSongPosition) // Note is in the past.
         continue;
 
-      if (noteData.time >= newSongPosition) // Note is in the future.
-        return;
+      if (noteData.time > newSongPosition) // Note is in the future.
+        return; // Assume all notes are also in the future.
 
       // Note was just hit.
 
@@ -3122,6 +3129,8 @@ class ChartEditorState extends HaxeUIState
     Conductor.forceBPM(null); // Disable the forced BPM.
     Conductor.mapTimeChanges(currentSongMetadata.timeChanges);
 
+    sortChartData();
+
     loadInstrumentalFromAsset(Paths.inst(songId));
 
     var voiceList:Array<String> = song.getDifficulty(selectedDifficulty).buildVoiceList();