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)
{
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;
}

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.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<FlxSound>
/**
* A group of FlxSounds that are all synced together.
* Unlike FlxSoundGroup, you cann also control their time and pitch.
*/
class SoundGroup extends FlxTypedGroup<FlxSound>
{
public var time(get, set):Float;
@ -13,16 +15,15 @@ class VoicesGroup extends FlxTypedGroup<FlxSound>
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<String> = null):VoicesGroup
@:deprecated("Create sound files and call add() instead")
public static function build(song:String, ?files:Array<String> = null):SoundGroup
{
var result = new VoicesGroup();
var result = new SoundGroup();
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.
* 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<FlxSound>
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<FlxSound>
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<FlxSound>
// 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<FlxSound>
{
#if FLX_PITCH
trace('Setting audio pitch to ' + val);
forEachAlive(function(snd)
{
forEachAlive(function(snd) {
snd.pitch = val;
});
#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;
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();
}
}