Merge branch 'rewrite/master' into feature/chart-editor-context-menus

This commit is contained in:
Cameron Taylor 2024-01-05 20:11:38 -05:00
commit da4e77bf44
32 changed files with 840 additions and 627 deletions

View file

@ -11,6 +11,7 @@ import funkin.data.song.SongDataUtils;
* A core class which handles musical timing throughout the game,
* both in gameplay and in menus.
*/
@:nullSafety
class Conductor
{
// onBeatHit is called every quarter note
@ -28,29 +29,53 @@ class Conductor
// 60 BPM = 240 sixteenth notes per minute = 4 onStepHit per second
// 7/8 = 3.5 beats per measure = 14 steps per measure
/**
* The current instance of the Conductor.
* If one doesn't currently exist, a new one will be created.
*
* You can also do stuff like store a reference to the Conductor and pass it around or temporarily replace it,
* or have a second Conductor running at the same time, or other weird stuff like that if you need to.
*/
public static var instance:Conductor = new Conductor();
/**
* Signal fired when the current Conductor instance advances to a new measure.
*/
public static var measureHit(default, null):FlxSignal = new FlxSignal();
/**
* Signal fired when the current Conductor instance advances to a new beat.
*/
public static var beatHit(default, null):FlxSignal = new FlxSignal();
/**
* Signal fired when the current Conductor instance advances to a new step.
*/
public static var stepHit(default, null):FlxSignal = new FlxSignal();
/**
* The list of time changes in the song.
* There should be at least one time change (at the beginning of the song) to define the BPM.
*/
static var timeChanges:Array<SongTimeChange> = [];
var timeChanges:Array<SongTimeChange> = [];
/**
* The most recent time change for the current song position.
*/
public static var currentTimeChange(default, null):Null<SongTimeChange>;
public var currentTimeChange(default, null):Null<SongTimeChange>;
/**
* The current position in the song in milliseconds.
* Update this every frame based on the audio position using `Conductor.update()`.
* Update this every frame based on the audio position using `Conductor.instance.update()`.
*/
public static var songPosition(default, null):Float = 0;
public var songPosition(default, null):Float = 0;
/**
* Beats per minute of the current song at the current time.
*/
public static var bpm(get, never):Float;
public var bpm(get, never):Float;
static function get_bpm():Float
function get_bpm():Float
{
if (bpmOverride != null) return bpmOverride;
@ -62,9 +87,9 @@ class Conductor
/**
* Beats per minute of the current song at the start time.
*/
public static var startingBPM(get, never):Float;
public var startingBPM(get, never):Float;
static function get_startingBPM():Float
function get_startingBPM():Float
{
if (bpmOverride != null) return bpmOverride;
@ -78,14 +103,14 @@ class Conductor
* The current value set by `forceBPM`.
* If false, BPM is determined by time changes.
*/
static var bpmOverride:Null<Float> = null;
var bpmOverride:Null<Float> = null;
/**
* Duration of a measure in milliseconds. Calculated based on bpm.
*/
public static var measureLengthMs(get, never):Float;
public var measureLengthMs(get, never):Float;
static function get_measureLengthMs():Float
function get_measureLengthMs():Float
{
return beatLengthMs * timeSignatureNumerator;
}
@ -93,9 +118,9 @@ class Conductor
/**
* Duration of a beat (quarter note) in milliseconds. Calculated based on bpm.
*/
public static var beatLengthMs(get, never):Float;
public var beatLengthMs(get, never):Float;
static function get_beatLengthMs():Float
function get_beatLengthMs():Float
{
// Tied directly to BPM.
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC);
@ -104,25 +129,25 @@ class Conductor
/**
* Duration of a step (sixtennth note) in milliseconds. Calculated based on bpm.
*/
public static var stepLengthMs(get, never):Float;
public var stepLengthMs(get, never):Float;
static function get_stepLengthMs():Float
function get_stepLengthMs():Float
{
return beatLengthMs / timeSignatureNumerator;
}
public static var timeSignatureNumerator(get, never):Int;
public var timeSignatureNumerator(get, never):Int;
static function get_timeSignatureNumerator():Int
function get_timeSignatureNumerator():Int
{
if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_NUM;
return currentTimeChange.timeSignatureNum;
}
public static var timeSignatureDenominator(get, never):Int;
public var timeSignatureDenominator(get, never):Int;
static function get_timeSignatureDenominator():Int
function get_timeSignatureDenominator():Int
{
if (currentTimeChange == null) return Constants.DEFAULT_TIME_SIGNATURE_DEN;
@ -132,44 +157,44 @@ class Conductor
/**
* Current position in the song, in measures.
*/
public static var currentMeasure(default, null):Int = 0;
public var currentMeasure(default, null):Int = 0;
/**
* Current position in the song, in beats.
*/
public static var currentBeat(default, null):Int = 0;
public var currentBeat(default, null):Int = 0;
/**
* Current position in the song, in steps.
*/
public static var currentStep(default, null):Int = 0;
public var currentStep(default, null):Int = 0;
/**
* Current position in the song, in measures and fractions of a measure.
*/
public static var currentMeasureTime(default, null):Float = 0;
public var currentMeasureTime(default, null):Float = 0;
/**
* Current position in the song, in beats and fractions of a measure.
*/
public static var currentBeatTime(default, null):Float = 0;
public var currentBeatTime(default, null):Float = 0;
/**
* Current position in the song, in steps and fractions of a step.
*/
public static var currentStepTime(default, null):Float = 0;
public var currentStepTime(default, null):Float = 0;
/**
* An offset tied to the current chart file to compensate for a delay in the instrumental.
*/
public static var instrumentalOffset:Float = 0;
public var instrumentalOffset:Float = 0;
/**
* The instrumental offset, in terms of steps.
*/
public static var instrumentalOffsetSteps(get, never):Float;
public var instrumentalOffsetSteps(get, never):Float;
static function get_instrumentalOffsetSteps():Float
function get_instrumentalOffsetSteps():Float
{
var startingStepLengthMs:Float = ((Constants.SECS_PER_MIN / startingBPM) * Constants.MS_PER_SEC) / timeSignatureNumerator;
@ -179,19 +204,19 @@ class Conductor
/**
* An offset tied to the file format of the audio file being played.
*/
public static var formatOffset:Float = 0;
public var formatOffset:Float = 0;
/**
* An offset set by the user to compensate for input lag.
*/
public static var inputOffset:Float = 0;
public 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 var beatsPerMeasure(get, never):Float;
static function get_beatsPerMeasure():Float
function get_beatsPerMeasure():Float
{
// NOTE: Not always an integer, for example 7/8 is 3.5 beats per measure
return stepsPerMeasure / Constants.STEPS_PER_BEAT;
@ -201,30 +226,15 @@ class Conductor
* The number of steps in a measure.
* TODO: I don't think this can be fractional?
*/
public static var stepsPerMeasure(get, never):Int;
public var stepsPerMeasure(get, never):Int;
static function get_stepsPerMeasure():Int
function get_stepsPerMeasure():Int
{
// TODO: Is this always an integer?
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() {}
public function new() {}
/**
* Forcibly defines the current BPM of the song.
@ -235,7 +245,7 @@ class Conductor
* WARNING: Avoid this for things like setting the BPM of the title screen music,
* you should have a metadata file for it instead.
*/
public static function forceBPM(?bpm:Float = null)
public function forceBPM(?bpm:Float = null)
{
if (bpm != null)
{
@ -246,7 +256,7 @@ class Conductor
// trace('[CONDUCTOR] Resetting BPM to default');
}
Conductor.bpmOverride = bpm;
this.bpmOverride = bpm;
}
/**
@ -256,29 +266,29 @@ class Conductor
* @param songPosition The current position in the song in milliseconds.
* Leave blank to use the FlxG.sound.music position.
*/
public static function update(?songPosition:Float)
public function update(?songPos:Float)
{
if (songPosition == null)
if (songPos == null)
{
// Take into account instrumental and file format song offsets.
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0;
}
var oldMeasure = currentMeasure;
var oldBeat = currentBeat;
var oldStep = currentStep;
var oldMeasure = this.currentMeasure;
var oldBeat = this.currentBeat;
var oldStep = this.currentStep;
// Set the song position we are at (for purposes of calculating note positions, etc).
Conductor.songPosition = songPosition;
this.songPosition = songPos;
currentTimeChange = timeChanges[0];
if (Conductor.songPosition > 0.0)
if (this.songPosition > 0.0)
{
for (i in 0...timeChanges.length)
{
if (songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i];
if (this.songPosition >= timeChanges[i].timeStamp) currentTimeChange = timeChanges[i];
if (songPosition < timeChanges[i].timeStamp) break;
if (this.songPosition < timeChanges[i].timeStamp) break;
}
}
@ -286,45 +296,49 @@ class Conductor
{
trace('WARNING: Conductor is broken, timeChanges is empty.');
}
else if (currentTimeChange != null && Conductor.songPosition > 0.0)
else if (currentTimeChange != null && this.songPosition > 0.0)
{
// roundDecimal prevents representing 8 as 7.9999999
currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
currentMeasureTime = currentStepTime / stepsPerMeasure;
currentStep = Math.floor(currentStepTime);
currentBeat = Math.floor(currentBeatTime);
currentMeasure = Math.floor(currentMeasureTime);
this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * 4) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6);
this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
this.currentMeasureTime = currentStepTime / stepsPerMeasure;
this.currentStep = Math.floor(currentStepTime);
this.currentBeat = Math.floor(currentBeatTime);
this.currentMeasure = Math.floor(currentMeasureTime);
}
else
{
// Assume a constant BPM equal to the forced value.
currentStepTime = FlxMath.roundDecimal((songPosition / stepLengthMs), 4);
currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
currentMeasureTime = currentStepTime / stepsPerMeasure;
currentStep = Math.floor(currentStepTime);
currentBeat = Math.floor(currentBeatTime);
currentMeasure = Math.floor(currentMeasureTime);
this.currentStepTime = FlxMath.roundDecimal((songPosition / stepLengthMs), 4);
this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT;
this.currentMeasureTime = currentStepTime / stepsPerMeasure;
this.currentStep = Math.floor(currentStepTime);
this.currentBeat = Math.floor(currentBeatTime);
this.currentMeasure = Math.floor(currentMeasureTime);
}
// FlxSignals are really cool.
if (currentStep != oldStep)
// Only fire the signal if we are THE Conductor.
if (this == Conductor.instance)
{
stepHit.dispatch();
}
// FlxSignals are really cool.
if (currentStep != oldStep)
{
Conductor.stepHit.dispatch();
}
if (currentBeat != oldBeat)
{
beatHit.dispatch();
}
if (currentBeat != oldBeat)
{
Conductor.beatHit.dispatch();
}
if (currentMeasure != oldMeasure)
{
measureHit.dispatch();
if (currentMeasure != oldMeasure)
{
Conductor.measureHit.dispatch();
}
}
}
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
public function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
{
timeChanges = [];
@ -338,24 +352,21 @@ class Conductor
// Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`.
if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0;
if (currentTimeChange.beatTime == null)
if (currentTimeChange.timeStamp <= 0.0)
{
if (currentTimeChange.timeStamp <= 0.0)
{
currentTimeChange.beatTime = 0.0;
}
else
{
// Calculate the beat time of this timestamp.
currentTimeChange.beatTime = 0.0;
currentTimeChange.beatTime = 0.0;
}
else
{
// Calculate the beat time of this timestamp.
currentTimeChange.beatTime = 0.0;
if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
{
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
4);
}
if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
{
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
4);
}
}
@ -368,13 +379,13 @@ class Conductor
}
// Update currentStepTime
Conductor.update(Conductor.songPosition);
this.update(Conductor.instance.songPosition);
}
/**
* Given a time in milliseconds, return a time in steps.
*/
public static function getTimeInSteps(ms:Float):Float
public function getTimeInSteps(ms:Float):Float
{
if (timeChanges.length == 0)
{
@ -411,7 +422,7 @@ class Conductor
/**
* Given a time in steps and fractional steps, return a time in milliseconds.
*/
public static function getStepTimeInMs(stepTime:Float):Float
public function getStepTimeInMs(stepTime:Float):Float
{
if (timeChanges.length == 0)
{
@ -447,7 +458,7 @@ class Conductor
/**
* Given a time in beats and fractional beats, return a time in milliseconds.
*/
public static function getBeatTimeInMs(beatTime:Float):Float
public function getBeatTimeInMs(beatTime:Float):Float
{
if (timeChanges.length == 0)
{
@ -480,13 +491,20 @@ class Conductor
}
}
public static function watchQuick():Void
{
FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition);
FlxG.watch.addQuick("bpm", Conductor.instance.bpm);
FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime);
FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime);
FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime);
}
/**
* Reset the Conductor, replacing the current instance with a fresh one.
*/
public static function reset():Void
{
beatHit.removeAll();
stepHit.removeAll();
mapTimeChanges([]);
forceBPM(null);
update(0);
Conductor.instance = new Conductor();
}
}

View file

@ -64,7 +64,7 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
else
remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, vis.numSamples));
remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
var fftSamples:Array<Float> = [];

