diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 7f7e2b356..4b1261d4b 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -1,23 +1,18 @@ package funkin; -import funkin.play.song.SongData.SongTimeChange; +import funkin.util.Constants; import flixel.util.FlxSignal; +import flixel.math.FlxMath; +import funkin.SongLoad.SwagSong; import funkin.play.song.Song.SongDifficulty; - -typedef BPMChangeEvent = -{ - var stepTime:Int; - var songTime:Float; - var bpm:Float; -} +import funkin.play.song.SongData.SongTimeChange; /** - * A global source of truth for timing information. + * A core class which handles musical timing throughout the game, + * both in gameplay and in menus. */ class Conductor { - static final STEPS_PER_BEAT:Int = 4; - // onBeatHit is called every quarter note // onStepHit is called every sixteenth note // 4/4 = 4 beats per measure = 16 steps per measure @@ -33,11 +28,22 @@ class Conductor // 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second // 7/8 = 3.5 beats per measure = 14 steps per measure + /** + * The list of time changes in the song. + * There should be at least one time change (at the beginning of the song) to define the BPM. + */ + static var timeChanges:Array = []; + + /** + * The current time change. + */ + static var currentTimeChange:SongTimeChange; + /** * The current position in the song in milliseconds. * Updated every frame based on the audio position. */ - public static var songPosition:Float; + public static var songPosition:Float = 0; /** * Beats per minute of the current song at the current time. @@ -48,33 +54,17 @@ class Conductor { if (bpmOverride != null) return bpmOverride; - if (currentTimeChange == null) return 100; + if (currentTimeChange == null) return Constants.DEFAULT_BPM; return currentTimeChange.bpm; } + /** + * The current value set by `forceBPM`. + * If false, BPM is determined by time changes. + */ static var bpmOverride:Null = null; - /** - * Current position in the song, in whole measures. - */ - public static var currentMeasure(default, null):Int; - - /** - * Current position in the song, in whole beats. - **/ - public static var currentBeat(default, null):Int; - - /** - * Current position in the song, in whole steps. - */ - public static var currentStep(default, null):Int; - - /** - * Current position in the song, in steps and fractions of a step. - */ - public static var currentStepTime(default, null):Float; - /** * Duration of a measure in milliseconds. Calculated based on bpm. */ @@ -86,119 +76,99 @@ class Conductor } /** - * Duration of a beat (quarter note) in milliseconds. Calculated based on bpm. + * Duration of a beat in milliseconds. Calculated based on bpm. */ public static var beatLengthMs(get, null):Float; static function get_beatLengthMs():Float { - // Tied directly to BPM. - return ((60 / bpm) * 1000); + return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC); } /** - * Duration of a step (sixteenth) in milliseconds. Calculated based on bpm. + * Duration of a step (quarter) in milliseconds. Calculated based on bpm. */ public static var stepLengthMs(get, null):Float; static function get_stepLengthMs():Float { - return beatLengthMs / STEPS_PER_BEAT; + return beatLengthMs / timeSignatureNumerator; } - /** - * The numerator of the current time signature (number of notes in a measure) - */ public static var timeSignatureNumerator(get, null):Int; static function get_timeSignatureNumerator():Int { - if (currentTimeChange == null) return 4; + if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_NUM; return currentTimeChange.timeSignatureNum; } - /** - * The numerator of the current time signature (length of notes in a measure) - */ public static var timeSignatureDenominator(get, null):Int; static function get_timeSignatureDenominator():Int { - if (currentTimeChange == null) return 4; + if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_DEN; return currentTimeChange.timeSignatureDen; } - public static var offset:Float = 0; - - // TODO: What's the difference between visualOffset and audioOffset? - public static var visualOffset:Float = 0; - public static var audioOffset:Float = 0; - - // - // Signals - // + /** + * Current position in the song, in measures. + */ + public static var currentMeasure(default, null):Int; /** - * Signal that is dispatched every measure. - * At 120 BPM 4/4, this is dispatched every 2 seconds. - * At 120 BPM 3/4, this is dispatched every 1.5 seconds. + * Current position in the song, in beats. */ - public static var measureHit(default, null):FlxSignal = new FlxSignal(); + public static var currentBeat(default, null):Int; /** - * Signal that is dispatched every beat. - * At 120 BPM 4/4, this is dispatched every 0.5 seconds. - * At 120 BPM 3/4, this is dispatched every 0.5 seconds. + * Current position in the song, in steps. */ + public static var currentStep(default, null):Int; + + /** + * Current position in the song, in measures and fractions of a measure. + */ + public static var currentMeasureTime(default, null):Float; + + /** + * Current position in the song, in beats and fractions of a measure. + */ + public static var currentBeatTime(default, null):Float; + + /** + * Current position in the song, in steps and fractions of a step. + */ + public static var currentStepTime(default, null):Float; + public static var beatHit(default, null):FlxSignal = new FlxSignal(); - - /** - * Signal that is dispatched when a step is hit. - * At 120 BPM 4/4, this is dispatched every 0.125 seconds. - * At 120 BPM 3/4, this is dispatched every 0.125 seconds. - */ public static var stepHit(default, null):FlxSignal = new FlxSignal(); - // - // Internal Variables - // - - /** - * The list of time changes in the song. - * There should be at least one time change (at the beginning of the song) to define the BPM. - */ - static var timeChanges:Array = []; - - /** - * The current time change. - */ - static var currentTimeChange:SongTimeChange; - public static var lastSongPos:Float; + public static var visualOffset:Float = 0; + public static var audioOffset:Float = 0; + public static var offset:Float = 0; - /** - * The number of beats (whole notes) in a measure. - */ - public static var beatsPerMeasure(get, null):Int; + public static var beatsPerMeasure(get, null):Float; - static function get_beatsPerMeasure():Int + static function get_beatsPerMeasure():Float { - return timeSignatureNumerator; + // NOTE: Not always an integer, for example 7/8 is 3.5 beats per measure + return stepsPerMeasure / Constants.STEPS_PER_BEAT; } - /** - * The number of steps (quarter-notes) in a measure. - */ public static var stepsPerMeasure(get, null):Int; static function get_stepsPerMeasure():Int { - // This is always 4, b - return timeSignatureNumerator * 4; + // TODO: Is this always an integer? + return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT); } + function new() {} + /** * Forcibly defines the current BPM of the song. * Useful for things like the chart editor that need to manipulate BPM in real time. @@ -208,16 +178,11 @@ class Conductor * WARNING: Avoid this for things like setting the BPM of the title screen music, * you should have a metadata file for it instead. */ - public static function forceBPM(?bpm:Float = null):Void + public static function forceBPM(?bpm:Float = null) { - if (bpm != null) - { - trace('[CONDUCTOR] Forcing BPM to ' + bpm); - } + if (bpm != null) trace('[CONDUCTOR] Forcing BPM to ' + bpm); else - { trace('[CONDUCTOR] Resetting BPM to default'); - } Conductor.bpmOverride = bpm; } @@ -228,13 +193,12 @@ class Conductor * @param songPosition The current position in the song in milliseconds. * Leave blank to use the FlxG.sound.music position. */ - public static function update(songPosition:Float = null):Void + public static function update(songPosition:Float = null) { if (songPosition == null) songPosition = (FlxG.sound.music != null) ? FlxG.sound.music.time + Conductor.offset : 0.0; - var oldMeasure:Int = currentMeasure; - var oldBeat:Int = currentBeat; - var oldStep:Int = currentStep; + var oldBeat = currentBeat; + var oldStep = currentStep; Conductor.songPosition = songPosition; @@ -252,16 +216,23 @@ class Conductor } else if (currentTimeChange != null) { - currentStepTime = (currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs; + // roundDecimal prevents representing 8 as 7.9999999 + currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6); + currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; + currentMeasureTime = currentStepTime / stepsPerMeasure; currentStep = Math.floor(currentStepTime); - currentBeat = Math.floor(currentStep / 4); + currentBeat = Math.floor(currentBeatTime); + currentMeasure = Math.floor(currentMeasureTime); } else { // Assume a constant BPM equal to the forced value. - currentStepTime = (songPosition / stepLengthMs); + currentStepTime = FlxMath.roundDecimal((songPosition / stepLengthMs), 4); + currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; + currentMeasureTime = currentStepTime / stepsPerMeasure; currentStep = Math.floor(currentStepTime); - currentBeat = Math.floor(currentStep / 4); + currentBeat = Math.floor(currentBeatTime); + currentMeasure = Math.floor(currentMeasureTime); } // FlxSignals are really cool. @@ -274,31 +245,52 @@ class Conductor { beatHit.dispatch(); } - - if (currentMeasure != oldMeasure) - { - measureHit.dispatch(); - } } - public static function mapTimeChanges(songTimeChanges:Array):Void + public static function mapTimeChanges(songTimeChanges:Array) { timeChanges = []; for (currentTimeChange in songTimeChanges) { + // TODO: Maybe handle this different? + // Do we care about BPM at negative timestamps? + // Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`. + if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0; + + if (currentTimeChange.beatTime == null) + { + if (currentTimeChange.timeStamp <= 0.0) + { + currentTimeChange.beatTime = 0.0; + } + else + { + // Calculate the beat time of this timestamp. + currentTimeChange.beatTime = 0.0; + + if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0) + { + var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1]; + currentTimeChange.beatTime = prevTimeChange.beatTime + + ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC); + } + } + } + timeChanges.push(currentTimeChange); } trace('Done mapping time changes: ' + timeChanges); - // Done. + // Update currentStepTime + Conductor.update(Conductor.songPosition); } /** * Given a time in milliseconds, return a time in steps. */ - public static function getTimeInSteps(ms:Float):Int + public static function getTimeInSteps(ms:Float):Float { if (timeChanges.length == 0) { @@ -307,7 +299,7 @@ class Conductor } else { - var resultStep:Int = 0; + var resultStep:Float = 0; var lastTimeChange:SongTimeChange = timeChanges[0]; for (timeChange in timeChanges) @@ -329,4 +321,14 @@ class Conductor return resultStep; } } + + public static function reset():Void + { + beatHit.removeAll(); + stepHit.removeAll(); + + mapTimeChanges([]); + forceBPM(null); + update(0); + } } diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 4b86d801c..6e383c8c1 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -67,9 +67,11 @@ class MusicBeatState extends FlxUIState implements IEventHandler if (FlxG.keys.justPressed.F5) debug_refreshModules(); // Display Conductor info in the watch window. - FlxG.watch.addQuick("songPos", Conductor.songPosition); - FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); + FlxG.watch.addQuick("songPosition", Conductor.songPosition); FlxG.watch.addQuick("bpm", Conductor.bpm); + FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime); + FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); + FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); dispatchEvent(new UpdateScriptEvent(elapsed)); } diff --git a/source/funkin/MusicBeatSubState.hx b/source/funkin/MusicBeatSubState.hx index ec0066734..244d2ceea 100644 --- a/source/funkin/MusicBeatSubState.hx +++ b/source/funkin/MusicBeatSubState.hx @@ -3,7 +3,6 @@ package funkin; import flixel.FlxSubState; import funkin.modding.IScriptedClass.IEventHandler; import flixel.util.FlxColor; -import funkin.Conductor.BPMChangeEvent; import funkin.modding.events.ScriptEvent; import funkin.modding.module.ModuleHandler; import flixel.text.FlxText; diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index 60dcfad38..ee2dfe5fd 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -6,7 +6,8 @@ import openfl.utils.Assets as OpenFlAssets; class Paths { - inline public static var SOUND_EXT = #if web "mp3" #else "ogg" #end; + public static var SOUND_EXT = #if web "mp3" #else "ogg" #end; + public static var VIDEO_EXT = "mp4"; static var currentLevel:String; @@ -96,14 +97,19 @@ class Paths return getPath('music/$key.$SOUND_EXT', MUSIC, library); } - inline static public function voices(song:String, ?suffix:String) + inline static public function videos(key:String, ?library:String) + { + return getPath('videos/$key.$VIDEO_EXT', BINARY, library); + } + + inline static public function voices(song:String, ?suffix:String = '') { if (suffix == null) suffix = ""; // no suffix, for a sorta backwards compatibility with older-ish voice files return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.$SOUND_EXT'; } - inline static public function inst(song:String, ?suffix:String) + inline static public function inst(song:String, ?suffix:String = '') { return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.$SOUND_EXT'; } diff --git a/source/funkin/PauseSubState.hx b/source/funkin/PauseSubState.hx index 77fdfabf1..d5584fbc7 100644 --- a/source/funkin/PauseSubState.hx +++ b/source/funkin/PauseSubState.hx @@ -4,7 +4,7 @@ import funkin.play.PlayStatePlaylist; import flixel.FlxSprite; import flixel.addons.transition.FlxTransitionableState; import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.system.FlxSound; +import flixel.sound.FlxSound; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; diff --git a/source/funkin/TitleState.hx b/source/funkin/TitleState.hx index bc6ef571d..e0a08731b 100644 --- a/source/funkin/TitleState.hx +++ b/source/funkin/TitleState.hx @@ -12,6 +12,8 @@ import flixel.util.FlxTimer; import funkin.audiovis.SpectogramSprite; import funkin.shaderslmfao.ColorSwap; import funkin.shaderslmfao.LeftMaskShader; +import funkin.play.song.SongData.SongDataParser; +import funkin.play.song.SongData.SongMetadata; import funkin.shaderslmfao.TitleOutline; import funkin.ui.AtlasText; import funkin.util.Constants; @@ -135,12 +137,7 @@ class TitleState extends MusicBeatState function startIntro() { - if (FlxG.sound.music == null || !FlxG.sound.music.playing) - { - FlxG.sound.playMusic(Paths.music('freakyMenu'), 0); - FlxG.sound.music.fadeIn(4, 0, 0.7); - Conductor.forceBPM(Constants.FREAKY_MENU_BPM); - } + playMenuMusic(); persistentUpdate = true; @@ -234,6 +231,18 @@ class TitleState extends MusicBeatState if (FlxG.sound.music != null) FlxG.sound.music.onComplete = function() FlxG.switchState(new VideoState()); } + function playMenuMusic():Void + { + if (FlxG.sound.music == null || !FlxG.sound.music.playing) + { + var freakyMenuMetadata:SongMetadata = SongDataParser.parseMusicMetadata('freakyMenu'); + Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges); + + FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); + FlxG.sound.music.fadeIn(4, 0, 0.7); + } + } + function getIntroTextShit():Array> { var fullText:String = Assets.getText(Paths.txt('introText')); diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index b4c2de6ab..f38dabea4 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -3,7 +3,7 @@ package funkin.play; import flixel.FlxG; import flixel.FlxObject; import flixel.FlxSprite; -import flixel.system.FlxSound; +import flixel.sound.FlxSound; import funkin.ui.story.StoryMenuState; import flixel.util.FlxColor; import flixel.util.FlxTimer; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 6dfbfcf65..911bf5491 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -894,7 +894,7 @@ class PlayState extends MusicBeatState trace('Song difficulty could not be loaded.'); } - Conductor.forceBPM(currentChart.getStartingBPM()); + // Conductor.forceBPM(currentChart.getStartingBPM()); vocals = currentChart.buildVocals(currentPlayerId); if (vocals.members.length == 0) @@ -1208,13 +1208,10 @@ class PlayState extends MusicBeatState camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95); } - FlxG.watch.addQuick('beatShit', Conductor.currentBeat); - FlxG.watch.addQuick('stepShit', Conductor.currentStep); if (currentStage != null) { FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } - FlxG.watch.addQuick('songPos', Conductor.songPosition); // Handle GF dance speed. // TODO: Add a song event for this. diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index 652ca0287..24cf78c2a 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -30,7 +30,8 @@ class VideoCutscene if (!openfl.Assets.exists(filePath)) { - trace('ERROR: Video file does not exist: ${filePath}'); + // Display a popup. + lime.app.Application.current.window.alert('Video file does not exist: ${filePath}', 'Error playing video'); return; } diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index a744c9a65..5015f1f93 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -3,6 +3,8 @@ package funkin.play.song; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import flixel.util.typeLimit.OneOfTwo; +import funkin.modding.events.ScriptEvent; +import funkin.modding.events.ScriptEventDispatcher; import funkin.play.song.ScriptedSong; import funkin.util.assets.DataAssets; import haxe.DynamicAccess; @@ -22,6 +24,7 @@ class SongDataParser static final DEFAULT_SONG_ID:String = 'UNKNOWN'; static final SONG_DATA_PATH:String = 'songs/'; + static final MUSIC_DATA_PATH:String = 'music/'; static final SONG_DATA_SUFFIX:String = '-metadata.json'; /** @@ -181,6 +184,36 @@ class SongDataParser return rawJson; } + public static function parseMusicMetadata(musicId:String):SongMetadata + { + var rawJson:String = loadMusicMetadataFile(musicId); + var jsonData:Dynamic = null; + try + { + jsonData = Json.parse(rawJson); + } + catch (e) {} + + var musicMetadata:SongMetadata = SongMigrator.migrateSongMetadata(jsonData, musicId); + musicMetadata = SongValidator.validateSongMetadata(musicMetadata, musicId); + + return musicMetadata; + } + + static function loadMusicMetadataFile(musicPath:String, variation:String = ''):String + { + var musicMetadataFilePath:String = (variation != '') ? Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata-$variation.json') : Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata.json'); + + var rawJson:String = Assets.getText(musicMetadataFilePath).trim(); + + while (!rawJson.endsWith("}")) + { + rawJson = rawJson.substr(0, rawJson.length - 1); + } + + return rawJson; + } + public static function parseSongChartData(songId:String, variation:String = ""):SongChartData { var rawJson:String = loadSongChartDataFile(songId, variation); @@ -369,8 +402,7 @@ abstract SongNoteData(RawSongNoteData) public function get_stepTime():Float { - // TODO: Account for changes in BPM. - return this.t / Conductor.stepLengthMs; + return Conductor.getTimeInSteps(this.t); } /** @@ -555,8 +587,7 @@ abstract SongEventData(RawSongEventData) public function get_stepTime():Float { - // TODO: Account for changes in BPM. - return this.t / Conductor.stepLengthMs; + return Conductor.getTimeInSteps(this.t); } public var event(get, set):String; @@ -769,7 +800,7 @@ typedef RawSongTimeChange = * Time in beats (int). The game will calculate further beat values based on this one, * so it can do it in a simple linear fashion. */ - var b:Int; + var b:Null; /** * Quarter notes per minute (float). Cannot be empty in the first element of the list, @@ -799,9 +830,9 @@ typedef RawSongTimeChange = * Add aliases to the minimalized property names of the typedef, * to improve readability. */ -abstract SongTimeChange(RawSongTimeChange) +abstract SongTimeChange(RawSongTimeChange) from RawSongTimeChange { - public function new(timeStamp:Float, beatTime:Int, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array) + public function new(timeStamp:Float, beatTime:Null, bpm:Float, timeSignatureNum:Int = 4, timeSignatureDen:Int = 4, beatTuplets:Array) { this = { @@ -826,14 +857,14 @@ abstract SongTimeChange(RawSongTimeChange) return this.t = value; } - public var beatTime(get, set):Int; + public var beatTime(get, set):Null; - public function get_beatTime():Int + public function get_beatTime():Null { return this.b; } - public function set_beatTime(value:Int):Int + public function set_beatTime(value:Null):Null { return this.b = value; } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index a23a04231..d89af7d6e 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1877,7 +1877,7 @@ class ChartEditorState extends HaxeUIState { // Handle extending the note as you drag. - // Since use Math.floor and stepCrochet here, the hold notes will be beat snapped. + // Since use Math.floor and stepLengthMs here, the hold notes will be beat snapped. var dragLengthSteps:Float = Math.floor((cursorMs - currentPlaceNoteData.time) / Conductor.stepLengthMs); // Without this, the newly placed note feels too short compared to the user's input. diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index ff2f12138..bf395c808 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -15,6 +15,7 @@ import funkin.modding.events.ScriptEventDispatcher; import funkin.play.PlayState; import funkin.play.PlayStatePlaylist; import funkin.play.song.Song; +import funkin.play.song.SongData.SongMetadata; import funkin.play.song.SongData.SongDataParser; import funkin.util.Constants; @@ -115,12 +116,7 @@ class StoryMenuState extends MusicBeatState transIn = FlxTransitionableState.defaultTransIn; transOut = FlxTransitionableState.defaultTransOut; - if (!FlxG.sound.music.playing) - { - FlxG.sound.playMusic(Paths.music('freakyMenu')); - FlxG.sound.music.fadeIn(4, 0, 0.7); - } - Conductor.forceBPM(Constants.FREAKY_MENU_BPM); + playMenuMusic(); if (stickerSubState != null) { @@ -129,8 +125,6 @@ class StoryMenuState extends MusicBeatState openSubState(stickerSubState); stickerSubState.degenStickers(); - - // resetSubState(); } persistentUpdate = persistentDraw = true; @@ -203,6 +197,18 @@ class StoryMenuState extends MusicBeatState #end } + function playMenuMusic():Void + { + if (FlxG.sound.music == null || !FlxG.sound.music.playing) + { + var freakyMenuMetadata:SongMetadata = SongDataParser.parseMusicMetadata('freakyMenu'); + Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges); + + FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); + FlxG.sound.music.fadeIn(4, 0, 0.7); + } + } + function updateData():Void { currentLevel = LevelRegistry.instance.fetchEntry(currentLevelId); diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index c1bac76c4..c5f9d1689 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -118,27 +118,72 @@ class Constants public static final DEFAULT_SONG:String = 'tutorial'; /** - * OTHER + * TIMING */ // ============================== + /** + * The number of seconds in a minute. + */ + public static final SECS_PER_MIN:Int = 60; + + /** + * The number of milliseconds in a second. + */ + public static final MS_PER_SEC:Int = 1000; + + /** + * The number of microseconds in a millisecond. + */ + public static final US_PER_MS:Int = 1000; + + /** + * The number of microseconds in a second. + */ + public static final US_PER_SEC:Int = US_PER_MS * MS_PER_SEC; + + /** + * The number of nanoseconds in a microsecond. + */ + public static final NS_PER_US:Int = 1000; + + /** + * The number of nanoseconds in a millisecond. + */ + public static final NS_PER_MS:Int = NS_PER_US * US_PER_MS; + + /** + * The number of nanoseconds in a second. + */ + public static final NS_PER_SEC:Int = NS_PER_US * US_PER_MS * MS_PER_SEC; + /** * All MP3 decoders introduce a playback delay of `528` samples, * which at 44,100 Hz (samples per second) is ~12 ms. */ - public static final MP3_DELAY_MS:Float = 528 / 44100 * 1000; + public static final MP3_DELAY_MS:Float = 528 / 44100 * MS_PER_SEC; + + /** + * The default BPM of the conductor. + */ + public static final DEFAULT_BPM:Float = 100.0; + + public static final DEFAULT_TIME_SIGNATURE_NUM:Int = 4; + + public static final DEFAULT_TIME_SIGNATURE_DEN:Int = 4; + + public static final STEPS_PER_BEAT:Int = 4; + + /** + * OTHER + */ + // ============================== /** * The scale factor to use when increasing the size of pixel art graphics. */ public static final PIXEL_ART_SCALE:Float = 6; - /** - * The BPM of the title screen and menu music. - * TODO: Move to metadata file. - */ - public static final FREAKY_MENU_BPM:Float = 102; - /** * The volume at which to play the countdown before the song starts. */