1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-03-28 04:50:01 +00:00

Merge pull request #384 from FunkinCrew/bugfix/precache-library-assets

Fix 2hot stuttering issues
This commit is contained in:
Cameron Taylor 2024-03-13 21:48:28 -04:00 committed by GitHub
commit b3b397f810
18 changed files with 263 additions and 94 deletions

2
assets

@ -1 +1 @@
Subproject commit fe8c987eb846ceb73b8518879b506111aaccdf80 Subproject commit f070c4a08fd634dfeb0bdf1fd03579614441722c

View file

@ -30,7 +30,7 @@ import funkin.modding.module.ModuleHandler;
import funkin.ui.title.TitleState; import funkin.ui.title.TitleState;
import funkin.util.CLIUtil; import funkin.util.CLIUtil;
import funkin.util.CLIUtil.CLIParams; import funkin.util.CLIUtil.CLIParams;
import funkin.util.tools.TimerTools; import funkin.util.TimerUtil;
import funkin.ui.transition.LoadingState; import funkin.ui.transition.LoadingState;
import funkin.util.TrackerUtil; import funkin.util.TrackerUtil;
#if discord_rpc #if discord_rpc
@ -221,7 +221,7 @@ class InitState extends FlxState
// NOTE: Registries must be imported and not referenced with fully qualified names, // NOTE: Registries must be imported and not referenced with fully qualified names,
// to ensure build macros work properly. // to ensure build macros work properly.
trace('Parsing game data...'); 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. SongEventRegistry.loadEventCache(); // SongEventRegistry is structured differently so it's not a BaseRegistry.
SongRegistry.instance.loadEntries(); SongRegistry.instance.loadEntries();
LevelRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries();
@ -238,7 +238,7 @@ class InitState extends FlxState
ModuleHandler.loadModuleCache(); ModuleHandler.loadModuleCache();
ModuleHandler.callOnCreate(); ModuleHandler.callOnCreate();
trace('Parsing game data took: ${TimerTools.ms(perfStart)}'); trace('Parsing game data took: ${TimerUtil.ms(perfStart)}');
} }
/** /**

View file

@ -8,6 +8,8 @@ import flixel.sound.FlxSound;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.system.FlxAssets.FlxSoundAsset; import flixel.system.FlxAssets.FlxSoundAsset;
import funkin.util.tools.ICloneable; 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.WaveformData;
import funkin.audio.waveform.WaveformDataParser; import funkin.audio.waveform.WaveformDataParser;
import flixel.math.FlxMath; import flixel.math.FlxMath;
@ -28,7 +30,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
/** /**
* Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible! * Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible!
*/ */
static var cache(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>(); static var pool(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>();
public var muted(default, set):Bool = false; public var muted(default, set):Bool = false;
@ -265,23 +267,55 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
} }
/** /**
* 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 key The key of the music you want to play. Music should be at `music/<key>/<key>.ogg`.
* @param volume How loud to play it (0 to 1). * @param overrideExisting Whether to override music if it is already playing.
* @param looped Whether to loop this sound. * @param mapTimeChanges Whether to check for `SongMusicData` to update the Conductor with.
* @param group The group to add this sound to. * Data should be at `music/<key>/<key>-metadata.json`.
* @param autoDestroy Whether to destroy this sound when it finishes playing. */
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<SongMusicData> = 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. * 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 autoPlay Whether to play the sound immediately or wait for a `play()` call.
* @param onComplete Called when the sound finished playing. * @param onComplete Called when the sound finished playing.
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds. * @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
* @return A `FunkinSound` object. * @return A `FunkinSound` object.
*/ */
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false, 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 ?onComplete:Void->Void, ?onLoad:Void->Void):FunkinSound
{ {
var sound:FunkinSound = cache.recycle(construct); var sound:FunkinSound = pool.recycle(construct);
// Load the sound. // Load the sound.
// Sets `exists = true` as a side effect. // Sets `exists = true` as a side effect.
@ -297,9 +331,11 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
sound.persist = true; sound.persist = true;
if (autoPlay) sound.play(); 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(); if (onLoad != null && sound._sound != null) onLoad();
FlxG.sound.list.remove(FlxG.sound.music);
return sound; return sound;
} }
@ -307,7 +343,7 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
{ {
var sound:FunkinSound = new FunkinSound(); var sound:FunkinSound = new FunkinSound();
cache.add(sound); pool.add(sound);
FlxG.sound.list.add(sound); FlxG.sound.list.add(sound);
return sound; return sound;

View file

@ -1,6 +1,6 @@
package funkin.audio.waveform; package funkin.audio.waveform;
import funkin.util.tools.TimerTools; import funkin.util.TimerUtil;
class WaveformDataParser class WaveformDataParser
{ {
@ -73,7 +73,7 @@ class WaveformDataParser
var outputData:Array<Int> = []; var outputData:Array<Int> = [];
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
for (pointIndex in 0...outputPointCount) for (pointIndex in 0...outputPointCount)
{ {
@ -110,7 +110,7 @@ class WaveformDataParser
var outputDataLength:Int = Std.int(outputData.length / channels / 2); var outputDataLength:Int = Std.int(outputData.length / channels / 2);
var result = new WaveformData(null, channels, sampleRate, samplesPerPoint, bitsPerSample, outputPointCount, outputData); 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; return result;
} }

View file

@ -441,6 +441,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
return {fileName: entryFilePath, contents: rawJson}; 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<BaseRegistry.JsonFile> function loadEntryChartFile(id:String, ?variation:String):Null<BaseRegistry.JsonFile>
{ {
variation = variation == null ? Constants.DEFAULT_VARIATION : variation; variation = variation == null ? Constants.DEFAULT_VARIATION : variation;

View file

@ -1,5 +1,6 @@
package funkin.play; package funkin.play;
import funkin.audio.FunkinSound;
import flixel.addons.display.FlxPieDial; import flixel.addons.display.FlxPieDial;
import flixel.addons.display.FlxPieDial; import flixel.addons.display.FlxPieDial;
import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionableState;
@ -2711,7 +2712,7 @@ class PlayState extends MusicBeatSubState
if (targetSongId == null) if (targetSongId == null)
{ {
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); FunkinSound.playMusic('freakyMenu');
// transIn = FlxTransitionableState.defaultTransIn; // transIn = FlxTransitionableState.defaultTransIn;
// transOut = FlxTransitionableState.defaultTransOut; // transOut = FlxTransitionableState.defaultTransOut;

View file

@ -6,7 +6,7 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxDirection; import flixel.util.FlxDirection;
import funkin.graphics.FunkinSprite; import funkin.graphics.FunkinSprite;
import funkin.play.PlayState; import funkin.play.PlayState;
import funkin.util.tools.TimerTools; import funkin.util.TimerUtil;
class PopUpStuff extends FlxTypedGroup<FlxSprite> class PopUpStuff extends FlxTypedGroup<FlxSprite>
{ {
@ -17,7 +17,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
public function displayRating(daRating:String) public function displayRating(daRating:String)
{ {
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
if (daRating == null) daRating = "good"; if (daRating == null) daRating = "good";
@ -59,12 +59,12 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
startDelay: Conductor.instance.beatLengthMs * 0.001 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 public function displayCombo(?combo:Int = 0):Int
{ {
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
if (combo == null) combo = 0; if (combo == null) combo = 0;
@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
daLoop++; daLoop++;
} }
trace('displayCombo took: ${TimerTools.seconds(perfStart)}'); trace('displayCombo took: ${TimerUtil.seconds(perfStart)}');
return combo; return combo;
} }

View file

@ -7,7 +7,7 @@ import funkin.audio.FunkinSound;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.util.FileUtil; import funkin.util.FileUtil;
import funkin.util.assets.SoundUtil; import funkin.util.assets.SoundUtil;
import funkin.util.tools.TimerTools; import funkin.util.TimerUtil;
import funkin.audio.waveform.WaveformData; import funkin.audio.waveform.WaveformData;
import funkin.audio.waveform.WaveformDataParser; import funkin.audio.waveform.WaveformDataParser;
import funkin.audio.waveform.WaveformSprite; import funkin.audio.waveform.WaveformSprite;
@ -129,41 +129,41 @@ class ChartEditorAudioHandler
public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool 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); var result:Bool = playInstrumental(state, instId);
if (!result) return false; if (!result) return false;
var perfB:Float = TimerTools.start(); var perfB:Float = TimerUtil.start();
stopExistingVocals(state); stopExistingVocals(state);
var perfC:Float = TimerTools.start(); var perfC:Float = TimerUtil.start();
result = playVocals(state, BF, playerId, instId); result = playVocals(state, BF, playerId, instId);
var perfD:Float = TimerTools.start(); var perfD:Float = TimerUtil.start();
// if (!result) return false; // if (!result) return false;
result = playVocals(state, DAD, opponentId, instId); result = playVocals(state, DAD, opponentId, instId);
// if (!result) return false; // if (!result) return false;
var perfE:Float = TimerTools.start(); var perfE:Float = TimerUtil.start();
state.hardRefreshOffsetsToolbox(); state.hardRefreshOffsetsToolbox();
var perfF:Float = TimerTools.start(); var perfF:Float = TimerUtil.start();
state.hardRefreshFreeplayToolbox(); state.hardRefreshFreeplayToolbox();
var perfG:Float = TimerTools.start(); var perfG:Float = TimerUtil.start();
trace('Switched to instrumental in ${TimerTools.seconds(perfA, perfB)}.'); trace('Switched to instrumental in ${TimerUtil.seconds(perfA, perfB)}.');
trace('Stopped existing vocals in ${TimerTools.seconds(perfB, perfC)}.'); trace('Stopped existing vocals in ${TimerUtil.seconds(perfB, perfC)}.');
trace('Played BF vocals in ${TimerTools.seconds(perfC, perfD)}.'); trace('Played BF vocals in ${TimerUtil.seconds(perfC, perfD)}.');
trace('Played DAD vocals in ${TimerTools.seconds(perfD, perfE)}.'); trace('Played DAD vocals in ${TimerUtil.seconds(perfD, perfE)}.');
trace('Hard refreshed offsets toolbox in ${TimerTools.seconds(perfE, perfF)}.'); trace('Hard refreshed offsets toolbox in ${TimerUtil.seconds(perfE, perfF)}.');
trace('Hard refreshed freeplay toolbox in ${TimerTools.seconds(perfF, perfG)}.'); trace('Hard refreshed freeplay toolbox in ${TimerUtil.seconds(perfF, perfG)}.');
return true; return true;
} }
@ -175,9 +175,9 @@ class ChartEditorAudioHandler
{ {
if (instId == '') instId = 'default'; if (instId == '') instId = 'default';
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId); var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
var instTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(instTrackData); var instTrack:Null<FunkinSound> = 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; if (instTrack == null) return false;
stopExistingInstrumental(state); stopExistingInstrumental(state);
@ -205,9 +205,9 @@ class ChartEditorAudioHandler
{ {
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}'; var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId); var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
var vocalTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(vocalTrackData); var vocalTrack:Null<FunkinSound> = 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(); if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
@ -218,9 +218,9 @@ class ChartEditorAudioHandler
case BF: case BF:
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack); state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
var waveformData:Null<WaveformData> = vocalTrack.waveformData; var waveformData:Null<WaveformData> = vocalTrack.waveformData;
trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
if (waveformData != null) if (waveformData != null)
{ {
@ -244,9 +244,9 @@ class ChartEditorAudioHandler
case DAD: case DAD:
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack); state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
var waveformData:Null<WaveformData> = vocalTrack.waveformData; var waveformData:Null<WaveformData> = vocalTrack.waveformData;
trace('Interpreted waveform data in ${TimerTools.seconds(perfStart)}.'); trace('Interpreted waveform data in ${TimerUtil.seconds(perfStart)}.');
if (waveformData != null) if (waveformData != null)
{ {

View file

@ -7,7 +7,7 @@ import funkin.audio.waveform.WaveformDataParser;
import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand; import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand;
import funkin.ui.haxeui.components.WaveformPlayer; import funkin.ui.haxeui.components.WaveformPlayer;
import funkin.ui.freeplay.FreeplayState; import funkin.ui.freeplay.FreeplayState;
import funkin.util.tools.TimerTools; import funkin.util.TimerUtil;
import haxe.ui.backend.flixel.components.SpriteWrapper; import haxe.ui.backend.flixel.components.SpriteWrapper;
import haxe.ui.components.Button; import haxe.ui.components.Button;
import haxe.ui.components.HorizontalSlider; import haxe.ui.components.HorizontalSlider;
@ -289,12 +289,12 @@ class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
// Build player waveform. // Build player waveform.
// waveformMusic.waveform.forceUpdate = true; // waveformMusic.waveform.forceUpdate = true;
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
var waveformData1 = playerVoice?.waveformData; var waveformData1 = playerVoice?.waveformData;
var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file! var waveformData2 = opponentVoice?.waveformData ?? playerVoice?.waveformData; // this null check is for songs that only have 1 vocals file!
var waveformData3 = chartEditorState.audioInstTrack.waveformData; var waveformData3 = chartEditorState.audioInstTrack.waveformData;
var waveformData = waveformData3.merge(waveformData1).merge(waveformData2); 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; 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. // Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.

View file

@ -1,21 +1,19 @@
package funkin.ui.freeplay; package funkin.ui.freeplay;
import flash.text.TextField; import openfl.text.TextField;
import flixel.addons.display.FlxGridOverlay; import flixel.addons.display.FlxGridOverlay;
import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.ui.FlxInputText; import flixel.addons.ui.FlxInputText;
import flixel.FlxCamera; import flixel.FlxCamera;
import flixel.FlxGame; import flixel.FlxGame;
import flixel.FlxSprite; import flixel.FlxSprite;
import funkin.graphics.FunkinSprite;
import flixel.FlxState; import flixel.FlxState;
import flixel.group.FlxGroup; import flixel.group.FlxGroup;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.group.FlxSpriteGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.input.touch.FlxTouch; import flixel.input.touch.FlxTouch;
import flixel.math.FlxAngle; import flixel.math.FlxAngle;
import flixel.math.FlxMath; import flixel.math.FlxMath;
import funkin.graphics.FunkinCamera;
import flixel.math.FlxPoint; import flixel.math.FlxPoint;
import flixel.system.debug.watch.Tracker.TrackerProfile; import flixel.system.debug.watch.Tracker.TrackerProfile;
import flixel.text.FlxText; import flixel.text.FlxText;
@ -24,9 +22,12 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxSpriteUtil; import flixel.util.FlxSpriteUtil;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.audio.FunkinSound;
import funkin.data.level.LevelRegistry; import funkin.data.level.LevelRegistry;
import funkin.data.song.SongRegistry; import funkin.data.song.SongRegistry;
import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.graphics.adobeanimate.FlxAtlasSprite;
import funkin.graphics.FunkinCamera;
import funkin.graphics.FunkinSprite;
import funkin.graphics.shaders.AngleMask; import funkin.graphics.shaders.AngleMask;
import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.HSVShader;
import funkin.graphics.shaders.PureColor; import funkin.graphics.shaders.PureColor;
@ -187,10 +188,7 @@ class FreeplayState extends MusicBeatSubState
isDebug = true; isDebug = true;
#end #end
if (FlxG.sound.music == null || (FlxG.sound.music != null && !FlxG.sound.music.playing)) FunkinSound.playMusic('freakyMenu');
{
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
}
// Add a null entry that represents the RANDOM option // Add a null entry that represents the RANDOM option
songs.push(null); 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; 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. 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); super.update(elapsed);
@ -983,7 +981,7 @@ class FreeplayState extends MusicBeatSubState
} }
} }
function changeDiff(change:Int = 0) function changeDiff(change:Int = 0):Void
{ {
touchTimer = 0; touchTimer = 0;
@ -1173,7 +1171,7 @@ class FreeplayState extends MusicBeatSubState
difficultyStars.difficulty = daSong?.songRating ?? 0; difficultyStars.difficulty = daSong?.songRating ?? 0;
} }
function changeSelection(change:Int = 0) function changeSelection(change:Int = 0):Void
{ {
// NGio.logEvent('Fresh'); // NGio.logEvent('Fresh');
FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); FlxG.sound.play(Paths.sound('scrollMenu'), 0.4);
@ -1228,7 +1226,7 @@ class FreeplayState extends MusicBeatSubState
// TODO: Stream the instrumental of the selected song? // TODO: Stream the instrumental of the selected song?
if (prevSelected == 0) if (prevSelected == 0)
{ {
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); FunkinSound.playMusic('freakyMenu');
FlxG.sound.music.fadeIn(2, 0, 0.8); FlxG.sound.music.fadeIn(2, 0, 0.8);
} }
} }
@ -1259,7 +1257,7 @@ class DifficultySelector extends FlxSprite
flipX = flipped; flipX = flipped;
} }
override function update(elapsed:Float) override function update(elapsed:Float):Void
{ {
if (flipX && controls.UI_RIGHT_P) moveShitDown(); if (flipX && controls.UI_RIGHT_P) moveShitDown();
if (!flipX && controls.UI_LEFT_P) moveShitDown(); if (!flipX && controls.UI_LEFT_P) moveShitDown();
@ -1267,7 +1265,7 @@ class DifficultySelector extends FlxSprite
super.update(elapsed); super.update(elapsed);
} }
function moveShitDown() function moveShitDown():Void
{ {
offset.y -= 5; offset.y -= 5;

View file

@ -12,8 +12,10 @@ import flixel.util.typeLimit.NextState;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.input.touch.FlxTouch; import flixel.input.touch.FlxTouch;
import flixel.text.FlxText; import flixel.text.FlxText;
import funkin.data.song.SongData.SongMusicData;
import flixel.tweens.FlxEase; import flixel.tweens.FlxEase;
import funkin.graphics.FunkinCamera; import funkin.graphics.FunkinCamera;
import funkin.audio.FunkinSound;
import flixel.tweens.FlxTween; import flixel.tweens.FlxTween;
import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatState;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
@ -51,7 +53,7 @@ class MainMenuState extends MusicBeatState
if (!(FlxG?.sound?.music?.playing ?? false)) if (!(FlxG?.sound?.music?.playing ?? false))
{ {
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu')); playMenuMusic();
} }
persistentUpdate = persistentDraw = true; persistentUpdate = persistentDraw = true;
@ -151,6 +153,11 @@ class MainMenuState extends MusicBeatState
// NG.core.calls.event.logEvent('swag').send(); // NG.core.calls.event.logEvent('swag').send();
} }
function playMenuMusic():Void
{
FunkinSound.playMusic('freakyMenu');
}
function resetCamStuff() function resetCamStuff()
{ {
FlxG.cameras.reset(new FunkinCamera()); FlxG.cameras.reset(new FunkinCamera());

View file

@ -16,6 +16,7 @@ import flixel.tweens.FlxTween;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.data.level.LevelRegistry; import funkin.data.level.LevelRegistry;
import funkin.audio.FunkinSound;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.PlayState; import funkin.play.PlayState;
@ -234,17 +235,7 @@ class StoryMenuState extends MusicBeatState
function playMenuMusic():Void function playMenuMusic():Void
{ {
if (FlxG.sound.music == null || !FlxG.sound.music.playing) FunkinSound.playMusic('freakyMenu');
{
var freakyMenuMetadata:Null<SongMusicData> = 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);
}
} }
function updateData():Void function updateData():Void

View file

@ -18,6 +18,7 @@ import funkin.graphics.FunkinSprite;
import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatState;
import funkin.data.song.SongData.SongMusicData; import funkin.data.song.SongData.SongMusicData;
import funkin.graphics.shaders.TitleOutline; import funkin.graphics.shaders.TitleOutline;
import funkin.audio.FunkinSound;
import funkin.ui.freeplay.FreeplayState; import funkin.ui.freeplay.FreeplayState;
import funkin.ui.AtlasText; import funkin.ui.AtlasText;
import openfl.Assets; import openfl.Assets;
@ -219,16 +220,11 @@ class TitleState extends MusicBeatState
function playMenuMusic():Void function playMenuMusic():Void
{ {
if (FlxG.sound.music == null || !FlxG.sound.music.playing) var shouldFadeIn = (FlxG.sound.music == null);
{ // Load music. Includes logic to handle BPM changes.
var freakyMenuMetadata:Null<SongMusicData> = SongRegistry.instance.parseMusicData('freakyMenu'); FunkinSound.playMusic('freakyMenu', false, true);
if (freakyMenuMetadata != null) // Fade from 0.0 to 0.7 over 4 seconds
{ if (shouldFadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7);
Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges);
}
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
FlxG.sound.music.fadeIn(4, 0, 0.7);
}
} }
function getIntroTextShit():Array<Array<String>> function getIntroTextShit():Array<Array<String>>

View file

@ -238,11 +238,38 @@ class LoadingState extends MusicBeatState
FunkinSprite.cacheTexture(Paths.image('shit', 'shared')); FunkinSprite.cacheTexture(Paths.image('shit', 'shared'));
FunkinSprite.cacheTexture(Paths.image('miss', 'shared')); // TODO: remove this 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.cacheAllNoteStyleTextures(noteStyle) // This will replace the stuff above!
// FunkinSprite.cacheAllCharacterTextures(player) // FunkinSprite.cacheAllCharacterTextures(player)
// FunkinSprite.cacheAllCharacterTextures(girlfriend) // FunkinSprite.cacheAllCharacterTextures(girlfriend)
// FunkinSprite.cacheAllCharacterTextures(opponent) // FunkinSprite.cacheAllCharacterTextures(opponent)
// FunkinSprite.cacheAllStageTextures(stage) // FunkinSprite.cacheAllStageTextures(stage)
// FunkinSprite.cacheAllSongTextures(stage)
FunkinSprite.purgeCache(); FunkinSprite.purgeCache();

View file

@ -1,9 +1,9 @@
package funkin.util.tools; package funkin.util;
import funkin.util.tools.FloatTools; import funkin.util.tools.FloatTools;
import haxe.Timer; import haxe.Timer;
class TimerTools class TimerUtil
{ {
public static function start():Float public static function start():Float
{ {

View file

@ -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<String>;
final posInfos:Null<haxe.PosInfos>;
/**
* 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.');
}
}

View file

@ -1,7 +1,7 @@
package funkin.util.plugins; package funkin.util.plugins;
import flixel.FlxBasic; 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. * 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) if (FlxG.keys.justPressed.INSERT)
{ {
var perfStart:Float = TimerTools.start(); var perfStart:Float = TimerUtil.start();
funkin.util.MemoryUtil.collect(true); funkin.util.MemoryUtil.collect(true);
trace('Memory GC took: ${TimerTools.seconds(perfStart)}'); trace('Memory GC took: ${TimerUtil.seconds(perfStart)}');
} }
} }

View file

@ -27,6 +27,36 @@ class StringTools
return result; 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". * Converts a string to lower kebab case. For example, "Hello World" becomes "hello-world".
* *