View file

@ -164,7 +164,7 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples));
else
remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples));
remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, numSamples));
var fftSamples:Array<Float> = [];
var i = remappedShit;
@ -235,15 +235,15 @@ class SpectogramSprite extends FlxTypedSpriteGroup<FlxSprite>
if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, numSamples));
else
{
if (curTime == Conductor.songPosition)
if (curTime == Conductor.instance.songPosition)
{
wavOptimiz = 3;
return; // already did shit, so finishes function early
}
curTime = Conductor.songPosition;
curTime = Conductor.instance.songPosition;
remappedShit = Std.int(FlxMath.remapToRange(Conductor.songPosition, 0, vis.snd.length, 0, numSamples));
remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, numSamples));
}
wavOptimiz = 8;

View file

@ -4,6 +4,7 @@ import funkin.data.event.SongEventRegistry;
import funkin.data.event.SongEventSchema;
import funkin.data.song.SongRegistry;
import thx.semver.Version;
import funkin.util.tools.ICloneable;
/**
* Data containing information about a song.
@ -11,7 +12,7 @@ import thx.semver.Version;
* Data which is only necessary in-game should be stored in the SongChartData.
*/
@:nullSafety
class SongMetadata
class SongMetadata implements ICloneable<SongMetadata>
{
/**
* A semantic versioning string for the song data format.
@ -86,16 +87,16 @@ class SongMetadata
* @param newVariation Set to a new variation ID to change the new metadata.
* @return The cloned SongMetadata
*/
public function clone(?newVariation:String = null):SongMetadata
public function clone():SongMetadata
{
var result:SongMetadata = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
var result:SongMetadata = new SongMetadata(this.songName, this.artist, this.variation);
result.version = this.version;
result.timeFormat = this.timeFormat;
result.divisions = this.divisions;
result.offsets = this.offsets;
result.timeChanges = this.timeChanges;
result.offsets = this.offsets.clone();
result.timeChanges = this.timeChanges.deepClone();
result.looped = this.looped;
result.playData = this.playData;
result.playData = this.playData.clone();
result.generatedBy = this.generatedBy;
return result;
@ -130,7 +131,7 @@ enum abstract SongTimeFormat(String) from String to String
var MILLISECONDS = 'ms';
}
class SongTimeChange
class SongTimeChange implements ICloneable<SongTimeChange>
{
public static final DEFAULT_SONGTIMECHANGE:SongTimeChange = new SongTimeChange(0, 100);
@ -151,7 +152,7 @@ class SongTimeChange
*/
@:optional
@:alias("b")
public var beatTime:Null<Float>;
public var beatTime:Float;
/**
* Quarter notes per minute (float). Cannot be empty in the first element of the list,
@ -197,6 +198,11 @@ class SongTimeChange
this.beatTuplets = beatTuplets == null ? DEFAULT_BEAT_TUPLETS : beatTuplets;
}
public function clone():SongTimeChange
{
return new SongTimeChange(this.timeStamp, this.bpm, this.timeSignatureNum, this.timeSignatureDen, this.beatTime, this.beatTuplets);
}
/**
* Produces a string representation suitable for debugging.
*/
@ -211,7 +217,7 @@ class SongTimeChange
* 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
class SongOffsets implements ICloneable<SongOffsets>
{
/**
* The offset, in milliseconds, to apply to the song's instrumental relative to the chart.
@ -281,6 +287,15 @@ class SongOffsets
return value;
}
public function clone():SongOffsets
{
var result:SongOffsets = new SongOffsets(this.instrumental);
result.altInstrumentals = this.altInstrumentals.clone();
result.vocals = this.vocals.clone();
return result;
}
/**
* Produces a string representation suitable for debugging.
*/
@ -294,7 +309,7 @@ class SongOffsets
* Metadata for a song only used for the music.
* For example, the menu music.
*/
class SongMusicData
class SongMusicData implements ICloneable<SongMusicData>
{
/**
* A semantic versioning string for the song data format.
@ -348,13 +363,13 @@ class SongMusicData
this.variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
}
public function clone(?newVariation:String = null):SongMusicData
public function clone():SongMusicData
{
var result:SongMusicData = new SongMusicData(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
var result:SongMusicData = new SongMusicData(this.songName, this.artist, this.variation);
result.version = this.version;
result.timeFormat = this.timeFormat;
result.divisions = this.divisions;
result.timeChanges = this.timeChanges;
result.timeChanges = this.timeChanges.clone();
result.looped = this.looped;
result.generatedBy = this.generatedBy;
@ -370,7 +385,7 @@ class SongMusicData
}
}
class SongPlayData
class SongPlayData implements ICloneable<SongPlayData>
{
/**
* The variations this song has. The associated metadata files should exist.
@ -419,6 +434,20 @@ class SongPlayData
ratings = new Map<String, Int>();
}
public function clone():SongPlayData
{
var result:SongPlayData = new SongPlayData();
result.songVariations = this.songVariations.clone();
result.difficulties = this.difficulties.clone();
result.characters = this.characters.clone();
result.stage = this.stage;
result.noteStyle = this.noteStyle;
result.ratings = this.ratings.clone();
result.album = this.album;
return result;
}
/**
* Produces a string representation suitable for debugging.
*/
@ -432,7 +461,7 @@ class SongPlayData
* Information about the characters used in this variation of the song.
* Create a new variation if you want to change the characters.
*/
class SongCharacterData
class SongCharacterData implements ICloneable<SongCharacterData>
{
@:optional
@:default('')
@ -462,6 +491,14 @@ class SongCharacterData
this.instrumental = instrumental;
}
public function clone():SongCharacterData
{
var result:SongCharacterData = new SongCharacterData(this.player, this.girlfriend, this.opponent, this.instrumental);
result.altInstrumentals = this.altInstrumentals.clone();
return result;
}
/**
* Produces a string representation suitable for debugging.
*/
@ -471,7 +508,7 @@ class SongCharacterData
}
}
class SongChartData
class SongChartData implements ICloneable<SongChartData>
{
@:default(funkin.data.song.SongRegistry.SONG_CHART_DATA_VERSION)
@:jcustomparse(funkin.data.DataParse.semverVersion)
@ -541,6 +578,24 @@ class SongChartData
return writer.write(this, pretty ? ' ' : null);
}
public function clone():SongChartData
{
// We have to manually perform the deep clone here because Map.deepClone() doesn't work.
var noteDataClone:Map<String, Array<SongNoteData>> = new Map<String, Array<SongNoteData>>();
for (key in this.notes.keys())
{
noteDataClone.set(key, this.notes.get(key).deepClone());
}
var eventDataClone:Array<SongEventData> = this.events.deepClone();
var result:SongChartData = new SongChartData(this.scrollSpeed.clone(), eventDataClone, noteDataClone);
result.version = this.version;
result.generatedBy = this.generatedBy;
result.variation = this.variation;
return result;
}
/**
* Produces a string representation suitable for debugging.
*/
@ -550,7 +605,7 @@ class SongChartData
}
}
class SongEventDataRaw
class SongEventDataRaw implements ICloneable<SongEventDataRaw>
{
/**
* The timestamp of the event. The timestamp is in the format of the song's time format.
@ -604,14 +659,19 @@ class SongEventDataRaw
{
if (_stepTime != null && !force) return _stepTime;
return _stepTime = Conductor.getTimeInSteps(this.time);
return _stepTime = Conductor.instance.getTimeInSteps(this.time);
}
public function clone():SongEventDataRaw
{
return new SongEventDataRaw(this.time, this.event, this.value);
}
}
/**
* Wrap SongEventData in an abstract so we can overload operators.
*/
@:forward(time, event, value, activated, getStepTime)
@:forward(time, event, value, activated, getStepTime, clone)
abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataRaw
{
public function new(time:Float, event:String, value:Dynamic = null)
@ -691,11 +751,6 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
return this.value == null ? null : cast Reflect.field(this.value, key);
}
public function clone():SongEventData
{
return new SongEventData(this.time, this.event, this.value);
}
@:op(A == B)
public function op_equals(other:SongEventData):Bool
{
@ -741,7 +796,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR
}
}
class SongNoteDataRaw
class SongNoteDataRaw implements ICloneable<SongNoteDataRaw>
{
/**
* The timestamp of the note. The timestamp is in the format of the song's time format.
@ -825,7 +880,7 @@ class SongNoteDataRaw
{
if (_stepTime != null && !force) return _stepTime;
return _stepTime = Conductor.getTimeInSteps(this.time);
return _stepTime = Conductor.instance.getTimeInSteps(this.time);
}
@:jignored
@ -841,7 +896,7 @@ class SongNoteDataRaw
if (_stepLength != null && !force) return _stepLength;
return _stepLength = Conductor.getTimeInSteps(this.time + this.length) - getStepTime();
return _stepLength = Conductor.instance.getTimeInSteps(this.time + this.length) - getStepTime();
}
public function setStepLength(value:Float):Void
@ -852,11 +907,16 @@ class SongNoteDataRaw
}
else
{
var lengthMs:Float = Conductor.getStepTimeInMs(value) - this.time;
var lengthMs:Float = Conductor.instance.getStepTimeInMs(value) - this.time;
this.length = lengthMs;
}
_stepLength = null;
}
public function clone():SongNoteDataRaw
{
return new SongNoteDataRaw(this.time, this.data, this.length, this.kind);
}
}
/**

View file

@ -40,7 +40,7 @@ class Countdown
stopCountdown();
PlayState.instance.isInCountdown = true;
Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5);
Conductor.instance.update(PlayState.instance.startTimestamp + Conductor.instance.beatLengthMs * -5);
// Handle onBeatHit events manually
// @:privateAccess
// PlayState.instance.dispatchEvent(new SongTimeScriptEvent(SONG_BEAT_HIT, 0, 0));
@ -48,7 +48,7 @@ class Countdown
// The timer function gets called based on the beat of the song.
countdownTimer = new FlxTimer();
countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) {
countdownTimer.start(Conductor.instance.beatLengthMs / 1000, function(tmr:FlxTimer) {
if (PlayState.instance == null)
{
tmr.cancel();
@ -158,7 +158,7 @@ class Countdown
{
stopCountdown();
// This will trigger PlayState.startSong()
Conductor.update(0);
Conductor.instance.update(0);
// PlayState.isInCountdown = false;
}
@ -225,7 +225,7 @@ class Countdown
countdownSprite.screenCenter();
// Fade sprite in, then out, then destroy it.
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.beatLengthMs / 1000,
FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000,
{
ease: FlxEase.cubeInOut,
onComplete: function(twn:FlxTween) {

View file

@ -129,7 +129,7 @@ class GameOverSubState extends MusicBeatSubState
gameOverMusic.stop();
// The conductor now represents the BPM of the game over music.
Conductor.update(0);
Conductor.instance.update(0);
}
var hasStartedAnimation:Bool = false;
@ -204,7 +204,7 @@ class GameOverSubState extends MusicBeatSubState
{
// Match the conductor to the music.
// This enables the stepHit and beatHit events.
Conductor.update(gameOverMusic.time);
Conductor.instance.update(gameOverMusic.time);
}
else
{

View file

@ -561,15 +561,15 @@ class PlayState extends MusicBeatSubState
}
// Prepare the Conductor.
Conductor.forceBPM(null);
Conductor.instance.forceBPM(null);
if (currentChart.offsets != null)
{
Conductor.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
Conductor.instance.instrumentalOffset = currentChart.offsets.getInstrumentalOffset();
}
Conductor.mapTimeChanges(currentChart.timeChanges);
Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp);
Conductor.instance.mapTimeChanges(currentChart.timeChanges);
Conductor.instance.update((Conductor.instance.beatLengthMs * -5) + startTimestamp);
// The song is now loaded. We can continue to initialize the play state.
initCameras();
@ -734,7 +734,7 @@ class PlayState extends MusicBeatSubState
// Reset music properly.
FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instrumentalOffset);
FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instance.instrumentalOffset);
FlxG.sound.music.pause();
if (!overrideMusic)
@ -785,22 +785,22 @@ class PlayState extends MusicBeatSubState
{
if (isInCountdown)
{
Conductor.update(Conductor.songPosition + elapsed * 1000);
if (Conductor.songPosition >= (startTimestamp)) startSong();
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000);
if (Conductor.instance.songPosition >= (startTimestamp)) startSong();
}
}
else
{
if (Constants.EXT_SOUND == 'mp3')
{
Conductor.formatOffset = Constants.MP3_DELAY_MS;
Conductor.instance.formatOffset = Constants.MP3_DELAY_MS;
}
else
{
Conductor.formatOffset = 0.0;
Conductor.instance.formatOffset = 0.0;
}
Conductor.update(); // Normal conductor update.
Conductor.instance.update(); // Normal conductor update.
}
var androidPause:Bool = false;
@ -942,7 +942,7 @@ class PlayState extends MusicBeatSubState
// TODO: Check that these work even when songPosition is less than 0.
if (songEvents != null && songEvents.length > 0)
{
var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.songPosition);
var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition);
if (songEventsToActivate.length > 0)
{
@ -950,7 +950,7 @@ class PlayState extends MusicBeatSubState
for (event in songEventsToActivate)
{
// If an event is trying to play, but it's over 5 seconds old, skip it.
if (event.time - Conductor.songPosition < -5000)
if (event.time - Conductor.instance.songPosition < -5000)
{
event.activated = true;
continue;
@ -1052,7 +1052,7 @@ class PlayState extends MusicBeatSubState
if (startTimer.finished)
{
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
currentSongLengthMs - Conductor.songPosition);
currentSongLengthMs - Conductor.instance.songPosition);
}
else
{
@ -1076,12 +1076,12 @@ class PlayState extends MusicBeatSubState
{
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
{
if (Conductor.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
+ ' ('
+ storyDifficultyText
+ ')', iconRPC, true,
currentSongLengthMs
- Conductor.songPosition);
- Conductor.instance.songPosition);
else
DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
}
@ -1154,17 +1154,17 @@ class PlayState extends MusicBeatSubState
if (!startingSong
&& FlxG.sound.music != null
&& (Math.abs(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset)) > 200
|| Math.abs(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset)) > 200))
&& (Math.abs(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200
|| Math.abs(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset)) > 200))
{
trace("VOCALS NEED RESYNC");
if (vocals != null) trace(vocals.checkSyncError(Conductor.songPosition + Conductor.instrumentalOffset));
trace(FlxG.sound.music.time - (Conductor.songPosition + Conductor.instrumentalOffset));
if (vocals != null) trace(vocals.checkSyncError(Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
trace(FlxG.sound.music.time - (Conductor.instance.songPosition + Conductor.instance.instrumentalOffset));
resyncVocals();
}
if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.currentStep));
if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.currentStep));
if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep));
if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep));
return true;
}
@ -1185,14 +1185,14 @@ class PlayState extends MusicBeatSubState
}
// Only zoom camera if we are zoomed by less than 35%.
if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.currentBeat % cameraZoomRate == 0)
if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.instance.currentBeat % cameraZoomRate == 0)
{
// Zoom camera in (1.5%)
FlxG.camera.zoom += cameraZoomIntensity * defaultCameraZoom;
// Hud zooms double (3%)
camHUD.zoom += hudCameraZoomIntensity * defaultHUDCameraZoom;
}
// trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.currentBeat} % ${cameraZoomRate} == ${Conductor.currentBeat % cameraZoomRate}}');
// trace('Not bopping camera: ${FlxG.camera.zoom} < ${(1.35 * defaultCameraZoom)} && ${cameraZoomRate} > 0 && ${Conductor.instance.currentBeat} % ${cameraZoomRate} == ${Conductor.instance.currentBeat % cameraZoomRate}}');
// That combo milestones that got spoiled that one time.
// Comes with NEAT visual and audio effects.
@ -1205,13 +1205,13 @@ class PlayState extends MusicBeatSubState
// TODO: Re-enable combo text (how to do this without sections?).
// if (currentSong != null)
// {
// shouldShowComboText = (Conductor.currentBeat % 8 == 7);
// var daSection = .getSong()[Std.int(Conductor.currentBeat / 16)];
// shouldShowComboText = (Conductor.instance.currentBeat % 8 == 7);
// var daSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16)];
// shouldShowComboText = shouldShowComboText && (daSection != null && daSection.mustHitSection);
// shouldShowComboText = shouldShowComboText && (Highscore.tallies.combo > 5);
//
// var daNextSection = .getSong()[Std.int(Conductor.currentBeat / 16) + 1];
// var isEndOfSong = .getSong().length < Std.int(Conductor.currentBeat / 16);
// var daNextSection = .getSong()[Std.int(Conductor.instance.currentBeat / 16) + 1];
// var isEndOfSong = .getSong().length < Std.int(Conductor.instance.currentBeat / 16);
// shouldShowComboText = shouldShowComboText && (isEndOfSong || (daNextSection != null && !daNextSection.mustHitSection));
// }
@ -1224,7 +1224,7 @@ class PlayState extends MusicBeatSubState
var frameShit:Float = (1 / 24) * 2; // equals 2 frames in the animation
new FlxTimer().start(((Conductor.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) {
new FlxTimer().start(((Conductor.instance.beatLengthMs / 1000) * 1.25) - frameShit, function(tmr) {
animShit.forceFinish();
});
}
@ -1261,10 +1261,10 @@ class PlayState extends MusicBeatSubState
if (currentStage == null) return;
// TODO: Add HEY! song events to Tutorial.
if (Conductor.currentBeat % 16 == 15
if (Conductor.instance.currentBeat % 16 == 15
&& currentStage.getDad().characterId == 'gf'
&& Conductor.currentBeat > 16
&& Conductor.currentBeat < 48)
&& Conductor.instance.currentBeat > 16
&& Conductor.instance.currentBeat < 48)
{
currentStage.getBoyfriend().playAnimation('hey', true);
currentStage.getDad().playAnimation('cheer', true);
@ -1575,7 +1575,7 @@ class PlayState extends MusicBeatSubState
trace('Song difficulty could not be loaded.');
}
// Conductor.forceBPM(currentChart.getStartingBPM());
// Conductor.instance.forceBPM(currentChart.getStartingBPM());
if (!overrideMusic)
{
@ -1706,7 +1706,7 @@ class PlayState extends MusicBeatSubState
FlxG.sound.music.onComplete = endSong;
// A negative instrumental offset means the song skips the first few milliseconds of the track.
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
trace('Playing vocals...');
add(vocals);
@ -1722,7 +1722,7 @@ class PlayState extends MusicBeatSubState
if (startTimestamp > 0)
{
// FlxG.sound.music.time = startTimestamp - Conductor.instrumentalOffset;
// FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset;
handleSkippedNotes();
}
}
@ -1800,7 +1800,7 @@ class PlayState extends MusicBeatSubState
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
if (Conductor.songPosition > hitWindowEnd)
if (Conductor.instance.songPosition > hitWindowEnd)
{
if (note.hasMissed) continue;
@ -1810,7 +1810,7 @@ class PlayState extends MusicBeatSubState
if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true;
}
else if (Conductor.songPosition > hitWindowCenter)
else if (Conductor.instance.songPosition > hitWindowCenter)
{
if (note.hasBeenHit) continue;
@ -1831,7 +1831,7 @@ class PlayState extends MusicBeatSubState
opponentStrumline.playNoteHoldCover(note.holdNoteSprite);
}
}
else if (Conductor.songPosition > hitWindowStart)
else if (Conductor.instance.songPosition > hitWindowStart)
{
if (note.hasBeenHit || note.hasMissed) continue;
@ -1877,14 +1877,14 @@ class PlayState extends MusicBeatSubState
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
if (Conductor.songPosition > hitWindowEnd)
if (Conductor.instance.songPosition > hitWindowEnd)
{
note.tooEarly = false;
note.mayHit = false;
note.hasMissed = true;
if (note.holdNoteSprite != null) note.holdNoteSprite.missedNote = true;
}
else if (Conductor.songPosition > hitWindowStart)
else if (Conductor.instance.songPosition > hitWindowStart)
{
note.tooEarly = false;
note.mayHit = true;
@ -1951,7 +1951,7 @@ class PlayState extends MusicBeatSubState
if (note == null || note.hasBeenHit) continue;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
if (Conductor.songPosition > hitWindowEnd)
if (Conductor.instance.songPosition > hitWindowEnd)
{
// We have passed this note.
// Flag the note for deletion without actually penalizing the player.
@ -2115,7 +2115,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
t: Std.int(Conductor.songPosition),
t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
@ -2125,7 +2125,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
t: Std.int(Conductor.songPosition),
t: Std.int(Conductor.instance.songPosition),
d: -1,
l: 20
});
@ -2186,7 +2186,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
t: Std.int(Conductor.songPosition),
t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
@ -2275,7 +2275,7 @@ class PlayState extends MusicBeatSubState
// Get the offset and compensate for input latency.
// Round inward (trim remainder) for consistency.
var noteDiff:Int = Std.int(Conductor.songPosition - daNote.noteData.time - inputLatencyMs);
var noteDiff:Int = Std.int(Conductor.instance.songPosition - daNote.noteData.time - inputLatencyMs);
var score = Scoring.scoreNote(noteDiff, PBOT1);
var daRating = Scoring.judgeNote(noteDiff, PBOT1);
@ -2330,7 +2330,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
t: Std.int(Conductor.songPosition),
t: Std.int(Conductor.instance.songPosition),
d: indices[i],
l: 20
});
@ -2340,7 +2340,7 @@ class PlayState extends MusicBeatSubState
{
inputSpitter.push(
{
t: Std.int(Conductor.songPosition),
t: Std.int(Conductor.instance.songPosition),
d: -1,
l: 20
});
@ -2739,15 +2739,15 @@ class PlayState extends MusicBeatSubState
{
FlxG.sound.music.pause();
var targetTimeSteps:Float = Conductor.currentStepTime + (Conductor.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections);
var targetTimeMs:Float = Conductor.getStepTimeInMs(targetTimeSteps);
var targetTimeSteps:Float = Conductor.instance.currentStepTime + (Conductor.instance.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections);
var targetTimeMs:Float = Conductor.instance.getStepTimeInMs(targetTimeSteps);
FlxG.sound.music.time = targetTimeMs;
handleSkippedNotes();
// regenNoteData(FlxG.sound.music.time);
Conductor.update(FlxG.sound.music.time);
Conductor.instance.update(FlxG.sound.music.time);
resyncVocals();
}

View file

@ -367,7 +367,7 @@ class BaseCharacter extends Bopper
// This lets you add frames to the end of the sing animation to ease back into the idle!
holdTimer += event.elapsed;
var singTimeSec:Float = singTimeSec * (Conductor.beatLengthMs * 0.001); // x beats, to ms.
var singTimeSec:Float = singTimeSec * (Conductor.instance.beatLengthMs * 0.001); // x beats, to ms.
if (getCurrentAnimation().endsWith('miss')) singTimeSec *= 2; // makes it feel more awkward when you miss

View file

@ -40,7 +40,7 @@ class ComboMilestone extends FlxTypedSpriteGroup<FlxSprite>
{
if (onScreenTime < 0.9)
{
new FlxTimer().start((Conductor.beatLengthMs / 1000) * 0.25, function(tmr) {
new FlxTimer().start((Conductor.instance.beatLengthMs / 1000) * 0.25, function(tmr) {
forceFinish();
});
}

View file

@ -59,7 +59,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
remove(rating, true);
rating.destroy();
},
startDelay: Conductor.beatLengthMs * 0.001
startDelay: Conductor.instance.beatLengthMs * 0.001
});
}
@ -110,7 +110,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
remove(comboSpr, true);
comboSpr.destroy();
},
startDelay: Conductor.beatLengthMs * 0.001
startDelay: Conductor.instance.beatLengthMs * 0.001
});
var seperatedScore:Array<Int> = [];
@ -157,7 +157,7 @@ class PopUpStuff extends FlxTypedGroup<FlxSprite>
remove(numScore, true);
numScore.destroy();
},
startDelay: Conductor.beatLengthMs * 0.002
startDelay: Conductor.instance.beatLengthMs * 0.002
});
daLoop++;

View file

@ -79,7 +79,8 @@ class ZoomCameraSongEvent extends SongEvent
return;
}
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.stepLengthMs * duration / 1000), {ease: easeFunction});
FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.instance.stepLengthMs * duration / 1000),
{ease: easeFunction});
}
}

View file

@ -279,7 +279,7 @@ class Strumline extends FlxSpriteGroup
var vwoosh:Float = 1.0;
var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
return Constants.PIXELS_PER_MS * (Conductor.instance.songPosition - strumTime) * scrollSpeed * vwoosh * (Preferences.downscroll ? 1 : -1);
}
function updateNotes():Void
@ -287,8 +287,8 @@ class Strumline extends FlxSpriteGroup
if (noteData.length == 0) return;
var songStart:Float = PlayState.instance?.startTimestamp ?? 0.0;
var hitWindowStart:Float = Conductor.songPosition - Constants.HIT_WINDOW_MS;
var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS;
var hitWindowStart:Float = Conductor.instance.songPosition - Constants.HIT_WINDOW_MS;
var renderWindowStart:Float = Conductor.instance.songPosition + RENDER_DISTANCE_MS;
for (noteIndex in nextNoteIndex...noteData.length)
{
@ -335,7 +335,7 @@ class Strumline extends FlxSpriteGroup
{
if (holdNote == null || !holdNote.alive) continue;
if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote)
if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote && !holdNote.missedNote)
{
if (isPlayer && !isKeyHeld(holdNote.noteDirection))
{
@ -349,7 +349,7 @@ class Strumline extends FlxSpriteGroup
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
if (holdNote.missedNote && Conductor.songPosition >= renderWindowEnd)
if (holdNote.missedNote && Conductor.instance.songPosition >= renderWindowEnd)
{
// Hold note is offscreen, kill it.
holdNote.visible = false;
@ -399,13 +399,13 @@ class Strumline extends FlxSpriteGroup
holdNote.y = this.y - INITIAL_OFFSET + calculateNoteYPos(holdNote.strumTime, vwoosh) + yOffset + STRUMLINE_SIZE / 2;
}
}
else if (Conductor.songPosition > holdNote.strumTime && holdNote.hitNote)
else if (Conductor.instance.songPosition > holdNote.strumTime && holdNote.hitNote)
{
// Hold note is currently being hit, clip it off.
holdConfirm(holdNote.noteDirection);
holdNote.visible = true;
holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.songPosition;
holdNote.sustainLength = (holdNote.strumTime + holdNote.fullSustainLength) - Conductor.instance.songPosition;
if (holdNote.sustainLength <= 10)
{

View file

@ -71,6 +71,27 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
}
}
function handleFunctionControls():Void
{
// Emergency exit button.
if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState());
// This can now be used in EVERY STATE YAY!
if (FlxG.keys.justPressed.F5) debug_refreshModules();
}
function handleQuickWatch():Void
{
// Display Conductor info in the watch window.
FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition);
FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
FlxG.watch.addQuick("bpm", Conductor.instance.bpm);
FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentBeatTime);
FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime);
FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime);
}
override function update(elapsed:Float)
{
super.update(elapsed);
@ -106,7 +127,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function stepHit():Bool
{
var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);
@ -117,7 +138,7 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
public function beatHit():Bool
{
var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
var event = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);

View file

@ -65,12 +65,8 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
if (FlxG.keys.justPressed.F5) debug_refreshModules();
// Display Conductor info in the watch window.
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("currentMeasureTime", Conductor.currentBeatTime);
FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime);
FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime);
Conductor.watchQuick();
dispatchEvent(new UpdateScriptEvent(elapsed));
}
@ -99,7 +95,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
*/
public function stepHit():Bool
{
var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep);
var event:ScriptEvent = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);
@ -115,7 +111,7 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl
*/
public function beatHit():Bool
{
var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.currentBeat, Conductor.currentStep);
var event:ScriptEvent = new SongTimeScriptEvent(SONG_BEAT_HIT, Conductor.instance.currentBeat, Conductor.instance.currentStep);
dispatchEvent(event);

