New refactor to VoicesGroup to make multiple vocal tracks actually work

This commit is contained in:
EliteMasterEric 2023-05-25 18:34:26 -04:00
parent 317c127730
commit 2f10de3594
6 changed files with 183 additions and 378 deletions

View file

@ -119,16 +119,21 @@ class SongLoad
switch (diff) switch (diff)
{ {
case 'easy': case 'easy':
speedShit = songData.speed.easy; speedShit = songData?.speed?.easy ?? 1.0;
case 'normal': case 'normal':
speedShit = songData.speed.normal; speedShit = songData?.speed?.normal ?? 1.0;
case 'hard': case 'hard':
speedShit = songData.speed.hard; speedShit = songData?.speed?.hard ?? 1.0;
} }
if (songData.speedMap[diff] == null) songData.speedMap[diff] = 1; if (songData?.speedMap == null || songData?.speedMap[diff] == null)
{
speedShit = songData.speedMap[diff]; speedShit = 1;
}
else
{
speedShit = songData.speedMap[diff];
}
return speedShit; return speedShit;
} }

View file

@ -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<FlxSound>
{
/**
* 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;
}
}

View file

@ -1,11 +1,13 @@
package funkin; package funkin.audio;
import flixel.group.FlxGroup.FlxTypedGroup; 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 * A group of FlxSounds that are all synced together.
class VoicesGroup extends FlxTypedGroup<FlxSound> * Unlike FlxSoundGroup, you cann also control their time and pitch.
*/
class SoundGroup extends FlxTypedGroup<FlxSound>
{ {
public var time(get, set):Float; public var time(get, set):Float;
@ -13,16 +15,15 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
public var pitch(get, set):Float; public var pitch(get, set):Float;
// make it a group that you add to?
public function new() public function new()
{ {
super(); super();
} }
// TODO: Remove this. @:deprecated("Create sound files and call add() instead")
public static function build(song:String, ?files:Array<String> = null):VoicesGroup public static function build(song:String, ?files:Array<String> = null):SoundGroup
{ {
var result = new VoicesGroup(); var result = new SoundGroup();
if (files == null) if (files == null)
{ {
@ -42,18 +43,17 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
} }
/** /**
* 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. * @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. * @return The largest deviation from the target time found.
*/ */
public function checkSyncError(?targetTime:Float):Float public function checkSyncError(?targetTime:Float):Float
{ {
var error:Float = 0; var error:Float = 0;
forEachAlive(function(snd) forEachAlive(function(snd) {
{
if (targetTime == null) targetTime = snd.time; if (targetTime == null) targetTime = snd.time;
else else
{ {
@ -64,31 +64,78 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
return error; 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() public function pause()
{ {
forEachAlive(function(snd) forEachAlive(function(sound:FlxSound) {
{ sound.pause();
snd.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) forEachAlive(function(sound:FlxSound) {
{ sound.play(forceRestart, startTime, endTime);
snd.play();
}); });
} }
/**
* 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() public function stop()
{ {
forEachAlive(function(snd) forEachAlive(function(sound:FlxSound) {
{ sound.stop();
snd.stop();
}); });
} }
/**
* Remove all sounds from the group.
*/
public override function clear():Void
{
this.stop();
super.clear();
}
function get_time():Float function get_time():Float
{ {
if (getFirstAlive() != null) return getFirstAlive().time; if (getFirstAlive() != null) return getFirstAlive().time;
@ -98,8 +145,7 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
function set_time(time:Float):Float function set_time(time:Float):Float
{ {
forEachAlive(function(snd) forEachAlive(function(snd) {
{
// account for different offsets per sound? // account for different offsets per sound?
snd.time = time; snd.time = time;
}); });
@ -117,8 +163,7 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
// in PlayState, adjust the code so that it only mutes the player1 vocal tracks? // in PlayState, adjust the code so that it only mutes the player1 vocal tracks?
function set_volume(volume:Float):Float function set_volume(volume:Float):Float
{ {
forEachAlive(function(snd) forEachAlive(function(snd) {
{
snd.volume = volume; snd.volume = volume;
}); });
@ -138,8 +183,7 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
{ {
#if FLX_PITCH #if FLX_PITCH
trace('Setting audio pitch to ' + val); trace('Setting audio pitch to ' + val);
forEachAlive(function(snd) forEachAlive(function(snd) {
{
snd.pitch = val; snd.pitch = val;
}); });
#end #end

View file

@ -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;
}
}

View file

@ -0,0 +1,68 @@
package funkin.audio;
import flixel.sound.FlxSound;
import flixel.group.FlxGroup.FlxTypedGroup;
class VoicesGroup extends SoundGroup
{
var playerVoices:FlxTypedGroup<FlxSound>;
var opponentVoices:FlxTypedGroup<FlxSound>;
/**
* 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<FlxSound>();
opponentVoices = new FlxTypedGroup<FlxSound>();
}
/**
* 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();
}
}

View file

@ -1,11 +1,13 @@
package funkin.play; package funkin.play;
import funkin.play.song.SongData.SongDataParser;
import flixel.sound.FlxSound;
import flixel.addons.transition.FlxTransitionableState;
import flixel.FlxCamera; import flixel.FlxCamera;
import flixel.FlxObject; import flixel.FlxObject;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.FlxState; import flixel.FlxState;
import flixel.FlxSubState; import flixel.FlxSubState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import flixel.input.keyboard.FlxKey; import flixel.input.keyboard.FlxKey;
import flixel.math.FlxMath; import flixel.math.FlxMath;
@ -17,18 +19,14 @@ import flixel.ui.FlxBar;
import flixel.util.FlxColor; import flixel.util.FlxColor;
import flixel.util.FlxSort; import flixel.util.FlxSort;
import flixel.util.FlxTimer; 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.charting.ChartingState;
import funkin.Highscore.Tallies;
import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.Strumline.StrumlineArrow; import funkin.Note;
import funkin.play.Strumline.StrumlineStyle;
import funkin.play.character.BaseCharacter; import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.cutscene.VanillaCutscenes; import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.event.SongEvent.SongEventParser; import funkin.play.event.SongEvent.SongEventParser;
import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring;
@ -39,6 +37,11 @@ import funkin.play.song.SongData.SongPlayableChar;
import funkin.play.song.SongValidator; import funkin.play.song.SongValidator;
import funkin.play.stage.Stage; import funkin.play.stage.Stage;
import funkin.play.stage.StageData.StageDataParser; 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.PopUpStuff;
import funkin.ui.PreferencesMenu; import funkin.ui.PreferencesMenu;
import funkin.ui.stageBuildShit.StageOffsetSubstate; import funkin.ui.stageBuildShit.StageOffsetSubstate;
@ -359,7 +362,7 @@ class PlayState extends MusicBeatState
if (currentChart != null) if (currentChart != null)
{ {
currentChart.cacheInst(); currentChart.cacheInst();
currentChart.cacheVocals(); currentChart.cacheVocals('bf');
} }
else else
{ {
@ -1059,9 +1062,11 @@ class PlayState extends MusicBeatState
currentSong.song = currentSong.song; currentSong.song = currentSong.song;
if (currentSong.needsVoices) vocals = VoicesGroup.build(currentSong.song, currentSong.voiceList); vocals = new VoicesGroup();
else var playerVocals:FlxSound = FlxG.sound.load(Paths.voices(currentSong.song, 'bf'), 1.0, false);
vocals = VoicesGroup.build(currentSong.song, null); 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() { vocals.members[0].onComplete = function() {
vocalsFinished = true; vocalsFinished = true;
@ -1427,6 +1432,8 @@ class PlayState extends MusicBeatState
FlxG.sound.music.volume = 1; FlxG.sound.music.volume = 1;
vocals.volume = 1; vocals.volume = 1;
vocals.playerVolume = 1;
vocals.opponentVolume = 1;
currentStage.resetStage(); currentStage.resetStage();
@ -1719,7 +1726,7 @@ class PlayState extends MusicBeatState
else else
{ {
// Volume of DAD. // 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 () // if ()
StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true; StoryMenuState.weekUnlocked[Std.int(Math.min(storyWeek + 1, StoryMenuState.weekUnlocked.length - 1))] = true;
if (currentSong.validScore) if (currentSong?.validScore)
{ {
NGio.unlockMedal(60961); NGio.unlockMedal(60961);
Highscore.saveWeekScore(storyWeek, campaignScore, storyDifficulty); Highscore.saveWeekScore(storyWeek, campaignScore, storyDifficulty);
@ -1929,7 +1936,7 @@ class PlayState extends MusicBeatState
FlxG.sound.music.stop(); FlxG.sound.music.stop();
vocals.stop(); vocals.stop();
if (currentSong.song.toLowerCase() == 'eggnog') if ((currentSong?.song ?? '').toLowerCase() == 'eggnog')
{ {
var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom, var blackShit:FlxSprite = new FlxSprite(-FlxG.width * FlxG.camera.zoom,
-FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); -FlxG.height * FlxG.camera.zoom).makeGraphic(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK);
@ -1948,7 +1955,7 @@ class PlayState extends MusicBeatState
{ {
previousCameraFollowPoint = cameraFollowPoint; previousCameraFollowPoint = cameraFollowPoint;
currentSong = SongLoad.loadFromJson(storyPlaylist[0].toLowerCase() + difficulty, storyPlaylist[0]); currentSong_NEW = SongDataParser.fetchSong(PlayState.storyPlaylist[0].toLowerCase());
LoadingState.loadAndSwitchState(new PlayState()); LoadingState.loadAndSwitchState(new PlayState());
} }
} }
@ -1992,7 +1999,7 @@ class PlayState extends MusicBeatState
{ {
var noteDiff:Float = Math.abs(strumtime - Conductor.songPosition); var noteDiff:Float = Math.abs(strumtime - Conductor.songPosition);
// boyfriend.playAnimation('hey'); // boyfriend.playAnimation('hey');
vocals.volume = 1; vocals.playerVolume = 1;
var isSick:Bool = false; var isSick:Bool = false;
var score = Scoring.scoreNote(noteDiff, PBOT1); var score = Scoring.scoreNote(noteDiff, PBOT1);
@ -2124,7 +2131,6 @@ class PlayState extends MusicBeatState
// TODO: Un-hardcode this stuff. // TODO: Un-hardcode this stuff.
if (currentStage.getDad().characterId == 'mom') if (currentStage.getDad().characterId == 'mom')
{ {
vocals.volume = 1;
} }
if (currentSong.song.toLowerCase() == 'tutorial') if (currentSong.song.toLowerCase() == 'tutorial')
@ -2312,7 +2318,7 @@ class PlayState extends MusicBeatState
if (event.playSound) if (event.playSound)
{ {
vocals.volume = 0; vocals.playerVolume = 0;
FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); 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) if (Highscore.tallies.combo != 0)
{ {
@ -2405,7 +2411,7 @@ class PlayState extends MusicBeatState
playerStrumline.getArrow(note.data.noteData).playAnimation('confirm', true); playerStrumline.getArrow(note.data.noteData).playAnimation('confirm', true);
note.wasGoodHit = true; note.wasGoodHit = true;
vocals.volume = 1; vocals.playerVolume = 1;
if (!note.isSustainNote) if (!note.isSustainNote)
{ {
@ -2742,15 +2748,10 @@ class PlayState extends MusicBeatState
* This function is called whenever Flixel switches switching to a new FlxState. * This function is called whenever Flixel switches switching to a new FlxState.
* @return Whether to actually switch to the new state. * @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) onComplete();
{
performCleanup();
}
return result;
} }
} }