Funkin/source/funkin/Conductor.hx

304 lines
7.1 KiB
Haxe
Raw Normal View History

package funkin;
2020-10-03 02:50:15 -04:00
2022-09-23 00:49:42 -04:00
import flixel.util.FlxSignal;
import funkin.SongLoad.SwagSong;
2022-09-22 06:34:03 -04:00
import funkin.play.song.Song.SongDifficulty;
import funkin.play.song.SongData.SongTimeChange;
typedef BPMChangeEvent =
{
var stepTime:Int;
var songTime:Float;
var bpm:Float;
}
2020-10-03 02:50:15 -04:00
class Conductor
{
/**
2022-09-22 06:34:03 -04:00
* 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.
*/
2022-09-23 00:49:42 -04:00
private static var timeChanges:Array<SongTimeChange> = [];
/**
2022-09-22 06:34:03 -04:00
* The current time change.
*/
2022-09-23 00:49:42 -04:00
private static var currentTimeChange:SongTimeChange;
2022-09-22 06:34:03 -04:00
/**
* The current position in the song in milliseconds.
* Updated every frame based on the audio position.
*/
public static var songPosition:Float;
/**
* Beats per minute of the current song at the current time.
*/
2022-09-26 17:18:19 -04:00
public static var bpm(get, null):Float;
2022-09-22 06:34:03 -04:00
static function get_bpm():Float
{
2022-09-26 17:18:19 -04:00
if (bpmOverride != null)
return bpmOverride;
2022-09-22 06:34:03 -04:00
if (currentTimeChange == null)
return 100;
return currentTimeChange.bpm;
}
2022-09-26 17:18:19 -04:00
static var bpmOverride:Null<Float> = null;
2022-09-22 06:34:03 -04:00
// OLD, replaced with timeChanges.
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
/**
* Duration of a beat in millisecond. Calculated based on bpm.
*/
public static var crochet(get, null):Float;
static function get_crochet():Float
{
return ((60 / bpm) * 1000);
}
/**
2022-12-14 15:33:30 -05:00
* Duration of a step (quarter) in milliseconds. Calculated based on bpm.
*/
public static var stepCrochet(get, null):Float;
static function get_stepCrochet():Float
{
2022-12-14 15:33:30 -05:00
return crochet / timeSignatureNumerator;
}
public static var timeSignatureNumerator(get, null):Int;
static function get_timeSignatureNumerator():Int
{
if (currentTimeChange == null)
return 4;
return currentTimeChange.timeSignatureNum;
}
public static var timeSignatureDenominator(get, null):Int;
static function get_timeSignatureDenominator():Int
{
if (currentTimeChange == null)
return 4;
return currentTimeChange.timeSignatureDen;
}
/**
* Current position in the song, in beats.
**/
public static var currentBeat(default, null):Int;
2022-09-22 06:34:03 -04:00
/**
* Current position in the song, in steps.
*/
public static var currentStep(default, null):Int;
2022-09-22 06:34:03 -04:00
/**
* Current position in the song, in steps and fractions of a step.
*/
public static var currentStepTime(default, null):Float;
2022-06-01 22:07:42 -04:00
2022-09-23 00:49:42 -04:00
public static var beatHit(default, null):FlxSignal = new FlxSignal();
public static var stepHit(default, null):FlxSignal = new FlxSignal();
2020-10-24 05:19:13 -04:00
public static var lastSongPos:Float;
2022-07-06 15:27:45 -04:00
public static var visualOffset:Float = 0;
public static var audioOffset:Float = 0;
2020-10-03 02:50:15 -04:00
public static var offset:Float = 0;
// TODO: Add code to update this.
public static var beatsPerMeasure(get, null):Int;
static function get_beatsPerMeasure():Int
{
return timeSignatureNumerator;
}
public static var stepsPerMeasure(get, null):Int;
static function get_stepsPerMeasure():Int
{
// Is this always x4?
return timeSignatureNumerator * 4;
}
2022-09-23 00:49:42 -04:00
private function new()
2022-09-22 06:34:03 -04:00
{
}
public static function getLastBPMChange()
{
var lastChange:BPMChangeEvent = {
stepTime: 0,
songTime: 0,
bpm: 0
}
for (i in 0...Conductor.bpmChangeMap.length)
{
if (Conductor.songPosition >= Conductor.bpmChangeMap[i].songTime)
lastChange = Conductor.bpmChangeMap[i];
if (Conductor.songPosition < Conductor.bpmChangeMap[i].songTime)
break;
}
return lastChange;
}
/**
* Forcibly defines the current BPM of the song.
* Useful for things like the chart editor that need to manipulate BPM in real time.
*
2022-12-14 15:33:30 -05:00
* Set to null to reset to the BPM defined by the timeChanges.
*
* WARNING: Avoid this for things like setting the BPM of the title screen music,
* you should have a metadata file for it instead.
*/
2022-12-14 15:33:30 -05:00
public static function forceBPM(?bpm:Float = null)
2022-09-22 06:34:03 -04:00
{
if (bpm != null)
trace('[CONDUCTOR] Forcing BPM to ' + bpm);
else
trace('[CONDUCTOR] Resetting BPM to default');
2022-09-26 17:18:19 -04:00
Conductor.bpmOverride = bpm;
2022-09-22 06:34:03 -04:00
}
/**
* Update the conductor with the current song position.
* BPM, current step, etc. will be re-calculated based on the song position.
2022-09-26 17:18:19 -04:00
*
* @param songPosition The current position in the song in milliseconds.
* Leave blank to use the FlxG.sound.music position.
2022-09-22 06:34:03 -04:00
*/
2022-09-26 17:18:19 -04:00
public static function update(songPosition:Float = null)
2022-09-22 06:34:03 -04:00
{
2022-09-26 17:18:19 -04:00
if (songPosition == null)
songPosition = (FlxG.sound.music != null) ? (FlxG.sound.music.time + Conductor.offset) : 0;
2022-09-23 00:49:42 -04:00
var oldBeat = currentBeat;
var oldStep = currentStep;
Conductor.songPosition = songPosition;
2022-09-26 17:18:19 -04:00
// Conductor.bpm = Conductor.getLastBPMChange().bpm;
2022-09-23 00:49:42 -04:00
currentTimeChange = timeChanges[0];
2022-09-23 00:49:42 -04:00
for (i in 0...timeChanges.length)
{
if (songPosition >= timeChanges[i].timeStamp)
2022-09-23 00:49:42 -04:00
currentTimeChange = timeChanges[i];
if (songPosition < timeChanges[i].timeStamp)
2022-09-23 00:49:42 -04:00
break;
}
2022-09-26 17:18:19 -04:00
if (currentTimeChange == null && bpmOverride == null)
{
trace('WARNING: Conductor is broken, timeChanges is empty.');
}
2022-09-26 17:18:19 -04:00
else if (currentTimeChange != null)
{
currentStepTime = (currentTimeChange.beatTime * 4) + (songPosition - currentTimeChange.timeStamp) / stepCrochet;
currentStep = Math.floor(currentStepTime);
currentBeat = Math.floor(currentStep / 4);
}
2022-09-26 17:18:19 -04:00
else
{
// Assume a constant BPM equal to the forced value.
currentStepTime = (songPosition / stepCrochet);
currentStep = Math.floor(currentStepTime);
2022-09-26 17:18:19 -04:00
currentBeat = Math.floor(currentStep / 4);
}
2022-09-23 00:49:42 -04:00
// FlxSignals are really cool.
if (currentStep != oldStep)
stepHit.dispatch();
if (currentBeat != oldBeat)
beatHit.dispatch();
2022-09-22 06:34:03 -04:00
}
2020-10-04 20:53:49 -04:00
2022-09-26 17:18:19 -04:00
@:deprecated // Switch to TimeChanges instead.
public static function mapBPMChanges(song:SwagSong)
{
bpmChangeMap = [];
var curBPM:Float = song.bpm;
var totalSteps:Int = 0;
var totalPos:Float = 0;
for (i in 0...SongLoad.getSong().length)
{
if (SongLoad.getSong()[i].changeBPM && SongLoad.getSong()[i].bpm != curBPM)
{
curBPM = SongLoad.getSong()[i].bpm;
var event:BPMChangeEvent = {
stepTime: totalSteps,
songTime: totalPos,
bpm: curBPM
};
bpmChangeMap.push(event);
}
var deltaSteps:Int = SongLoad.getSong()[i].lengthInSteps;
totalSteps += deltaSteps;
totalPos += ((60 / curBPM) * 1000 / 4) * deltaSteps;
}
}
2022-09-22 06:34:03 -04:00
2022-12-08 19:33:47 -05:00
public static function mapTimeChanges(songTimeChanges:Array<SongTimeChange>)
2022-09-22 06:34:03 -04:00
{
timeChanges = [];
for (currentTimeChange in songTimeChanges)
2022-09-22 06:34:03 -04:00
{
timeChanges.push(currentTimeChange);
}
2022-09-23 00:49:42 -04:00
trace('Done mapping time changes: ' + timeChanges);
2022-09-23 00:49:42 -04:00
// Done.
}
/**
* Given a time in milliseconds, return a time in steps.
*/
public static function getTimeInSteps(ms:Float):Int
{
if (timeChanges.length == 0)
{
// Assume a constant BPM equal to the forced value.
return Math.floor(ms / stepCrochet);
}
else
{
var resultStep:Int = 0;
var lastTimeChange:SongTimeChange = timeChanges[0];
for (timeChange in timeChanges)
{
if (ms >= timeChange.timeStamp)
{
lastTimeChange = timeChange;
resultStep = lastTimeChange.beatTime * 4;
2022-09-23 00:49:42 -04:00
}
else
{
// This time change is after the requested time.
break;
2022-09-23 00:49:42 -04:00
}
}
2022-09-22 06:34:03 -04:00
resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepCrochet);
return resultStep;
}
2022-09-22 06:34:03 -04:00
}
2020-10-03 02:50:15 -04:00
}