From 93f653969a7e6b683c6aa7c8eb585ca4222c5f3f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 15:33:12 -0400 Subject: [PATCH 1/9] Added new set of constants --- source/funkin/util/Constants.hx | 61 ++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 8 deletions(-) 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. */ From 956277ff4e8493daf0acd357cf740defd7d70131 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 15:33:34 -0400 Subject: [PATCH 2/9] Updates and fixes to Conductor for use with changing BPMs. --- source/funkin/Conductor.hx | 329 +++++++++++++++++-------------------- 1 file changed, 155 insertions(+), 174 deletions(-) diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 7f7e2b356..5af454e34 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,138 +28,6 @@ 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 current position in the song in milliseconds. - * Updated every frame based on the audio position. - */ - public static var songPosition:Float; - - /** - * Beats per minute of the current song at the current time. - */ - public static var bpm(get, null):Float; - - static function get_bpm():Float - { - if (bpmOverride != null) return bpmOverride; - - if (currentTimeChange == null) return 100; - - return currentTimeChange.bpm; - } - - 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. - */ - public static var measureLengthMs(get, null):Float; - - static function get_measureLengthMs():Float - { - return beatLengthMs * timeSignatureNumerator; - } - - /** - * Duration of a beat (quarter note) 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); - } - - /** - * Duration of a step (sixteenth) in milliseconds. Calculated based on bpm. - */ - public static var stepLengthMs(get, null):Float; - - static function get_stepLengthMs():Float - { - return beatLengthMs / STEPS_PER_BEAT; - } - - /** - * 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; - - 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; - - 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 - // - - /** - * 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. - */ - public static var measureHit(default, null):FlxSignal = new FlxSignal(); - - /** - * 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. - */ - 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. @@ -176,11 +39,104 @@ class Conductor */ static var currentTimeChange:SongTimeChange; - public static var lastSongPos:Float; + /** + * The current position in the song in milliseconds. + * Updated every frame based on the audio position. + */ + public static var songPosition:Float = 0; /** - * The number of beats (whole notes) in a measure. + * Beats per minute of the current song at the current time. */ + public static var bpm(get, null):Float; + + static function get_bpm():Float + { + if (bpmOverride != null) return bpmOverride; + + 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; + + /** + * Duration of a measure in milliseconds. Calculated based on bpm. + */ + public static var measureLengthMs(get, null):Float; + + static function get_measureLengthMs():Float + { + return crochet * timeSignatureNumerator; + } + + /** + * Duration of a beat in milliseconds. Calculated based on bpm. + */ + public static var crochet(get, null):Float; + + static function get_crochet():Float + { + return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC); + } + + /** + * Duration of a step (quarter) in milliseconds. Calculated based on bpm. + */ + public static var stepCrochet(get, null):Float; + + static function get_stepCrochet():Float + { + return crochet / timeSignatureNumerator; + } + + public static var timeSignatureNumerator(get, null):Int; + + static function get_timeSignatureNumerator():Int + { + if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_NUM; + + return currentTimeChange.timeSignatureNum; + } + + public static var timeSignatureDenominator(get, null):Int; + + static function get_timeSignatureDenominator():Int + { + if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_DEN; + + return currentTimeChange.timeSignatureDen; + } + + /** + * Current position in the song, in beats. + **/ + public static var currentBeat(default, null):Int; + + /** + * Current position in the song, in 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; + + public static var beatHit(default, null):FlxSignal = new FlxSignal(); + public static var stepHit(default, null):FlxSignal = new FlxSignal(); + + public static var lastSongPos:Float; + public static var visualOffset:Float = 0; + public static var audioOffset:Float = 0; + public static var offset:Float = 0; + + // TODO: Add code to update this. public static var beatsPerMeasure(get, null):Int; static function get_beatsPerMeasure():Int @@ -188,17 +144,16 @@ class Conductor return timeSignatureNumerator; } - /** - * 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; + // Is this always x4? + return timeSignatureNumerator * 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 +163,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 +178,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,14 +201,15 @@ 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) / stepCrochet, 6); currentStep = Math.floor(currentStepTime); currentBeat = Math.floor(currentStep / 4); } else { // Assume a constant BPM equal to the forced value. - currentStepTime = (songPosition / stepLengthMs); + currentStepTime = (songPosition / stepCrochet); currentStep = Math.floor(currentStepTime); currentBeat = Math.floor(currentStep / 4); } @@ -274,40 +224,61 @@ 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) { // Assume a constant BPM equal to the forced value. - return Math.floor(ms / stepLengthMs); + return Math.floor(ms / stepCrochet); } else { - var resultStep:Int = 0; + var resultStep:Float = 0; var lastTimeChange:SongTimeChange = timeChanges[0]; for (timeChange in timeChanges) @@ -324,9 +295,19 @@ class Conductor } } - resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepLengthMs); + resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepCrochet); return resultStep; } } + + public static function reset():Void + { + beatHit.removeAll(); + stepHit.removeAll(); + + mapTimeChanges([]); + forceBPM(null); + update(0); + } } From 66f56dbf2cfbfe679c1e740742ee68ddaf8388d7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 15:34:07 -0400 Subject: [PATCH 3/9] system->sound --- source/funkin/PauseSubState.hx | 2 +- source/funkin/play/GameOverSubState.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 7c39cef56..1631fc0d7 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; From 8685054696c5e4445d4953d7bc53a208d1259aa7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 15:34:34 -0400 Subject: [PATCH 4/9] Freaky Menu uses metadata.json for BPM data. --- source/funkin/TitleState.hx | 19 +++++++--- source/funkin/play/PlayState.hx | 2 +- source/funkin/play/song/SongData.hx | 47 ++++++++++++++++++++---- source/funkin/ui/story/StoryMenuState.hx | 21 +++++++---- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/source/funkin/TitleState.hx b/source/funkin/TitleState.hx index bc6ef571d..e217cbec3 100644 --- a/source/funkin/TitleState.hx +++ b/source/funkin/TitleState.hx @@ -135,12 +135,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 +229,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 = SongData.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/PlayState.hx b/source/funkin/play/PlayState.hx index f39000633..24595e6d6 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -887,7 +887,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) diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index 282734d10..6058df766 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -20,6 +20,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'; /** @@ -176,6 +177,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.json('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata-$variation') : Paths.json('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata'); + + 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); @@ -365,7 +396,7 @@ abstract SongNoteData(RawSongNoteData) public function get_stepTime():Float { // TODO: Account for changes in BPM. - return this.t / Conductor.stepLengthMs; + return this.t / Conductor.stepCrochet; } /** @@ -551,7 +582,7 @@ abstract SongEventData(RawSongEventData) public function get_stepTime():Float { // TODO: Account for changes in BPM. - return this.t / Conductor.stepLengthMs; + return this.t / Conductor.stepCrochet; } public var event(get, set):String; @@ -764,7 +795,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, @@ -794,9 +825,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 = { @@ -821,14 +852,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/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 1dc59f3ec..055db1f71 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -115,12 +115,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 +124,6 @@ class StoryMenuState extends MusicBeatState openSubState(stickerSubState); stickerSubState.degenStickers(); - - // resetSubState(); } persistentUpdate = persistentDraw = true; @@ -203,6 +196,18 @@ class StoryMenuState extends MusicBeatState #end } + function playMenuMusic():Void + { + if (FlxG.sound.music == null || !FlxG.sound.music.playing) + { + var freakyMenuMetadata:SongMetadata = SongData.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); From 1ef17770e90990bb8adf517a860fd8bee4ef1a5f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 16:16:49 -0400 Subject: [PATCH 5/9] crochet->lengthMs --- source/funkin/Conductor.hx | 59 ++++++++++++++----- .../ui/debug/charting/ChartEditorState.hx | 2 +- source/funkin/ui/story/StoryMenuState.hx | 3 +- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 5af454e34..909dd789c 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -72,15 +72,15 @@ class Conductor static function get_measureLengthMs():Float { - return crochet * timeSignatureNumerator; + return beatLengthMs * timeSignatureNumerator; } /** * Duration of a beat in milliseconds. Calculated based on bpm. */ - public static var crochet(get, null):Float; + public static var beatLengthMs(get, null):Float; - static function get_crochet():Float + static function get_beatLengthMs():Float { return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC); } @@ -88,11 +88,11 @@ class Conductor /** * Duration of a step (quarter) in milliseconds. Calculated based on bpm. */ - public static var stepCrochet(get, null):Float; + public static var stepLengthMs(get, null):Float; - static function get_stepCrochet():Float + static function get_stepLengthMs():Float { - return crochet / timeSignatureNumerator; + return beatLengthMs / timeSignatureNumerator; } public static var timeSignatureNumerator(get, null):Int; @@ -113,9 +113,21 @@ class Conductor return currentTimeChange.timeSignatureDen; } + public static var beatsPerMeasure(get, null):Float; + + static function get_beatsPerMeasure():Float + { + return timeSignatureNumerator / timeSignatureDenominator * 4; + } + + /** + * Current position in the song, in measures. + */ + public static var currentMeasure(default, null):Int; + /** * Current position in the song, in beats. - **/ + */ public static var currentBeat(default, null):Int; /** @@ -123,6 +135,16 @@ class Conductor */ 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. */ @@ -136,12 +158,11 @@ class Conductor public static var audioOffset:Float = 0; public static var offset:Float = 0; - // TODO: Add code to update this. public static var beatsPerMeasure(get, null):Int; static function get_beatsPerMeasure():Int { - return timeSignatureNumerator; + return stepsPerMeasure / Constants.STEPS_PER_BEAT; } public static var stepsPerMeasure(get, null):Int; @@ -149,7 +170,7 @@ class Conductor static function get_stepsPerMeasure():Int { // Is this always x4? - return timeSignatureNumerator * Constants.STEPS_PER_BEAT; + return timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT; } function new() {} @@ -202,16 +223,22 @@ class Conductor else if (currentTimeChange != null) { // roundDecimal prevents representing 8 as 7.9999999 - currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepCrochet, 6); + 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 / stepCrochet); + 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,7 +301,7 @@ class Conductor if (timeChanges.length == 0) { // Assume a constant BPM equal to the forced value. - return Math.floor(ms / stepCrochet); + return Math.floor(ms / stepLengthMs); } else { @@ -295,7 +322,7 @@ class Conductor } } - resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepCrochet); + resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepLengthMs); return resultStep; } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 18e34c72b..8b50c29cf 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 055db1f71..927bdc431 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; @@ -200,7 +201,7 @@ class StoryMenuState extends MusicBeatState { if (FlxG.sound.music == null || !FlxG.sound.music.playing) { - var freakyMenuMetadata:SongMetadata = SongData.parseMusicMetadata('freakyMenu'); + var freakyMenuMetadata:SongMetadata = SongDataParser.parseMusicMetadata('freakyMenu'); Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges); FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); From cddc50bbe5c877154fdcd1e4ab08fd68e87d9be3 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 16:17:04 -0400 Subject: [PATCH 6/9] Rework quickWatch --- source/funkin/MusicBeatState.hx | 6 ++++-- source/funkin/MusicBeatSubState.hx | 1 - source/funkin/play/PlayState.hx | 3 --- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 2b97951f9..ef01e4d12 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -66,9 +66,11 @@ class MusicBeatState extends FlxUIState 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 5c6635a02..b9a2d0b81 100644 --- a/source/funkin/MusicBeatSubState.hx +++ b/source/funkin/MusicBeatSubState.hx @@ -2,7 +2,6 @@ package funkin; import flixel.FlxSubState; 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/play/PlayState.hx b/source/funkin/play/PlayState.hx index 24595e6d6..92356db09 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1201,13 +1201,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. From 49ce962e3478e5ee3a01ceaa0545d8d22dc956e4 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 16:17:25 -0400 Subject: [PATCH 7/9] Make metadata file loading actually work --- source/funkin/Paths.hx | 4 ++-- source/funkin/TitleState.hx | 4 +++- source/funkin/play/song/SongData.hx | 8 +++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index 60dcfad38..3943d84ee 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -96,14 +96,14 @@ class Paths return getPath('music/$key.$SOUND_EXT', MUSIC, library); } - inline static public function voices(song:String, ?suffix:String) + 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/TitleState.hx b/source/funkin/TitleState.hx index e217cbec3..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; @@ -233,7 +235,7 @@ class TitleState extends MusicBeatState { if (FlxG.sound.music == null || !FlxG.sound.music.playing) { - var freakyMenuMetadata:SongMetadata = SongData.parseMusicMetadata('freakyMenu'); + var freakyMenuMetadata:SongMetadata = SongDataParser.parseMusicMetadata('freakyMenu'); Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges); FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0); diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index 6058df766..9e3d56b77 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -195,7 +195,7 @@ class SongDataParser static function loadMusicMetadataFile(musicPath:String, variation:String = ''):String { - var musicMetadataFilePath:String = (variation != '') ? Paths.json('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata-$variation') : Paths.json('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata'); + 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(); @@ -395,8 +395,7 @@ abstract SongNoteData(RawSongNoteData) public function get_stepTime():Float { - // TODO: Account for changes in BPM. - return this.t / Conductor.stepCrochet; + return Conductor.getTimeInSteps(this.t); } /** @@ -581,8 +580,7 @@ abstract SongEventData(RawSongEventData) public function get_stepTime():Float { - // TODO: Account for changes in BPM. - return this.t / Conductor.stepCrochet; + return Conductor.getTimeInSteps(this.t); } public var event(get, set):String; From bb8b0b59b19dcfaeba535be7f3ab738898170bfb Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 16:46:40 -0400 Subject: [PATCH 8/9] Video cutscenes in Week 7 no longer broken --- source/funkin/Paths.hx | 8 +++++++- source/funkin/play/PlayState.hx | 18 ++++++++++++++++++ source/funkin/play/cutscene/VideoCutscene.hx | 3 ++- source/funkin/play/song/SongData.hx | 5 +++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index 3943d84ee..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,6 +97,11 @@ class Paths return getPath('music/$key.$SOUND_EXT', MUSIC, library); } + 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 diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 92356db09..2506c2433 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1422,6 +1422,7 @@ class PlayState extends MusicBeatState // Handle keybinds. if (!isInCutscene && !disableKeys) keyShit(true); if (!isInCutscene && !disableKeys) debugKeyShit(); + if (isInCutscene && !disableKeys) handleCutsceneKeys(elapsed); // Dispatch the onUpdate event to scripted elements. dispatchEvent(new UpdateScriptEvent(elapsed)); @@ -1429,6 +1430,23 @@ class PlayState extends MusicBeatState static final CUTSCENE_KEYS:Array = [SPACE, ESCAPE, ENTER]; + function handleCutsceneKeys(elapsed:Float):Void + { + if (VideoCutscene.isPlaying()) + { + // This is a video cutscene. + + if (controls.CUTSCENE_SKIP) + { + trySkipVideoCutscene(elapsed); + } + else + { + trySkipVideoCutscene(-1); + } + } + } + public function trySkipVideoCutscene(elapsed:Float):Void { if (skipTimer == null || skipTimer.animation == null) return; 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 9e3d56b77..41fc4515e 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -1,6 +1,8 @@ package funkin.play.song; 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; @@ -96,6 +98,9 @@ class SongDataParser { var song:Song = songCache.get(songId); trace('Successfully fetch song: ${songId}'); + + var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false); + ScriptEventDispatcher.callEvent(song, event); return song; } else From 36898a8c04ab137700c9aa72116ceda0b9e783b7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 2 Jul 2023 16:46:49 -0400 Subject: [PATCH 9/9] Few more conductor tweaks --- source/funkin/Conductor.hx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 909dd789c..4b1261d4b 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -113,13 +113,6 @@ class Conductor return currentTimeChange.timeSignatureDen; } - public static var beatsPerMeasure(get, null):Float; - - static function get_beatsPerMeasure():Float - { - return timeSignatureNumerator / timeSignatureDenominator * 4; - } - /** * Current position in the song, in measures. */ @@ -158,10 +151,11 @@ class Conductor public static var audioOffset:Float = 0; public static var offset:Float = 0; - public static var beatsPerMeasure(get, null):Int; + public static var beatsPerMeasure(get, null):Float; - static function get_beatsPerMeasure():Int + static function get_beatsPerMeasure():Float { + // NOTE: Not always an integer, for example 7/8 is 3.5 beats per measure return stepsPerMeasure / Constants.STEPS_PER_BEAT; } @@ -169,8 +163,8 @@ class Conductor static function get_stepsPerMeasure():Int { - // Is this always x4? - return timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT; + // TODO: Is this always an integer? + return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT); } function new() {}