diff --git a/assets b/assets index 221667bc2..2599e1b6b 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 221667bc207c5c59896f0b8fdf74260be8efd630 +Subproject commit 2599e1b6b039886df62c4d0d19aa14b23708eccd diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 705283768..a9e8dbffa 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -30,7 +30,7 @@ import funkin.modding.module.ModuleHandler; import funkin.ui.title.TitleState; import funkin.util.CLIUtil; import funkin.util.CLIUtil.CLIParams; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; import funkin.ui.transition.LoadingState; import funkin.util.TrackerUtil; #if discord_rpc @@ -159,7 +159,7 @@ class InitState extends FlxState // NOTE: Registries must be imported and not referenced with fully qualified names, // to ensure build macros work properly. trace('Parsing game data...'); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry. SongRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries(); @@ -176,7 +176,7 @@ class InitState extends FlxState ModuleHandler.loadModuleCache(); ModuleHandler.callOnCreate(); - trace('Parsing game data took: ${TimerTools.ms(perfStart)}'); + trace('Parsing game data took: ${TimerUtil.ms(perfStart)}'); } /** diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index a1e14d705..9efa6ed50 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -8,6 +8,8 @@ import flixel.sound.FlxSound; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.system.FlxAssets.FlxSoundAsset; import funkin.util.tools.ICloneable; +import funkin.data.song.SongData.SongMusicData; +import funkin.data.song.SongRegistry; import funkin.audio.waveform.WaveformData; import funkin.audio.waveform.WaveformDataParser; import flixel.math.FlxMath; @@ -28,7 +30,7 @@ class FunkinSound extends FlxSound implements ICloneable /** * Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible! */ - static var cache(default, null):FlxTypedGroup = new FlxTypedGroup(); + static var pool(default, null):FlxTypedGroup = new FlxTypedGroup(); public var muted(default, set):Bool = false; @@ -265,23 +267,55 @@ class FunkinSound extends FlxSound implements ICloneable } /** - * Creates a new `FunkinSound` object. + * Creates a new `FunkinSound` object and loads it as the current music track. * - * @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead. - * @param volume How loud to play it (0 to 1). - * @param looped Whether to loop this sound. - * @param group The group to add this sound to. - * @param autoDestroy Whether to destroy this sound when it finishes playing. + * @param key The key of the music you want to play. Music should be at `music//.ogg`. + * @param overrideExisting Whether to override music if it is already playing. + * @param mapTimeChanges Whether to check for `SongMusicData` to update the Conductor with. + * Data should be at `music//-metadata.json`. + */ + public static function playMusic(key:String, overrideExisting:Bool = false, mapTimeChanges:Bool = true):Void + { + if (!overrideExisting && FlxG.sound.music?.playing) return; + + if (mapTimeChanges) + { + var songMusicData:Null = SongRegistry.instance.parseMusicData(key); + // Will fall back and return null if the metadata doesn't exist or can't be parsed. + if (songMusicData != null) + { + Conductor.instance.mapTimeChanges(songMusicData.timeChanges); + } + else + { + FlxG.log.warn('Tried and failed to find music metadata for $key'); + } + } + + FlxG.sound.music = FunkinSound.load(Paths.music('$key/$key')); + + // Prevent repeat update() and onFocus() calls. + FlxG.sound.list.remove(FlxG.sound.music); + } + + /** + * Creates a new `FunkinSound` object synchronously. + * + * @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead. + * @param volume How loud to play it (0 to 1). + * @param looped Whether to loop this sound. + * @param group The group to add this sound to. + * @param autoDestroy Whether to destroy this sound when it finishes playing. * Leave this value set to `false` if you want to re-use this `FunkinSound` instance. - * @param autoPlay Whether to play the sound immediately or wait for a `play()` call. - * @param onComplete Called when the sound finished playing. - * @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds. - * @return A `FunkinSound` object. + * @param autoPlay Whether to play the sound immediately or wait for a `play()` call. + * @param onComplete Called when the sound finished playing. + * @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds. + * @return A `FunkinSound` object. */ public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false, ?onComplete:Void->Void, ?onLoad:Void->Void):FunkinSound { - var sound:FunkinSound = cache.recycle(construct); + var sound:FunkinSound = pool.recycle(construct); // Load the sound. // Sets `exists = true` as a side effect. @@ -297,9 +331,11 @@ class FunkinSound extends FlxSound implements ICloneable sound.persist = true; if (autoPlay) sound.play(); - // Call OnlLoad() because the sound already loaded + // Call onLoad() because the sound already loaded if (onLoad != null && sound._sound != null) onLoad(); + FlxG.sound.list.remove(FlxG.sound.music); + return sound; } @@ -307,7 +343,7 @@ class FunkinSound extends FlxSound implements ICloneable { var sound:FunkinSound = new FunkinSound(); - cache.add(sound); + pool.add(sound); FlxG.sound.list.add(sound); return sound; diff --git a/source/funkin/audio/waveform/WaveformDataParser.hx b/source/funkin/audio/waveform/WaveformDataParser.hx index c667f2002..5aa54d744 100644 --- a/source/funkin/audio/waveform/WaveformDataParser.hx +++ b/source/funkin/audio/waveform/WaveformDataParser.hx @@ -1,6 +1,6 @@ package funkin.audio.waveform; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; class WaveformDataParser { @@ -73,7 +73,7 @@ class WaveformDataParser var outputData:Array = []; - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); for (pointIndex in 0...outputPointCount) { @@ -110,7 +110,7 @@ class WaveformDataParser var outputDataLength:Int = Std.int(outputData.length / channels / 2); var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData); - trace('[WAVEFORM] Interpreted audio buffer in ${TimerTools.seconds(perfStart)}.'); + trace('[WAVEFORM] Interpreted audio buffer in ${TimerUtil.seconds(perfStart)}.'); return result; } diff --git a/source/funkin/data/song/SongRegistry.hx b/source/funkin/data/song/SongRegistry.hx index dad287e82..e2edc055a 100644 --- a/source/funkin/data/song/SongRegistry.hx +++ b/source/funkin/data/song/SongRegistry.hx @@ -441,6 +441,13 @@ class SongRegistry extends BaseRegistry return {fileName: entryFilePath, contents: rawJson}; } + function hasMusicDataFile(id:String, ?variation:String):Bool + { + variation = variation == null ? Constants.DEFAULT_VARIATION : variation; + var entryFilePath:String = Paths.file('music/$id/$id-metadata${variation == Constants.DEFAULT_VARIATION ? '' : '-$variation'}.json'); + return openfl.Assets.exists(entryFilePath); + } + function loadEntryChartFile(id:String, ?variation:String):Null { variation = variation == null ? Constants.DEFAULT_VARIATION : variation; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a6e4b4632..7609c356b 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,5 +1,6 @@ package funkin.play; +import funkin.audio.FunkinSound; import flixel.addons.display.FlxPieDial; import flixel.addons.display.FlxPieDial; import flixel.addons.transition.FlxTransitionableState; @@ -2711,7 +2712,7 @@ class PlayState extends MusicBeatSubState if (targetSongId == null) { - FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); + FunkinSound.playMusic('freakyMenu'); // transIn = FlxTransitionableState.defaultTransIn; // transOut = FlxTransitionableState.defaultTransOut; diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index 0fe50f513..69ce40140 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -6,7 +6,7 @@ import flixel.tweens.FlxTween; import flixel.util.FlxDirection; import funkin.graphics.FunkinSprite; import funkin.play.PlayState; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; class PopUpStuff extends FlxTypedGroup { @@ -17,7 +17,7 @@ class PopUpStuff extends FlxTypedGroup public function displayRating(daRating:String) { - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); if (daRating == null) daRating = "good"; @@ -59,12 +59,12 @@ class PopUpStuff extends FlxTypedGroup startDelay: Conductor.instance.beatLengthMs * 0.001 }); - trace('displayRating took: ${TimerTools.seconds(perfStart)}'); + trace('displayRating took: ${TimerUtil.seconds(perfStart)}'); } public function displayCombo(?combo:Int = 0):Int { - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); if (combo == null) combo = 0; @@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup daLoop++; } - trace('displayCombo took: ${TimerTools.seconds(perfStart)}'); + trace('displayCombo took: ${TimerUtil.seconds(perfStart)}'); return combo; } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 889f5764f..1e1d165f3 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -7,7 +7,7 @@ import funkin.audio.FunkinSound; import funkin.play.character.BaseCharacter.CharacterType; import funkin.util.FileUtil; import funkin.util.assets.SoundUtil; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; import funkin.audio.waveform.WaveformData; import funkin.audio.waveform.WaveformDataParser; import funkin.audio.waveform.WaveformSprite; @@ -129,41 +129,41 @@ class ChartEditorAudioHandler public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool { - var perfA:Float = TimerTools.start(); + var perfA:Float = TimerUtil.start(); var result:Bool = playInstrumental(state, instId); if (!result) return false; - var perfB:Float = TimerTools.start(); + var perfB:Float = TimerUtil.start(); stopExistingVocals(state); - var perfC:Float = TimerTools.start(); + var perfC:Float = TimerUtil.start(); result = playVocals(state, BF, playerId, instId); - var perfD:Float = TimerTools.start(); + var perfD:Float = TimerUtil.start(); // if (!result) return false; result = playVocals(state, DAD, opponentId, instId); // if (!result) return false; - var perfE:Float = TimerTools.start(); + var perfE:Float = TimerUtil.start(); state.hardRefreshOffsetsToolbox(); - var perfF:Float = TimerTools.start(); + var perfF:Float = TimerUtil.start(); state.hardRefreshFreeplayToolbox(); - var perfG:Float = TimerTools.start(); + var perfG:Float = TimerUtil.start(); - trace('Switched to instrumental in ${TimerTools.seconds(perfA, perfB)}.'); - trace('Stopped existing vocals in ${TimerTools.seconds(perfB, perfC)}.'); - trace('Played BF vocals in ${TimerTools.seconds(perfC, perfD)}.'); - trace('Played DAD vocals in ${TimerTools.seconds(perfD, perfE)}.'); - trace('Hard refreshed offsets toolbox in ${TimerTools.seconds(perfE, perfF)}.'); - trace('Hard refreshed freeplay toolbox in ${TimerTools.seconds(perfF, perfG)}.'); + trace('Switched to instrumental in ${TimerUtil.seconds(perfA, perfB)}.'); + trace('Stopped existing vocals in ${TimerUtil.seconds(perfB, perfC)}.'); + trace('Played BF vocals in ${TimerUtil.seconds(perfC, perfD)}.'); + trace('Played DAD vocals in ${TimerUtil.seconds(perfD, perfE)}.'); + trace('Hard refreshed offsets toolbox in ${TimerUtil.seconds(perfE, perfF)}.'); + trace('Hard refreshed freeplay toolbox in ${TimerUtil.seconds(perfF, perfG)}.'); return true; } @@ -175,9 +175,9 @@ class ChartEditorAudioHandler { if (instId == '') instId = 'default'; var instTrackData:Null = state.audioInstTrackData.get(instId); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var instTrack:Null = SoundUtil.buildSoundFromBytes(instTrackData); - trace('Built instrumental track in ${TimerTools.seconds(perfStart)} seconds.'); + trace('Built instrumental track in ${TimerUtil.seconds(perfStart)} seconds.'); if (instTrack == null) return false; stopExistingInstrumental(state); @@ -205,9 +205,9 @@ class ChartEditorAudioHandler { var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}'; var vocalTrackData:Null = state.audioVocalTrackData.get(trackId); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var vocalTrack:Null = SoundUtil.buildSoundFromBytes(vocalTrackData); - trace('Built vocal track in ${TimerTools.seconds(perfStart)}.'); + trace('Built vocal track in ${TimerUtil.seconds(perfStart)}.'); if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup(); @@ -218,9 +218,9 @@ class ChartEditorAudioHandler case BF: state.audioVocalTrackGroup.addPlayerVoice(vocalTrack); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var waveformData:Null = vocalTrack.waveformData; - trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); + trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.'); if (waveformData != null) { @@ -244,9 +244,9 @@ class ChartEditorAudioHandler case DAD: state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var waveformData:Null = vocalTrack.waveformData; - trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); + trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.'); if (waveformData != null) { diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx index 28d435c54..c65781259 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorFreeplayToolbox.hx @@ -7,7 +7,7 @@ import funkin.audio.waveform.WaveformDataParser; import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand; import funkin.ui.haxeui.components.WaveformPlayer; import funkin.ui.freeplay.FreeplayState; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; import haxe.ui.backend.flixel.components.SpriteWrapper; import haxe.ui.components.Button; import haxe.ui.components.HorizontalSlider; @@ -289,12 +289,12 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox // Build player waveform. // waveformMusic.waveform.forceUpdate = true; - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); var waveformData1 = playerVoice?.waveformData; var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file! var waveformData3 = chartEditorState.audioInstTrack.waveformData; var waveformData = waveformData3.merge(waveformData1).merge(waveformData2); - trace('Waveform data merging took: ${TimerTools.seconds(perfStart)}'); + trace('Waveform data merging took: ${TimerUtil.seconds(perfStart)}'); waveformMusic.waveform.waveformData = waveformData; // Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it. diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 45f9a4d27..ec873d103 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1,21 +1,19 @@ package funkin.ui.freeplay; -import flash.text.TextField; +import openfl.text.TextField; import flixel.addons.display.FlxGridOverlay; import flixel.addons.transition.FlxTransitionableState; import flixel.addons.ui.FlxInputText; import flixel.FlxCamera; import flixel.FlxGame; import flixel.FlxSprite; -import funkin.graphics.FunkinSprite; import flixel.FlxState; import flixel.group.FlxGroup; import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.group.FlxSpriteGroup; +import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.input.touch.FlxTouch; import flixel.math.FlxAngle; import flixel.math.FlxMath; -import funkin.graphics.FunkinCamera; import flixel.math.FlxPoint; import flixel.system.debug.watch.Tracker.TrackerProfile; import flixel.text.FlxText; @@ -24,9 +22,12 @@ import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxSpriteUtil; import flixel.util.FlxTimer; +import funkin.audio.FunkinSound; import funkin.data.level.LevelRegistry; import funkin.data.song.SongRegistry; import funkin.graphics.adobeanimate.FlxAtlasSprite; +import funkin.graphics.FunkinCamera; +import funkin.graphics.FunkinSprite; import funkin.graphics.shaders.AngleMask; import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.PureColor; @@ -187,10 +188,7 @@ class FreeplayState extends MusicBeatSubState isDebug = true; #end - if (FlxG.sound.music == null || (FlxG.sound.music != null && !FlxG.sound.music.playing)) - { - FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); - } + FunkinSound.playMusic('freakyMenu'); // Add a null entry that represents the RANDOM option songs.push(null); @@ -590,7 +588,7 @@ class FreeplayState extends MusicBeatSubState }); } - public function generateSongList(?filterStuff:SongFilter, force:Bool = false) + public function generateSongList(?filterStuff:SongFilter, force:Bool = false):Void { curSelected = 1; @@ -693,7 +691,7 @@ class FreeplayState extends MusicBeatSubState var busy:Bool = false; // Set to true once the user has pressed enter to select a song. - override function update(elapsed:Float) + override function update(elapsed:Float):Void { super.update(elapsed); @@ -983,7 +981,7 @@ class FreeplayState extends MusicBeatSubState } } - function changeDiff(change:Int = 0) + function changeDiff(change:Int = 0):Void { touchTimer = 0; @@ -1173,7 +1171,7 @@ class FreeplayState extends MusicBeatSubState difficultyStars.difficulty = daSong?.songRating ?? 0; } - function changeSelection(change:Int = 0) + function changeSelection(change:Int = 0):Void { // NGio.logEvent('Fresh'); FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); @@ -1228,7 +1226,7 @@ class FreeplayState extends MusicBeatSubState // TODO: Stream the instrumental of the selected song? if (prevSelected == 0) { - FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); + FunkinSound.playMusic('freakyMenu'); FlxG.sound.music.fadeIn(2, 0, 0.8); } } @@ -1259,7 +1257,7 @@ class DifficultySelector extends FlxSprite flipX = flipped; } - override function update(elapsed:Float) + override function update(elapsed:Float):Void { if (flipX && controls.UI_RIGHT_P) moveShitDown(); if (!flipX && controls.UI_LEFT_P) moveShitDown(); @@ -1267,7 +1265,7 @@ class DifficultySelector extends FlxSprite super.update(elapsed); } - function moveShitDown() + function moveShitDown():Void { offset.y -= 5; diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 8842c37de..1892bdec1 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -12,8 +12,10 @@ import flixel.util.typeLimit.NextState; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.input.touch.FlxTouch; import flixel.text.FlxText; +import funkin.data.song.SongData.SongMusicData; import flixel.tweens.FlxEase; import funkin.graphics.FunkinCamera; +import funkin.audio.FunkinSound; import flixel.tweens.FlxTween; import funkin.ui.MusicBeatState; import flixel.util.FlxTimer; @@ -51,7 +53,7 @@ class MainMenuState extends MusicBeatState if (!(FlxG?.sound?.music?.playing ?? false)) { - FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); + playMenuMusic(); } persistentUpdate = persistentDraw = true; @@ -151,6 +153,11 @@ class MainMenuState extends MusicBeatState // NG.core.calls.event.logEvent('swag').send(); } + function playMenuMusic():Void + { + FunkinSound.playMusic('freakyMenu'); + } + function resetCamStuff() { FlxG.cameras.reset(new FunkinCamera()); diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 404dfb67e..1f78eb375 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -16,6 +16,7 @@ import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxTimer; import funkin.data.level.LevelRegistry; +import funkin.audio.FunkinSound; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.play.PlayState; @@ -234,17 +235,7 @@ class StoryMenuState extends MusicBeatState function playMenuMusic():Void { - if (FlxG.sound.music == null || !FlxG.sound.music.playing) - { - var freakyMenuMetadata:Null = SongRegistry.instance.parseMusicData('freakyMenu'); - if (freakyMenuMetadata != null) - { - Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges); - } - - FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); - FlxG.sound.music.fadeIn(4, 0, 0.7); - } + FunkinSound.playMusic('freakyMenu'); } function updateData():Void diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 5424e2255..1c194d80d 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -18,6 +18,7 @@ import funkin.graphics.FunkinSprite; import funkin.ui.MusicBeatState; import funkin.data.song.SongData.SongMusicData; import funkin.graphics.shaders.TitleOutline; +import funkin.audio.FunkinSound; import funkin.ui.freeplay.FreeplayState; import funkin.ui.AtlasText; import openfl.Assets; @@ -219,16 +220,11 @@ class TitleState extends MusicBeatState function playMenuMusic():Void { - if (FlxG.sound.music == null || !FlxG.sound.music.playing) - { - var freakyMenuMetadata:Null = SongRegistry.instance.parseMusicData('freakyMenu'); - if (freakyMenuMetadata != null) - { - Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges); - } - FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); - FlxG.sound.music.fadeIn(4, 0, 0.7); - } + var shouldFadeIn = (FlxG.sound.music == null); + // Load music. Includes logic to handle BPM changes. + FunkinSound.playMusic('freakyMenu', false, true); + // Fade from 0.0 to 0.7 over 4 seconds + if (shouldFadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7); } function getIntroTextShit():Array> diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index 5f755872f..c53af36de 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -238,11 +238,38 @@ class LoadingState extends MusicBeatState FunkinSprite.cacheTexture(Paths.image('shit', 'shared')); FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this + // List all image assets in the level's library. + // This is crude and I want to remove it when we have a proper asset caching system. + // TODO: Get rid of this junk! + var library = openfl.utils.Assets.getLibrary(PlayStatePlaylist.campaignId); + var assets = library.list(lime.utils.AssetType.IMAGE); + trace('Got ${assets.length} assets: ${assets}'); + + // TODO: assets includes non-images! This is a bug with Polymod + for (asset in assets) + { + // Exclude items of the wrong type. + var path = '${PlayStatePlaylist.campaignId}:${asset}'; + // TODO DUMB HACK DUMB HACK why doesn't filtering by AssetType.IMAGE above work + // I will fix this properly later I swear -eric + if (!path.endsWith('.png')) continue; + + FunkinSprite.cacheTexture(path); + + // Another dumb hack: FlxAnimate fetches from OpenFL's BitmapData cache directly and skips the FlxGraphic cache. + // Since FlxGraphic tells OpenFL to not cache it, we have to do it manually. + if (path.endsWith('spritemap1.png')) + { + openfl.Assets.getBitmapData(path, true); + } + } + // FunkinSprite.cacheAllNoteStyleTextures(noteStyle) // This will replace the stuff above! // FunkinSprite.cacheAllCharacterTextures(player) // FunkinSprite.cacheAllCharacterTextures(girlfriend) // FunkinSprite.cacheAllCharacterTextures(opponent) // FunkinSprite.cacheAllStageTextures(stage) + // FunkinSprite.cacheAllSongTextures(stage) FunkinSprite.purgeCache(); diff --git a/source/funkin/util/tools/TimerTools.hx b/source/funkin/util/TimerUtil.hx similarity index 93% rename from source/funkin/util/tools/TimerTools.hx rename to source/funkin/util/TimerUtil.hx index 5322ada92..caf49341b 100644 --- a/source/funkin/util/tools/TimerTools.hx +++ b/source/funkin/util/TimerUtil.hx @@ -1,9 +1,9 @@ -package funkin.util.tools; +package funkin.util; import funkin.util.tools.FloatTools; import haxe.Timer; -class TimerTools +class TimerUtil { public static function start():Float { diff --git a/source/funkin/util/logging/Perf.hx b/source/funkin/util/logging/Perf.hx new file mode 100644 index 000000000..83da7a32f --- /dev/null +++ b/source/funkin/util/logging/Perf.hx @@ -0,0 +1,76 @@ +package funkin.util.logging; + +/** + * A small utility class for timing how long functions take. + * Specify a string as a label (or don't, by default it uses the name of the function it was called from.) + * + * Example: + * ```haxe + * + * var perf = new Perf(); + * ... + * perf.print(); + * ``` + */ +class Perf +{ + final startTime:Float; + final label:Null; + final posInfos:Null; + + /** + * Create a new performance marker. + * @param label Optionally specify a label to use for the performance marker. Defaults to the function name. + * @param posInfos The position of the calling function. Used to build the default label. + * Note: `haxe.PosInfos` is magic and automatically populated by the compiler! + */ + public function new(?label:String, ?posInfos:haxe.PosInfos) + { + this.label = label; + this.posInfos = posInfos; + startTime = current(); + } + + /** + * The current timestamp, in fractional seconds. + * @return The current timestamp. + */ + static function current():Float + { + #if sys + // This one is more accurate if it's available. + return Sys.time(); + #else + return haxe.Timer.stamp(); + #end + } + + /** + * The duration in seconds since this `Perf` was created. + * @return The duration in seconds + */ + public function duration():Float + { + return current() - startTime; + } + + /** + * A rounded millisecond duration + * @return The duration in milliseconds + */ + public function durationClean():Float + { + var round:Float = 100; + return Math.floor(duration() * Constants.MS_PER_SEC * round) / round; + } + + /** + * Cleanly prints the duration since this `Perf` was created. + */ + public function print():Void + { + var label:String = label ?? (posInfos == null ? 'unknown' : '${posInfos.className}#${posInfos.methodName}()'); + + trace('[PERF] [$label] Took ${durationClean()}ms.'); + } +} diff --git a/source/funkin/util/plugins/MemoryGCPlugin.hx b/source/funkin/util/plugins/MemoryGCPlugin.hx index 67a4fe18e..4b89fa637 100644 --- a/source/funkin/util/plugins/MemoryGCPlugin.hx +++ b/source/funkin/util/plugins/MemoryGCPlugin.hx @@ -1,7 +1,7 @@ package funkin.util.plugins; import flixel.FlxBasic; -import funkin.util.tools.TimerTools; +import funkin.util.TimerUtil; /** * A plugin which adds functionality to press `Ins` to immediately perform memory garbage collection. @@ -24,9 +24,9 @@ class MemoryGCPlugin extends FlxBasic if (FlxG.keys.justPressed.INSERT) { - var perfStart:Float = TimerTools.start(); + var perfStart:Float = TimerUtil.start(); funkin.util.MemoryUtil.collect(true); - trace('Memory GC took: ${TimerTools.seconds(perfStart)}'); + trace('Memory GC took: ${TimerUtil.seconds(perfStart)}'); } } diff --git a/source/funkin/util/tools/StringTools.hx b/source/funkin/util/tools/StringTools.hx index 0585ffeae..e69a13f1a 100644 --- a/source/funkin/util/tools/StringTools.hx +++ b/source/funkin/util/tools/StringTools.hx @@ -27,6 +27,36 @@ class StringTools return result; } + /** + * Strip a given prefix from a string. + * @param value The string to strip. + * @param prefix The prefix to strip. If the prefix isn't found, the original string is returned. + * @return The stripped string. + */ + public static function stripPrefix(value:String, prefix:String):String + { + if (value.startsWith(prefix)) + { + return value.substr(prefix.length); + } + return value; + } + + /** + * Strip a given suffix from a string. + * @param value The string to strip. + * @param suffix The suffix to strip. If the suffix isn't found, the original string is returned. + * @return The stripped string. + */ + public static function stripSuffix(value:String, suffix:String):String + { + if (value.endsWith(suffix)) + { + return value.substr(0, value.length - suffix.length); + } + return value; + } + /** * Converts a string to lower kebab case. For example, "Hello World" becomes "hello-world". *