mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-12-25 22:47:52 +00:00
Vocal group work
This commit is contained in:
parent
19b2f3799a
commit
234dc0ac19
|
@ -135,6 +135,14 @@
|
|||
|
||||
<haxelib name="hxcpp-debug-server" if="desktop debug" />
|
||||
|
||||
<!--
|
||||
With these options enabled, console popup and beep no longer occur.
|
||||
You can still see the log messages by opening the console (F2).
|
||||
Be sure to remove these during cleanup and bugfix testing!
|
||||
-->
|
||||
<haxedef name="FLX_NO_ERROR_SOUND" />
|
||||
<haxedef name="FLX_NO_ERROR_CONSOLE" />
|
||||
|
||||
<!--Disable the Flixel core focus lost screen-->
|
||||
<haxedef name="FLX_NO_FOCUS_LOST_SCREEN" />
|
||||
|
||||
|
|
11
hmm.json
11
hmm.json
|
@ -21,13 +21,6 @@
|
|||
"ref": "157eaf3",
|
||||
"url": "https://github.com/MasterEric/flixel-addons"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "dev",
|
||||
"url": "https://github.com/MasterEric/flixel-addons"
|
||||
},
|
||||
{
|
||||
"name": "flixel-ui",
|
||||
"type": "haxelib",
|
||||
|
@ -100,8 +93,8 @@
|
|||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "4f999ac",
|
||||
"url": "https://github.com/openfl/openfl"
|
||||
"ref": "3fd5763",
|
||||
"url": "https://github.com/MasterEric/openfl/"
|
||||
},
|
||||
{
|
||||
"name": "polymod",
|
||||
|
|
206
source/funkin/audio/FlxAudioGroup.hx
Normal file
206
source/funkin/audio/FlxAudioGroup.hx
Normal file
|
@ -0,0 +1,206 @@
|
|||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
119
source/funkin/audio/VocalGroup.hx
Normal file
119
source/funkin/audio/VocalGroup.hx
Normal file
|
@ -0,0 +1,119 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -196,17 +196,11 @@ class HealthIcon extends FlxSprite
|
|||
// Make the health icons bump (the update function causes them to lerp back down).
|
||||
if (this.width > this.height)
|
||||
{
|
||||
var targetSize = Std.int(CoolUtil.coolLerp(this.width + HEALTH_ICON_SIZE * 0.2, HEALTH_ICON_SIZE, 0.15));
|
||||
targetSize = Std.int(Math.min(targetSize, HEALTH_ICON_SIZE * 1.2));
|
||||
|
||||
setGraphicSize(targetSize, 0);
|
||||
setGraphicSize(this.width + (HEALTH_ICON_SIZE * this.size.x * 0.2), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetSize = Std.int(CoolUtil.coolLerp(this.height + HEALTH_ICON_SIZE * 0.2, HEALTH_ICON_SIZE, 0.15));
|
||||
targetSize = Std.int(Math.min(targetSize, HEALTH_ICON_SIZE * 1.2));
|
||||
|
||||
setGraphicSize(0, targetSize);
|
||||
setGraphicSize(0, this.height + (HEALTH_ICON_SIZE * this.size.y * 0.2));
|
||||
}
|
||||
this.updateHitbox();
|
||||
}
|
||||
|
|
|
@ -278,6 +278,26 @@ class ChartEditorState extends HaxeUIState
|
|||
return isViewDownscroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether hitsounds are enabled for at least one character.
|
||||
*/
|
||||
var hitsoundsEnabled(get, null):Bool;
|
||||
|
||||
function get_hitsoundsEnabled():Bool
|
||||
{
|
||||
return hitsoundsEnabledPlayer || hitsoundsEnabledOpponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether hitsounds are enabled for the player.
|
||||
*/
|
||||
var hitsoundsEnabledPlayer:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether hitsounds are enabled for the opponent.
|
||||
*/
|
||||
var hitsoundsEnabledOpponent:Bool = true;
|
||||
|
||||
/**
|
||||
* Whether the user's mouse cursor is hovering over a SOLID component of the HaxeUI.
|
||||
* If so, ignore mouse events underneath.
|
||||
|
@ -1063,13 +1083,25 @@ class ChartEditorState extends HaxeUIState
|
|||
});
|
||||
setUISelected('menubarItemMetronomeEnabled', shouldPlayMetronome);
|
||||
|
||||
addUIChangeListener('menubarItemPlayerHitsounds', (event:UIEvent) ->
|
||||
{
|
||||
hitsoundsEnabledPlayer = event.value;
|
||||
});
|
||||
setUISelected('menubarItemPlayerHitsounds', hitsoundsEnabledPlayer);
|
||||
|
||||
addUIChangeListener('menubarItemOpponentHitsounds', (event:UIEvent) ->
|
||||
{
|
||||
hitsoundsEnabledOpponent = event.value;
|
||||
});
|
||||
setUISelected('menubarItemOpponentHitsounds', hitsoundsEnabledOpponent);
|
||||
|
||||
var instVolumeLabel:Label = findComponent('menubarLabelVolumeInstrumental', Label);
|
||||
addUIChangeListener('menubarItemVolumeInstrumental', (event:UIEvent) ->
|
||||
{
|
||||
var volume:Float = event.value / 100.0;
|
||||
if (audioInstTrack != null)
|
||||
audioInstTrack.volume = volume;
|
||||
instVolumeLabel.text = 'Instrumental - ${event.value}%';
|
||||
instVolumeLabel.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||
});
|
||||
|
||||
var vocalsVolumeLabel:Label = findComponent('menubarLabelVolumeVocals', Label);
|
||||
|
@ -1078,7 +1110,7 @@ class ChartEditorState extends HaxeUIState
|
|||
var volume:Float = event.value / 100.0;
|
||||
if (audioVocalTrackGroup != null)
|
||||
audioVocalTrackGroup.volume = volume;
|
||||
vocalsVolumeLabel.text = 'Vocals - ${event.value}%';
|
||||
vocalsVolumeLabel.text = 'Vocals - ${Std.int(event.value)}%';
|
||||
});
|
||||
|
||||
var playbackSpeedLabel:Label = findComponent('menubarLabelPlaybackSpeed', Label);
|
||||
|
@ -1091,7 +1123,7 @@ class ChartEditorState extends HaxeUIState
|
|||
if (audioVocalTrackGroup != null)
|
||||
audioVocalTrackGroup.pitch = pitch;
|
||||
#end
|
||||
playbackSpeedLabel.text = 'Playback Speed - ${pitch}x';
|
||||
playbackSpeedLabel.text = 'Playback Speed - ${Std.int(event.value * 100) / 100}x';
|
||||
});
|
||||
|
||||
addUIChangeListener('menubarItemToggleToolboxTools', (event:UIEvent) ->
|
||||
|
@ -2265,7 +2297,9 @@ class ChartEditorState extends HaxeUIState
|
|||
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
|
||||
|
||||
var oldStepTime = Conductor.currentStepTime;
|
||||
var oldSongPosition = Conductor.songPosition;
|
||||
Conductor.update(audioInstTrack.time);
|
||||
handleHitsounds(oldSongPosition, Conductor.songPosition);
|
||||
// Resync vocals.
|
||||
if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||
audioVocalTrackGroup.time = audioInstTrack.time;
|
||||
|
@ -2279,8 +2313,9 @@ class ChartEditorState extends HaxeUIState
|
|||
else
|
||||
{
|
||||
// Else, move the entire view.
|
||||
|
||||
var oldSongPosition = Conductor.songPosition;
|
||||
Conductor.update(audioInstTrack.time);
|
||||
handleHitsounds(oldSongPosition, Conductor.songPosition);
|
||||
// Resync vocals.
|
||||
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||
audioVocalTrackGroup.time = audioInstTrack.time;
|
||||
|
@ -2302,6 +2337,36 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the playback of hitsounds.
|
||||
*/
|
||||
function handleHitsounds(oldSongPosition:Float, newSongPosition:Float):Void {
|
||||
if (!hitsoundsEnabled)
|
||||
return;
|
||||
|
||||
// Assume notes are sorted by time.
|
||||
for (noteData in currentSongChartNoteData) {
|
||||
if (noteData.time < oldSongPosition)
|
||||
// Note is in the past.
|
||||
continue;
|
||||
|
||||
if (noteData.time >= newSongPosition)
|
||||
// Note is in the future.
|
||||
return;
|
||||
|
||||
// Note was just hit.
|
||||
switch (noteData.getStrumlineIndex()) {
|
||||
case 0: // Player
|
||||
if (hitsoundsEnabledPlayer)
|
||||
playSound(Paths.sound('funnyNoise/funnyNoise-09'));
|
||||
case 1: // Opponent
|
||||
if (hitsoundsEnabledOpponent)
|
||||
playSound(Paths.sound('funnyNoise/funnyNoise-010'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function startAudioPlayback()
|
||||
{
|
||||
if (audioInstTrack != null)
|
||||
|
|
Loading…
Reference in a new issue