From 2f10de3594283a6addadf1793ff3b751a730a8e2 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 25 May 2023 18:34:26 -0400 Subject: [PATCH] New refactor to VoicesGroup to make multiple vocal tracks actually work --- source/funkin/SongLoad.hx | 17 +- source/funkin/audio/FlxAudioGroup.hx | 194 ------------------ .../{VoicesGroup.hx => audio/SoundGroup.hx} | 104 +++++++--- source/funkin/audio/VocalGroup.hx | 119 ----------- source/funkin/audio/VoicesGroup.hx | 68 ++++++ source/funkin/play/PlayState.hx | 59 +++--- 6 files changed, 183 insertions(+), 378 deletions(-) delete mode 100644 source/funkin/audio/FlxAudioGroup.hx rename source/funkin/{VoicesGroup.hx => audio/SoundGroup.hx} (55%) delete mode 100644 source/funkin/audio/VocalGroup.hx create mode 100644 source/funkin/audio/VoicesGroup.hx diff --git a/source/funkin/SongLoad.hx b/source/funkin/SongLoad.hx index 07c21e22c..d4ad0023e 100644 --- a/source/funkin/SongLoad.hx +++ b/source/funkin/SongLoad.hx @@ -119,16 +119,21 @@ class SongLoad switch (diff) { case 'easy': - speedShit = songData.speed.easy; + speedShit = songData?.speed?.easy ?? 1.0; case 'normal': - speedShit = songData.speed.normal; + speedShit = songData?.speed?.normal ?? 1.0; case 'hard': - speedShit = songData.speed.hard; + speedShit = songData?.speed?.hard ?? 1.0; } - if (songData.speedMap[diff] == null) songData.speedMap[diff] = 1; - - speedShit = songData.speedMap[diff]; + if (songData?.speedMap == null || songData?.speedMap[diff] == null) + { + speedShit = 1; + } + else + { + speedShit = songData.speedMap[diff]; + } return speedShit; } diff --git a/source/funkin/audio/FlxAudioGroup.hx b/source/funkin/audio/FlxAudioGroup.hx deleted file mode 100644 index b506eaed9..000000000 --- a/source/funkin/audio/FlxAudioGroup.hx +++ /dev/null @@ -1,194 +0,0 @@ -package funkin.audio; - -import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.system.FlxSound; - -/** - * A group of FlxSounds which can be controlled as a whole. - * - * Add sounds to the group using `add()`, and then control them - * as a whole using the properties and methods of this class. - * - * It is assumed that all the sounds will play at the same time, - * and have the same duration. - */ -class FlxAudioGroup extends FlxTypedGroup -{ - /** - * The position in time of the sounds in the group. - * Measured in milliseconds. - */ - public var time(get, set):Float; - - function get_time():Float - { - if (getFirstAlive() != null) return getFirstAlive().time; - else - return 0; - } - - function set_time(time:Float):Float - { - forEachAlive(function(sound:FlxSound) { - // account for different offsets per sound? - sound.time = time; - }); - - return time; - } - - /** - * The volume of the sounds in the group. - */ - public var volume(get, set):Float; - - function get_volume():Float - { - if (getFirstAlive() != null) return getFirstAlive().volume; - else - return 1.0; - } - - function set_volume(volume:Float):Float - { - forEachAlive(function(sound:FlxSound) { - sound.volume = volume; - }); - - return volume; - } - - /** - * The pitch of the sounds in the group, as a multiplier of 1.0x. - * `2.0` would play the audio twice as fast with a higher pitch, - * and `0.5` would play the audio at half speed with a lower pitch. - */ - public var pitch(get, set):Float; - - function get_pitch():Float - { - #if FLX_PITCH - if (getFirstAlive() != null) return getFirstAlive().pitch; - else - #end - return 1; - } - - function set_pitch(val:Float):Float - { - #if FLX_PITCH - trace('Setting audio pitch to ' + val); - forEachAlive(function(sound:FlxSound) { - sound.pitch = val; - }); - #end - return val; - } - - /** - * Whether members of the group should be destroyed when they finish playing. - */ - public var autoDestroyMembers(default, set):Bool = false; - - function set_autoDestroyMembers(value:Bool):Bool - { - autoDestroyMembers = value; - forEachAlive(function(sound:FlxSound) { - sound.autoDestroy = value; - }); - return value; - } - - /** - * Add a sound to the group. - */ - public override function add(sound:FlxSound):FlxSound - { - var result:FlxSound = super.add(sound); - - if (result == null) return null; - - // Apply parameters to the new sound. - result.autoDestroy = this.autoDestroyMembers; - result.pitch = this.pitch; - result.volume = this.volume; - - // We have to play, then pause the sound to set the time, - // else the sound will restart immediately when played. - result.play(true, 0.0); - result.pause(); - result.time = this.time; - - return result; - } - - /** - * Pause all the sounds in the group. - */ - public function pause() - { - forEachAlive(function(sound:FlxSound) { - sound.pause(); - }); - } - - /** - * Play all the sounds in the group. - */ - public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float) - { - forEachAlive(function(sound:FlxSound) { - sound.play(forceRestart, startTime, endTime); - }); - } - - /** - * Resume all the sounds in the group. - */ - public function resume() - { - forEachAlive(function(sound:FlxSound) { - sound.resume(); - }); - } - - /** - * Stop all the sounds in the group. - */ - public function stop() - { - forEachAlive(function(sound:FlxSound) { - sound.stop(); - }); - } - - public override function clear():Void - { - this.stop(); - - super.clear(); - } - - /** - * Calculates the deviation of the sounds in the group from the target time. - * - * @param targetTime The time to compare the sounds to. - * If null, the current time of the first sound in the group is used. - * @return The largest deviation of the sounds in the group from the target time. - */ - public function calcDeviation(?targetTime:Float):Float - { - var deviation:Float = 0; - - forEachAlive(function(sound:FlxSound) { - if (targetTime == null) targetTime = sound.time; - else - { - var diff:Float = sound.time - targetTime; - if (Math.abs(diff) > Math.abs(deviation)) deviation = diff; - } - }); - - return deviation; - } -} diff --git a/source/funkin/VoicesGroup.hx b/source/funkin/audio/SoundGroup.hx similarity index 55% rename from source/funkin/VoicesGroup.hx rename to source/funkin/audio/SoundGroup.hx index cf1014f29..df197148b 100644 --- a/source/funkin/VoicesGroup.hx +++ b/source/funkin/audio/SoundGroup.hx @@ -1,11 +1,13 @@ -package funkin; +package funkin.audio; import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.system.FlxSound; +import flixel.sound.FlxSound; -// different than FlxSoundGroup cuz this can control all the sounds time and shit -// when needed -class VoicesGroup extends FlxTypedGroup +/** + * A group of FlxSounds that are all synced together. + * Unlike FlxSoundGroup, you cann also control their time and pitch. + */ +class SoundGroup extends FlxTypedGroup { public var time(get, set):Float; @@ -13,16 +15,15 @@ class VoicesGroup extends FlxTypedGroup public var pitch(get, set):Float; - // make it a group that you add to? public function new() { super(); } - // TODO: Remove this. - public static function build(song:String, ?files:Array = null):VoicesGroup + @:deprecated("Create sound files and call add() instead") + public static function build(song:String, ?files:Array = null):SoundGroup { - var result = new VoicesGroup(); + var result = new SoundGroup(); if (files == null) { @@ -42,18 +43,17 @@ class VoicesGroup extends FlxTypedGroup } /** - * Finds the largest deviation from the desired time inside this VoicesGroup. + * Finds the largest deviation from the desired time inside this SoundGroup. * * @param targetTime The time to check against. - * If none is provided, it checks the time of all members against the first member of this VoicesGroup. + * If none is provided, it checks the time of all members against the first member of this SoundGroup. * @return The largest deviation from the target time found. */ public function checkSyncError(?targetTime:Float):Float { var error:Float = 0; - forEachAlive(function(snd) - { + forEachAlive(function(snd) { if (targetTime == null) targetTime = snd.time; else { @@ -64,31 +64,78 @@ class VoicesGroup extends FlxTypedGroup return error; } - // prob a better / cleaner way to do all these forEach stuff? + /** + * Add a sound to the group. + */ + public override function add(sound:FlxSound):FlxSound + { + var result:FlxSound = super.add(sound); + + if (result == null) return null; + + // We have to play, then pause the sound to set the time, + // else the sound will restart immediately when played. + result.play(true, 0.0); + result.pause(); + result.time = this.time; + + // Apply parameters to the new sound. + result.pitch = this.pitch; + result.volume = this.volume; + + return result; + } + + /** + * Pause all the sounds in the group. + */ public function pause() { - forEachAlive(function(snd) - { - snd.pause(); + forEachAlive(function(sound:FlxSound) { + sound.pause(); }); } - public function play() + /** + * Play all the sounds in the group. + */ + public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float) { - forEachAlive(function(snd) - { - snd.play(); + forEachAlive(function(sound:FlxSound) { + sound.play(forceRestart, startTime, endTime); }); } + /** + * Resume all the sounds in the group. + */ + public function resume() + { + forEachAlive(function(sound:FlxSound) { + sound.resume(); + }); + } + + /** + * Stop all the sounds in the group. + */ public function stop() { - forEachAlive(function(snd) - { - snd.stop(); + forEachAlive(function(sound:FlxSound) { + sound.stop(); }); } + /** + * Remove all sounds from the group. + */ + public override function clear():Void + { + this.stop(); + + super.clear(); + } + function get_time():Float { if (getFirstAlive() != null) return getFirstAlive().time; @@ -98,8 +145,7 @@ class VoicesGroup extends FlxTypedGroup function set_time(time:Float):Float { - forEachAlive(function(snd) - { + forEachAlive(function(snd) { // account for different offsets per sound? snd.time = time; }); @@ -117,8 +163,7 @@ class VoicesGroup extends FlxTypedGroup // in PlayState, adjust the code so that it only mutes the player1 vocal tracks? function set_volume(volume:Float):Float { - forEachAlive(function(snd) - { + forEachAlive(function(snd) { snd.volume = volume; }); @@ -138,8 +183,7 @@ class VoicesGroup extends FlxTypedGroup { #if FLX_PITCH trace('Setting audio pitch to ' + val); - forEachAlive(function(snd) - { + forEachAlive(function(snd) { snd.pitch = val; }); #end diff --git a/source/funkin/audio/VocalGroup.hx b/source/funkin/audio/VocalGroup.hx deleted file mode 100644 index 31539eab1..000000000 --- a/source/funkin/audio/VocalGroup.hx +++ /dev/null @@ -1,119 +0,0 @@ -package funkin.audio; - -import flixel.system.FlxSound; - -/** - * An audio group that allows for specific control of vocal tracks. - */ -class VocalGroup extends FlxAudioGroup -{ - /** - * The player's vocal track. - */ - var playerVocals:FlxSound; - - /** - * The opponent's vocal track. - */ - var opponentVocals:FlxSound; - - /** - * The volume of the player's vocal track. - * Nore that this value is multiplied by the overall volume of the group. - */ - public var playerVolume(default, set):Float; - - function set_playerVolume(value:Float):Float - { - playerVolume = value; - if (playerVocals != null) - { - // Make sure volume is capped at 1.0. - playerVocals.volume = Math.min(playerVolume * this.volume, 1.0); - } - return playerVolume; - } - - /** - * The volume of the opponent's vocal track. - * Nore that this value is multiplied by the overall volume of the group. - */ - public var opponentVolume(default, set):Float; - - function set_opponentVolume(value:Float):Float - { - opponentVolume = value; - if (opponentVocals != null) - { - // Make sure volume is capped at 1.0. - opponentVocals.volume = opponentVolume * this.volume; - } - return opponentVolume; - } - - /** - * Sets up the player's vocal track. - * Stops and removes the existing player track if one exists. - */ - public function setPlayerVocals(sound:FlxSound):FlxSound - { - if (playerVocals != null) - { - playerVocals.stop(); - remove(playerVocals); - playerVocals = null; - } - - playerVocals = add(sound); - playerVocals.volume = this.playerVolume * this.volume; - - return playerVocals; - } - - /** - * Sets up the opponent's vocal track. - * Stops and removes the existing player track if one exists. - */ - public function setOpponentVocals(sound:FlxSound):FlxSound - { - if (opponentVocals != null) - { - opponentVocals.stop(); - remove(opponentVocals); - opponentVocals = null; - } - - opponentVocals = add(sound); - opponentVocals.volume = this.opponentVolume * this.volume; - - return opponentVocals; - } - - /** - * In this extension of FlxAudioGroup, there is a separate overall volume - * which affects all the members of the group. - */ - var _volume = 1.0; - - override function get_volume():Float - { - return _volume; - } - - override function set_volume(value:Float):Float - { - _volume = super.set_volume(value); - - if (playerVocals != null) - { - playerVocals.volume = playerVolume * _volume; - } - - if (opponentVocals != null) - { - opponentVocals.volume = opponentVolume * _volume; - } - - return _volume; - } -} diff --git a/source/funkin/audio/VoicesGroup.hx b/source/funkin/audio/VoicesGroup.hx new file mode 100644 index 000000000..9f688eb48 --- /dev/null +++ b/source/funkin/audio/VoicesGroup.hx @@ -0,0 +1,68 @@ +package funkin.audio; + +import flixel.sound.FlxSound; +import flixel.group.FlxGroup.FlxTypedGroup; + +class VoicesGroup extends SoundGroup +{ + var playerVoices:FlxTypedGroup; + var opponentVoices:FlxTypedGroup; + + /** + * Control the volume of only the sounds in the player group. + */ + public var playerVolume(default, set):Float; + + /** + * Control the volume of only the sounds in the opponent group. + */ + public var opponentVolume(default, set):Float; + + public function new() + { + super(); + playerVoices = new FlxTypedGroup(); + opponentVoices = new FlxTypedGroup(); + } + + /** + * Add a voice to the player group. + */ + public function addPlayerVoice(sound:FlxSound):Void + { + super.add(sound); + playerVoices.add(sound); + } + + function set_playerVolume(volume:Float):Float + { + playerVoices.forEachAlive(function(voice:FlxSound) { + voice.volume = volume; + }); + return playerVolume = volume; + } + + /** + * Add a voice to the opponent group. + */ + public function addOpponentVoice(sound:FlxSound):Void + { + super.add(sound); + opponentVoices.add(sound); + } + + function set_opponentVolume(volume:Float):Float + { + opponentVoices.forEachAlive(function(voice:FlxSound) { + voice.volume = volume; + }); + return opponentVolume = volume; + } + + public override function clear():Void + { + playerVoices.clear(); + opponentVoices.clear(); + super.clear(); + } +} diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 0d99744a1..b261a54d9 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,11 +1,13 @@ package funkin.play; +import funkin.play.song.SongData.SongDataParser; +import flixel.sound.FlxSound; +import flixel.addons.transition.FlxTransitionableState; import flixel.FlxCamera; import flixel.FlxObject; import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxSubState; -import flixel.addons.transition.FlxTransitionableState; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.input.keyboard.FlxKey; import flixel.math.FlxMath; @@ -17,18 +19,14 @@ import flixel.ui.FlxBar; import flixel.util.FlxColor; import flixel.util.FlxSort; import flixel.util.FlxTimer; -import funkin.Highscore.Tallies; -import funkin.Note; -import funkin.Section.SwagSection; -import funkin.SongLoad.SwagSong; import funkin.charting.ChartingState; +import funkin.Highscore.Tallies; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; -import funkin.play.Strumline.StrumlineArrow; -import funkin.play.Strumline.StrumlineStyle; +import funkin.Note; import funkin.play.character.BaseCharacter; -import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData; +import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.cutscene.VanillaCutscenes; import funkin.play.event.SongEvent.SongEventParser; import funkin.play.scoring.Scoring; @@ -39,6 +37,11 @@ import funkin.play.song.SongData.SongPlayableChar; import funkin.play.song.SongValidator; import funkin.play.stage.Stage; import funkin.play.stage.StageData.StageDataParser; +import funkin.play.Strumline.StrumlineArrow; +import funkin.play.Strumline.StrumlineStyle; +import funkin.Section.SwagSection; +import funkin.SongLoad.SwagSong; +import funkin.audio.VoicesGroup; import funkin.ui.PopUpStuff; import funkin.ui.PreferencesMenu; import funkin.ui.stageBuildShit.StageOffsetSubstate; @@ -359,7 +362,7 @@ class PlayState extends MusicBeatState if (currentChart != null) { currentChart.cacheInst(); - currentChart.cacheVocals(); + currentChart.cacheVocals('bf'); } else { @@ -1059,9 +1062,11 @@ class PlayState extends MusicBeatState currentSong.song = currentSong.song; - if (currentSong.needsVoices) vocals = VoicesGroup.build(currentSong.song, currentSong.voiceList); - else - vocals = VoicesGroup.build(currentSong.song, null); + vocals = new VoicesGroup(); + var playerVocals:FlxSound = FlxG.sound.load(Paths.voices(currentSong.song, 'bf'), 1.0, false); + vocals.addPlayerVoice(playerVocals); + var opponentVocals:FlxSound = FlxG.sound.load(Paths.voices(currentSong.song, 'dad'), 1.0, false); + vocals.addOpponentVoice(opponentVocals); vocals.members[0].onComplete = function() { vocalsFinished = true; @@ -1427,6 +1432,8 @@ class PlayState extends MusicBeatState FlxG.sound.music.volume = 1; vocals.volume = 1; + vocals.playerVolume = 1; + vocals.opponentVolume = 1; currentStage.resetStage(); @@ -1719,7 +1726,7 @@ class PlayState extends MusicBeatState else { // Volume of DAD. - if (currentSong != null && currentSong.needsVoices) vocals.volume = 1; + if (currentSong != null && currentSong.needsVoices) vocals.opponentVolume = 1; } } @@ -1903,7 +1910,7 @@ class PlayState extends MusicBeatState // if () StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true; - if (currentSong.validScore) + if (currentSong?.validScore) { NGio.unlockMedal(60961); Highscore.saveWeekScore(storyWeek, campaignScore, storyDifficulty); @@ -1929,7 +1936,7 @@ class PlayState extends MusicBeatState FlxG.sound.music.stop(); vocals.stop(); - if (currentSong.song.toLowerCase() == 'eggnog') + if ((currentSong?.song ?? '').toLowerCase() == 'eggnog') { var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom, -FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); @@ -1948,7 +1955,7 @@ class PlayState extends MusicBeatState { previousCameraFollowPoint = cameraFollowPoint; - currentSong = SongLoad.loadFromJson(storyPlaylist[0].toLowerCase() + difficulty, storyPlaylist[0]); + currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase()); LoadingState.loadAndSwitchState(new PlayState()); } } @@ -1992,7 +1999,7 @@ class PlayState extends MusicBeatState { var noteDiff:Float = Math.abs(strumtime - Conductor.songPosition); // boyfriend.playAnimation('hey'); - vocals.volume = 1; + vocals.playerVolume = 1; var isSick:Bool = false; var score = Scoring.scoreNote(noteDiff, PBOT1); @@ -2124,7 +2131,6 @@ class PlayState extends MusicBeatState // TODO: Un-hardcode this stuff. if (currentStage.getDad().characterId == 'mom') { - vocals.volume = 1; } if (currentSong.song.toLowerCase() == 'tutorial') @@ -2312,7 +2318,7 @@ class PlayState extends MusicBeatState if (event.playSound) { - vocals.volume = 0; + vocals.playerVolume = 0; FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); } } @@ -2367,7 +2373,7 @@ class PlayState extends MusicBeatState }); } } - vocals.volume = 0; + vocals.playerVolume = 0; if (Highscore.tallies.combo != 0) { @@ -2405,7 +2411,7 @@ class PlayState extends MusicBeatState playerStrumline.getArrow(note.data.noteData).playAnimation('confirm', true); note.wasGoodHit = true; - vocals.volume = 1; + vocals.playerVolume = 1; if (!note.isSustainNote) { @@ -2742,15 +2748,10 @@ class PlayState extends MusicBeatState * This function is called whenever Flixel switches switching to a new FlxState. * @return Whether to actually switch to the new state. */ - override function switchTo(nextState:FlxState):Bool + override function startOutro(onComplete:() -> Void):Void { - var result = super.switchTo(nextState); + performCleanup(); - if (result) - { - performCleanup(); - } - - return result; + onComplete(); } }