View file

@ -21,6 +21,7 @@ import flixel.system.FlxAssets.FlxSoundAsset;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.tweens.misc.VarTween;
import haxe.ui.Toolkit;
import flixel.util.FlxColor;
import flixel.util.FlxSort;
import flixel.util.FlxTimer;
@ -274,13 +275,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function get_songLengthInSteps():Float
{
return Conductor.getTimeInSteps(songLengthInMs);
return Conductor.instance.getTimeInSteps(songLengthInMs);
}
function set_songLengthInSteps(value:Float):Float
{
// Getting a reasonable result from setting songLengthInSteps requires that Conductor.mapBPMChanges be called first.
songLengthInMs = Conductor.getStepTimeInMs(value);
// Getting a reasonable result from setting songLengthInSteps requires that Conductor.instance.mapBPMChanges be called first.
songLengthInMs = Conductor.instance.getStepTimeInMs(value);
return value;
}
@ -394,12 +395,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function get_scrollPositionInMs():Float
{
return Conductor.getStepTimeInMs(scrollPositionInSteps);
return Conductor.instance.getStepTimeInMs(scrollPositionInSteps);
}
function set_scrollPositionInMs(value:Float):Float
{
scrollPositionInSteps = Conductor.getTimeInSteps(value);
scrollPositionInSteps = Conductor.instance.getTimeInSteps(value);
return value;
}
@ -453,13 +454,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function get_playheadPositionInMs():Float
{
if (audioVisGroup != null && audioVisGroup.playerVis != null)
audioVisGroup.playerVis.realtimeStartOffset = -Conductor.getStepTimeInMs(playheadPositionInSteps);
return Conductor.getStepTimeInMs(playheadPositionInSteps);
audioVisGroup.playerVis.realtimeStartOffset = -Conductor.instance.getStepTimeInMs(playheadPositionInSteps);
return Conductor.instance.getStepTimeInMs(playheadPositionInSteps);
}
function set_playheadPositionInMs(value:Float):Float
{
playheadPositionInSteps = Conductor.getTimeInSteps(value);
playheadPositionInSteps = Conductor.instance.getTimeInSteps(value);
if (audioVisGroup != null && audioVisGroup.playerVis != null) audioVisGroup.playerVis.realtimeStartOffset = -value;
return value;
@ -874,6 +875,70 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
return Save.get().chartEditorHasBackup = value;
}
/**
* A list of previous working file paths.
* Also known as the "recent files" list.
* The first element is [null] if the current working file has not been saved anywhere yet.
*/
public var previousWorkingFilePaths(default, set):Array<Null<String>> = [null];
function set_previousWorkingFilePaths(value:Array<Null<String>>):Array<Null<String>>
{
// Called only when the WHOLE LIST is overridden.
previousWorkingFilePaths = value;
applyWindowTitle();
populateOpenRecentMenu();
applyCanQuickSave();
return value;
}
/**
* The current file path which the chart editor is working with.
* If `null`, the current chart has not been saved yet.
*/
public var currentWorkingFilePath(get, set):Null<String>;
function get_currentWorkingFilePath():Null<String>
{
return previousWorkingFilePaths[0];
}
function set_currentWorkingFilePath(value:Null<String>):Null<String>
{
if (value == previousWorkingFilePaths[0]) return value;
if (previousWorkingFilePaths.contains(null))
{
// Filter all instances of `null` from the array.
previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null<String>):Bool {
return x != null;
});
}
if (previousWorkingFilePaths.contains(value))
{
// Move the path to the front of the list.
previousWorkingFilePaths.remove(value);
previousWorkingFilePaths.unshift(value);
}
else
{
// Add the path to the front of the list.
previousWorkingFilePaths.unshift(value);
}
while (previousWorkingFilePaths.length > Constants.MAX_PREVIOUS_WORKING_FILES)
{
// Remove the last path in the list.
previousWorkingFilePaths.pop();
}
populateOpenRecentMenu();
applyWindowTitle();
return value;
}
/**
* Whether the difficulty tree view in the toolbox has been modified and needs to be updated.
* This happens when we add/remove difficulties.
@ -903,6 +968,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var commandHistoryDirty:Bool = true;
/**
* If true, we are currently in the process of quitting the chart editor.
* Skip any update functions as most of them will call a crash.
*/
var criticalFailure:Bool = false;
// Input
/**
@ -1733,70 +1804,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var params:Null<ChartEditorParams>;
/**
* A list of previous working file paths.
* Also known as the "recent files" list.
* The first element is [null] if the current working file has not been saved anywhere yet.
*/
public var previousWorkingFilePaths(default, set):Array<Null<String>> = [null];
function set_previousWorkingFilePaths(value:Array<Null<String>>):Array<Null<String>>
{
// Called only when the WHOLE LIST is overridden.
previousWorkingFilePaths = value;
applyWindowTitle();
populateOpenRecentMenu();
applyCanQuickSave();
return value;
}
/**
* The current file path which the chart editor is working with.
* If `null`, the current chart has not been saved yet.
*/
public var currentWorkingFilePath(get, set):Null<String>;
function get_currentWorkingFilePath():Null<String>
{
return previousWorkingFilePaths[0];
}
function set_currentWorkingFilePath(value:Null<String>):Null<String>
{
if (value == previousWorkingFilePaths[0]) return value;
if (previousWorkingFilePaths.contains(null))
{
// Filter all instances of `null` from the array.
previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null<String>):Bool {
return x != null;
});
}
if (previousWorkingFilePaths.contains(value))
{
// Move the path to the front of the list.
previousWorkingFilePaths.remove(value);
previousWorkingFilePaths.unshift(value);
}
else
{
// Add the path to the front of the list.
previousWorkingFilePaths.unshift(value);
}
while (previousWorkingFilePaths.length > Constants.MAX_PREVIOUS_WORKING_FILES)
{
// Remove the last path in the list.
previousWorkingFilePaths.pop();
}
populateOpenRecentMenu();
applyWindowTitle();
return value;
}
public function new(?params:ChartEditorParams)
{
super();
@ -2419,13 +2426,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
else
{
Conductor.currentTimeChange.bpm += 1;
Conductor.instance.currentTimeChange.bpm += 1;
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
}
playbarBPM.onRightClick = _ -> {
Conductor.currentTimeChange.bpm -= 1;
Conductor.instance.currentTimeChange.bpm -= 1;
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
@ -2474,9 +2481,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemPaste.onClick = _ -> {
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
var targetStep:Float = Conductor.getTimeInSteps(targetMs);
var targetStep:Float = Conductor.instance.getTimeInSteps(targetMs);
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
var targetSnappedMs:Float = Conductor.instance.getStepTimeInMs(targetSnappedStep);
performCommand(new PasteItemsCommand(targetSnappedMs));
};
@ -2706,10 +2713,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
saveDataDirty = false;
}
var displayAutosavePopup:Bool = false;
/**
* UPDATE FUNCTIONS
*/
function autoSave():Void
function autoSave(?beforePlaytest:Bool = false):Void
{
var needsAutoSave:Bool = saveDataDirty;
@ -2727,13 +2736,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (needsAutoSave)
{
this.exportAllSongData(true, null);
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
{
text: "Take Me There",
callback: openBackupsFolder,
}
]);
if (beforePlaytest)
{
displayAutosavePopup = true;
}
else
{
displayAutosavePopup = false;
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
{
text: "Take Me There",
callback: openBackupsFolder,
}
]);
}
}
#end
}
@ -2801,7 +2818,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
public override function update(elapsed:Float):Void
{
// Override F4 behavior to include the autosave.
if (FlxG.keys.justPressed.F4)
if (FlxG.keys.justPressed.F4 && !criticalFailure)
{
quitChartEditor();
return;
@ -2810,6 +2827,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// dispatchEvent gets called here.
super.update(elapsed);
if (criticalFailure) return;
// These ones happen even if the modal dialog is open.
handleMusicPlayback(elapsed);
handleNoteDisplay();
@ -2849,7 +2868,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.isPlaying))
{
playMetronomeTick(Conductor.currentBeat % 4 == 0);
playMetronomeTick(Conductor.instance.currentBeat % 4 == 0);
}
// Show the mouse cursor.
@ -2869,8 +2888,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (audioInstTrack != null && audioInstTrack.isPlaying)
{
if (healthIconDad != null) healthIconDad.onStepHit(Conductor.currentStep);
if (healthIconBF != null) healthIconBF.onStepHit(Conductor.currentStep);
if (healthIconDad != null) healthIconDad.onStepHit(Conductor.instance.currentStep);
if (healthIconBF != null) healthIconBF.onStepHit(Conductor.instance.currentStep);
}
// Updating these every step keeps it more accurate.
@ -2898,12 +2917,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
audioInstTrack.update(elapsed);
// If the song starts 50ms in, make sure we start the song there.
if (Conductor.instrumentalOffset < 0)
if (Conductor.instance.instrumentalOffset < 0)
{
if (audioInstTrack.time < -Conductor.instrumentalOffset)
if (audioInstTrack.time < -Conductor.instance.instrumentalOffset)
{
trace('Resetting instrumental time to ${- Conductor.instrumentalOffset}ms');
audioInstTrack.time = -Conductor.instrumentalOffset;
trace('Resetting instrumental time to ${- Conductor.instance.instrumentalOffset}ms');
audioInstTrack.time = -Conductor.instance.instrumentalOffset;
}
}
}
@ -2914,16 +2933,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
// If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat!
var oldStepTime:Float = Conductor.currentStepTime;
var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset;
Conductor.update(audioInstTrack.time);
handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset);
var oldStepTime:Float = Conductor.instance.currentStepTime;
var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
Conductor.instance.update(audioInstTrack.time);
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
// Resync vocals.
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
{
audioVocalTrackGroup.time = audioInstTrack.time;
}
var diffStepTime:Float = Conductor.currentStepTime - oldStepTime;
var diffStepTime:Float = Conductor.instance.currentStepTime - oldStepTime;
// Move the playhead.
playheadPositionInPixels += diffStepTime * GRID_SIZE;
@ -2933,9 +2952,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
// Else, move the entire view.
var oldSongPosition:Float = Conductor.songPosition + Conductor.instrumentalOffset;
Conductor.update(audioInstTrack.time);
handleHitsounds(oldSongPosition, Conductor.songPosition + Conductor.instrumentalOffset);
var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
Conductor.instance.update(audioInstTrack.time);
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
// Resync vocals.
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
{
@ -2944,7 +2963,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// We need time in fractional steps here to allow the song to actually play.
// Also account for a potentially offset playhead.
scrollPositionInPixels = (Conductor.currentStepTime + Conductor.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels;
scrollPositionInPixels = (Conductor.instance.currentStepTime + Conductor.instance.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels;
// DO NOT move song to scroll position here specifically.
@ -3075,8 +3094,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Let's try testing only notes within a certain range of the view area.
// TODO: I don't think this messes up really long sustains, does it?
var viewAreaTopMs:Float = scrollPositionInMs - (Conductor.measureLengthMs * 2); // Is 2 measures enough?
var viewAreaBottomMs:Float = scrollPositionInMs + (Conductor.measureLengthMs * 2); // Is 2 measures enough?
var viewAreaTopMs:Float = scrollPositionInMs - (Conductor.instance.measureLengthMs * 2); // Is 2 measures enough?
var viewAreaBottomMs:Float = scrollPositionInMs + (Conductor.instance.measureLengthMs * 2); // Is 2 measures enough?
// Add notes that are now visible.
for (noteData in currentSongChartNoteData)
@ -3363,14 +3382,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// PAGE UP = Jump up to nearest measure
if (pageUpKeyHandler.activated)
{
var measureHeight:Float = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.floor(playheadPos / measureHeight) * measureHeight;
// If we would move less than one grid, instead move to the top of the previous measure.
var targetScrollAmount = Math.abs(targetScrollPosition - playheadPos);
if (targetScrollAmount < GRID_SIZE)
{
targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.beatsPerMeasure;
targetScrollPosition -= GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
scrollAmount = targetScrollPosition - playheadPos;
@ -3379,21 +3398,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (playbarButtonPressed == 'playbarBack')
{
playbarButtonPressed = '';
scrollAmount = -GRID_SIZE * 4 * Conductor.beatsPerMeasure;
scrollAmount = -GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
shouldPause = true;
}
// PAGE DOWN = Jump down to nearest measure
if (pageDownKeyHandler.activated)
{
var measureHeight:Float = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
var measureHeight:Float = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var targetScrollPosition:Float = Math.ceil(playheadPos / measureHeight) * measureHeight;
// If we would move less than one grid, instead move to the top of the next measure.
var targetScrollAmount = Math.abs(targetScrollPosition - playheadPos);
if (targetScrollAmount < GRID_SIZE)
{
targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.beatsPerMeasure;
targetScrollPosition += GRID_SIZE * Constants.STEPS_PER_BEAT * Conductor.instance.beatsPerMeasure;
}
scrollAmount = targetScrollPosition - playheadPos;
@ -3402,7 +3421,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (playbarButtonPressed == 'playbarForward')
{
playbarButtonPressed = '';
scrollAmount = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
scrollAmount = GRID_SIZE * 4 * Conductor.instance.beatsPerMeasure;
shouldPause = true;
}
@ -3607,10 +3626,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// The song position of the cursor, in steps.
var cursorFractionalStep:Float = cursorY / GRID_SIZE;
var cursorMs:Float = Conductor.getStepTimeInMs(cursorFractionalStep);
var cursorMs:Float = Conductor.instance.getStepTimeInMs(cursorFractionalStep);
// Round the cursor step to the nearest snap quant.
var cursorSnappedStep:Float = Math.floor(cursorFractionalStep / noteSnapRatio) * noteSnapRatio;
var cursorSnappedMs:Float = Conductor.getStepTimeInMs(cursorSnappedStep);
var cursorSnappedMs:Float = Conductor.instance.getStepTimeInMs(cursorSnappedStep);
// The direction value for the column at the cursor.
var cursorGridPos:Int = Math.floor(cursorX / GRID_SIZE);
@ -3632,7 +3651,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// We released the mouse. Select the notes in the box.
var cursorFractionalStepStart:Float = cursorYStart / GRID_SIZE;
var cursorStepStart:Int = Math.floor(cursorFractionalStepStart);
var cursorMsStart:Float = Conductor.getStepTimeInMs(cursorStepStart);
var cursorMsStart:Float = Conductor.instance.getStepTimeInMs(cursorStepStart);
var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE);
var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE);
@ -3868,11 +3887,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var dragDistanceMs:Float = 0;
if (dragTargetNote != null && dragTargetNote.noteData != null)
{
dragDistanceMs = Conductor.getStepTimeInMs(dragTargetNote.noteData.getStepTime() + dragDistanceSteps) - dragTargetNote.noteData.time;
dragDistanceMs = Conductor.instance.getStepTimeInMs(dragTargetNote.noteData.getStepTime() + dragDistanceSteps) - dragTargetNote.noteData.time;
}
else if (dragTargetEvent != null && dragTargetEvent.eventData != null)
{
dragDistanceMs = Conductor.getStepTimeInMs(dragTargetEvent.eventData.getStepTime() + dragDistanceSteps) - dragTargetEvent.eventData.time;
dragDistanceMs = Conductor.instance.getStepTimeInMs(dragTargetEvent.eventData.getStepTime() + dragDistanceSteps) - dragTargetEvent.eventData.time;
}
var dragDistanceColumns:Int = dragTargetCurrentColumn;
@ -3932,7 +3951,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
stepTime = dragTargetEvent.eventData.getStepTime();
}
var dragDistanceSteps:Float = Conductor.getTimeInSteps(cursorSnappedMs).clamp(0, songLengthInSteps - (1 * noteSnapRatio)) - stepTime;
var dragDistanceSteps:Float = Conductor.instance.getTimeInSteps(cursorSnappedMs).clamp(0, songLengthInSteps - (1 * noteSnapRatio)) - stepTime;
var data:Int = 0;
var noteGridPos:Int = 0;
if (dragTargetNote != null && dragTargetNote.noteData != null)
@ -3964,8 +3983,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Handle extending the note as you drag.
var stepTime:Float = inline currentPlaceNoteData.getStepTime();
var dragLengthSteps:Float = Conductor.getTimeInSteps(cursorSnappedMs) - stepTime;
var dragLengthMs:Float = dragLengthSteps * Conductor.stepLengthMs;
var dragLengthSteps:Float = Conductor.instance.getTimeInSteps(cursorSnappedMs) - stepTime;
var dragLengthMs:Float = dragLengthSteps * Conductor.instance.stepLengthMs;
var dragLengthPixels:Float = dragLengthSteps * GRID_SIZE;
if (gridGhostNote != null && gridGhostNote.noteData != null && gridGhostHoldNote != null)
@ -4465,7 +4484,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (playbarHeadLayout.playbarHead.value != songPosPercent) playbarHeadLayout.playbarHead.value = songPosPercent;
}
var songPos:Float = Conductor.songPosition + Conductor.instrumentalOffset;
var songPos:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset;
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;
@ -4521,11 +4540,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / noteSnapRatio;
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
var playheadPosSnappedMs:Float = playheadPosStep * Conductor.stepLengthMs * noteSnapRatio;
var playheadPosSnappedMs:Float = playheadPosStep * Conductor.instance.stepLengthMs * noteSnapRatio;
// Look for notes within 1 step of the playhead.
var notesAtPos:Array<SongNoteData> = SongDataUtils.getNotesInTimeRange(currentSongChartNoteData, playheadPosSnappedMs,
playheadPosSnappedMs + Conductor.stepLengthMs * noteSnapRatio);
playheadPosSnappedMs + Conductor.instance.stepLengthMs * noteSnapRatio);
notesAtPos = SongDataUtils.getNotesWithData(notesAtPos, [column]);
if (notesAtPos.length == 0)
@ -4632,6 +4651,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
FlxG.switchState(new MainMenuState());
resetWindowTitle();
criticalFailure = true;
}
/**
@ -4722,9 +4743,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
var targetStep:Float = Conductor.getTimeInSteps(targetMs);
var targetStep:Float = Conductor.instance.getTimeInSteps(targetMs);
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
var targetSnappedMs:Float = Conductor.instance.getStepTimeInMs(targetSnappedStep);
targetSnappedMs;
}
performCommand(new PasteItemsCommand(targetMs));
@ -4862,7 +4883,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
function testSongInPlayState(minimal:Bool = false):Void
{
autoSave();
autoSave(true);
stopWelcomeMusic();
@ -5066,7 +5087,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Remove any notes past the end of the song.
var songCutoffPointSteps:Float = songLengthInSteps - 0.1;
var songCutoffPointMs:Float = Conductor.getStepTimeInMs(songCutoffPointSteps);
var songCutoffPointMs:Float = Conductor.instance.getStepTimeInMs(songCutoffPointSteps);
currentSongChartNoteData = SongDataUtils.clampSongNoteData(currentSongChartNoteData, 0.0, songCutoffPointMs);
currentSongChartEventData = SongDataUtils.clampSongEventData(currentSongChartEventData, 0.0, songCutoffPointMs);
@ -5168,7 +5189,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var prevDifficulty = availableDifficulties[availableDifficulties.length - 1];
selectedDifficulty = prevDifficulty;
Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges);
Conductor.instance.mapTimeChanges(this.currentSongMetadata.timeChanges);
refreshDifficultyTreeSelection();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
@ -5230,9 +5251,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Update the songPosition in the audio tracks.
if (audioInstTrack != null)
{
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instrumentalOffset;
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instance.instrumentalOffset;
// Update the songPosition in the Conductor.
Conductor.update(audioInstTrack.time);
Conductor.instance.update(audioInstTrack.time);
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = audioInstTrack.time;
}
@ -5292,6 +5313,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.persistentUpdate = true;
this.persistentDraw = true;
if (displayAutosavePopup)
{
displayAutosavePopup = false;
Toolkit.callLater(() -> {
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
{
text: "Take Me There",
callback: openBackupsFolder,
}
]);
});
}
moveSongToScrollPosition();
fadeInWelcomeMusic(7, 10);
@ -5551,7 +5586,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
trace('ERROR: Instrumental track is null!');
}
this.songLengthInMs = (audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset;
this.songLengthInMs = (audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset;
// Many things get reset when song length changes.
healthIconsDirty = true;

View file

@ -34,7 +34,12 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
state.currentSongMetadata.timeChanges = timeChanges;
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.notePreviewViewportBoundsDirty = true;
state.scrollPositionInPixels = 0;
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
}
public function undo(state:ChartEditorState):Void
@ -51,7 +56,12 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
state.currentSongMetadata.timeChanges = timeChanges;
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
state.noteDisplayDirty = true;
state.notePreviewDirty = true;
state.notePreviewViewportBoundsDirty = true;
state.scrollPositionInPixels = 0;
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
}
public function toString():String

View file

@ -33,7 +33,7 @@ class MoveEventsCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultEvent = event.clone();
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
movedEvents.push(resultEvent);
}

View file

@ -21,8 +21,8 @@ class MoveItemsCommand implements ChartEditorCommand
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, offset:Float, columns:Int)
{
// Clone the notes to prevent editing from affecting the history.
this.notes = [for (note in notes) note.clone()];
this.events = [for (event in events) event.clone()];
this.notes = notes.clone();
this.events = events.clone();
this.offset = offset;
this.columns = columns;
this.movedNotes = [];
@ -41,7 +41,7 @@ class MoveItemsCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultNote = note.clone();
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
ChartEditorState.STRUMLINE_SIZE * 2 - 1));
@ -52,7 +52,7 @@ class MoveItemsCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultEvent = event.clone();
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultEvent.time = (resultEvent.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
movedEvents.push(resultEvent);
}

View file

@ -34,7 +34,7 @@ class MoveNotesCommand implements ChartEditorCommand
{
// Clone the notes to prevent editing from affecting the history.
var resultNote = note.clone();
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultNote.time = (resultNote.time + offset).clamp(0, Conductor.instance.getStepTimeInMs(state.songLengthInSteps - (1 * state.noteSnapRatio)));
resultNote.data = ChartEditorState.gridColumnToNoteData((ChartEditorState.noteDataToGridColumn(resultNote.data) + columns).clamp(0,
ChartEditorState.STRUMLINE_SIZE * 2 - 1));

View file

@ -32,9 +32,9 @@ class PasteItemsCommand implements ChartEditorCommand
return;
}
var stepEndOfSong:Float = Conductor.getTimeInSteps(state.songLengthInMs);
var stepEndOfSong:Float = Conductor.instance.getTimeInSteps(state.songLengthInMs);
var stepCutoff:Float = stepEndOfSong - 1.0;
var msCutoff:Float = Conductor.getStepTimeInMs(stepCutoff);
var msCutoff:Float = Conductor.instance.getStepTimeInMs(stepCutoff);
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
addedNotes = SongDataUtils.clampSongNoteData(addedNotes, 0.0, msCutoff);

View file

@ -185,7 +185,7 @@ class ChartEditorAudioHandler
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
state.audioVisGroup.addPlayerVis(vocalTrack);
state.audioVisGroup.playerVis.x = 885;
state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195;
state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195;
state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
state.audioVisGroup.playerVis.detail = 1;
@ -196,7 +196,7 @@ class ChartEditorAudioHandler
state.audioVisGroup.addOpponentVis(vocalTrack);
state.audioVisGroup.opponentVis.x = 435;
state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195;
state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195;
state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
state.audioVisGroup.opponentVis.detail = 1;

View file

@ -684,8 +684,8 @@ class ChartEditorDialogHandler
state.songMetadata.set(targetVariation, newSongMetadata);
Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
state.selectedVariation = Constants.DEFAULT_VARIATION;
state.selectedDifficulty = state.availableDifficulties[0];

View file

@ -43,7 +43,8 @@ class ChartEditorImportExportHandler
var variation = (metadata.variation == null || metadata.variation == '') ? Constants.DEFAULT_VARIATION : metadata.variation;
// Clone to prevent modifying the original.
var metadataClone:SongMetadata = metadata.clone(variation);
var metadataClone:SongMetadata = metadata.clone();
metadataClone.variation = variation;
if (metadataClone != null) songMetadata.set(variation, metadataClone);
var chartData:Null<SongChartData> = SongRegistry.instance.parseEntryChartData(songId, metadata.variation);
@ -114,9 +115,9 @@ class ChartEditorImportExportHandler
state.songMetadata = newSongMetadata;
state.songChartData = newSongChartData;
Conductor.forceBPM(null); // Disable the forced BPM.
Conductor.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
Conductor.instance.forceBPM(null); // Disable the forced BPM.
Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
state.notePreviewDirty = true;
state.notePreviewViewportBoundsDirty = true;
@ -415,16 +416,34 @@ class ChartEditorImportExportHandler
]);
// We have to force write because the program will die before the save dialog is closed.
trace('Force exporting to $targetPath...');
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
if (onSaveCb != null) onSaveCb(targetPath);
try
{
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
// On success.
if (onSaveCb != null) onSaveCb(targetPath);
}
catch (e)
{
// On failure.
if (onCancelCb != null) onCancelCb();
}
}
else
{
// Force write since we know what file the user wants to overwrite.
trace('Force exporting to $targetPath...');
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
state.saveDataDirty = false;
if (onSaveCb != null) onSaveCb(targetPath);
try
{
// On success.
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
state.saveDataDirty = false;
if (onSaveCb != null) onSaveCb(targetPath);
}
catch (e)
{
// On failure.
if (onCancelCb != null) onCancelCb();
}
}
}
else

View file

@ -125,7 +125,7 @@ class ChartEditorThemeHandler
// 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall.
// This gets reused to fill the screen.
var gridWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT);
var gridHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.stepsPerMeasure);
var gridHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure);
state.gridBitmap = FlxGridOverlay.createGrid(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, gridWidth, gridHeight, true, gridColor1, gridColor2);
// Selection borders
@ -142,7 +142,7 @@ class ChartEditorThemeHandler
selectionBorderColor);
// Selection borders horizontally along the middle.
for (i in 1...(Conductor.stepsPerMeasure))
for (i in 1...(Conductor.instance.stepsPerMeasure))
{
state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2),
state.gridBitmap.width, ChartEditorState.GRID_SELECTION_BORDER_WIDTH),
@ -197,9 +197,9 @@ class ChartEditorThemeHandler
};
// Selection borders horizontally in the middle.
for (i in 1...(Conductor.stepsPerMeasure))
for (i in 1...(Conductor.instance.stepsPerMeasure))
{
if ((i % Conductor.beatsPerMeasure) == 0)
if ((i % Conductor.instance.beatsPerMeasure) == 0)
{
state.gridBitmap.fillRect(new Rectangle(0, (ChartEditorState.GRID_SIZE * i) - (GRID_BEAT_DIVIDER_WIDTH / 2), state.gridBitmap.width,
GRID_BEAT_DIVIDER_WIDTH),

View file

@ -120,9 +120,9 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
if (event.value == null) return;
chartEditorState.currentInstrumentalOffset = event.value;
Conductor.instrumentalOffset = event.value;
Conductor.instance.instrumentalOffset = event.value;
// Update song length.
chartEditorState.songLengthInMs = (chartEditorState.audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset;
chartEditorState.songLengthInMs = (chartEditorState.audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset;
};
inputOffsetVocal.onChange = function(event:UIEvent) {

View file

@ -75,7 +75,7 @@ class LatencyState extends MusicBeatSubState
// funnyStatsGraph.hi
Conductor.forceBPM(60);
Conductor.instance.forceBPM(60);
noteGrp = new FlxTypedGroup<NoteSprite>();
add(noteGrp);
@ -91,14 +91,14 @@ class LatencyState extends MusicBeatSubState
// // musSpec.visType = FREQUENCIES;
// add(musSpec);
for (beat in 0...Math.floor(FlxG.sound.music.length / Conductor.beatLengthMs))
for (beat in 0...Math.floor(FlxG.sound.music.length / Conductor.instance.beatLengthMs))
{
var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * Conductor.beatLengthMs), FlxG.height - 15);
var beatTick:FlxSprite = new FlxSprite(songPosToX(beat * Conductor.instance.beatLengthMs), FlxG.height - 15);
beatTick.makeGraphic(2, 15);
beatTick.alpha = 0.3;
add(beatTick);
var offsetTxt:FlxText = new FlxText(songPosToX(beat * Conductor.beatLengthMs), FlxG.height - 26, 0, "swag");
var offsetTxt:FlxText = new FlxText(songPosToX(beat * Conductor.instance.beatLengthMs), FlxG.height - 26, 0, "swag");
offsetTxt.alpha = 0.5;
diffGrp.add(offsetTxt);
@ -130,7 +130,7 @@ class LatencyState extends MusicBeatSubState
for (i in 0...32)
{
var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault(), Conductor.beatLengthMs * i);
var note:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault(), Conductor.instance.beatLengthMs * i);
noteGrp.add(note);
}
@ -146,9 +146,9 @@ class LatencyState extends MusicBeatSubState
override function stepHit():Bool
{
if (Conductor.currentStep % 4 == 2)
if (Conductor.instance.currentStep % 4 == 2)
{
blocks.members[((Conductor.currentBeat % 8) + 1) % 8].alpha = 0.5;
blocks.members[((Conductor.instance.currentBeat % 8) + 1) % 8].alpha = 0.5;
}
return super.stepHit();
@ -156,11 +156,11 @@ class LatencyState extends MusicBeatSubState
override function beatHit():Bool
{
if (Conductor.currentBeat % 8 == 0) blocks.forEach(blok -> {
if (Conductor.instance.currentBeat % 8 == 0) blocks.forEach(blok -> {
blok.alpha = 0;
});
blocks.members[Conductor.currentBeat % 8].alpha = 1;
blocks.members[Conductor.instance.currentBeat % 8].alpha = 1;
// block.visible = !block.visible;
return super.beatHit();
@ -192,17 +192,17 @@ class LatencyState extends MusicBeatSubState
if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed;
Conductor.update(swagSong.getTimeWithDiff() - Conductor.inputOffset);
// Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
Conductor.instance.update(swagSong.getTimeWithDiff() - Conductor.instance.inputOffset);
// Conductor.instance.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp;
songPosVis.x = songPosToX(Conductor.songPosition);
songVisFollowAudio.x = songPosToX(Conductor.songPosition - Conductor.instrumentalOffset);
songVisFollowVideo.x = songPosToX(Conductor.songPosition - Conductor.inputOffset);
songPosVis.x = songPosToX(Conductor.instance.songPosition);
songVisFollowAudio.x = songPosToX(Conductor.instance.songPosition - Conductor.instance.instrumentalOffset);
songVisFollowVideo.x = songPosToX(Conductor.instance.songPosition - Conductor.instance.inputOffset);
offsetText.text = "INST Offset: " + Conductor.instrumentalOffset + "ms";
offsetText.text += "\nINPUT Offset: " + Conductor.inputOffset + "ms";
offsetText.text += "\ncurrentStep: " + Conductor.currentStep;
offsetText.text += "\ncurrentBeat: " + Conductor.currentBeat;
offsetText.text = "INST Offset: " + Conductor.instance.instrumentalOffset + "ms";
offsetText.text += "\nINPUT Offset: " + Conductor.instance.inputOffset + "ms";
offsetText.text += "\ncurrentStep: " + Conductor.instance.currentStep;
offsetText.text += "\ncurrentBeat: " + Conductor.instance.currentBeat;
var avgOffsetInput:Float = 0;
@ -221,24 +221,24 @@ class LatencyState extends MusicBeatSubState
{
if (FlxG.keys.justPressed.RIGHT)
{
Conductor.instrumentalOffset += 1.0 * multiply;
Conductor.instance.instrumentalOffset += 1.0 * multiply;
}
if (FlxG.keys.justPressed.LEFT)
{
Conductor.instrumentalOffset -= 1.0 * multiply;
Conductor.instance.instrumentalOffset -= 1.0 * multiply;
}
}
else
{
if (FlxG.keys.justPressed.RIGHT)
{
Conductor.inputOffset += 1.0 * multiply;
Conductor.instance.inputOffset += 1.0 * multiply;
}
if (FlxG.keys.justPressed.LEFT)
{
Conductor.inputOffset -= 1.0 * multiply;
Conductor.instance.inputOffset -= 1.0 * multiply;
}
}
@ -250,7 +250,7 @@ class LatencyState extends MusicBeatSubState
}*/
noteGrp.forEach(function(daNote:NoteSprite) {
daNote.y = (strumLine.y - ((Conductor.songPosition - Conductor.instrumentalOffset) - daNote.noteData.time) * 0.45);
daNote.y = (strumLine.y - ((Conductor.instance.songPosition - Conductor.instance.instrumentalOffset) - daNote.noteData.time) * 0.45);
daNote.x = strumLine.x + 30;
if (daNote.y < strumLine.y) daNote.alpha = 0.5;
@ -258,7 +258,7 @@ class LatencyState extends MusicBeatSubState
if (daNote.y < 0 - daNote.height)
{
daNote.alpha = 1;
// daNote.data.strumTime += Conductor.beatLengthMs * 8;
// daNote.data.strumTime += Conductor.instance.beatLengthMs * 8;
}
});
@ -267,14 +267,14 @@ class LatencyState extends MusicBeatSubState
function generateBeatStuff()
{
Conductor.update(swagSong.getTimeWithDiff());
Conductor.instance.update(swagSong.getTimeWithDiff());
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length;
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs);
getDiff -= Conductor.inputOffset;
var closestBeat:Int = Math.round(Conductor.instance.songPosition / Conductor.instance.beatLengthMs) % diffGrp.members.length;
var getDiff:Float = Conductor.instance.songPosition - (closestBeat * Conductor.instance.beatLengthMs);
getDiff -= Conductor.instance.inputOffset;
// lil fix for end of song
if (closestBeat == 0 && getDiff >= Conductor.beatLengthMs * 2) getDiff -= FlxG.sound.music.length;
if (closestBeat == 0 && getDiff >= Conductor.instance.beatLengthMs * 2) getDiff -= FlxG.sound.music.length;
trace("\tDISTANCE TO CLOSEST BEAT: " + getDiff + "ms");
trace("\tCLOSEST BEAT: " + closestBeat);

