mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-07-17 17:50:38 +00:00
Merge pull request #238 from FunkinCrew/feature/chart-editor-offsets-part-1
Implement instrumental and vocal offsets into the PlayState.
This commit is contained in:
commit
8d8b8743ae
|
@ -45,6 +45,8 @@ 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.
|
||||||
*/
|
*/
|
||||||
|
@ -144,13 +146,27 @@ 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 beatHit(default, null):FlxSignal = new FlxSignal();
|
||||||
public static var stepHit(default, null):FlxSignal = new FlxSignal();
|
public static var stepHit(default, null):FlxSignal = new FlxSignal();
|
||||||
|
|
||||||
public static var lastSongPos:Float;
|
public static var lastSongPos:Float;
|
||||||
public static var visualOffset:Float = 0;
|
|
||||||
public static var audioOffset:Float = 0;
|
/**
|
||||||
public static var offset:Float = 0;
|
* An offset tied to the current chart file to compensate for a delay in the instrumental.
|
||||||
|
*/
|
||||||
|
public static var instrumentalOffset:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An offset tied to the file format of the audio file being played.
|
||||||
|
*/
|
||||||
|
public static var formatOffset:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An offset set by the user to compensate for input lag.
|
||||||
|
*/
|
||||||
|
public static var inputOffset:Float = 0;
|
||||||
|
|
||||||
public static var beatsPerMeasure(get, never):Float;
|
public static var beatsPerMeasure(get, never):Float;
|
||||||
|
|
||||||
|
@ -200,15 +216,24 @@ class Conductor
|
||||||
* @param songPosition The current position in the song in milliseconds.
|
* @param songPosition The current position in the song in milliseconds.
|
||||||
* Leave blank to use the FlxG.sound.music position.
|
* Leave blank to use the FlxG.sound.music position.
|
||||||
*/
|
*/
|
||||||
public static function update(songPosition:Float = null)
|
public static function update(?songPosition:Float)
|
||||||
{
|
{
|
||||||
if (songPosition == null) songPosition = (FlxG.sound.music != null) ? FlxG.sound.music.time + Conductor.offset : 0.0;
|
if (songPosition == null)
|
||||||
|
{
|
||||||
|
// Take into account instrumental and file format song offsets.
|
||||||
|
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
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).
|
||||||
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];
|
||||||
for (i in 0...timeChanges.length)
|
for (i in 0...timeChanges.length)
|
||||||
{
|
{
|
||||||
|
@ -230,6 +255,9 @@ 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
|
||||||
{
|
{
|
||||||
|
@ -240,6 +268,8 @@ 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.
|
||||||
|
|
|
@ -11,12 +11,22 @@ class VoicesGroup extends SoundGroup
|
||||||
/**
|
/**
|
||||||
* Control the volume of only the sounds in the player group.
|
* Control the volume of only the sounds in the player group.
|
||||||
*/
|
*/
|
||||||
public var playerVolume(default, set):Float;
|
public var playerVolume(default, set):Float = 1.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control the volume of only the sounds in the opponent group.
|
* Control the volume of only the sounds in the opponent group.
|
||||||
*/
|
*/
|
||||||
public var opponentVolume(default, set):Float;
|
public var opponentVolume(default, set):Float = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time offset for the player's vocal track.
|
||||||
|
*/
|
||||||
|
public var playerVoicesOffset(default, set):Float = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time offset for the opponent's vocal track.
|
||||||
|
*/
|
||||||
|
public var opponentVoicesOffset(default, set):Float = 0.0;
|
||||||
|
|
||||||
public function new()
|
public function new()
|
||||||
{
|
{
|
||||||
|
@ -42,6 +52,57 @@ class VoicesGroup extends SoundGroup
|
||||||
return playerVolume = volume;
|
return playerVolume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override function set_time(time:Float):Float
|
||||||
|
{
|
||||||
|
forEachAlive(function(snd) {
|
||||||
|
// account for different offsets per sound?
|
||||||
|
snd.time = time;
|
||||||
|
});
|
||||||
|
|
||||||
|
playerVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time -= playerVoicesOffset;
|
||||||
|
});
|
||||||
|
opponentVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time -= opponentVoicesOffset;
|
||||||
|
});
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_playerVoicesOffset(offset:Float):Float
|
||||||
|
{
|
||||||
|
playerVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time += playerVoicesOffset;
|
||||||
|
voice.time -= offset;
|
||||||
|
});
|
||||||
|
return playerVoicesOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_opponentVoicesOffset(offset:Float):Float
|
||||||
|
{
|
||||||
|
opponentVoices.forEachAlive(function(voice:FlxSound) {
|
||||||
|
voice.time += opponentVoicesOffset;
|
||||||
|
voice.time -= 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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -34,6 +34,12 @@ class SongMetadata
|
||||||
@:default(false)
|
@:default(false)
|
||||||
public var looped:Bool;
|
public var looped:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumental and vocal offsets. Optional, defaults to 0.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
public var offsets:SongOffsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data relating to the song's gameplay.
|
* Data relating to the song's gameplay.
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +65,7 @@ class SongMetadata
|
||||||
this.artist = artist;
|
this.artist = artist;
|
||||||
this.timeFormat = 'ms';
|
this.timeFormat = 'ms';
|
||||||
this.divisions = null;
|
this.divisions = null;
|
||||||
|
this.offsets = new SongOffsets();
|
||||||
this.timeChanges = [new SongTimeChange(0, 100)];
|
this.timeChanges = [new SongTimeChange(0, 100)];
|
||||||
this.looped = false;
|
this.looped = false;
|
||||||
this.playData = new SongPlayData();
|
this.playData = new SongPlayData();
|
||||||
|
@ -196,6 +203,90 @@ class SongTimeChange
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offsets to apply to the song's instrumental and vocals, relative to the chart.
|
||||||
|
* These are intended to correct for issues with the chart, or with the song's audio (for example a 10ms delay before the song starts).
|
||||||
|
* This is independent of the offsets applied in the user's settings, which are applied after these offsets and intended to correct for the user's hardware.
|
||||||
|
*/
|
||||||
|
class SongOffsets
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The offset, in milliseconds, to apply to the song's instrumental relative to the chart.
|
||||||
|
* For example, setting this to `-10.0` will start the instrumental 10ms earlier than the chart.
|
||||||
|
*
|
||||||
|
* Setting this to `-5000.0` means the chart start 5 seconds into the song.
|
||||||
|
* Setting this to `5000.0` means there will be 5 seconds of silence before the song starts.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default(0)
|
||||||
|
public var instrumental:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply different offsets to different alternate instrumentals.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([])
|
||||||
|
public var altInstrumentals:Map<String, Float>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset, in milliseconds, to apply to the song's vocals, relative to the chart.
|
||||||
|
* These are applied ON TOP OF the instrumental offset.
|
||||||
|
*/
|
||||||
|
@:optional
|
||||||
|
@:default([])
|
||||||
|
public var vocals:Map<String, Float>;
|
||||||
|
|
||||||
|
public function new(instrumental:Float = 0.0, ?altInstrumentals:Map<String, Float>, ?vocals:Map<String, Float>)
|
||||||
|
{
|
||||||
|
this.instrumental = instrumental;
|
||||||
|
this.altInstrumentals = altInstrumentals == null ? new Map<String, Float>() : altInstrumentals;
|
||||||
|
this.vocals = vocals == null ? new Map<String, Float>() : vocals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInstrumentalOffset(?instrumental:String):Float
|
||||||
|
{
|
||||||
|
if (instrumental == null || instrumental == '') return this.instrumental;
|
||||||
|
|
||||||
|
if (!this.altInstrumentals.exists(instrumental)) return this.instrumental;
|
||||||
|
|
||||||
|
return this.altInstrumentals.get(instrumental);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInstrumentalOffset(value:Float, ?instrumental:String):Float
|
||||||
|
{
|
||||||
|
if (instrumental == null || instrumental == '')
|
||||||
|
{
|
||||||
|
this.instrumental = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.altInstrumentals.set(instrumental, value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVocalOffset(charId:String):Float
|
||||||
|
{
|
||||||
|
if (!this.vocals.exists(charId)) return 0.0;
|
||||||
|
|
||||||
|
return this.vocals.get(charId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setVocalOffset(charId:String, value:Float):Float
|
||||||
|
{
|
||||||
|
this.vocals.set(charId, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a string representation suitable for debugging.
|
||||||
|
*/
|
||||||
|
public function toString():String
|
||||||
|
{
|
||||||
|
return 'SongOffsets(${this.instrumental}ms, ${this.altInstrumentals}, ${this.vocals})';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata for a song only used for the music.
|
* Metadata for a song only used for the music.
|
||||||
* For example, the menu music.
|
* For example, the menu music.
|
||||||
|
@ -309,6 +400,7 @@ class SongPlayData
|
||||||
* The difficulty ratings for this song as displayed in Freeplay.
|
* The difficulty ratings for this song as displayed in Freeplay.
|
||||||
* Key is a difficulty ID or `default`.
|
* Key is a difficulty ID or `default`.
|
||||||
*/
|
*/
|
||||||
|
@:optional
|
||||||
@:default(['default' => 1])
|
@:default(['default' => 1])
|
||||||
public var ratings:Map<String, Int>;
|
public var ratings:Map<String, Int>;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
* Handle breaking changes by incrementing this value
|
* Handle breaking changes by incrementing this value
|
||||||
* and adding migration to the `migrateStageData()` function.
|
* and adding migration to the `migrateStageData()` function.
|
||||||
*/
|
*/
|
||||||
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.0";
|
public static final SONG_METADATA_VERSION:thx.semver.Version = "2.2.1";
|
||||||
|
|
||||||
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";
|
public static final SONG_METADATA_VERSION_RULE:thx.semver.VersionRule = "2.2.x";
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ class SongMetadata_v2_1_0
|
||||||
*/
|
*/
|
||||||
public var playData:SongPlayData_v2_1_0;
|
public var playData:SongPlayData_v2_1_0;
|
||||||
|
|
||||||
|
// In metadata `v2.2.1`, `SongOffsets` was added.
|
||||||
|
// var offsets:SongOffsets;
|
||||||
// ==========
|
// ==========
|
||||||
// UNMODIFIED VALUES
|
// UNMODIFIED VALUES
|
||||||
// ==========
|
// ==========
|
||||||
|
|
|
@ -179,6 +179,13 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public var songScore:Int = 0;
|
public var songScore:Int = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start at this point in the song once the countdown is done.
|
||||||
|
* For example, if `startTimestamp` is `30000`, the song will start at the 30 second mark.
|
||||||
|
* Used for chart playtesting or practice.
|
||||||
|
*/
|
||||||
|
public var startTimestamp:Float = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An empty FlxObject contained in the scene.
|
* An empty FlxObject contained in the scene.
|
||||||
* The current gameplay camera will always follow this object. Tween its position to move the camera smoothly.
|
* The current gameplay camera will always follow this object. Tween its position to move the camera smoothly.
|
||||||
|
@ -254,10 +261,6 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
public var disableKeys:Bool = false;
|
public var disableKeys:Bool = false;
|
||||||
|
|
||||||
public var startTimestamp:Float = 0.0;
|
|
||||||
|
|
||||||
var overrideMusic:Bool = false;
|
|
||||||
|
|
||||||
public var isSubState(get, never):Bool;
|
public var isSubState(get, never):Bool;
|
||||||
|
|
||||||
function get_isSubState():Bool
|
function get_isSubState():Bool
|
||||||
|
@ -317,6 +320,18 @@ class PlayState extends MusicBeatSubState
|
||||||
*/
|
*/
|
||||||
var skipHeldTimer:Float = 0;
|
var skipHeldTimer:Float = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the PlayState was started with instrumentals and vocals already provided.
|
||||||
|
* Used by the chart editor to prevent replacing the music.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
@ -553,6 +568,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Prepare the Conductor.
|
// Prepare the Conductor.
|
||||||
Conductor.forceBPM(null);
|
Conductor.forceBPM(null);
|
||||||
|
Conductor.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
|
||||||
Conductor.mapTimeChanges(currentChart.timeChanges);
|
Conductor.mapTimeChanges(currentChart.timeChanges);
|
||||||
Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
|
Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
|
||||||
|
|
||||||
|
@ -719,8 +735,8 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
// Reset music properly.
|
// Reset music properly.
|
||||||
|
|
||||||
|
FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instrumentalOffset);
|
||||||
FlxG.sound.music.pause();
|
FlxG.sound.music.pause();
|
||||||
FlxG.sound.music.time = (startTimestamp);
|
|
||||||
|
|
||||||
if (!overrideMusic)
|
if (!overrideMusic)
|
||||||
{
|
{
|
||||||
|
@ -771,17 +787,40 @@ class PlayState extends MusicBeatSubState
|
||||||
if (isInCountdown)
|
if (isInCountdown)
|
||||||
{
|
{
|
||||||
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
||||||
if (Conductor.songPosition >= startTimestamp) startSong();
|
if (Conductor.songPosition >= (startTimestamp)) startSong();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// DO NOT FORGET TO REMOVE THE HARDCODE! WHEN I MAKE BETTER OFFSET SYSTEM!
|
if (Constants.EXT_SOUND == 'mp3')
|
||||||
|
{
|
||||||
|
Conductor.formatOffset = Constants.MP3_DELAY_MS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Conductor.formatOffset = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
// :nerd: um ackshually it's not 13 it's 11.97278911564
|
if (songStartDelay > 0)
|
||||||
if (Constants.EXT_SOUND == 'mp3') Conductor.offset = Constants.MP3_DELAY_MS;
|
{
|
||||||
|
// Code to handle the song not starting yet (positive instrumental offset in metadata).
|
||||||
Conductor.update();
|
// 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.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Make beat events still happen.
|
||||||
|
Conductor.update(Conductor.songPosition + elapsed * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Conductor.update(); // Normal conductor update.
|
||||||
|
}
|
||||||
|
|
||||||
if (!isGamePaused)
|
if (!isGamePaused)
|
||||||
{
|
{
|
||||||
|
@ -1157,12 +1196,12 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (!startingSong
|
if (!startingSong
|
||||||
&& FlxG.sound.music != null
|
&& FlxG.sound.music != null
|
||||||
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200
|
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPositionNoOffset)) > 200
|
||||||
|| Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200))
|
|| Math.abs(vocals.checkSyncError(Conductor.songPositionNoOffset)) > 200))
|
||||||
{
|
{
|
||||||
trace("VOCALS NEED RESYNC");
|
trace("VOCALS NEED RESYNC");
|
||||||
if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition - Conductor.offset));
|
if (vocals != null) trace(vocals.checkSyncError(Conductor.songPositionNoOffset));
|
||||||
trace(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset));
|
trace(FlxG.sound.music.time - (Conductor.songPositionNoOffset));
|
||||||
resyncVocals();
|
resyncVocals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1705,12 +1744,28 @@ class PlayState extends MusicBeatSubState
|
||||||
}
|
}
|
||||||
|
|
||||||
FlxG.sound.music.onComplete = endSong;
|
FlxG.sound.music.onComplete = endSong;
|
||||||
FlxG.sound.music.play();
|
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||||
FlxG.sound.music.time = startTimestamp;
|
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||||
|
FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
|
||||||
|
|
||||||
trace('Playing vocals...');
|
trace('Playing vocals...');
|
||||||
add(vocals);
|
add(vocals);
|
||||||
vocals.play();
|
|
||||||
resyncVocals();
|
if (FlxG.sound.music.time < 0)
|
||||||
|
{
|
||||||
|
// 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();
|
||||||
|
resyncVocals();
|
||||||
|
}
|
||||||
|
|
||||||
#if discord_rpc
|
#if discord_rpc
|
||||||
// Updating Discord Rich Presence (with Time Left)
|
// Updating Discord Rich Presence (with Time Left)
|
||||||
|
@ -1719,7 +1774,7 @@ class PlayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (startTimestamp > 0)
|
if (startTimestamp > 0)
|
||||||
{
|
{
|
||||||
FlxG.sound.music.time = startTimestamp;
|
FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
|
||||||
handleSkippedNotes();
|
handleSkippedNotes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1731,13 +1786,13 @@ class PlayState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (_exiting || vocals == null) return;
|
if (_exiting || vocals == null) return;
|
||||||
|
|
||||||
// Skip this if the music is paused (GameOver, Pause menu, 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();
|
||||||
Conductor.update();
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
package funkin.play.song;
|
package funkin.play.song;
|
||||||
|
|
||||||
import funkin.util.SortUtil;
|
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
import openfl.utils.Assets;
|
|
||||||
import funkin.modding.events.ScriptEvent;
|
|
||||||
import funkin.modding.IScriptedClass;
|
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.IRegistryEntry;
|
||||||
|
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.SongNoteData;
|
|
||||||
import funkin.data.song.SongRegistry;
|
|
||||||
import funkin.data.song.SongData.SongMetadata;
|
import funkin.data.song.SongData.SongMetadata;
|
||||||
import funkin.data.song.SongData.SongCharacterData;
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
|
import funkin.data.song.SongData.SongOffsets;
|
||||||
import funkin.data.song.SongData.SongTimeChange;
|
import funkin.data.song.SongData.SongTimeChange;
|
||||||
import funkin.data.song.SongData.SongTimeFormat;
|
import funkin.data.song.SongData.SongTimeFormat;
|
||||||
import funkin.data.IRegistryEntry;
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.modding.IScriptedClass;
|
||||||
|
import funkin.util.SortUtil;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a data structure managing information about the current song.
|
* This is a data structure managing information about the current song.
|
||||||
|
@ -172,6 +173,7 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
|
||||||
difficulty.timeChanges = metadata.timeChanges;
|
difficulty.timeChanges = metadata.timeChanges;
|
||||||
difficulty.looped = metadata.looped;
|
difficulty.looped = metadata.looped;
|
||||||
difficulty.generatedBy = metadata.generatedBy;
|
difficulty.generatedBy = metadata.generatedBy;
|
||||||
|
difficulty.offsets = metadata.offsets;
|
||||||
|
|
||||||
difficulty.stage = metadata.playData.stage;
|
difficulty.stage = metadata.playData.stage;
|
||||||
difficulty.noteStyle = metadata.playData.noteStyle;
|
difficulty.noteStyle = metadata.playData.noteStyle;
|
||||||
|
@ -391,6 +393,7 @@ class SongDifficulty
|
||||||
public var timeFormat:SongTimeFormat = Constants.DEFAULT_TIMEFORMAT;
|
public var timeFormat:SongTimeFormat = Constants.DEFAULT_TIMEFORMAT;
|
||||||
public var divisions:Null<Int> = null;
|
public var divisions:Null<Int> = null;
|
||||||
public var looped:Bool = false;
|
public var looped:Bool = false;
|
||||||
|
public var offsets:SongOffsets = new SongOffsets();
|
||||||
public var generatedBy:String = SongRegistry.DEFAULT_GENERATEDBY;
|
public var generatedBy:String = SongRegistry.DEFAULT_GENERATEDBY;
|
||||||
|
|
||||||
public var timeChanges:Array<SongTimeChange> = [];
|
public var timeChanges:Array<SongTimeChange> = [];
|
||||||
|
@ -542,6 +545,9 @@ class SongDifficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.playerVoicesOffset = offsets.getVocalOffset(characters.player);
|
||||||
|
result.opponentVoicesOffset = offsets.getVocalOffset(characters.opponent);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("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);
|
||||||
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
||||||
|
|
|
@ -66,6 +66,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
|
||||||
|
|
||||||
// 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("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);
|
||||||
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
|
||||||
|
|
|
@ -192,15 +192,15 @@ class LatencyState extends MusicBeatSubState
|
||||||
|
|
||||||
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed;
|
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed;
|
||||||
|
|
||||||
Conductor.update(swagSong.getTimeWithDiff() - Conductor.offset);
|
Conductor.update(swagSong.getTimeWithDiff() - Conductor.inputOffset);
|
||||||
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
|
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
|
||||||
|
|
||||||
songPosVis.x = songPosToX(Conductor.songPosition);
|
songPosVis.x = songPosToX(Conductor.songPosition);
|
||||||
songVisFollowAudio.x = songPosToX(Conductor.songPosition - Conductor.audioOffset);
|
songVisFollowAudio.x = songPosToX(Conductor.songPosition - Conductor.instrumentalOffset);
|
||||||
songVisFollowVideo.x = songPosToX(Conductor.songPosition - Conductor.visualOffset);
|
songVisFollowVideo.x = songPosToX(Conductor.songPosition - Conductor.inputOffset);
|
||||||
|
|
||||||
offsetText.text = "AUDIO Offset: " + Conductor.audioOffset + "ms";
|
offsetText.text = "INST Offset: " + Conductor.instrumentalOffset + "ms";
|
||||||
offsetText.text += "\nVIDOE Offset: " + Conductor.visualOffset + "ms";
|
offsetText.text += "\nINPUT Offset: " + Conductor.inputOffset + "ms";
|
||||||
offsetText.text += "\ncurrentStep: " + Conductor.currentStep;
|
offsetText.text += "\ncurrentStep: " + Conductor.currentStep;
|
||||||
offsetText.text += "\ncurrentBeat: " + Conductor.currentBeat;
|
offsetText.text += "\ncurrentBeat: " + Conductor.currentBeat;
|
||||||
|
|
||||||
|
@ -221,24 +221,24 @@ class LatencyState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
if (FlxG.keys.justPressed.RIGHT)
|
if (FlxG.keys.justPressed.RIGHT)
|
||||||
{
|
{
|
||||||
Conductor.audioOffset += 1 * multiply;
|
Conductor.instrumentalOffset += 1.0 * multiply;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.LEFT)
|
if (FlxG.keys.justPressed.LEFT)
|
||||||
{
|
{
|
||||||
Conductor.audioOffset -= 1 * multiply;
|
Conductor.instrumentalOffset -= 1.0 * multiply;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (FlxG.keys.justPressed.RIGHT)
|
if (FlxG.keys.justPressed.RIGHT)
|
||||||
{
|
{
|
||||||
Conductor.visualOffset += 1 * multiply;
|
Conductor.inputOffset += 1.0 * multiply;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.justPressed.LEFT)
|
if (FlxG.keys.justPressed.LEFT)
|
||||||
{
|
{
|
||||||
Conductor.visualOffset -= 1 * multiply;
|
Conductor.inputOffset -= 1.0 * multiply;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ class LatencyState extends MusicBeatSubState
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
noteGrp.forEach(function(daNote:NoteSprite) {
|
noteGrp.forEach(function(daNote:NoteSprite) {
|
||||||
daNote.y = (strumLine.y - ((Conductor.songPosition - Conductor.audioOffset) - daNote.noteData.time) * 0.45);
|
daNote.y = (strumLine.y - ((Conductor.songPosition - Conductor.instrumentalOffset) - daNote.noteData.time) * 0.45);
|
||||||
daNote.x = strumLine.x + 30;
|
daNote.x = strumLine.x + 30;
|
||||||
|
|
||||||
if (daNote.y < strumLine.y) daNote.alpha = 0.5;
|
if (daNote.y < strumLine.y) daNote.alpha = 0.5;
|
||||||
|
@ -271,7 +271,7 @@ class LatencyState extends MusicBeatSubState
|
||||||
|
|
||||||
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length;
|
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length;
|
||||||
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs);
|
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs);
|
||||||
getDiff -= Conductor.visualOffset;
|
getDiff -= Conductor.inputOffset;
|
||||||
|
|
||||||
// lil fix for end of song
|
// lil fix for end of song
|
||||||
if (closestBeat == 0 && getDiff >= Conductor.beatLengthMs * 2) getDiff -= FlxG.sound.music.length;
|
if (closestBeat == 0 && getDiff >= Conductor.beatLengthMs * 2) getDiff -= FlxG.sound.music.length;
|
||||||
|
|
Loading…
Reference in a new issue