diff --git a/assets b/assets
index 2d694cdfd..8b8086ac0 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 2d694cdfd90b9cc516b3e41a552212dd9e6d037b
+Subproject commit 8b8086ac0ff072e23996828f19a05f064dafe5cf
diff --git a/source/funkin/PlayerSettings.hx b/source/funkin/PlayerSettings.hx
index 2e3e0e425..5a9351bba 100644
--- a/source/funkin/PlayerSettings.hx
+++ b/source/funkin/PlayerSettings.hx
@@ -84,9 +84,9 @@ class PlayerSettings
   function addKeyboard():Void
   {
     var useDefault = true;
-    if (Save.get().hasControls(id, Keys))
+    if (Save.instance.hasControls(id, Keys))
     {
-      var keyControlData = Save.get().getControls(id, Keys);
+      var keyControlData = Save.instance.getControls(id, Keys);
       trace("keyControlData: " + haxe.Json.stringify(keyControlData));
       useDefault = false;
       controls.fromSaveData(keyControlData, Keys);
@@ -112,9 +112,9 @@ class PlayerSettings
   function addGamepad(gamepad:FlxGamepad)
   {
     var useDefault = true;
-    if (Save.get().hasControls(id, Gamepad(gamepad.id)))
+    if (Save.instance.hasControls(id, Gamepad(gamepad.id)))
     {
-      var padControlData = Save.get().getControls(id, Gamepad(gamepad.id));
+      var padControlData = Save.instance.getControls(id, Gamepad(gamepad.id));
       trace("padControlData: " + haxe.Json.stringify(padControlData));
       useDefault = false;
       controls.addGamepadWithSaveData(gamepad.id, padControlData);
@@ -141,7 +141,7 @@ class PlayerSettings
     if (keyData != null)
     {
       trace("saving key data: " + haxe.Json.stringify(keyData));
-      Save.get().setControls(id, Keys, keyData);
+      Save.instance.setControls(id, Keys, keyData);
     }
 
     if (controls.gamepadsAdded.length > 0)
@@ -150,7 +150,7 @@ class PlayerSettings
       if (padData != null)
       {
         trace("saving pad data: " + haxe.Json.stringify(padData));
-        Save.get().setControls(id, Gamepad(controls.gamepadsAdded[0]), padData);
+        Save.instance.setControls(id, Gamepad(controls.gamepadsAdded[0]), padData);
       }
     }
   }
diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx
index 039a4c285..60c7a996a 100644
--- a/source/funkin/Preferences.hx
+++ b/source/funkin/Preferences.hx
@@ -15,12 +15,12 @@ class Preferences
 
   static function get_naughtyness():Bool
   {
-    return Save.get().options.naughtyness;
+    return Save.instance.options.naughtyness;
   }
 
   static function set_naughtyness(value:Bool):Bool
   {
-    var save = Save.get();
+    var save = Save.instance;
     save.options.naughtyness = value;
     save.flush();
     return value;
@@ -34,12 +34,12 @@ class Preferences
 
   static function get_downscroll():Bool
   {
-    return Save.get().options.downscroll;
+    return Save.instance.options.downscroll;
   }
 
   static function set_downscroll(value:Bool):Bool
   {
-    var save = Save.get();
+    var save = Save.instance;
     save.options.downscroll = value;
     save.flush();
     return value;
@@ -53,12 +53,12 @@ class Preferences
 
   static function get_flashingLights():Bool
   {
-    return Save.get().options.flashingLights;
+    return Save.instance.options.flashingLights;
   }
 
   static function set_flashingLights(value:Bool):Bool
   {
-    var save = Save.get();
+    var save = Save.instance;
     save.options.flashingLights = value;
     save.flush();
     return value;
@@ -72,12 +72,12 @@ class Preferences
 
   static function get_zoomCamera():Bool
   {
-    return Save.get().options.zoomCamera;
+    return Save.instance.options.zoomCamera;
   }
 
   static function set_zoomCamera(value:Bool):Bool
   {
-    var save = Save.get();
+    var save = Save.instance;
     save.options.zoomCamera = value;
     save.flush();
     return value;
@@ -91,17 +91,17 @@ class Preferences
 
   static function get_debugDisplay():Bool
   {
-    return Save.get().options.debugDisplay;
+    return Save.instance.options.debugDisplay;
   }
 
   static function set_debugDisplay(value:Bool):Bool
   {
-    if (value != Save.get().options.debugDisplay)
+    if (value != Save.instance.options.debugDisplay)
     {
       toggleDebugDisplay(value);
     }
 
-    var save = Save.get();
+    var save = Save.instance;
     save.options.debugDisplay = value;
     save.flush();
     return value;
@@ -115,14 +115,14 @@ class Preferences
 
   static function get_autoPause():Bool
   {
-    return Save.get().options.autoPause;
+    return Save.instance.options.autoPause;
   }
 
   static function set_autoPause(value:Bool):Bool
   {
-    if (value != Save.get().options.autoPause) FlxG.autoPause = value;
+    if (value != Save.instance.options.autoPause) FlxG.autoPause = value;
 
-    var save = Save.get();
+    var save = Save.instance;
     save.options.autoPause = value;
     save.flush();
     return value;
diff --git a/source/funkin/api/newgrounds/NGUtil.hx b/source/funkin/api/newgrounds/NGUtil.hx
index c8289fc46..e9902a798 100644
--- a/source/funkin/api/newgrounds/NGUtil.hx
+++ b/source/funkin/api/newgrounds/NGUtil.hx
@@ -86,10 +86,10 @@ class NGUtil
     #end
 
     var onSessionFail:Error->Void = null;
-    if (sessionId == null && Save.get().ngSessionId != null)
+    if (sessionId == null && Save.instance.ngSessionId != null)
     {
       trace("using stored session id");
-      sessionId = Save.get().ngSessionId;
+      sessionId = Save.instance.ngSessionId;
       onSessionFail = function(error) savedSessionFailed = true;
     }
     #end
@@ -159,8 +159,8 @@ class NGUtil
   static function onNGLogin():Void
   {
     trace('logged in! user:${NG.core.user.name}');
-    Save.get().ngSessionId = NG.core.sessionId;
-    Save.get().flush();
+    Save.instance.ngSessionId = NG.core.sessionId;
+    Save.instance.flush();
     // Load medals then call onNGMedalFetch()
     NG.core.requestMedals(onNGMedalFetch);
 
@@ -174,8 +174,8 @@ class NGUtil
   {
     NG.core.logOut();
 
-    Save.get().ngSessionId = null;
-    Save.get().flush();
+    Save.instance.ngSessionId = null;
+    Save.instance.flush();
   }
 
   // --- MEDALS
diff --git a/source/funkin/api/newgrounds/NGio.hx b/source/funkin/api/newgrounds/NGio.hx
index e505bdedf..c1f8ad3ba 100644
--- a/source/funkin/api/newgrounds/NGio.hx
+++ b/source/funkin/api/newgrounds/NGio.hx
@@ -86,10 +86,10 @@ class NGio
     #end
 
     var onSessionFail:Error->Void = null;
-    if (sessionId == null && Save.get().ngSessionId != null)
+    if (sessionId == null && Save.instance.ngSessionId != null)
     {
       trace("using stored session id");
-      sessionId = Save.get().ngSessionId;
+      sessionId = Save.instance.ngSessionId;
       onSessionFail = function(error) savedSessionFailed = true;
     }
     #end
@@ -159,8 +159,8 @@ class NGio
   static function onNGLogin():Void
   {
     trace('logged in! user:${NG.core.user.name}');
-    Save.get().ngSessionId = NG.core.sessionId;
-    Save.get().flush();
+    Save.instance.ngSessionId = NG.core.sessionId;
+    Save.instance.flush();
     // Load medals then call onNGMedalFetch()
     NG.core.requestMedals(onNGMedalFetch);
 
@@ -174,8 +174,8 @@ class NGio
   {
     NG.core.logOut();
 
-    Save.get().ngSessionId = null;
-    Save.get().flush();
+    Save.instance.ngSessionId = null;
+    Save.instance.flush();
   }
 
   // --- MEDALS
diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx
index f1e82aee9..889f63073 100644
--- a/source/funkin/modding/PolymodHandler.hx
+++ b/source/funkin/modding/PolymodHandler.hx
@@ -61,7 +61,7 @@ class PolymodHandler
     createModRoot();
 
     trace("Initializing Polymod (using configured mods)...");
-    loadModsById(Save.get().enabledModIds);
+    loadModsById(Save.instance.enabledModIds);
   }
 
   /**
@@ -236,7 +236,7 @@ class PolymodHandler
 
   public static function getEnabledMods():Array<ModMetadata>
   {
-    var modIds = Save.get().enabledModIds;
+    var modIds = Save.instance.enabledModIds;
     var modMetadata = getAllMods();
     var enabledMods = [];
     for (item in modMetadata)
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 5cb2e20f3..5421f1d2d 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -282,6 +282,12 @@ class PlayState extends MusicBeatSubState
    */
   public var isPracticeMode:Bool = false;
 
+  /**
+   * Whether the player has dropped below zero health,
+   * and we are just waiting for an animation to play out before transitioning.
+   */
+  public var isPlayerDying:Bool = false;
+
   /**
    * In Minimal Mode, the stage and characters are not loaded and a standard background is used.
    */
@@ -785,6 +791,7 @@ class PlayState extends MusicBeatSubState
       persistentDraw = true;
 
       startingSong = true;
+      isPlayerDying = false;
 
       inputSpitter = [];
 
@@ -953,7 +960,7 @@ class PlayState extends MusicBeatSubState
       }
       #end
 
-      if (health <= Constants.HEALTH_MIN && !isPracticeMode)
+      if (health <= Constants.HEALTH_MIN && !isPracticeMode && !isPlayerDying)
       {
         vocals.pause();
         FlxG.sound.music.pause();
@@ -979,20 +986,30 @@ class PlayState extends MusicBeatSubState
         }
         #end
 
-        var gameOverSubState = new GameOverSubState(
-          {
-            isChartingMode: isChartingMode,
-            transparent: persistentDraw
+        isPlayerDying = true;
+
+        var deathPreTransitionDelay = currentStage?.getBoyfriend()?.getDeathPreTransitionDelay() ?? 0.0;
+        if (deathPreTransitionDelay > 0)
+        {
+          new FlxTimer().start(deathPreTransitionDelay, function(_) {
+            moveToGameOver();
           });
-        FlxTransitionableSubState.skipNextTransIn = true;
-        FlxTransitionableSubState.skipNextTransOut = true;
-        openSubState(gameOverSubState);
+        }
+        else
+        {
+          // Transition immediately.
+          moveToGameOver();
+        }
 
         #if discord_rpc
         // Game Over doesn't get his own variable because it's only used here
         DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
         #end
       }
+      else if (isPlayerDying)
+      {
+        // Wait up.
+      }
     }
 
     processSongEvents();
@@ -1008,6 +1025,18 @@ class PlayState extends MusicBeatSubState
     justUnpaused = false;
   }
 
+  function moveToGameOver():Void
+  {
+    var gameOverSubState = new GameOverSubState(
+      {
+        isChartingMode: isChartingMode,
+        transparent: persistentDraw
+      });
+    FlxTransitionableSubState.skipNextTransIn = true;
+    FlxTransitionableSubState.skipNextTransOut = true;
+    openSubState(gameOverSubState);
+  }
+
   function processSongEvents():Void
   {
     // Query and activate song events.
@@ -2592,9 +2621,9 @@ class PlayState extends MusicBeatSubState
           accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
         };
 
-      if (Save.get().isSongHighScore(currentSong.id, currentDifficulty, data))
+      if (Save.instance.isSongHighScore(currentSong.id, currentDifficulty, data))
       {
-        Save.get().setSongScore(currentSong.id, currentDifficulty, data);
+        Save.instance.setSongScore(currentSong.id, currentDifficulty, data);
         #if newgrounds
         NGio.postScore(score, currentSong.id);
         #end
@@ -2642,9 +2671,9 @@ class PlayState extends MusicBeatSubState
               accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
             };
 
-          if (Save.get().isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
+          if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
           {
-            Save.get().setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
+            Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
             #if newgrounds
             NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
             #end
diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx
index bfba3715a..cf5311bdc 100644
--- a/source/funkin/play/character/BaseCharacter.hx
+++ b/source/funkin/play/character/BaseCharacter.hx
@@ -198,6 +198,11 @@ class BaseCharacter extends Bopper
     return _data.death?.cameraZoom ?? 1.0;
   }
 
+  public function getDeathPreTransitionDelay():Float
+  {
+    return _data.death?.preTransitionDelay ?? 0.0;
+  }
+
   /**
    * Gets the value of flipX from the character data.
    * `!getFlipX()` is the direction Boyfriend should face.
diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx
index 23710274c..56d7b7793 100644
--- a/source/funkin/play/character/CharacterData.hx
+++ b/source/funkin/play/character/CharacterData.hx
@@ -751,4 +751,10 @@ typedef DeathData =
    * @default 1.0
    */
   var ?cameraZoom:Float;
+
+  /**
+   * Impose a delay between when the character reaches `0` health and when the death animation plays.
+   * @default 0.0
+   */
+  var ?preTransitionDelay:Float;
 }
diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx
index 6246dcb58..73ba8efa0 100644
--- a/source/funkin/save/Save.hx
+++ b/source/funkin/save/Save.hx
@@ -11,8 +11,7 @@ import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
 import thx.semver.Version;
 
 @:nullSafety
-@:forward(volume, mute)
-abstract Save(RawSaveData)
+class Save
 {
   // Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null.
   public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.2";
@@ -25,6 +24,20 @@ abstract Save(RawSaveData)
   static final SAVE_PATH_LEGACY:String = 'ninjamuffin99';
   static final SAVE_NAME_LEGACY:String = 'funkin';
 
+  public static var instance(get, never):Save;
+  static var _instance:Null<Save> = null;
+
+  static function get_instance():Save
+  {
+    if (_instance == null)
+    {
+      _instance = new Save(FlxG.save.data);
+    }
+    return _instance;
+  }
+
+  var data:RawSaveData;
+
   public static function load():Void
   {
     trace("[SAVE] Loading save...");
@@ -33,84 +46,85 @@ abstract Save(RawSaveData)
     loadFromSlot(1);
   }
 
-  public static function get():Save
-  {
-    return FlxG.save.data;
-  }
-
   /**
    * Constructing a new Save will load the default values.
    */
-  public function new()
+  public function new(data:RawSaveData)
   {
-    this =
-      {
-        version: Save.SAVE_DATA_VERSION,
+    this.data = data;
 
-        volume: 1.0,
-        mute: false,
+    if (this.data == null) data = Save.getDefault();
+  }
 
-        api:
-          {
-            newgrounds:
-              {
-                sessionId: null,
-              }
-          },
-        scores:
-          {
-            // No saved scores.
-            levels: [],
-            songs: [],
-          },
-        options:
-          {
-            // Reasonable defaults.
-            naughtyness: true,
-            downscroll: false,
-            flashingLights: true,
-            zoomCamera: true,
-            debugDisplay: false,
-            autoPause: true,
+  public static function getDefault():RawSaveData
+  {
+    return {
+      version: Save.SAVE_DATA_VERSION,
 
-            controls:
-              {
-                // Leave controls blank so defaults are loaded.
-                p1:
-                  {
-                    keyboard: {},
-                    gamepad: {},
-                  },
-                p2:
-                  {
-                    keyboard: {},
-                    gamepad: {},
-                  },
-              },
-          },
+      volume: 1.0,
+      mute: false,
 
-        mods:
-          {
-            // No mods enabled.
-            enabledMods: [],
-            modOptions: [],
-          },
+      api:
+        {
+          newgrounds:
+            {
+              sessionId: null,
+            }
+        },
+      scores:
+        {
+          // No saved scores.
+          levels: [],
+          songs: [],
+        },
+      options:
+        {
+          // Reasonable defaults.
+          naughtyness: true,
+          downscroll: false,
+          flashingLights: true,
+          zoomCamera: true,
+          debugDisplay: false,
+          autoPause: true,
 
-        optionsChartEditor:
-          {
-            // Reasonable defaults.
-            previousFiles: [],
-            noteQuant: 3,
-            chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None,
-            theme: ChartEditorTheme.Light,
-            playtestStartTime: false,
-            downscroll: false,
-            metronomeVolume: 1.0,
-            hitsoundVolumePlayer: 1.0,
-            hitsoundVolumeOpponent: 1.0,
-            themeMusic: true
-          },
-      };
+          controls:
+            {
+              // Leave controls blank so defaults are loaded.
+              p1:
+                {
+                  keyboard: {},
+                  gamepad: {},
+                },
+              p2:
+                {
+                  keyboard: {},
+                  gamepad: {},
+                },
+            },
+        },
+
+      mods:
+        {
+          // No mods enabled.
+          enabledMods: [],
+          modOptions: [],
+        },
+
+      optionsChartEditor:
+        {
+          // Reasonable defaults.
+          previousFiles: [],
+          noteQuant: 3,
+          chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None,
+          theme: ChartEditorTheme.Light,
+          playtestStartTime: false,
+          downscroll: false,
+          metronomeVolume: 1.0,
+          hitsoundVolumePlayer: 1.0,
+          hitsoundVolumeOpponent: 1.0,
+          themeMusic: true
+        },
+    };
   }
 
   /**
@@ -120,7 +134,7 @@ abstract Save(RawSaveData)
 
   function get_options():SaveDataOptions
   {
-    return this.options;
+    return data.options;
   }
 
   /**
@@ -130,7 +144,7 @@ abstract Save(RawSaveData)
 
   function get_modOptions():Map<String, Dynamic>
   {
-    return this.mods.modOptions;
+    return data.mods.modOptions;
   }
 
   /**
@@ -140,232 +154,232 @@ abstract Save(RawSaveData)
 
   function get_ngSessionId():Null<String>
   {
-    return this.api.newgrounds.sessionId;
+    return data.api.newgrounds.sessionId;
   }
 
   function set_ngSessionId(value:Null<String>):Null<String>
   {
-    this.api.newgrounds.sessionId = value;
+    data.api.newgrounds.sessionId = value;
     flush();
-    return this.api.newgrounds.sessionId;
+    return data.api.newgrounds.sessionId;
   }
 
   public var enabledModIds(get, set):Array<String>;
 
   function get_enabledModIds():Array<String>
   {
-    return this.mods.enabledMods;
+    return data.mods.enabledMods;
   }
 
   function set_enabledModIds(value:Array<String>):Array<String>
   {
-    this.mods.enabledMods = value;
+    data.mods.enabledMods = value;
     flush();
-    return this.mods.enabledMods;
+    return data.mods.enabledMods;
   }
 
   public var chartEditorPreviousFiles(get, set):Array<String>;
 
   function get_chartEditorPreviousFiles():Array<String>
   {
-    if (this.optionsChartEditor.previousFiles == null) this.optionsChartEditor.previousFiles = [];
+    if (data.optionsChartEditor.previousFiles == null) data.optionsChartEditor.previousFiles = [];
 
-    return this.optionsChartEditor.previousFiles;
+    return data.optionsChartEditor.previousFiles;
   }
 
   function set_chartEditorPreviousFiles(value:Array<String>):Array<String>
   {
     // Set and apply.
-    this.optionsChartEditor.previousFiles = value;
+    data.optionsChartEditor.previousFiles = value;
     flush();
-    return this.optionsChartEditor.previousFiles;
+    return data.optionsChartEditor.previousFiles;
   }
 
   public var chartEditorHasBackup(get, set):Bool;
 
   function get_chartEditorHasBackup():Bool
   {
-    if (this.optionsChartEditor.hasBackup == null) this.optionsChartEditor.hasBackup = false;
+    if (data.optionsChartEditor.hasBackup == null) data.optionsChartEditor.hasBackup = false;
 
-    return this.optionsChartEditor.hasBackup;
+    return data.optionsChartEditor.hasBackup;
   }
 
   function set_chartEditorHasBackup(value:Bool):Bool
   {
     // Set and apply.
-    this.optionsChartEditor.hasBackup = value;
+    data.optionsChartEditor.hasBackup = value;
     flush();
-    return this.optionsChartEditor.hasBackup;
+    return data.optionsChartEditor.hasBackup;
   }
 
   public var chartEditorNoteQuant(get, set):Int;
 
   function get_chartEditorNoteQuant():Int
   {
-    if (this.optionsChartEditor.noteQuant == null) this.optionsChartEditor.noteQuant = 3;
+    if (data.optionsChartEditor.noteQuant == null) data.optionsChartEditor.noteQuant = 3;
 
-    return this.optionsChartEditor.noteQuant;
+    return data.optionsChartEditor.noteQuant;
   }
 
   function set_chartEditorNoteQuant(value:Int):Int
   {
     // Set and apply.
-    this.optionsChartEditor.noteQuant = value;
+    data.optionsChartEditor.noteQuant = value;
     flush();
-    return this.optionsChartEditor.noteQuant;
+    return data.optionsChartEditor.noteQuant;
   }
 
   public var chartEditorLiveInputStyle(get, set):ChartEditorLiveInputStyle;
 
   function get_chartEditorLiveInputStyle():ChartEditorLiveInputStyle
   {
-    if (this.optionsChartEditor.chartEditorLiveInputStyle == null) this.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None;
+    if (data.optionsChartEditor.chartEditorLiveInputStyle == null) data.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None;
 
-    return this.optionsChartEditor.chartEditorLiveInputStyle;
+    return data.optionsChartEditor.chartEditorLiveInputStyle;
   }
 
   function set_chartEditorLiveInputStyle(value:ChartEditorLiveInputStyle):ChartEditorLiveInputStyle
   {
     // Set and apply.
-    this.optionsChartEditor.chartEditorLiveInputStyle = value;
+    data.optionsChartEditor.chartEditorLiveInputStyle = value;
     flush();
-    return this.optionsChartEditor.chartEditorLiveInputStyle;
+    return data.optionsChartEditor.chartEditorLiveInputStyle;
   }
 
   public var chartEditorDownscroll(get, set):Bool;
 
   function get_chartEditorDownscroll():Bool
   {
-    if (this.optionsChartEditor.downscroll == null) this.optionsChartEditor.downscroll = false;
+    if (data.optionsChartEditor.downscroll == null) data.optionsChartEditor.downscroll = false;
 
-    return this.optionsChartEditor.downscroll;
+    return data.optionsChartEditor.downscroll;
   }
 
   function set_chartEditorDownscroll(value:Bool):Bool
   {
     // Set and apply.
-    this.optionsChartEditor.downscroll = value;
+    data.optionsChartEditor.downscroll = value;
     flush();
-    return this.optionsChartEditor.downscroll;
+    return data.optionsChartEditor.downscroll;
   }
 
   public var chartEditorPlaytestStartTime(get, set):Bool;
 
   function get_chartEditorPlaytestStartTime():Bool
   {
-    if (this.optionsChartEditor.playtestStartTime == null) this.optionsChartEditor.playtestStartTime = false;
+    if (data.optionsChartEditor.playtestStartTime == null) data.optionsChartEditor.playtestStartTime = false;
 
-    return this.optionsChartEditor.playtestStartTime;
+    return data.optionsChartEditor.playtestStartTime;
   }
 
   function set_chartEditorPlaytestStartTime(value:Bool):Bool
   {
     // Set and apply.
-    this.optionsChartEditor.playtestStartTime = value;
+    data.optionsChartEditor.playtestStartTime = value;
     flush();
-    return this.optionsChartEditor.playtestStartTime;
+    return data.optionsChartEditor.playtestStartTime;
   }
 
   public var chartEditorTheme(get, set):ChartEditorTheme;
 
   function get_chartEditorTheme():ChartEditorTheme
   {
-    if (this.optionsChartEditor.theme == null) this.optionsChartEditor.theme = ChartEditorTheme.Light;
+    if (data.optionsChartEditor.theme == null) data.optionsChartEditor.theme = ChartEditorTheme.Light;
 
-    return this.optionsChartEditor.theme;
+    return data.optionsChartEditor.theme;
   }
 
   function set_chartEditorTheme(value:ChartEditorTheme):ChartEditorTheme
   {
     // Set and apply.
-    this.optionsChartEditor.theme = value;
+    data.optionsChartEditor.theme = value;
     flush();
-    return this.optionsChartEditor.theme;
+    return data.optionsChartEditor.theme;
   }
 
   public var chartEditorMetronomeVolume(get, set):Float;
 
   function get_chartEditorMetronomeVolume():Float
   {
-    if (this.optionsChartEditor.metronomeVolume == null) this.optionsChartEditor.metronomeVolume = 1.0;
+    if (data.optionsChartEditor.metronomeVolume == null) data.optionsChartEditor.metronomeVolume = 1.0;
 
-    return this.optionsChartEditor.metronomeVolume;
+    return data.optionsChartEditor.metronomeVolume;
   }
 
   function set_chartEditorMetronomeVolume(value:Float):Float
   {
     // Set and apply.
-    this.optionsChartEditor.metronomeVolume = value;
+    data.optionsChartEditor.metronomeVolume = value;
     flush();
-    return this.optionsChartEditor.metronomeVolume;
+    return data.optionsChartEditor.metronomeVolume;
   }
 
   public var chartEditorHitsoundVolumePlayer(get, set):Float;
 
   function get_chartEditorHitsoundVolumePlayer():Float
   {
-    if (this.optionsChartEditor.hitsoundVolumePlayer == null) this.optionsChartEditor.hitsoundVolumePlayer = 1.0;
+    if (data.optionsChartEditor.hitsoundVolumePlayer == null) data.optionsChartEditor.hitsoundVolumePlayer = 1.0;
 
-    return this.optionsChartEditor.hitsoundVolumePlayer;
+    return data.optionsChartEditor.hitsoundVolumePlayer;
   }
 
   function set_chartEditorHitsoundVolumePlayer(value:Float):Float
   {
     // Set and apply.
-    this.optionsChartEditor.hitsoundVolumePlayer = value;
+    data.optionsChartEditor.hitsoundVolumePlayer = value;
     flush();
-    return this.optionsChartEditor.hitsoundVolumePlayer;
+    return data.optionsChartEditor.hitsoundVolumePlayer;
   }
 
   public var chartEditorHitsoundVolumeOpponent(get, set):Float;
 
   function get_chartEditorHitsoundVolumeOpponent():Float
   {
-    if (this.optionsChartEditor.hitsoundVolumeOpponent == null) this.optionsChartEditor.hitsoundVolumeOpponent = 1.0;
+    if (data.optionsChartEditor.hitsoundVolumeOpponent == null) data.optionsChartEditor.hitsoundVolumeOpponent = 1.0;
 
-    return this.optionsChartEditor.hitsoundVolumeOpponent;
+    return data.optionsChartEditor.hitsoundVolumeOpponent;
   }
 
   function set_chartEditorHitsoundVolumeOpponent(value:Float):Float
   {
     // Set and apply.
-    this.optionsChartEditor.hitsoundVolumeOpponent = value;
+    data.optionsChartEditor.hitsoundVolumeOpponent = value;
     flush();
-    return this.optionsChartEditor.hitsoundVolumeOpponent;
+    return data.optionsChartEditor.hitsoundVolumeOpponent;
   }
 
   public var chartEditorThemeMusic(get, set):Bool;
 
   function get_chartEditorThemeMusic():Bool
   {
-    if (this.optionsChartEditor.themeMusic == null) this.optionsChartEditor.themeMusic = true;
+    if (data.optionsChartEditor.themeMusic == null) data.optionsChartEditor.themeMusic = true;
 
-    return this.optionsChartEditor.themeMusic;
+    return data.optionsChartEditor.themeMusic;
   }
 
   function set_chartEditorThemeMusic(value:Bool):Bool
   {
     // Set and apply.
-    this.optionsChartEditor.themeMusic = value;
+    data.optionsChartEditor.themeMusic = value;
     flush();
-    return this.optionsChartEditor.themeMusic;
+    return data.optionsChartEditor.themeMusic;
   }
 
   public var chartEditorPlaybackSpeed(get, set):Float;
 
   function get_chartEditorPlaybackSpeed():Float
   {
-    if (this.optionsChartEditor.playbackSpeed == null) this.optionsChartEditor.playbackSpeed = 1.0;
+    if (data.optionsChartEditor.playbackSpeed == null) data.optionsChartEditor.playbackSpeed = 1.0;
 
-    return this.optionsChartEditor.playbackSpeed;
+    return data.optionsChartEditor.playbackSpeed;
   }
 
   function set_chartEditorPlaybackSpeed(value:Float):Float
   {
     // Set and apply.
-    this.optionsChartEditor.playbackSpeed = value;
+    data.optionsChartEditor.playbackSpeed = value;
     flush();
-    return this.optionsChartEditor.playbackSpeed;
+    return data.optionsChartEditor.playbackSpeed;
   }
 
   /**
@@ -377,11 +391,11 @@ abstract Save(RawSaveData)
    */
   public function getLevelScore(levelId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
   {
-    var level = this.scores.levels.get(levelId);
+    var level = data.scores.levels.get(levelId);
     if (level == null)
     {
       level = [];
-      this.scores.levels.set(levelId, level);
+      data.scores.levels.set(levelId, level);
     }
 
     return level.get(difficultyId);
@@ -392,11 +406,11 @@ abstract Save(RawSaveData)
    */
   public function setLevelScore(levelId:String, difficultyId:String, score:SaveScoreData):Void
   {
-    var level = this.scores.levels.get(levelId);
+    var level = data.scores.levels.get(levelId);
     if (level == null)
     {
       level = [];
-      this.scores.levels.set(levelId, level);
+      data.scores.levels.set(levelId, level);
     }
     level.set(difficultyId, score);
 
@@ -405,11 +419,11 @@ abstract Save(RawSaveData)
 
   public function isLevelHighScore(levelId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool
   {
-    var level = this.scores.levels.get(levelId);
+    var level = data.scores.levels.get(levelId);
     if (level == null)
     {
       level = [];
-      this.scores.levels.set(levelId, level);
+      data.scores.levels.set(levelId, level);
     }
 
     var currentScore = level.get(difficultyId);
@@ -448,11 +462,11 @@ abstract Save(RawSaveData)
    */
   public function getSongScore(songId:String, difficultyId:String = 'normal'):Null<SaveScoreData>
   {
-    var song = this.scores.songs.get(songId);
+    var song = data.scores.songs.get(songId);
     if (song == null)
     {
       song = [];
-      this.scores.songs.set(songId, song);
+      data.scores.songs.set(songId, song);
     }
     return song.get(difficultyId);
   }
@@ -462,11 +476,11 @@ abstract Save(RawSaveData)
    */
   public function setSongScore(songId:String, difficultyId:String, score:SaveScoreData):Void
   {
-    var song = this.scores.songs.get(songId);
+    var song = data.scores.songs.get(songId);
     if (song == null)
     {
       song = [];
-      this.scores.songs.set(songId, song);
+      data.scores.songs.set(songId, song);
     }
     song.set(difficultyId, score);
 
@@ -482,11 +496,11 @@ abstract Save(RawSaveData)
    */
   public function isSongHighScore(songId:String, difficultyId:String = 'normal', score:SaveScoreData):Bool
   {
-    var song = this.scores.songs.get(songId);
+    var song = data.scores.songs.get(songId);
     if (song == null)
     {
       song = [];
-      this.scores.songs.set(songId, song);
+      data.scores.songs.set(songId, song);
     }
 
     var currentScore = song.get(difficultyId);
@@ -527,9 +541,9 @@ abstract Save(RawSaveData)
     switch (inputType)
     {
       case Keys:
-        return (playerId == 0) ? this.options.controls.p1.keyboard : this.options.controls.p2.keyboard;
+        return (playerId == 0) ? data.options.controls.p1.keyboard : data.options.controls.p2.keyboard;
       case Gamepad(_):
-        return (playerId == 0) ? this.options.controls.p1.gamepad : this.options.controls.p2.gamepad;
+        return (playerId == 0) ? data.options.controls.p1.gamepad : data.options.controls.p2.gamepad;
     }
   }
 
@@ -547,20 +561,20 @@ abstract Save(RawSaveData)
       case Keys:
         if (playerId == 0)
         {
-          this.options.controls.p1.keyboard = controls;
+          data.options.controls.p1.keyboard = controls;
         }
         else
         {
-          this.options.controls.p2.keyboard = controls;
+          data.options.controls.p2.keyboard = controls;
         }
       case Gamepad(_):
         if (playerId == 0)
         {
-          this.options.controls.p1.gamepad = controls;
+          data.options.controls.p1.gamepad = controls;
         }
         else
         {
-          this.options.controls.p2.gamepad = controls;
+          data.options.controls.p2.gamepad = controls;
         }
     }
 
@@ -581,6 +595,36 @@ abstract Save(RawSaveData)
     }
   }
 
+  /**
+   * The user's current volume setting.
+   */
+  public var volume(get, set):Float;
+
+  function get_volume():Float
+  {
+    return data.volume;
+  }
+
+  function set_volume(value:Float):Float
+  {
+    return data.volume = value;
+  }
+
+  /**
+   * Whether the user's volume is currently muted.
+   */
+  public var mute(get, set):Bool;
+
+  function get_mute():Bool
+  {
+    return data.mute;
+  }
+
+  function set_mute(value:Bool):Bool
+  {
+    return data.mute = value;
+  }
+
   /**
    * Call this to make sure the save data is written to disk.
    */
@@ -606,17 +650,22 @@ abstract Save(RawSaveData)
       if (legacySaveData != null)
       {
         trace('[SAVE] Found legacy save data, converting...');
-        FlxG.save.mergeData(SaveDataMigrator.migrateFromLegacy(legacySaveData));
+        var gameSave = SaveDataMigrator.migrate(legacySaveData);
+        @:privateAccess
+        FlxG.save.mergeData(gameSave.data);
+      }
+      else
+      {
+        trace('[SAVE] No legacy save data found.');
       }
     }
     else
     {
       trace('[SAVE] Loaded save data.');
-      FlxG.save.mergeData(SaveDataMigrator.migrate(FlxG.save.data));
+      @:privateAccess
+      var gameSave = SaveDataMigrator.migrate(FlxG.save.data);
+      FlxG.save.mergeData(gameSave.data);
     }
-
-    trace('[SAVE] Done loading save data.');
-    trace(FlxG.save.data);
   }
 
   static function fetchLegacySaveData():Null<RawSaveData_v1_0_0>
diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx
index f995660f7..00637d52a 100644
--- a/source/funkin/save/migrator/SaveDataMigrator.hx
+++ b/source/funkin/save/migrator/SaveDataMigrator.hx
@@ -19,21 +19,21 @@ class SaveDataMigrator
     {
       trace('[SAVE] No version found in save data! Returning blank data.');
       trace(inputData);
-      return new Save();
+      return new Save(Save.getDefault());
     }
     else
     {
       if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE))
       {
-        // Simply cast the structured data.
-        var save:Save = inputData;
+        // Simply import the structured data.
+        var save:Save = new Save(inputData);
         return save;
       }
       else
       {
         trace('[SAVE] Invalid save data version! Returning blank data.');
         trace(inputData);
-        return new Save();
+        return new Save(Save.getDefault());
       }
     }
   }
@@ -45,7 +45,7 @@ class SaveDataMigrator
   {
     var inputSaveData:RawSaveData_v1_0_0 = cast inputData;
 
-    var result:Save = new Save();
+    var result:Save = new Save(Save.getDefault());
 
     result.volume = inputSaveData.volume;
     result.mute = inputSaveData.mute;
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index a662ce2bd..78e73bf27 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -920,12 +920,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
 
   function get_shouldShowBackupAvailableDialog():Bool
   {
-    return Save.get().chartEditorHasBackup;
+    return Save.instance.chartEditorHasBackup;
   }
 
   function set_shouldShowBackupAvailableDialog(value:Bool):Bool
   {
-    return Save.get().chartEditorHasBackup = value;
+    return Save.instance.chartEditorHasBackup = value;
   }
 
   /**
@@ -2163,7 +2163,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
 
   public function loadPreferences():Void
   {
-    var save:Save = Save.get();
+    var save:Save = Save.instance;
 
     if (previousWorkingFilePaths[0] == null)
     {
@@ -2191,7 +2191,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
 
   public function writePreferences(hasBackup:Bool):Void
   {
-    var save:Save = Save.get();
+    var save:Save = Save.instance;
 
     // Can't use filter() because of null safety checking!
     var filteredWorkingFilePaths:Array<String> = [];
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index a1799481e..50f85571b 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -1001,7 +1001,7 @@ class FreeplayState extends MusicBeatSubState
     var daSong = songs[curSelected];
     if (daSong != null)
     {
-      var songScore:SaveScoreData = Save.get().getSongScore(songs[curSelected].songId, currentDifficulty);
+      var songScore:SaveScoreData = Save.instance.getSongScore(songs[curSelected].songId, currentDifficulty);
       intendedScore = songScore?.score ?? 0;
       intendedCompletion = songScore?.accuracy ?? 0.0;
       rememberedDifficulty = currentDifficulty;
@@ -1189,7 +1189,7 @@ class FreeplayState extends MusicBeatSubState
     var daSongCapsule = grpCapsules.members[curSelected];
     if (daSongCapsule.songData != null)
     {
-      var songScore:SaveScoreData = Save.get().getSongScore(daSongCapsule.songData.songId, currentDifficulty);
+      var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty);
       intendedScore = songScore?.score ?? 0;
       intendedCompletion = songScore?.accuracy ?? 0.0;
       diffIdsCurrent = daSongCapsule.songData.songDifficulties;
diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx
index 9012f3672..ba1d2ed21 100644
--- a/source/funkin/ui/story/StoryMenuState.hx
+++ b/source/funkin/ui/story/StoryMenuState.hx
@@ -649,7 +649,7 @@ class StoryMenuState extends MusicBeatState
     tracklistText.screenCenter(X);
     tracklistText.x -= FlxG.width * 0.35;
 
-    var levelScore:Null<SaveScoreData> = Save.get().getLevelScore(currentLevelId, currentDifficultyId);
+    var levelScore:Null<SaveScoreData> = Save.instance.getLevelScore(currentLevelId, currentDifficultyId);
     highScore = levelScore?.score ?? 0;
     // levelScore.accuracy
   }