View file

@ -238,7 +238,7 @@ class StoryMenuState extends MusicBeatState
var freakyMenuMetadata:Null<SongMusicData> = SongRegistry.instance.parseMusicData('freakyMenu');
if (freakyMenuMetadata != null)
{
Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges);
Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges);
}
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
@ -317,7 +317,7 @@ class StoryMenuState extends MusicBeatState
override function update(elapsed:Float)
{
Conductor.update();
Conductor.instance.update();
highScoreLerp = Std.int(MathUtil.coolLerp(highScoreLerp, highScore, 0.5));

View file

@ -221,7 +221,7 @@ class TitleState extends MusicBeatState
var freakyMenuMetadata:Null<SongMusicData> = SongRegistry.instance.parseMusicData('freakyMenu');
if (freakyMenuMetadata != null)
{
Conductor.mapTimeChanges(freakyMenuMetadata.timeChanges);
Conductor.instance.mapTimeChanges(freakyMenuMetadata.timeChanges);
}
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'), 0);
FlxG.sound.music.fadeIn(4, 0, 0.7);
@ -256,7 +256,7 @@ class TitleState extends MusicBeatState
if (FlxG.keys.pressed.DOWN) FlxG.sound.music.pitch -= 0.5 * elapsed;
#end
Conductor.update();
Conductor.instance.update();
/* if (FlxG.onMobile)
{
@ -280,7 +280,7 @@ class TitleState extends MusicBeatState
FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG});
}
if (FlxG.sound.music != null) Conductor.update(FlxG.sound.music.time);
if (FlxG.sound.music != null) Conductor.instance.update(FlxG.sound.music.time);
if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen;
// do controls.PAUSE | controls.ACCEPT instead?
@ -390,7 +390,7 @@ class TitleState extends MusicBeatState
var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music);
add(spec);
Conductor.forceBPM(190);
Conductor.instance.forceBPM(190);
FlxG.camera.flash(FlxColor.WHITE, 1);
FlxG.sound.play(Paths.sound('confirmMenu'), 0.7);
}
@ -442,13 +442,13 @@ class TitleState extends MusicBeatState
if (!skippedIntro)
{
// FlxG.log.add(Conductor.currentBeat);
// FlxG.log.add(Conductor.instance.currentBeat);
// if the user is draggin the window some beats will
// be missed so this is just to compensate
if (Conductor.currentBeat > lastBeat)
if (Conductor.instance.currentBeat > lastBeat)
{
// TODO: Why does it perform ALL the previous steps each beat?
for (i in lastBeat...Conductor.currentBeat)
for (i in lastBeat...Conductor.instance.currentBeat)
{
switch (i + 1)
{
@ -483,11 +483,11 @@ class TitleState extends MusicBeatState
}
}
}
lastBeat = Conductor.currentBeat;
lastBeat = Conductor.instance.currentBeat;
}
if (skippedIntro)
{
if (cheatActive && Conductor.currentBeat % 2 == 0) swagShader.update(0.125);
if (cheatActive && Conductor.instance.currentBeat % 2 == 0) swagShader.update(0.125);
if (logoBl != null && logoBl.animation != null) logoBl.animation.play('bump', true);

View file

@ -77,6 +77,22 @@ class ArrayTools
array.pop();
}
/**
* Create a new array with all elements of the given array, to prevent modifying the original.
*/
public static function clone<T>(array:Array<T>):Array<T>
{
return [for (element in array) element];
}
/**
* Create a new array with clones of all elements of the given array, to prevent modifying the original.
*/
public static function deepClone<T, U:ICloneable<T>>(array:Array<U>):Array<T>
{
return [for (element in array) element.clone()];
}
/**
* Return true only if both arrays contain the same elements (possibly in a different order).
* @param a The first array to compare.

View file

@ -0,0 +1,10 @@
package funkin.util.tools;
/**
* Implement this on a class to enable `Array<T>.deepClone()` to work on it.
* NOTE: T should be the type of the class that implements this interface.
*/
interface ICloneable<T>
{
public function clone():T;
}

View file

@ -25,6 +25,33 @@ class MapTools
return [for (i in map.iterator()) i];
}
/**
* Create a new array with all elements of the given array, to prevent modifying the original.
*/
public static function clone<K, T>(map:Map<K, T>):Map<K, T>
{
return map.copy();
}
/**
* Create a new array with clones of all elements of the given array, to prevent modifying the original.
*/
public static function deepClone<K, T, U:ICloneable<T>>(map:Map<K, U>):Map<K, T>
{
// TODO: This function does NOT work.
throw "Not implemented";
/*
var newMap:Map<K, T> = [];
// Replace each value with a clone of itself.
for (key in newMap.keys())
{
newMap.set(key, newMap.get(key).clone());
}
return newMap;
*/
}
/**
* Return a list of keys from the map (as an array, rather than an iterator).
* TODO: Rename this?

View file

@ -31,23 +31,23 @@ class ConductorTest extends FunkinTest
{
// NOTE: Expected value comes first.
Assert.areEqual([], Conductor.timeChanges);
Assert.areEqual(null, Conductor.currentTimeChange);
Assert.areEqual([], Conductor.instance.timeChanges);
Assert.areEqual(null, Conductor.instance.currentTimeChange);
Assert.areEqual(0, Conductor.songPosition);
Assert.areEqual(Constants.DEFAULT_BPM, Conductor.bpm);
Assert.areEqual(null, Conductor.bpmOverride);
Assert.areEqual(0, Conductor.instance.songPosition);
Assert.areEqual(Constants.DEFAULT_BPM, Conductor.instance.bpm);
Assert.areEqual(null, Conductor.instance.bpmOverride);
Assert.areEqual(600, Conductor.beatLengthMs);
Assert.areEqual(600, Conductor.instance.beatLengthMs);
Assert.areEqual(4, Conductor.timeSignatureNumerator);
Assert.areEqual(4, Conductor.timeSignatureDenominator);
Assert.areEqual(4, Conductor.instance.timeSignatureNumerator);
Assert.areEqual(4, Conductor.instance.timeSignatureDenominator);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
Assert.areEqual(0.0, Conductor.currentStepTime);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
Assert.areEqual(0.0, Conductor.instance.currentStepTime);
Assert.areEqual(150, Conductor.stepLengthMs);
Assert.areEqual(150, Conductor.instance.stepLengthMs);
}
/**
@ -60,23 +60,23 @@ class ConductorTest extends FunkinTest
var currentConductorState:Null<ConductorState> = conductorState;
Assert.isNotNull(currentConductorState);
Assert.areEqual(0, Conductor.songPosition);
Assert.areEqual(0, Conductor.instance.songPosition);
step(); // 1
var BPM_100_STEP_TIME = 1 / 9;
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(1 / 9, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(1 / 9, Conductor.instance.currentStepTime);
step(7); // 8
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 8, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(8 / 9, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 8, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(8 / 9, Conductor.instance.currentStepTime);
Assert.areEqual(0, currentConductorState.beatsHit);
Assert.areEqual(0, currentConductorState.stepsHit);
@ -88,10 +88,10 @@ class ConductorTest extends FunkinTest
currentConductorState.beatsHit = 0;
currentConductorState.stepsHit = 0;
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 9, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(1, Conductor.currentStep);
FunkinAssert.areNear(1.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 9, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(1, Conductor.instance.currentStep);
FunkinAssert.areNear(1.0, Conductor.instance.currentStepTime);
step(35 - 9); // 35
@ -100,10 +100,10 @@ class ConductorTest extends FunkinTest
currentConductorState.beatsHit = 0;
currentConductorState.stepsHit = 0;
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 35, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(3, Conductor.currentStep);
FunkinAssert.areNear(3.0 + 8 / 9, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 35, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(3, Conductor.instance.currentStep);
FunkinAssert.areNear(3.0 + 8 / 9, Conductor.instance.currentStepTime);
step(); // 36
@ -112,83 +112,83 @@ class ConductorTest extends FunkinTest
currentConductorState.beatsHit = 0;
currentConductorState.stepsHit = 0;
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 36, Conductor.songPosition);
Assert.areEqual(1, Conductor.currentBeat);
Assert.areEqual(4, Conductor.currentStep);
FunkinAssert.areNear(4.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 36, Conductor.instance.songPosition);
Assert.areEqual(1, Conductor.instance.currentBeat);
Assert.areEqual(4, Conductor.instance.currentStep);
FunkinAssert.areNear(4.0, Conductor.instance.currentStepTime);
step(50 - 36); // 50
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 50, Conductor.songPosition);
Assert.areEqual(1, Conductor.currentBeat);
Assert.areEqual(5, Conductor.currentStep);
FunkinAssert.areNear(5.555555, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 50, Conductor.instance.songPosition);
Assert.areEqual(1, Conductor.instance.currentBeat);
Assert.areEqual(5, Conductor.instance.currentStep);
FunkinAssert.areNear(5.555555, Conductor.instance.currentStepTime);
step(49); // 99
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 99, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(11, Conductor.currentStep);
FunkinAssert.areNear(11.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 99, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(11, Conductor.instance.currentStep);
FunkinAssert.areNear(11.0, Conductor.instance.currentStepTime);
step(1); // 100
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 100, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(11, Conductor.currentStep);
FunkinAssert.areNear(11.111111, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 100, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(11, Conductor.instance.currentStep);
FunkinAssert.areNear(11.111111, Conductor.instance.currentStepTime);
}
@Test
function testUpdateForcedBPM():Void
{
Conductor.forceBPM(60);
Conductor.instance.forceBPM(60);
Assert.areEqual(0, Conductor.songPosition);
Assert.areEqual(0, Conductor.instance.songPosition);
// 60 beats per minute = 1 beat per second
// 1 beat per second = 1/60 beats per frame = 4/60 steps per frame
step(); // Advances time 1/60 of 1 second.
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(4 / 60, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(4 / 60, Conductor.instance.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
step(14 - 1); // 14
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 14, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(1.0 - 4 / 60, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 14, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(1.0 - 4 / 60, Conductor.instance.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
step(); // 15
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(1, Conductor.currentStep);
FunkinAssert.areNear(1.0, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(1, Conductor.instance.currentStep);
FunkinAssert.areNear(1.0, Conductor.instance.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
step(45 - 1); // 59
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(3, Conductor.currentStep);
FunkinAssert.areNear(4.0 - 4 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(3, Conductor.instance.currentStep);
FunkinAssert.areNear(4.0 - 4 / 60, Conductor.instance.currentStepTime);
step(); // 60
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
Assert.areEqual(1, Conductor.currentBeat);
Assert.areEqual(4, Conductor.currentStep);
FunkinAssert.areNear(4.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition);
Assert.areEqual(1, Conductor.instance.currentBeat);
Assert.areEqual(4, Conductor.instance.currentStep);
FunkinAssert.areNear(4.0, Conductor.instance.currentStepTime);
step(); // 61
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
Assert.areEqual(1, Conductor.currentBeat);
Assert.areEqual(4, Conductor.currentStep);
FunkinAssert.areNear(4.0 + 4 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition);
Assert.areEqual(1, Conductor.instance.currentBeat);
Assert.areEqual(4, Conductor.instance.currentStep);
FunkinAssert.areNear(4.0 + 4 / 60, Conductor.instance.currentStepTime);
}
@Test
@ -196,50 +196,50 @@ class ConductorTest extends FunkinTest
{
// Start the song with a BPM of 120.
var songTimeChanges:Array<SongTimeChange> = [new SongTimeChange(0, 120)];
Conductor.mapTimeChanges(songTimeChanges);
Conductor.instance.mapTimeChanges(songTimeChanges);
// All should be at 0.
FunkinAssert.areNear(0, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
FunkinAssert.areNear(0, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(0.0, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
// 120 beats per minute = 2 beat per second
// 2 beat per second = 2/60 beats per frame = 16/120 steps per frame
step(); // Advances time 1/60 of 1 second.
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(16 / 120, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
step(15 - 1); // 15
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(2, Conductor.currentStep);
FunkinAssert.areNear(2.0, Conductor.currentStepTime); // 2/60 of 1 beat = 8/60 of 1 step
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(2, Conductor.instance.currentStep);
FunkinAssert.areNear(2.0, Conductor.instance.currentStepTime); // 2/60 of 1 beat = 8/60 of 1 step
step(45 - 1); // 59
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
Assert.areEqual(1, Conductor.currentBeat);
Assert.areEqual(7, Conductor.currentStep);
FunkinAssert.areNear(7.0 + 104 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition);
Assert.areEqual(1, Conductor.instance.currentBeat);
Assert.areEqual(7, Conductor.instance.currentStep);
FunkinAssert.areNear(7.0 + 104 / 120, Conductor.instance.currentStepTime);
step(); // 60
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(8, Conductor.currentStep);
FunkinAssert.areNear(8.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(8, Conductor.instance.currentStep);
FunkinAssert.areNear(8.0, Conductor.instance.currentStepTime);
step(); // 61
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(8, Conductor.currentStep);
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(8, Conductor.instance.currentStep);
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.instance.currentStepTime);
}
@Test
@ -247,57 +247,57 @@ class ConductorTest extends FunkinTest
{
// Start the song with a BPM of 120.
var songTimeChanges:Array<SongTimeChange> = [new SongTimeChange(0, 120), new SongTimeChange(3000, 90)];
Conductor.mapTimeChanges(songTimeChanges);
Conductor.instance.mapTimeChanges(songTimeChanges);
// All should be at 0.
FunkinAssert.areNear(0, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
FunkinAssert.areNear(0, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(0.0, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
// 120 beats per minute = 2 beat per second
// 2 beat per second = 2/60 beats per frame = 16/120 steps per frame
step(); // Advances time 1/60 of 1 second.
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(16 / 120, Conductor.instance.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step
step(60 - 1 - 1); // 59
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
Assert.areEqual(1, Conductor.currentBeat);
Assert.areEqual(7, Conductor.currentStep);
FunkinAssert.areNear(7.0 + 104 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition);
Assert.areEqual(1, Conductor.instance.currentBeat);
Assert.areEqual(7, Conductor.instance.currentStep);
FunkinAssert.areNear(7.0 + 104 / 120, Conductor.instance.currentStepTime);
step(); // 60
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(8, Conductor.currentStep);
FunkinAssert.areNear(8.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(8, Conductor.instance.currentStep);
FunkinAssert.areNear(8.0, Conductor.instance.currentStepTime);
step(); // 61
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(8, Conductor.currentStep);
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(8, Conductor.instance.currentStep);
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.instance.currentStepTime);
step(179 - 61); // 179
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.songPosition);
Assert.areEqual(5, Conductor.currentBeat);
Assert.areEqual(23, Conductor.currentStep);
FunkinAssert.areNear(23.0 + 52 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.instance.songPosition);
Assert.areEqual(5, Conductor.instance.currentBeat);
Assert.areEqual(23, Conductor.instance.currentStep);
FunkinAssert.areNear(23.0 + 52 / 60, Conductor.instance.currentStepTime);
step(); // 180 (3 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.songPosition);
Assert.areEqual(6, Conductor.currentBeat);
Assert.areEqual(24, Conductor.currentStep);
FunkinAssert.areNear(24.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.instance.songPosition);
Assert.areEqual(6, Conductor.instance.currentBeat);
Assert.areEqual(24, Conductor.instance.currentStep);
FunkinAssert.areNear(24.0, Conductor.instance.currentStepTime);
step(); // 181 (3 + 1/60 seconds)
// BPM has switched to 90!
@ -305,24 +305,24 @@ class ConductorTest extends FunkinTest
// 1.5 beat per second = 1.5/60 beats per frame = 3/120 beats per frame
// = 12/120 steps per frame
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.songPosition);
Assert.areEqual(6, Conductor.currentBeat);
Assert.areEqual(24, Conductor.currentStep);
FunkinAssert.areNear(24.0 + 12 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.instance.songPosition);
Assert.areEqual(6, Conductor.instance.currentBeat);
Assert.areEqual(24, Conductor.instance.currentStep);
FunkinAssert.areNear(24.0 + 12 / 120, Conductor.instance.currentStepTime);
step(59); // 240 (4 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.songPosition);
Assert.areEqual(7, Conductor.currentBeat);
Assert.areEqual(30, Conductor.currentStep);
FunkinAssert.areNear(30.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.instance.songPosition);
Assert.areEqual(7, Conductor.instance.currentBeat);
Assert.areEqual(30, Conductor.instance.currentStep);
FunkinAssert.areNear(30.0, Conductor.instance.currentStepTime);
step(); // 241 (4 + 1/60 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.songPosition);
Assert.areEqual(7, Conductor.currentBeat);
Assert.areEqual(30, Conductor.currentStep);
FunkinAssert.areNear(30.0 + 12 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.instance.songPosition);
Assert.areEqual(7, Conductor.instance.currentBeat);
Assert.areEqual(30, Conductor.instance.currentStep);
FunkinAssert.areNear(30.0 + 12 / 120, Conductor.instance.currentStepTime);
}
@Test
@ -334,63 +334,63 @@ class ConductorTest extends FunkinTest
new SongTimeChange(3000, 90),
new SongTimeChange(6000, 180)
];
Conductor.mapTimeChanges(songTimeChanges);
Conductor.instance.mapTimeChanges(songTimeChanges);
// Verify time changes.
Assert.areEqual(3, Conductor.timeChanges.length);
FunkinAssert.areNear(0, Conductor.timeChanges[0].beatTime);
FunkinAssert.areNear(6, Conductor.timeChanges[1].beatTime);
FunkinAssert.areNear(10.5, Conductor.timeChanges[2].beatTime);
Assert.areEqual(3, Conductor.instance.timeChanges.length);
FunkinAssert.areNear(0, Conductor.instance.timeChanges[0].beatTime);
FunkinAssert.areNear(6, Conductor.instance.timeChanges[1].beatTime);
FunkinAssert.areNear(10.5, Conductor.instance.timeChanges[2].beatTime);
// All should be at 0.
FunkinAssert.areNear(0, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
FunkinAssert.areNear(0, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(0.0, Conductor.instance.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
// 120 beats per minute = 2 beat per second
// 2 beat per second = 2/60 beats per frame = 16/120 steps per frame
step(); // Advances time 1/60 of 1 second.
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
Assert.areEqual(0, Conductor.currentBeat);
Assert.areEqual(0, Conductor.currentStep);
FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.instance.songPosition);
Assert.areEqual(0, Conductor.instance.currentBeat);
Assert.areEqual(0, Conductor.instance.currentStep);
FunkinAssert.areNear(16 / 120, Conductor.instance.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step
step(60 - 1 - 1); // 59
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
Assert.areEqual(1, Conductor.currentBeat);
Assert.areEqual(7, Conductor.currentStep);
FunkinAssert.areNear(7 + 104 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.instance.songPosition);
Assert.areEqual(1, Conductor.instance.currentBeat);
Assert.areEqual(7, Conductor.instance.currentStep);
FunkinAssert.areNear(7 + 104 / 120, Conductor.instance.currentStepTime);
step(); // 60
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(8, Conductor.currentStep);
FunkinAssert.areNear(8.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(8, Conductor.instance.currentStep);
FunkinAssert.areNear(8.0, Conductor.instance.currentStepTime);
step(); // 61
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
Assert.areEqual(2, Conductor.currentBeat);
Assert.areEqual(8, Conductor.currentStep);
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.instance.songPosition);
Assert.areEqual(2, Conductor.instance.currentBeat);
Assert.areEqual(8, Conductor.instance.currentStep);
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.instance.currentStepTime);
step(179 - 61); // 179
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.songPosition);
Assert.areEqual(5, Conductor.currentBeat);
Assert.areEqual(23, Conductor.currentStep);
FunkinAssert.areNear(23.0 + 52 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.instance.songPosition);
Assert.areEqual(5, Conductor.instance.currentBeat);
Assert.areEqual(23, Conductor.instance.currentStep);
FunkinAssert.areNear(23.0 + 52 / 60, Conductor.instance.currentStepTime);
step(); // 180 (3 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.songPosition);
Assert.areEqual(6, Conductor.currentBeat);
Assert.areEqual(24, Conductor.currentStep); // 23.999 => 24
FunkinAssert.areNear(24.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.instance.songPosition);
Assert.areEqual(6, Conductor.instance.currentBeat);
Assert.areEqual(24, Conductor.instance.currentStep); // 23.999 => 24
FunkinAssert.areNear(24.0, Conductor.instance.currentStepTime);
step(); // 181 (3 + 1/60 seconds)
// BPM has switched to 90!
@ -398,45 +398,45 @@ class ConductorTest extends FunkinTest
// 1.5 beat per second = 1.5/60 beats per frame = 3/120 beats per frame
// = 12/120 steps per frame
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.songPosition);
Assert.areEqual(6, Conductor.currentBeat);
Assert.areEqual(24, Conductor.currentStep);
FunkinAssert.areNear(24.0 + 12 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.instance.songPosition);
Assert.areEqual(6, Conductor.instance.currentBeat);
Assert.areEqual(24, Conductor.instance.currentStep);
FunkinAssert.areNear(24.0 + 12 / 120, Conductor.instance.currentStepTime);
step(60 - 1 - 1); // 240 (4 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 239, Conductor.songPosition);
Assert.areEqual(7, Conductor.currentBeat);
Assert.areEqual(29, Conductor.currentStep);
FunkinAssert.areNear(29.0 + 108 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 239, Conductor.instance.songPosition);
Assert.areEqual(7, Conductor.instance.currentBeat);
Assert.areEqual(29, Conductor.instance.currentStep);
FunkinAssert.areNear(29.0 + 108 / 120, Conductor.instance.currentStepTime);
step(); // 240 (4 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.songPosition);
Assert.areEqual(7, Conductor.currentBeat);
Assert.areEqual(30, Conductor.currentStep);
FunkinAssert.areNear(30.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.instance.songPosition);
Assert.areEqual(7, Conductor.instance.currentBeat);
Assert.areEqual(30, Conductor.instance.currentStep);
FunkinAssert.areNear(30.0, Conductor.instance.currentStepTime);
step(); // 241 (4 + 1/60 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.songPosition);
Assert.areEqual(7, Conductor.currentBeat);
Assert.areEqual(30, Conductor.currentStep);
FunkinAssert.areNear(30.0 + 12 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.instance.songPosition);
Assert.areEqual(7, Conductor.instance.currentBeat);
Assert.areEqual(30, Conductor.instance.currentStep);
FunkinAssert.areNear(30.0 + 12 / 120, Conductor.instance.currentStepTime);
step(359 - 241); // 359 (5 + 59/60 seconds)
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 359, Conductor.songPosition);
Assert.areEqual(10, Conductor.currentBeat);
Assert.areEqual(41, Conductor.currentStep);
FunkinAssert.areNear(41 + 108 / 120, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 359, Conductor.instance.songPosition);
Assert.areEqual(10, Conductor.instance.currentBeat);
Assert.areEqual(41, Conductor.instance.currentStep);
FunkinAssert.areNear(41 + 108 / 120, Conductor.instance.currentStepTime);
step(); // 360
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 360, Conductor.songPosition);
Assert.areEqual(10, Conductor.currentBeat);
Assert.areEqual(42, Conductor.currentStep); // 41.999
FunkinAssert.areNear(42.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 360, Conductor.instance.songPosition);
Assert.areEqual(10, Conductor.instance.currentBeat);
Assert.areEqual(42, Conductor.instance.currentStep); // 41.999
FunkinAssert.areNear(42.0, Conductor.instance.currentStepTime);
step(); // 361
// BPM has switched to 180!
@ -444,24 +444,24 @@ class ConductorTest extends FunkinTest
// 3 beat per second = 3/60 beats per frame
// = 12/60 steps per frame
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 361, Conductor.songPosition);
Assert.areEqual(10, Conductor.currentBeat);
Assert.areEqual(42, Conductor.currentStep);
FunkinAssert.areNear(42.0 + 12 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 361, Conductor.instance.songPosition);
Assert.areEqual(10, Conductor.instance.currentBeat);
Assert.areEqual(42, Conductor.instance.currentStep);
FunkinAssert.areNear(42.0 + 12 / 60, Conductor.instance.currentStepTime);
step(); // 362
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 362, Conductor.songPosition);
Assert.areEqual(10, Conductor.currentBeat);
Assert.areEqual(42, Conductor.currentStep);
FunkinAssert.areNear(42.0 + 24 / 60, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 362, Conductor.instance.songPosition);
Assert.areEqual(10, Conductor.instance.currentBeat);
Assert.areEqual(42, Conductor.instance.currentStep);
FunkinAssert.areNear(42.0 + 24 / 60, Conductor.instance.currentStepTime);
step(3); // 365
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 365, Conductor.songPosition);
Assert.areEqual(10, Conductor.currentBeat);
Assert.areEqual(43, Conductor.currentStep); // 42.999 => 42
FunkinAssert.areNear(43.0, Conductor.currentStepTime);
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 365, Conductor.instance.songPosition);
Assert.areEqual(10, Conductor.instance.currentBeat);
Assert.areEqual(43, Conductor.instance.currentStep); // 42.999 => 42
FunkinAssert.areNear(43.0, Conductor.instance.currentStepTime);
}
}
@ -504,6 +504,6 @@ class ConductorState extends FlxState
super.update(elapsed);
// On each step, increment the Conductor as though the song was playing.
Conductor.update(Conductor.songPosition + elapsed * Constants.MS_PER_SEC);
Conductor.instance.update(Conductor.instance.songPosition + elapsed * Constants.MS_PER_SEC);
}
}