mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-11-28 07:07:16 +00:00
Merge pull request #239 from FunkinCrew/feature/chart-editor-offsets-part-2
Feature/chart editor offsets part 2
This commit is contained in:
commit
cf41eb661d
2
assets
2
assets
|
|
@ -1 +1 @@
|
||||||
Subproject commit d063a6c6a0fd05df02ed04456203b5d7e71e3de8
|
Subproject commit 3c8ac202bbb93bf84c7dcd0697a9d7121d3c5283
|
||||||
|
|
@ -45,8 +45,6 @@ class Conductor
|
||||||
*/
|
*/
|
||||||
public static var songPosition(default, null):Float = 0;
|
public static var songPosition(default, null):Float = 0;
|
||||||
|
|
||||||
public static var songPositionNoOffset(default, null):Float = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beats per minute of the current song at the current time.
|
* Beats per minute of the current song at the current time.
|
||||||
*/
|
*/
|
||||||
|
|
@ -61,6 +59,21 @@ class Conductor
|
||||||
return currentTimeChange.bpm;
|
return currentTimeChange.bpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beats per minute of the current song at the start time.
|
||||||
|
*/
|
||||||
|
public static var startingBPM(get, never):Float;
|
||||||
|
|
||||||
|
static function get_startingBPM():Float
|
||||||
|
{
|
||||||
|
if (bpmOverride != null) return bpmOverride;
|
||||||
|
|
||||||
|
var timeChange = timeChanges[0];
|
||||||
|
if (timeChange == null) return Constants.DEFAULT_BPM;
|
||||||
|
|
||||||
|
return timeChange.bpm;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current value set by `forceBPM`.
|
* The current value set by `forceBPM`.
|
||||||
* If false, BPM is determined by time changes.
|
* If false, BPM is determined by time changes.
|
||||||
|
|
@ -146,18 +159,23 @@ class Conductor
|
||||||
*/
|
*/
|
||||||
public static var currentStepTime(default, null):Float;
|
public static var currentStepTime(default, null):Float;
|
||||||
|
|
||||||
public static var currentStepTimeNoOffset(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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An offset tied to the current chart file to compensate for a delay in the instrumental.
|
* An offset tied to the current chart file to compensate for a delay in the instrumental.
|
||||||
*/
|
*/
|
||||||
public static var instrumentalOffset:Float = 0;
|
public static var instrumentalOffset:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instrumental offset, in terms of steps.
|
||||||
|
*/
|
||||||
|
public static var instrumentalOffsetSteps(get, never):Float;
|
||||||
|
|
||||||
|
static function get_instrumentalOffsetSteps():Float
|
||||||
|
{
|
||||||
|
var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||||
|
|
||||||
|
return instrumentalOffset / startingStepLengthMs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An offset tied to the file format of the audio file being played.
|
* An offset tied to the file format of the audio file being played.
|
||||||
*/
|
*/
|
||||||
|
|
@ -168,6 +186,9 @@ class Conductor
|
||||||
*/
|
*/
|
||||||
public static var inputOffset:Float = 0;
|
public static var inputOffset:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of beats in a measure. May be fractional depending on the time signature.
|
||||||
|
*/
|
||||||
public static var beatsPerMeasure(get, never):Float;
|
public static var beatsPerMeasure(get, never):Float;
|
||||||
|
|
||||||
static function get_beatsPerMeasure():Float
|
static function get_beatsPerMeasure():Float
|
||||||
|
|
@ -176,6 +197,10 @@ class Conductor
|
||||||
return stepsPerMeasure / Constants.STEPS_PER_BEAT;
|
return stepsPerMeasure / Constants.STEPS_PER_BEAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of steps in a measure.
|
||||||
|
* TODO: I don't think this can be fractional?
|
||||||
|
*/
|
||||||
public static var stepsPerMeasure(get, never):Int;
|
public static var stepsPerMeasure(get, never):Int;
|
||||||
|
|
||||||
static function get_stepsPerMeasure():Int
|
static function get_stepsPerMeasure():Int
|
||||||
|
|
@ -184,6 +209,21 @@ class Conductor
|
||||||
return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT);
|
return Std.int(timeSignatureNumerator / timeSignatureDenominator * Constants.STEPS_PER_BEAT * Constants.STEPS_PER_BEAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal fired when the Conductor advances to a new measure.
|
||||||
|
*/
|
||||||
|
public static var measureHit(default, null):FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal fired when the Conductor advances to a new beat.
|
||||||
|
*/
|
||||||
|
public static var beatHit(default, null):FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal fired when the Conductor advances to a new step.
|
||||||
|
*/
|
||||||
|
public static var stepHit(default, null):FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
function new() {}
|
function new() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -224,29 +264,29 @@ class Conductor
|
||||||
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
|
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oldMeasure = currentMeasure;
|
||||||
var oldBeat = currentBeat;
|
var oldBeat = currentBeat;
|
||||||
var oldStep = currentStep;
|
var oldStep = currentStep;
|
||||||
|
|
||||||
// Set the song position we are at (for purposes of calculating note positions, etc).
|
// Set the song position we are at (for purposes of calculating note positions, etc).
|
||||||
Conductor.songPosition = songPosition;
|
Conductor.songPosition = songPosition;
|
||||||
|
|
||||||
// Exclude the offsets we just included earlier.
|
|
||||||
// This is the "true" song position that the audio should be using.
|
|
||||||
Conductor.songPositionNoOffset = Conductor.songPosition - instrumentalOffset - formatOffset;
|
|
||||||
|
|
||||||
currentTimeChange = timeChanges[0];
|
currentTimeChange = timeChanges[0];
|
||||||
|
if (Conductor.songPosition > 0.0)
|
||||||
|
{
|
||||||
for (i in 0...timeChanges.length)
|
for (i in 0...timeChanges.length)
|
||||||
{
|
{
|
||||||
if (songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i];
|
if (songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i];
|
||||||
|
|
||||||
if (songPosition < timeChanges[i].timeStamp) break;
|
if (songPosition < timeChanges[i].timeStamp) break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (currentTimeChange == null && bpmOverride == null && FlxG.sound.music != null)
|
if (currentTimeChange == null && bpmOverride == null && FlxG.sound.music != null)
|
||||||
{
|
{
|
||||||
trace('WARNING: Conductor is broken, timeChanges is empty.');
|
trace('WARNING: Conductor is broken, timeChanges is empty.');
|
||||||
}
|
}
|
||||||
else if (currentTimeChange != null)
|
else if (currentTimeChange != null && Conductor.songPosition > 0.0)
|
||||||
{
|
{
|
||||||
// roundDecimal prevents representing 8 as 7.9999999
|
// roundDecimal prevents representing 8 as 7.9999999
|
||||||
currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
||||||
|
|
@ -255,9 +295,6 @@ class Conductor
|
||||||
currentStep = Math.floor(currentStepTime);
|
currentStep = Math.floor(currentStepTime);
|
||||||
currentBeat = Math.floor(currentBeatTime);
|
currentBeat = Math.floor(currentBeatTime);
|
||||||
currentMeasure = Math.floor(currentMeasureTime);
|
currentMeasure = Math.floor(currentMeasureTime);
|
||||||
|
|
||||||
currentStepTimeNoOffset = FlxMath.roundDecimal((currentTimeChange.beatTime * 4)
|
|
||||||
+ (songPositionNoOffset - currentTimeChange.timeStamp) / stepLengthMs, 6);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -268,8 +305,6 @@ class Conductor
|
||||||
currentStep = Math.floor(currentStepTime);
|
currentStep = Math.floor(currentStepTime);
|
||||||
currentBeat = Math.floor(currentBeatTime);
|
currentBeat = Math.floor(currentBeatTime);
|
||||||
currentMeasure = Math.floor(currentMeasureTime);
|
currentMeasure = Math.floor(currentMeasureTime);
|
||||||
|
|
||||||
currentStepTimeNoOffset = FlxMath.roundDecimal((songPositionNoOffset / stepLengthMs), 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlxSignals are really cool.
|
// FlxSignals are really cool.
|
||||||
|
|
@ -282,6 +317,11 @@ class Conductor
|
||||||
{
|
{
|
||||||
beatHit.dispatch();
|
beatHit.dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentMeasure != oldMeasure)
|
||||||
|
{
|
||||||
|
measureHit.dispatch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
|
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
|
||||||
|
|
|
||||||
169
source/funkin/audio/FunkinSound.hx
Normal file
169
source/funkin/audio/FunkinSound.hx
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
package funkin.audio;
|
||||||
|
|
||||||
|
#if flash11
|
||||||
|
import flash.media.Sound;
|
||||||
|
import flash.utils.ByteArray;
|
||||||
|
#end
|
||||||
|
import flixel.sound.FlxSound;
|
||||||
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
|
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||||
|
import openfl.Assets;
|
||||||
|
#if (openfl >= "8.0.0")
|
||||||
|
import openfl.utils.AssetType;
|
||||||
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A FlxSound which adds additional functionality:
|
||||||
|
* - Delayed playback via negative song position.
|
||||||
|
*/
|
||||||
|
@:nullSafety
|
||||||
|
class FunkinSound extends FlxSound
|
||||||
|
{
|
||||||
|
static var cache(default, null):FlxTypedGroup<FunkinSound> = new FlxTypedGroup<FunkinSound>();
|
||||||
|
|
||||||
|
public var isPlaying(get, never):Bool;
|
||||||
|
|
||||||
|
function get_isPlaying():Bool
|
||||||
|
{
|
||||||
|
return this.playing || this._shouldPlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we in a state where the song should play but time is negative?
|
||||||
|
*/
|
||||||
|
var _shouldPlay:Bool = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For debug purposes.
|
||||||
|
*/
|
||||||
|
var _label:String = "unknown";
|
||||||
|
|
||||||
|
public function new()
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function update(elapsedSec:Float)
|
||||||
|
{
|
||||||
|
if (!playing && !_shouldPlay) return;
|
||||||
|
|
||||||
|
if (_time < 0)
|
||||||
|
{
|
||||||
|
var elapsedMs = elapsedSec * Constants.MS_PER_SEC;
|
||||||
|
_time += elapsedMs;
|
||||||
|
if (_time >= 0)
|
||||||
|
{
|
||||||
|
super.play();
|
||||||
|
_shouldPlay = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
super.update(elapsedSec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function play(forceRestart:Bool = false, startTime:Float = 0, ?endTime:Float):FunkinSound
|
||||||
|
{
|
||||||
|
if (!exists) return this;
|
||||||
|
|
||||||
|
if (forceRestart)
|
||||||
|
{
|
||||||
|
cleanup(false, true);
|
||||||
|
}
|
||||||
|
else if (playing)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTime < 0)
|
||||||
|
{
|
||||||
|
this.active = true;
|
||||||
|
this._shouldPlay = true;
|
||||||
|
this._time = startTime;
|
||||||
|
this.endTime = endTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_paused)
|
||||||
|
{
|
||||||
|
resume();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startSound(startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.endTime = endTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function pause():FunkinSound
|
||||||
|
{
|
||||||
|
super.pause();
|
||||||
|
this._shouldPlay = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function resume():FunkinSound
|
||||||
|
{
|
||||||
|
if (this._time < 0)
|
||||||
|
{
|
||||||
|
this._shouldPlay = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
super.resume();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new `FunkinSound` object.
|
||||||
|
*
|
||||||
|
* @param embeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
|
||||||
|
* @param volume How loud to play it (0 to 1).
|
||||||
|
* @param looped Whether to loop this sound.
|
||||||
|
* @param group The group to add this sound to.
|
||||||
|
* @param autoDestroy Whether to destroy this sound when it finishes playing.
|
||||||
|
* Leave this value set to `false` if you want to re-use this `FunkinSound` instance.
|
||||||
|
* @param autoPlay Whether to play the sound immediately or wait for a `play()` call.
|
||||||
|
* @param onComplete Called when the sound finished playing.
|
||||||
|
* @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds.
|
||||||
|
* @return A `FunkinSound` object.
|
||||||
|
*/
|
||||||
|
public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false,
|
||||||
|
?onComplete:Void->Void, ?onLoad:Void->Void):FunkinSound
|
||||||
|
{
|
||||||
|
var sound:FunkinSound = cache.recycle(construct);
|
||||||
|
|
||||||
|
sound.loadEmbedded(embeddedSound, looped, autoDestroy, onComplete);
|
||||||
|
|
||||||
|
if (embeddedSound is String)
|
||||||
|
{
|
||||||
|
sound._label = embeddedSound;
|
||||||
|
}
|
||||||
|
|
||||||
|
sound.volume = volume;
|
||||||
|
sound.group = FlxG.sound.defaultSoundGroup;
|
||||||
|
sound.persist = true;
|
||||||
|
if (autoPlay) sound.play();
|
||||||
|
|
||||||
|
// Call OnlLoad() because the sound already loaded
|
||||||
|
if (onLoad != null && sound._sound != null) onLoad();
|
||||||
|
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function construct():FunkinSound
|
||||||
|
{
|
||||||
|
var sound:FunkinSound = new FunkinSound();
|
||||||
|
|
||||||
|
cache.add(sound);
|
||||||
|
FlxG.sound.list.add(sound);
|
||||||
|
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,13 @@ package funkin.audio;
|
||||||
|
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A group of FlxSounds that are all synced together.
|
* A group of FunkinSounds that are all synced together.
|
||||||
* Unlike FlxSoundGroup, you cann also control their time and pitch.
|
* Unlike FlxSoundGroup, you can also control their time and pitch.
|
||||||
*/
|
*/
|
||||||
class SoundGroup extends FlxTypedGroup<FlxSound>
|
class SoundGroup extends FlxTypedGroup<FunkinSound>
|
||||||
{
|
{
|
||||||
public var time(get, set):Float;
|
public var time(get, set):Float;
|
||||||
|
|
||||||
|
|
@ -28,14 +29,13 @@ class SoundGroup extends FlxTypedGroup<FlxSound>
|
||||||
if (files == null)
|
if (files == null)
|
||||||
{
|
{
|
||||||
// Add an empty voice.
|
// Add an empty voice.
|
||||||
result.add(new FlxSound());
|
result.add(new FunkinSound());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (sndFile in files)
|
for (sndFile in files)
|
||||||
{
|
{
|
||||||
var snd:FlxSound = new FlxSound().loadEmbedded(Paths.voices(song, '$sndFile'));
|
var snd:FunkinSound = FunkinSound.load(Paths.voices(song, '$sndFile'));
|
||||||
FlxG.sound.list.add(snd); // adds it to sound group for proper volumes
|
|
||||||
result.add(snd); // adds it to main group for other shit
|
result.add(snd); // adds it to main group for other shit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,9 +67,9 @@ class SoundGroup extends FlxTypedGroup<FlxSound>
|
||||||
/**
|
/**
|
||||||
* Add a sound to the group.
|
* Add a sound to the group.
|
||||||
*/
|
*/
|
||||||
public override function add(sound:FlxSound):FlxSound
|
public override function add(sound:FunkinSound):FunkinSound
|
||||||
{
|
{
|
||||||
var result:FlxSound = super.add(sound);
|
var result:FunkinSound = super.add(sound);
|
||||||
|
|
||||||
if (result == null) return null;
|
if (result == null) return null;
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ class SoundGroup extends FlxTypedGroup<FlxSound>
|
||||||
*/
|
*/
|
||||||
public function pause()
|
public function pause()
|
||||||
{
|
{
|
||||||
forEachAlive(function(sound:FlxSound) {
|
forEachAlive(function(sound:FunkinSound) {
|
||||||
sound.pause();
|
sound.pause();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +107,7 @@ class SoundGroup extends FlxTypedGroup<FlxSound>
|
||||||
*/
|
*/
|
||||||
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
|
public function play(forceRestart:Bool = false, startTime:Float = 0.0, ?endTime:Float)
|
||||||
{
|
{
|
||||||
forEachAlive(function(sound:FlxSound) {
|
forEachAlive(function(sound:FunkinSound) {
|
||||||
sound.play(forceRestart, startTime, endTime);
|
sound.play(forceRestart, startTime, endTime);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ class SoundGroup extends FlxTypedGroup<FlxSound>
|
||||||
*/
|
*/
|
||||||
public function resume()
|
public function resume()
|
||||||
{
|
{
|
||||||
forEachAlive(function(sound:FlxSound) {
|
forEachAlive(function(sound:FunkinSound) {
|
||||||
sound.resume();
|
sound.resume();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ class SoundGroup extends FlxTypedGroup<FlxSound>
|
||||||
*/
|
*/
|
||||||
public function stop()
|
public function stop()
|
||||||
{
|
{
|
||||||
forEachAlive(function(sound:FlxSound) {
|
forEachAlive(function(sound:FunkinSound) {
|
||||||
sound.stop();
|
sound.stop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +151,7 @@ class SoundGroup extends FlxTypedGroup<FlxSound>
|
||||||
|
|
||||||
function set_time(time:Float):Float
|
function set_time(time:Float):Float
|
||||||
{
|
{
|
||||||
forEachAlive(function(snd) {
|
forEachAlive(function(snd:FunkinSound) {
|
||||||
// account for different offsets per sound?
|
// account for different offsets per sound?
|
||||||
snd.time = time;
|
snd.time = time;
|
||||||
});
|
});
|
||||||
|
|
@ -169,7 +169,7 @@ class SoundGroup 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:FunkinSound) {
|
||||||
snd.volume = volume;
|
snd.volume = volume;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -189,7 +189,7 @@ class SoundGroup 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:FunkinSound) {
|
||||||
snd.pitch = val;
|
snd.pitch = val;
|
||||||
});
|
});
|
||||||
#end
|
#end
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package funkin.audio;
|
package funkin.audio;
|
||||||
|
|
||||||
import flixel.sound.FlxSound;
|
import funkin.audio.FunkinSound;
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
|
|
||||||
class VoicesGroup extends SoundGroup
|
class VoicesGroup extends SoundGroup
|
||||||
{
|
{
|
||||||
var playerVoices:FlxTypedGroup<FlxSound>;
|
var playerVoices:FlxTypedGroup<FunkinSound>;
|
||||||
var opponentVoices:FlxTypedGroup<FlxSound>;
|
var opponentVoices:FlxTypedGroup<FunkinSound>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control the volume of only the sounds in the player group.
|
* Control the volume of only the sounds in the player group.
|
||||||
|
|
@ -31,14 +31,14 @@ class VoicesGroup extends SoundGroup
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
playerVoices = new FlxTypedGroup<FlxSound>();
|
playerVoices = new FlxTypedGroup<FunkinSound>();
|
||||||
opponentVoices = new FlxTypedGroup<FlxSound>();
|
opponentVoices = new FlxTypedGroup<FunkinSound>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a voice to the player group.
|
* Add a voice to the player group.
|
||||||
*/
|
*/
|
||||||
public function addPlayerVoice(sound:FlxSound):Void
|
public function addPlayerVoice(sound:FunkinSound):Void
|
||||||
{
|
{
|
||||||
super.add(sound);
|
super.add(sound);
|
||||||
playerVoices.add(sound);
|
playerVoices.add(sound);
|
||||||
|
|
@ -46,7 +46,7 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
function set_playerVolume(volume:Float):Float
|
function set_playerVolume(volume:Float):Float
|
||||||
{
|
{
|
||||||
playerVoices.forEachAlive(function(voice:FlxSound) {
|
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.volume = volume;
|
voice.volume = volume;
|
||||||
});
|
});
|
||||||
return playerVolume = volume;
|
return playerVolume = volume;
|
||||||
|
|
@ -59,10 +59,10 @@ class VoicesGroup extends SoundGroup
|
||||||
snd.time = time;
|
snd.time = time;
|
||||||
});
|
});
|
||||||
|
|
||||||
playerVoices.forEachAlive(function(voice:FlxSound) {
|
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time -= playerVoicesOffset;
|
voice.time -= playerVoicesOffset;
|
||||||
});
|
});
|
||||||
opponentVoices.forEachAlive(function(voice:FlxSound) {
|
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time -= opponentVoicesOffset;
|
voice.time -= opponentVoicesOffset;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
function set_playerVoicesOffset(offset:Float):Float
|
function set_playerVoicesOffset(offset:Float):Float
|
||||||
{
|
{
|
||||||
playerVoices.forEachAlive(function(voice:FlxSound) {
|
playerVoices.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time += playerVoicesOffset;
|
voice.time += playerVoicesOffset;
|
||||||
voice.time -= offset;
|
voice.time -= offset;
|
||||||
});
|
});
|
||||||
|
|
@ -80,33 +80,17 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
function set_opponentVoicesOffset(offset:Float):Float
|
function set_opponentVoicesOffset(offset:Float):Float
|
||||||
{
|
{
|
||||||
opponentVoices.forEachAlive(function(voice:FlxSound) {
|
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.time += opponentVoicesOffset;
|
voice.time += opponentVoicesOffset;
|
||||||
voice.time -= offset;
|
voice.time -= offset;
|
||||||
});
|
});
|
||||||
return opponentVoicesOffset = offset;
|
return opponentVoicesOffset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function update(elapsed:Float):Void
|
|
||||||
{
|
|
||||||
forEachAlive(function(snd) {
|
|
||||||
if (snd.time < 0)
|
|
||||||
{
|
|
||||||
// Sync the time without calling update().
|
|
||||||
// time gets reset if it's negative.
|
|
||||||
snd.time += elapsed * 1000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
snd.update(elapsed);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a voice to the opponent group.
|
* Add a voice to the opponent group.
|
||||||
*/
|
*/
|
||||||
public function addOpponentVoice(sound:FlxSound):Void
|
public function addOpponentVoice(sound:FunkinSound):Void
|
||||||
{
|
{
|
||||||
super.add(sound);
|
super.add(sound);
|
||||||
opponentVoices.add(sound);
|
opponentVoices.add(sound);
|
||||||
|
|
@ -114,7 +98,7 @@ class VoicesGroup extends SoundGroup
|
||||||
|
|
||||||
function set_opponentVolume(volume:Float):Float
|
function set_opponentVolume(volume:Float):Float
|
||||||
{
|
{
|
||||||
opponentVoices.forEachAlive(function(voice:FlxSound) {
|
opponentVoices.forEachAlive(function(voice:FunkinSound) {
|
||||||
voice.volume = volume;
|
voice.volume = volume;
|
||||||
});
|
});
|
||||||
return opponentVolume = volume;
|
return opponentVolume = volume;
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class SongMetadata
|
||||||
result.version = this.version;
|
result.version = this.version;
|
||||||
result.timeFormat = this.timeFormat;
|
result.timeFormat = this.timeFormat;
|
||||||
result.divisions = this.divisions;
|
result.divisions = this.divisions;
|
||||||
|
result.offsets = this.offsets;
|
||||||
result.timeChanges = this.timeChanges;
|
result.timeChanges = this.timeChanges;
|
||||||
result.looped = this.looped;
|
result.looped = this.looped;
|
||||||
result.playData = this.playData;
|
result.playData = this.playData;
|
||||||
|
|
|
||||||
|
|
@ -326,12 +326,6 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
var overrideMusic:Bool = false;
|
var overrideMusic:Bool = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* After the song starts, the song offset may dictate we wait before the instrumental starts.
|
|
||||||
* This variable represents that delay, and is subtracted from until it reaches 0, before calling `music.play()`
|
|
||||||
*/
|
|
||||||
var songStartDelay:Float = 0.0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forcibly disables all update logic while the game moves back to the Menu state.
|
* Forcibly disables all update logic while the game moves back to the Menu state.
|
||||||
* This is used only when a critical error occurs and the game absolutely cannot continue.
|
* This is used only when a critical error occurs and the game absolutely cannot continue.
|
||||||
|
|
@ -806,36 +800,8 @@ class PlayState extends MusicBeatSubState
|
||||||
Conductor.formatOffset = 0.0;
|
Conductor.formatOffset = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (songStartDelay > 0)
|
|
||||||
{
|
|
||||||
// Code to handle the song not starting yet (positive instrumental offset in metadata).
|
|
||||||
// Wait for offset to elapse before actually hitting play on the instrumental.
|
|
||||||
songStartDelay -= elapsed * 1000;
|
|
||||||
if (songStartDelay <= 0)
|
|
||||||
{
|
|
||||||
FlxG.sound.music.play();
|
|
||||||
Conductor.update(); // Normal conductor update.
|
Conductor.update(); // Normal conductor update.
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Make beat events still happen.
|
|
||||||
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Conductor.update(); // Normal conductor update.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isGamePaused)
|
|
||||||
{
|
|
||||||
// Interpolation type beat
|
|
||||||
if (Conductor.lastSongPos != Conductor.songPosition)
|
|
||||||
{
|
|
||||||
Conductor.lastSongPos = Conductor.songPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var androidPause:Bool = false;
|
var androidPause:Bool = false;
|
||||||
|
|
||||||
|
|
@ -1071,7 +1037,7 @@ class PlayState extends MusicBeatSubState
|
||||||
if (event.eventCanceled) return;
|
if (event.eventCanceled) return;
|
||||||
|
|
||||||
// Resume
|
// Resume
|
||||||
FlxG.sound.music.play();
|
FlxG.sound.music.play(FlxG.sound.music.time);
|
||||||
|
|
||||||
if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals();
|
if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals();
|
||||||
|
|
||||||
|
|
@ -1201,12 +1167,12 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (!startingSong
|
if (!startingSong
|
||||||
&& FlxG.sound.music != null
|
&& FlxG.sound.music != null
|
||||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPositionNoOffset)) > 200
|
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset)) > 200
|
||||||
|| Math.abs(vocals.checkSyncError(Conductor.songPositionNoOffset)) > 200))
|
|| Math.abs(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset)) > 200))
|
||||||
{
|
{
|
||||||
trace("VOCALS NEED RESYNC");
|
trace("VOCALS NEED RESYNC");
|
||||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.songPositionNoOffset));
|
if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset));
|
||||||
trace(FlxG.sound.music.time - (Conductor.songPositionNoOffset));
|
trace(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset));
|
||||||
resyncVocals();
|
resyncVocals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1756,21 +1722,9 @@ class PlayState extends MusicBeatSubState
|
||||||
trace('Playing vocals...');
|
trace('Playing vocals...');
|
||||||
add(vocals);
|
add(vocals);
|
||||||
|
|
||||||
if (FlxG.sound.music.time < 0)
|
FlxG.sound.music.play(FlxG.sound.music.time);
|
||||||
{
|
|
||||||
// A positive instrumentalOffset means Conductor.songPosition will still be negative after the countdown elapses.
|
|
||||||
trace('POSITIVE OFFSET: Waiting to start song...');
|
|
||||||
songStartDelay = Math.abs(FlxG.sound.music.time);
|
|
||||||
FlxG.sound.music.time = 0;
|
|
||||||
FlxG.sound.music.pause();
|
|
||||||
vocals.pause();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FlxG.sound.music.play();
|
|
||||||
vocals.play();
|
vocals.play();
|
||||||
resyncVocals();
|
resyncVocals();
|
||||||
}
|
|
||||||
|
|
||||||
#if discord_rpc
|
#if discord_rpc
|
||||||
// Updating Discord Rich Presence (with Time Left)
|
// Updating Discord Rich Presence (with Time Left)
|
||||||
|
|
@ -1779,7 +1733,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (startTimestamp > 0)
|
if (startTimestamp > 0)
|
||||||
{
|
{
|
||||||
FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
|
// FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
|
||||||
handleSkippedNotes();
|
handleSkippedNotes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1793,11 +1747,10 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
// Skip this if the music is paused (GameOver, Pause menu, start-of-song offset, etc.)
|
||||||
if (!FlxG.sound.music.playing) return;
|
if (!FlxG.sound.music.playing) return;
|
||||||
if (songStartDelay > 0) return;
|
|
||||||
|
|
||||||
vocals.pause();
|
vocals.pause();
|
||||||
|
|
||||||
FlxG.sound.music.play();
|
FlxG.sound.music.play(FlxG.sound.music.time);
|
||||||
|
|
||||||
vocals.time = FlxG.sound.music.time;
|
vocals.time = FlxG.sound.music.time;
|
||||||
vocals.play(false, FlxG.sound.music.time);
|
vocals.play(false, FlxG.sound.music.time);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package funkin.play.song;
|
||||||
|
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.data.IRegistryEntry;
|
import funkin.data.IRegistryEntry;
|
||||||
import funkin.data.song.SongData.SongCharacterData;
|
import funkin.data.song.SongData.SongCharacterData;
|
||||||
import funkin.data.song.SongData.SongChartData;
|
import funkin.data.song.SongData.SongChartData;
|
||||||
|
|
@ -454,7 +455,11 @@ class SongDifficulty
|
||||||
public inline function playInst(volume:Float = 1.0, looped:Bool = false):Void
|
public inline function playInst(volume:Float = 1.0, looped:Bool = false):Void
|
||||||
{
|
{
|
||||||
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : '';
|
||||||
FlxG.sound.playMusic(Paths.inst(this.song.id, suffix), volume, looped);
|
|
||||||
|
FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped);
|
||||||
|
|
||||||
|
// Workaround for a bug where FlxG.sound.music.update() was being called twice.
|
||||||
|
FlxG.sound.list.remove(FlxG.sound.music);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -532,16 +537,16 @@ class SongDifficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add player vocals.
|
// Add player vocals.
|
||||||
if (voiceList[0] != null) result.addPlayerVoice(new FlxSound().loadEmbedded(Assets.getSound(voiceList[0])));
|
if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(Assets.getSound(voiceList[0])));
|
||||||
// Add opponent vocals.
|
// Add opponent vocals.
|
||||||
if (voiceList[1] != null) result.addOpponentVoice(new FlxSound().loadEmbedded(Assets.getSound(voiceList[1])));
|
if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(Assets.getSound(voiceList[1])));
|
||||||
|
|
||||||
// Add additional vocals.
|
// Add additional vocals.
|
||||||
if (voiceList.length > 2)
|
if (voiceList.length > 2)
|
||||||
{
|
{
|
||||||
for (i in 2...voiceList.length)
|
for (i in 2...voiceList.length)
|
||||||
{
|
{
|
||||||
result.add(new FlxSound().loadEmbedded(Assets.getSound(voiceList[i])));
|
result.add(FunkinSound.load(Assets.getSound(voiceList[i])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
||||||
{
|
{
|
||||||
// Display Conductor info in the watch window.
|
// Display Conductor info in the watch window.
|
||||||
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
|
FlxG.watch.addQuick("songPosition", Conductor.songPosition);
|
||||||
|
FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset);
|
||||||
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
|
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
|
||||||
FlxG.watch.addQuick("bpm", Conductor.bpm);
|
FlxG.watch.addQuick("bpm", Conductor.bpm);
|
||||||
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
|
FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime);
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,14 @@ import flixel.util.FlxSort;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.audio.visualize.PolygonSpectogram;
|
import funkin.audio.visualize.PolygonSpectogram;
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
import funkin.data.song.SongData.SongCharacterData;
|
import funkin.data.song.SongData.SongCharacterData;
|
||||||
import funkin.data.song.SongData.SongChartData;
|
import funkin.data.song.SongData.SongChartData;
|
||||||
import funkin.data.song.SongData.SongEventData;
|
import funkin.data.song.SongData.SongEventData;
|
||||||
import funkin.data.song.SongData.SongMetadata;
|
import funkin.data.song.SongData.SongMetadata;
|
||||||
import funkin.data.song.SongData.SongNoteData;
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
|
import funkin.data.song.SongData.SongOffsets;
|
||||||
import funkin.data.song.SongDataUtils;
|
import funkin.data.song.SongDataUtils;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import funkin.input.Cursor;
|
import funkin.input.Cursor;
|
||||||
|
|
@ -247,6 +249,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Make sure playhead doesn't go outside the song.
|
// Make sure playhead doesn't go outside the song.
|
||||||
if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs;
|
if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs;
|
||||||
|
|
||||||
|
onSongLengthChanged();
|
||||||
|
|
||||||
return this.songLengthInMs;
|
return this.songLengthInMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -898,7 +902,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
* Replaced when switching instrumentals.
|
* Replaced when switching instrumentals.
|
||||||
* `null` until an instrumental track is loaded.
|
* `null` until an instrumental track is loaded.
|
||||||
*/
|
*/
|
||||||
var audioInstTrack:Null<FlxSound> = null;
|
var audioInstTrack:Null<FunkinSound> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The raw byte data for the instrumental audio tracks.
|
* The raw byte data for the instrumental audio tracks.
|
||||||
|
|
@ -1170,6 +1174,41 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
return currentSongMetadata.artist = value;
|
return currentSongMetadata.artist = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience property to get the song offset data for the current variation.
|
||||||
|
*/
|
||||||
|
var currentSongOffsets(get, set):SongOffsets;
|
||||||
|
|
||||||
|
function get_currentSongOffsets():SongOffsets
|
||||||
|
{
|
||||||
|
if (currentSongMetadata.offsets == null)
|
||||||
|
{
|
||||||
|
// Initialize to the default value if not set.
|
||||||
|
currentSongMetadata.offsets = new SongOffsets();
|
||||||
|
}
|
||||||
|
return currentSongMetadata.offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_currentSongOffsets(value:SongOffsets):SongOffsets
|
||||||
|
{
|
||||||
|
return currentSongMetadata.offsets = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentInstrumentalOffset(get, set):Float;
|
||||||
|
|
||||||
|
function get_currentInstrumentalOffset():Float
|
||||||
|
{
|
||||||
|
// TODO: Apply for alt instrumentals.
|
||||||
|
return currentSongOffsets.getInstrumentalOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_currentInstrumentalOffset(value:Float):Float
|
||||||
|
{
|
||||||
|
// TODO: Apply for alt instrumentals.
|
||||||
|
currentSongOffsets.setInstrumentalOffset(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The variation ID for the difficulty which is currently being edited.
|
* The variation ID for the difficulty which is currently being edited.
|
||||||
*/
|
*/
|
||||||
|
|
@ -2199,7 +2238,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
playbarHeadDragging = true;
|
playbarHeadDragging = true;
|
||||||
|
|
||||||
// If we were dragging the playhead while the song was playing, resume playing.
|
// If we were dragging the playhead while the song was playing, resume playing.
|
||||||
if (audioInstTrack != null && audioInstTrack.playing)
|
if (audioInstTrack != null && audioInstTrack.isPlaying)
|
||||||
{
|
{
|
||||||
playbarHeadDraggingWasPlaying = true;
|
playbarHeadDraggingWasPlaying = true;
|
||||||
stopAudioPlayback();
|
stopAudioPlayback();
|
||||||
|
|
@ -2213,10 +2252,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
playbarHeadLayout.playbarHead.onDrag = function(_:DragEvent) {
|
playbarHeadLayout.playbarHead.onDrag = function(_:DragEvent) {
|
||||||
if (playbarHeadDragging)
|
if (playbarHeadDragging)
|
||||||
{
|
{
|
||||||
var value:Null<Float> = playbarHeadLayout.playbarHead?.value;
|
|
||||||
|
|
||||||
// Set the song position to where the playhead was moved to.
|
// Set the song position to where the playhead was moved to.
|
||||||
scrollPositionInPixels = songLengthInPixels * ((value ?? 0.0) / 100);
|
scrollPositionInPixels = (songLengthInPixels) * playbarHeadLayout.playbarHead.value / 100;
|
||||||
// Update the conductor and audio tracks to match.
|
// Update the conductor and audio tracks to match.
|
||||||
moveSongToScrollPosition();
|
moveSongToScrollPosition();
|
||||||
}
|
}
|
||||||
|
|
@ -2399,8 +2436,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
menubarItemGoToBackupsFolder.onClick = _ -> this.openBackupsFolder();
|
menubarItemGoToBackupsFolder.onClick = _ -> this.openBackupsFolder();
|
||||||
#else
|
#else
|
||||||
// Disable the menu item if we're not on a desktop platform.
|
// Disable the menu item if we're not on a desktop platform.
|
||||||
var menubarItemGoToBackupsFolder = findComponent('menubarItemGoToBackupsFolder', MenuItem);
|
menubarItemGoToBackupsFolder.disabled = true;
|
||||||
if (menubarItemGoToBackupsFolder != null) menubarItemGoToBackupsFolder.disabled = true;
|
|
||||||
#end
|
#end
|
||||||
|
|
||||||
menubarItemUserGuide.onClick = _ -> this.openUserGuideDialog();
|
menubarItemUserGuide.onClick = _ -> this.openUserGuideDialog();
|
||||||
|
|
@ -2430,7 +2466,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
menubarItemLoadVocals.onClick = _ -> this.openUploadVocalsDialog(true);
|
menubarItemLoadVocals.onClick = _ -> this.openUploadVocalsDialog(true);
|
||||||
|
|
||||||
menubarItemVolumeMetronome.onChange = event -> {
|
menubarItemVolumeMetronome.onChange = event -> {
|
||||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
var volume:Float = event.value.toFloat() / 100.0;
|
||||||
metronomeVolume = volume;
|
metronomeVolume = volume;
|
||||||
menubarLabelVolumeMetronome.text = 'Metronome - ${Std.int(event.value)}%';
|
menubarLabelVolumeMetronome.text = 'Metronome - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
|
|
@ -2443,26 +2479,26 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent;
|
menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent;
|
||||||
|
|
||||||
menubarItemVolumeHitsound.onChange = event -> {
|
menubarItemVolumeHitsound.onChange = event -> {
|
||||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
var volume:Float = event.value.toFloat() / 100.0;
|
||||||
hitsoundVolume = volume;
|
hitsoundVolume = volume;
|
||||||
menubarLabelVolumeHitsound.text = 'Hitsound - ${Std.int(event.value)}%';
|
menubarLabelVolumeHitsound.text = 'Hitsound - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
menubarItemVolumeHitsound.value = Std.int(hitsoundVolume * 100);
|
menubarItemVolumeHitsound.value = Std.int(hitsoundVolume * 100);
|
||||||
|
|
||||||
menubarItemVolumeInstrumental.onChange = event -> {
|
menubarItemVolumeInstrumental.onChange = event -> {
|
||||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
var volume:Float = event.value.toFloat() / 100.0;
|
||||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||||
menubarLabelVolumeInstrumental.text = 'Instrumental - ${Std.int(event.value)}%';
|
menubarLabelVolumeInstrumental.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
|
|
||||||
menubarItemVolumeVocals.onChange = event -> {
|
menubarItemVolumeVocals.onChange = event -> {
|
||||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
var volume:Float = event.value.toFloat() / 100.0;
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
||||||
menubarLabelVolumeVocals.text = 'Voices - ${Std.int(event.value)}%';
|
menubarLabelVolumeVocals.text = 'Voices - ${Std.int(event.value)}%';
|
||||||
}
|
}
|
||||||
|
|
||||||
menubarItemPlaybackSpeed.onChange = event -> {
|
menubarItemPlaybackSpeed.onChange = event -> {
|
||||||
var pitch:Float = event.value * 2.0 / 100.0;
|
var pitch:Float = (event.value * 2.0) / 100.0;
|
||||||
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
||||||
#if FLX_PITCH
|
#if FLX_PITCH
|
||||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||||
|
|
@ -2621,7 +2657,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
super.update(elapsed);
|
super.update(elapsed);
|
||||||
|
|
||||||
// These ones happen even if the modal dialog is open.
|
// These ones happen even if the modal dialog is open.
|
||||||
handleMusicPlayback();
|
handleMusicPlayback(elapsed);
|
||||||
handleNoteDisplay();
|
handleNoteDisplay();
|
||||||
|
|
||||||
// These ones only happen if the modal dialog is not open.
|
// These ones only happen if the modal dialog is not open.
|
||||||
|
|
@ -2657,7 +2693,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// dispatchEvent gets called here.
|
// dispatchEvent gets called here.
|
||||||
if (!super.beatHit()) return false;
|
if (!super.beatHit()) return false;
|
||||||
|
|
||||||
if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.playing))
|
if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.isPlaying))
|
||||||
{
|
{
|
||||||
playMetronomeTick(Conductor.currentBeat % 4 == 0);
|
playMetronomeTick(Conductor.currentBeat % 4 == 0);
|
||||||
}
|
}
|
||||||
|
|
@ -2673,7 +2709,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// dispatchEvent gets called here.
|
// dispatchEvent gets called here.
|
||||||
if (!super.stepHit()) return false;
|
if (!super.stepHit()) return false;
|
||||||
|
|
||||||
if (audioInstTrack != null && audioInstTrack.playing)
|
if (audioInstTrack != null && audioInstTrack.isPlaying)
|
||||||
{
|
{
|
||||||
if (healthIconDad != null) healthIconDad.onStepHit(Conductor.currentStep);
|
if (healthIconDad != null) healthIconDad.onStepHit(Conductor.currentStep);
|
||||||
if (healthIconBF != null) healthIconBF.onStepHit(Conductor.currentStep);
|
if (healthIconBF != null) healthIconBF.onStepHit(Conductor.currentStep);
|
||||||
|
|
@ -2694,18 +2730,36 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
/**
|
/**
|
||||||
* Handle syncronizing the conductor with the music playback.
|
* Handle syncronizing the conductor with the music playback.
|
||||||
*/
|
*/
|
||||||
function handleMusicPlayback():Void
|
function handleMusicPlayback(elapsed:Float):Void
|
||||||
{
|
{
|
||||||
if (audioInstTrack != null && audioInstTrack.playing)
|
if (audioInstTrack != null)
|
||||||
|
{
|
||||||
|
// This normally gets called by FlxG.sound.update()
|
||||||
|
// but we handle instrumental updates manually to prevent FlxG.sound.music.update()
|
||||||
|
// from being called twice when we move to the PlayState.
|
||||||
|
audioInstTrack.update(elapsed);
|
||||||
|
|
||||||
|
// If the song starts 50ms in, make sure we start the song there.
|
||||||
|
if (Conductor.instrumentalOffset < 0)
|
||||||
|
{
|
||||||
|
if (audioInstTrack.time < -Conductor.instrumentalOffset)
|
||||||
|
{
|
||||||
|
trace('Resetting instrumental time to ${- Conductor.instrumentalOffset}ms');
|
||||||
|
audioInstTrack.time = -Conductor.instrumentalOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioInstTrack != null && audioInstTrack.isPlaying)
|
||||||
{
|
{
|
||||||
if (FlxG.keys.pressed.ALT)
|
if (FlxG.keys.pressed.ALT)
|
||||||
{
|
{
|
||||||
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
|
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
|
||||||
|
|
||||||
var oldStepTime:Float = Conductor.currentStepTime;
|
var oldStepTime:Float = Conductor.currentStepTime;
|
||||||
var oldSongPosition:Float = Conductor.songPosition;
|
var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset;
|
||||||
Conductor.update(audioInstTrack.time);
|
Conductor.update(audioInstTrack.time);
|
||||||
handleHitsounds(oldSongPosition, Conductor.songPosition);
|
handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset);
|
||||||
// Resync vocals.
|
// Resync vocals.
|
||||||
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||||
{
|
{
|
||||||
|
|
@ -2721,9 +2775,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Else, move the entire view.
|
// Else, move the entire view.
|
||||||
var oldSongPosition:Float = Conductor.songPosition;
|
var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset;
|
||||||
Conductor.update(audioInstTrack.time);
|
Conductor.update(audioInstTrack.time);
|
||||||
handleHitsounds(oldSongPosition, Conductor.songPosition);
|
handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset);
|
||||||
// Resync vocals.
|
// Resync vocals.
|
||||||
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||||
{
|
{
|
||||||
|
|
@ -2732,7 +2786,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
// We need time in fractional steps here to allow the song to actually play.
|
// We need time in fractional steps here to allow the song to actually play.
|
||||||
// Also account for a potentially offset playhead.
|
// Also account for a potentially offset playhead.
|
||||||
scrollPositionInPixels = Conductor.currentStepTime * GRID_SIZE - playheadPositionInPixels;
|
scrollPositionInPixels = (Conductor.currentStepTime + Conductor.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels;
|
||||||
|
|
||||||
// DO NOT move song to scroll position here specifically.
|
// DO NOT move song to scroll position here specifically.
|
||||||
|
|
||||||
|
|
@ -4193,22 +4247,23 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
playbarHeadLayout.x = 4;
|
playbarHeadLayout.x = 4;
|
||||||
playbarHeadLayout.y = FlxG.height - 48 - 8;
|
playbarHeadLayout.y = FlxG.height - 48 - 8;
|
||||||
|
|
||||||
var songPos:Float = Conductor.songPosition;
|
|
||||||
var songRemaining:Float = Math.max(songLengthInMs - songPos, 0.0);
|
|
||||||
|
|
||||||
// Move the playhead to match the song position, if we aren't dragging it.
|
// Move the playhead to match the song position, if we aren't dragging it.
|
||||||
if (!playbarHeadDragging)
|
if (!playbarHeadDragging)
|
||||||
{
|
{
|
||||||
var songPosPercent:Float = songPos / songLengthInMs * 100;
|
var songPosPercent = scrollPositionInPixels / (songLengthInPixels) * 100;
|
||||||
|
|
||||||
if (playbarHeadLayout.playbarHead.value != songPosPercent) playbarHeadLayout.playbarHead.value = songPosPercent;
|
if (playbarHeadLayout.playbarHead.value != songPosPercent) playbarHeadLayout.playbarHead.value = songPosPercent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var songPosSeconds:String = Std.string(Math.floor((songPos / 1000) % 60)).lpad('0', 2);
|
var songPos:Float = Conductor.songPosition + Conductor.instrumentalOffset;
|
||||||
var songPosMinutes:String = Std.string(Math.floor((songPos / 1000) / 60)).lpad('0', 2);
|
var songPosSeconds:String = Std.string(Math.floor((Math.abs(songPos) / 1000) % 60)).lpad('0', 2);
|
||||||
|
var songPosMinutes:String = Std.string(Math.floor((Math.abs(songPos) / 1000) / 60)).lpad('0', 2);
|
||||||
|
if (songPos < 0) songPosMinutes = '-' + songPosMinutes;
|
||||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}';
|
var songPosString:String = '${songPosMinutes}:${songPosSeconds}';
|
||||||
|
|
||||||
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
||||||
|
|
||||||
|
var songRemaining:Float = Math.max(songLengthInMs - songPos, 0.0);
|
||||||
var songRemainingSeconds:String = Std.string(Math.floor((songRemaining / 1000) % 60)).lpad('0', 2);
|
var songRemainingSeconds:String = Std.string(Math.floor((songRemaining / 1000) % 60)).lpad('0', 2);
|
||||||
var songRemainingMinutes:String = Std.string(Math.floor((songRemaining / 1000) / 60)).lpad('0', 2);
|
var songRemainingMinutes:String = Std.string(Math.floor((songRemaining / 1000) / 60)).lpad('0', 2);
|
||||||
var songRemainingString:String = '-${songRemainingMinutes}:${songRemainingSeconds}';
|
var songRemainingString:String = '-${songRemainingMinutes}:${songRemainingSeconds}';
|
||||||
|
|
@ -4524,6 +4579,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
super.handleQuickWatch();
|
super.handleQuickWatch();
|
||||||
|
|
||||||
|
FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0);
|
||||||
|
|
||||||
FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels);
|
FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels);
|
||||||
FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels);
|
FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels);
|
||||||
|
|
||||||
|
|
@ -4551,6 +4608,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
autoSave();
|
autoSave();
|
||||||
|
|
||||||
|
stopWelcomeMusic();
|
||||||
|
|
||||||
var startTimestamp:Float = 0;
|
var startTimestamp:Float = 0;
|
||||||
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs;
|
||||||
|
|
||||||
|
|
@ -4605,7 +4664,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
});
|
});
|
||||||
|
|
||||||
// Override music.
|
// Override music.
|
||||||
if (audioInstTrack != null) FlxG.sound.music = audioInstTrack;
|
if (audioInstTrack != null)
|
||||||
|
{
|
||||||
|
FlxG.sound.music = audioInstTrack;
|
||||||
|
}
|
||||||
if (audioVocalTrackGroup != null) targetState.vocals = audioVocalTrackGroup;
|
if (audioVocalTrackGroup != null) targetState.vocals = audioVocalTrackGroup;
|
||||||
|
|
||||||
this.persistentUpdate = false;
|
this.persistentUpdate = false;
|
||||||
|
|
@ -4734,6 +4796,29 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
this.switchToInstrumental(currentInstrumentalId, currentSongMetadata.playData.characters.player, currentSongMetadata.playData.characters.opponent);
|
this.switchToInstrumental(currentInstrumentalId, currentSongMetadata.playData.characters.player, currentSongMetadata.playData.characters.opponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSongLengthChanged():Void
|
||||||
|
{
|
||||||
|
if (gridTiledSprite != null) gridTiledSprite.height = songLengthInPixels;
|
||||||
|
if (gridPlayheadScrollArea != null)
|
||||||
|
{
|
||||||
|
gridPlayheadScrollArea.setGraphicSize(Std.int(gridPlayheadScrollArea.width), songLengthInPixels);
|
||||||
|
gridPlayheadScrollArea.updateHitbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any notes past the end of the song.
|
||||||
|
var songCutoffPointSteps:Float = songLengthInSteps - 0.1;
|
||||||
|
var songCutoffPointMs:Float = Conductor.getStepTimeInMs(songCutoffPointSteps);
|
||||||
|
currentSongChartNoteData = SongDataUtils.clampSongNoteData(currentSongChartNoteData, 0.0, songCutoffPointMs);
|
||||||
|
currentSongChartEventData = SongDataUtils.clampSongEventData(currentSongChartEventData, 0.0, songCutoffPointMs);
|
||||||
|
|
||||||
|
scrollPositionInPixels = 0;
|
||||||
|
playheadPositionInPixels = 0;
|
||||||
|
notePreviewDirty = true;
|
||||||
|
notePreviewViewportBoundsDirty = true;
|
||||||
|
noteDisplayDirty = true;
|
||||||
|
moveSongToScrollPosition();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CHART DATA FUNCTIONS
|
* CHART DATA FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
|
@ -4885,11 +4970,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Update the songPosition in the audio tracks.
|
// Update the songPosition in the audio tracks.
|
||||||
if (audioInstTrack != null)
|
if (audioInstTrack != null)
|
||||||
{
|
{
|
||||||
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instrumentalOffset;
|
||||||
// Update the songPosition in the Conductor.
|
// Update the songPosition in the Conductor.
|
||||||
Conductor.update(audioInstTrack.time);
|
Conductor.update(audioInstTrack.time);
|
||||||
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = audioInstTrack.time;
|
||||||
}
|
}
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs;
|
|
||||||
|
|
||||||
// We need to update the note sprites because we changed the scroll position.
|
// We need to update the note sprites because we changed the scroll position.
|
||||||
noteDisplayDirty = true;
|
noteDisplayDirty = true;
|
||||||
|
|
@ -4949,6 +5034,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
moveSongToScrollPosition();
|
moveSongToScrollPosition();
|
||||||
|
|
||||||
|
fadeInWelcomeMusic(7, 10);
|
||||||
|
|
||||||
// Reapply the volume.
|
// Reapply the volume.
|
||||||
var instTargetVolume:Float = menubarItemVolumeInstrumental.value ?? 1.0;
|
var instTargetVolume:Float = menubarItemVolumeInstrumental.value ?? 1.0;
|
||||||
var vocalTargetVolume:Float = menubarItemVolumeVocals.value ?? 1.0;
|
var vocalTargetVolume:Float = menubarItemVolumeVocals.value ?? 1.0;
|
||||||
|
|
@ -5245,15 +5332,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
if (audioInstTrack == null) return;
|
if (audioInstTrack == null) return;
|
||||||
|
|
||||||
if (audioInstTrack.playing)
|
if (audioInstTrack.isPlaying)
|
||||||
{
|
{
|
||||||
fadeInWelcomeMusic(7, 10);
|
// Pause
|
||||||
stopAudioPlayback();
|
stopAudioPlayback();
|
||||||
|
fadeInWelcomeMusic(7, 10);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
stopWelcomeMusic();
|
// Play
|
||||||
startAudioPlayback();
|
startAudioPlayback();
|
||||||
|
stopWelcomeMusic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5263,32 +5352,24 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
// Prevent the time from skipping back to 0 when the song ends.
|
// Prevent the time from skipping back to 0 when the song ends.
|
||||||
audioInstTrack.onComplete = function() {
|
audioInstTrack.onComplete = function() {
|
||||||
if (audioInstTrack != null) audioInstTrack.pause();
|
if (audioInstTrack != null)
|
||||||
|
{
|
||||||
|
audioInstTrack.pause();
|
||||||
|
// Keep the track at the end.
|
||||||
|
audioInstTrack.time = audioInstTrack.length;
|
||||||
|
}
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||||
};
|
};
|
||||||
|
|
||||||
songLengthInMs = audioInstTrack.length;
|
|
||||||
|
|
||||||
if (gridTiledSprite != null) gridTiledSprite.height = songLengthInPixels;
|
|
||||||
if (gridPlayheadScrollArea != null)
|
|
||||||
{
|
|
||||||
gridPlayheadScrollArea.setGraphicSize(Std.int(gridPlayheadScrollArea.width), songLengthInPixels);
|
|
||||||
gridPlayheadScrollArea.updateHitbox();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace('[WARN] Instrumental track was null!');
|
trace('ERROR: Instrumental track is null!');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretty much everything is going to need to be reset.
|
this.songLengthInMs = (audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset;
|
||||||
scrollPositionInPixels = 0;
|
|
||||||
playheadPositionInPixels = 0;
|
// Many things get reset when song length changes.
|
||||||
notePreviewDirty = true;
|
|
||||||
notePreviewViewportBoundsDirty = true;
|
|
||||||
noteDisplayDirty = true;
|
|
||||||
healthIconsDirty = true;
|
healthIconsDirty = true;
|
||||||
moveSongToScrollPosition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package funkin.ui.debug.charting.handlers;
|
||||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||||
import flixel.system.FlxSound;
|
import flixel.system.FlxSound;
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
import funkin.util.FileUtil;
|
import funkin.util.FileUtil;
|
||||||
import funkin.util.assets.SoundUtil;
|
import funkin.util.assets.SoundUtil;
|
||||||
|
|
@ -139,12 +140,14 @@ class ChartEditorAudioHandler
|
||||||
{
|
{
|
||||||
if (instId == '') instId = 'default';
|
if (instId == '') instId = 'default';
|
||||||
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
||||||
var instTrack:Null<FlxSound> = SoundUtil.buildFlxSoundFromBytes(instTrackData);
|
var instTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(instTrackData);
|
||||||
if (instTrack == null) return false;
|
if (instTrack == null) return false;
|
||||||
|
|
||||||
stopExistingInstrumental(state);
|
stopExistingInstrumental(state);
|
||||||
state.audioInstTrack = instTrack;
|
state.audioInstTrack = instTrack;
|
||||||
state.postLoadInstrumental();
|
state.postLoadInstrumental();
|
||||||
|
// Workaround for a bug where FlxG.sound.music.update() was being called twice.
|
||||||
|
FlxG.sound.list.remove(instTrack);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +168,7 @@ class ChartEditorAudioHandler
|
||||||
{
|
{
|
||||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||||
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
||||||
var vocalTrack:Null<FlxSound> = SoundUtil.buildFlxSoundFromBytes(vocalTrackData);
|
var vocalTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(vocalTrackData);
|
||||||
|
|
||||||
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
||||||
|
|
||||||
|
|
@ -175,17 +178,21 @@ class ChartEditorAudioHandler
|
||||||
{
|
{
|
||||||
case BF:
|
case BF:
|
||||||
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||||
|
state.audioVocalTrackGroup.playerVoicesOffset = state.currentSongOffsets.getVocalOffset(charId);
|
||||||
return true;
|
return true;
|
||||||
case DAD:
|
case DAD:
|
||||||
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||||
|
state.audioVocalTrackGroup.opponentVoicesOffset = state.currentSongOffsets.getVocalOffset(charId);
|
||||||
return true;
|
return true;
|
||||||
case OTHER:
|
case OTHER:
|
||||||
state.audioVocalTrackGroup.add(vocalTrack);
|
state.audioVocalTrackGroup.add(vocalTrack);
|
||||||
|
// TODO: Add offset for other characters.
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -673,6 +673,7 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
state.songMetadata.set(targetVariation, newSongMetadata);
|
state.songMetadata.set(targetVariation, newSongMetadata);
|
||||||
|
|
||||||
|
Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
|
||||||
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
||||||
|
|
||||||
state.difficultySelectDirty = true;
|
state.difficultySelectDirty = true;
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ class ChartEditorImportExportHandler
|
||||||
state.songChartData = newSongChartData;
|
state.songChartData = newSongChartData;
|
||||||
|
|
||||||
Conductor.forceBPM(null); // Disable the forced BPM.
|
Conductor.forceBPM(null); // Disable the forced BPM.
|
||||||
|
Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
|
||||||
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
||||||
|
|
||||||
state.notePreviewDirty = true;
|
state.notePreviewDirty = true;
|
||||||
|
|
|
||||||
|
|
@ -620,6 +620,27 @@ class ChartEditorToolboxHandler
|
||||||
};
|
};
|
||||||
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
|
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
|
||||||
|
|
||||||
|
var inputOffsetInst:Null<NumberStepper> = toolbox.findComponent('inputOffsetInst', NumberStepper);
|
||||||
|
if (inputOffsetInst == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputOffsetInst component.';
|
||||||
|
inputOffsetInst.onChange = function(event:UIEvent) {
|
||||||
|
if (event.value == null) return;
|
||||||
|
|
||||||
|
state.currentInstrumentalOffset = event.value;
|
||||||
|
Conductor.instrumentalOffset = event.value;
|
||||||
|
// Update song length.
|
||||||
|
state.songLengthInMs = (state.audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset;
|
||||||
|
};
|
||||||
|
inputOffsetInst.value = state.currentInstrumentalOffset;
|
||||||
|
|
||||||
|
var inputOffsetVocal:Null<NumberStepper> = toolbox.findComponent('inputOffsetVocal', NumberStepper);
|
||||||
|
if (inputOffsetVocal == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputOffsetVocal component.';
|
||||||
|
inputOffsetVocal.onChange = function(event:UIEvent) {
|
||||||
|
if (event.value == null) return;
|
||||||
|
|
||||||
|
state.currentSongMetadata.offsets.setVocalOffset(state.currentSongMetadata.playData.characters.player, event.value);
|
||||||
|
};
|
||||||
|
inputOffsetVocal.value = state.currentSongMetadata.offsets.getVocalOffset(state.currentSongMetadata.playData.characters.player);
|
||||||
|
|
||||||
var labelScrollSpeed:Null<Label> = toolbox.findComponent('labelScrollSpeed', Label);
|
var labelScrollSpeed:Null<Label> = toolbox.findComponent('labelScrollSpeed', Label);
|
||||||
if (labelScrollSpeed == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find labelScrollSpeed component.';
|
if (labelScrollSpeed == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find labelScrollSpeed component.';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package funkin.util.assets;
|
package funkin.util.assets;
|
||||||
|
|
||||||
import haxe.io.Bytes;
|
import haxe.io.Bytes;
|
||||||
import flixel.system.FlxSound;
|
import openfl.media.Sound as OpenFLSound;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
|
|
||||||
class SoundUtil
|
class SoundUtil
|
||||||
{
|
{
|
||||||
|
|
@ -11,13 +12,13 @@ class SoundUtil
|
||||||
* @param input The byte data.
|
* @param input The byte data.
|
||||||
* @return The playable sound, or `null` if loading failed.
|
* @return The playable sound, or `null` if loading failed.
|
||||||
*/
|
*/
|
||||||
public static function buildFlxSoundFromBytes(input:Null<Bytes>):Null<FlxSound>
|
public static function buildSoundFromBytes(input:Null<Bytes>):Null<FunkinSound>
|
||||||
{
|
{
|
||||||
if (input == null) return null;
|
if (input == null) return null;
|
||||||
|
|
||||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
var openflSound:OpenFLSound = new OpenFLSound();
|
||||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(input), input.length);
|
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(input), input.length);
|
||||||
var output:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
var output:FunkinSound = FunkinSound.load(openflSound, 1.0, false);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue