Merge branch 'Time-Signature-Changes' into new-conductor
# Conflicts: # .gitignore # source/funkin/backend/FunkinSprite.hx # source/funkin/backend/chart/ChartData.hx # source/funkin/backend/chart/EventsData.hx # source/funkin/backend/system/Conductor.hx # source/funkin/backend/utils/CoolUtil.hx # source/funkin/editors/charter/Charter.hx # source/funkin/editors/charter/CharterEvent.hx # source/funkin/editors/charter/CharterEventScreen.hx # source/funkin/editors/charter/CharterMetaDataScreen.hx # source/funkin/editors/charter/SongCreationScreen.hx # source/funkin/game/PlayState.hx
This commit is contained in:
@@ -4,56 +4,58 @@ import flixel.FlxState;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import funkin.backend.chart.ChartData;
|
||||
import funkin.backend.system.interfaces.IBeatReceiver;
|
||||
import funkin.editors.charter.Charter;
|
||||
|
||||
typedef BPMChangeEvent =
|
||||
{
|
||||
var stepTime:Float;
|
||||
var songTime:Float;
|
||||
var bpm:Float;
|
||||
enum BeatType {
|
||||
BEAT;
|
||||
MEASURE;
|
||||
STEP;
|
||||
}
|
||||
|
||||
class Conductor
|
||||
@:structInit
|
||||
class BPMChangeEvent
|
||||
{
|
||||
public var songTime:Float;
|
||||
public var bpm:Float;
|
||||
public var beatsPerMeasure:Float = 4;
|
||||
public var stepsPerBeat:Int = 4;
|
||||
|
||||
public var endSongTime:Float = 0;
|
||||
public var endStepTime:Float = 0;
|
||||
public var continuous:Bool = false;
|
||||
|
||||
public var stepTime:Float;
|
||||
public var beatTime:Float;
|
||||
public var measureTime:Float;
|
||||
}
|
||||
|
||||
final class Conductor
|
||||
{
|
||||
public static function getBeats(?every:BeatType, interval:Float, offset:Float = 0):Float {
|
||||
final beat = switch(every) {
|
||||
case MEASURE: curMeasureFloat;
|
||||
case STEP: curStepFloat;
|
||||
default: curBeatFloat;
|
||||
}
|
||||
if (interval <= 0) return beat - offset;
|
||||
else return Math.floor((beat - offset) / interval) * interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* FlxSignals
|
||||
*/
|
||||
public static var onMeasureHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
|
||||
public static var onBeatHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
|
||||
public static var onStepHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
|
||||
public static var onBPMChange:FlxTypedSignal<Float->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Current BPM
|
||||
*/
|
||||
public static var bpm:Float = 100;
|
||||
|
||||
/**
|
||||
* Current Crochet (time per beat), in milliseconds.
|
||||
*/
|
||||
public static var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds
|
||||
|
||||
/**
|
||||
* Current StepCrochet (time per step), in milliseconds.
|
||||
*/
|
||||
public static var stepCrochet:Float = crochet / 4; // steps in milliseconds
|
||||
|
||||
/**
|
||||
* Number of beats per measure (top number in time signature). Defaults to 4.
|
||||
*/
|
||||
public static var beatsPerMeasure:Float = 4;
|
||||
|
||||
/**
|
||||
* Number of steps per beat (bottom number in time signature). Defaults to 4.
|
||||
*/
|
||||
public static var stepsPerBeat:Int = 4;
|
||||
|
||||
public static var onBPMChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal();
|
||||
public static var onTimeSignatureChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Current position of the song, in milliseconds.
|
||||
*/
|
||||
public static var songPosition(get, default):Float;
|
||||
private static function get_songPosition() {
|
||||
if (songOffset != Options.songOffset) trace(songOffset = Options.songOffset);
|
||||
if (songOffset != Options.songOffset) songOffset = Options.songOffset;
|
||||
return songPosition - songOffset;
|
||||
}
|
||||
|
||||
@@ -62,6 +64,88 @@ class Conductor
|
||||
*/
|
||||
public static var songOffset:Float = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Current bpmChangeMap index
|
||||
*/
|
||||
public static var curChangeIndex:Int = 0;
|
||||
|
||||
/**
|
||||
* Current bpmChangeMap
|
||||
*/
|
||||
public static var curChange(get, never):BPMChangeEvent;
|
||||
private static var dummyChange:BPMChangeEvent = {bpm: 100, beatsPerMeasure: 4, stepsPerBeat: 4, songTime: 0, stepTime: 0, beatTime: 0, measureTime: 0};
|
||||
private static function get_curChange()
|
||||
return bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex];
|
||||
|
||||
/**
|
||||
* Current BPM
|
||||
*/
|
||||
public static var bpm(get, never):Float;
|
||||
private static function get_bpm()
|
||||
return (curChangeIndex == 0 || bpmChangeMap.length == 0) ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex);
|
||||
|
||||
/**
|
||||
* Starting BPM
|
||||
*/
|
||||
public static var startingBPM(get, never):Float;
|
||||
private static function get_startingBPM()
|
||||
return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[0]).bpm;
|
||||
|
||||
/**
|
||||
* Current Crochet (time per beat), in milliseconds.
|
||||
* It should be crotchet but ehhh, now it's there for backward compatibility.
|
||||
*/
|
||||
public static var crochet(get, never):Float;
|
||||
private static function get_crochet() return 15000 * stepsPerBeat / bpm;
|
||||
|
||||
/**
|
||||
* Current StepCrochet (time per step), in milliseconds.
|
||||
*/
|
||||
public static var stepCrochet(get, never):Float;
|
||||
private static function get_stepCrochet() return 15000 / bpm;
|
||||
|
||||
/**
|
||||
* Number of beats per mesure (top number in time signature). Defaults to 4.
|
||||
*/
|
||||
public static var beatsPerMeasure(get, never):Float;
|
||||
private static function get_beatsPerMeasure()
|
||||
return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).beatsPerMeasure;
|
||||
|
||||
/**
|
||||
* Number of steps per beat. Defaults to 4.
|
||||
* Not a divisor number for time signature, it does the complete opposite.
|
||||
* It's because CNE Conductor is based in sixteenth note instead of beat.
|
||||
*/
|
||||
public static var stepsPerBeat(get, never):Int;
|
||||
private static function get_stepsPerBeat()
|
||||
return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).stepsPerBeat;
|
||||
|
||||
/**
|
||||
* How much value notes to divide for beat (bottom or divisor number in time signature).
|
||||
* Only for a convinient way to access divisor instead of multiply by steps per beat.
|
||||
*/
|
||||
public static var denominator(get, never):Float;
|
||||
private static function get_denominator() return FlxMath.roundDecimal(16 / stepsPerBeat, 2);
|
||||
|
||||
/**
|
||||
* Last step from BPM Change
|
||||
*/
|
||||
public static var lastStepChange(get, never):Float;
|
||||
private static function get_lastStepChange() return curChange.stepTime;
|
||||
|
||||
/**
|
||||
* Last beat from BPM Change
|
||||
*/
|
||||
public static var lastBeatChange(get, never):Float;
|
||||
private static function get_lastBeatChange() return curChange.beatTime;
|
||||
|
||||
/**
|
||||
* Last measure from BPM Change
|
||||
*/
|
||||
public static var lastMeasureChange(get, never):Float;
|
||||
private static function get_lastMeasureChange() return curChange.measureTime;
|
||||
|
||||
/**
|
||||
* Current step
|
||||
*/
|
||||
@@ -77,7 +161,6 @@ class Conductor
|
||||
*/
|
||||
public static var curMeasure:Int = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Current step, as a `Float` (ex: 4.94, instead of 4)
|
||||
*/
|
||||
@@ -100,53 +183,143 @@ class Conductor
|
||||
/**
|
||||
* Array of all BPM changes that have been mapped.
|
||||
*/
|
||||
public static var bpmChangeMap:Array<BPMChangeEvent> = [];
|
||||
public static var bpmChangeMap:Array<BPMChangeEvent>;
|
||||
|
||||
/**
|
||||
* Array of all events that have been rejected by the Conductor.
|
||||
*/
|
||||
public static var invalidEvents:Array<ChartEvent> = [];
|
||||
|
||||
private static var validEventNames:Array<String> = ["BPM Change", "Time Signature Change", "Continuous BPM Change"];
|
||||
|
||||
@:dox(hide) public function new() {}
|
||||
|
||||
public static function reset() {
|
||||
songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0;
|
||||
bpmChangeMap = [];
|
||||
changeBPM(0);
|
||||
curChangeIndex = 0;
|
||||
changeBPM();
|
||||
}
|
||||
|
||||
public static function changeBPM(bpm:Float = 100, beatsPerMeasure:Float = 4, stepsPerBeat:Int = 4)
|
||||
bpmChangeMap = [{bpm: bpm, beatsPerMeasure: beatsPerMeasure, stepsPerBeat: stepsPerBeat, songTime: 0, stepTime: 0, beatTime: 0, measureTime: 0}];
|
||||
|
||||
public static function setupSong(SONG:ChartData) {
|
||||
reset();
|
||||
mapBPMChanges(SONG);
|
||||
changeBPM(SONG.meta.bpm, cast SONG.meta.beatsPerMeasure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4));
|
||||
}
|
||||
|
||||
private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float):BPMChangeEvent {
|
||||
var beatTime:Float, measureTime:Float, stepTime:Float;
|
||||
stepTime = (curChange.continuous ? curChange.endStepTime : curChange.stepTime) + (time - (curChange.continuous ? curChange.endSongTime : curChange.songTime)) / (15000 / curChange.bpm);
|
||||
|
||||
beatTime = curChange.beatTime + (stepTime - curChange.stepTime) / curChange.stepsPerBeat;
|
||||
measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / curChange.beatsPerMeasure;
|
||||
|
||||
bpmChangeMap.push(curChange = {
|
||||
songTime: time,
|
||||
stepTime: stepTime,
|
||||
beatTime: beatTime,
|
||||
measureTime: measureTime,
|
||||
bpm: bpm,
|
||||
beatsPerMeasure: curChange.beatsPerMeasure,
|
||||
stepsPerBeat: curChange.stepsPerBeat
|
||||
});
|
||||
return curChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps BPM changes from a song.
|
||||
* @param song Song to map BPM changes from.
|
||||
*/
|
||||
public static function mapBPMChanges(song:ChartData)
|
||||
{
|
||||
bpmChangeMap = [
|
||||
{
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: song.meta.bpm
|
||||
}
|
||||
];
|
||||
|
||||
public static function mapBPMChanges(song:ChartData) {
|
||||
var curChange:BPMChangeEvent = {
|
||||
songTime: 0,
|
||||
stepTime: 0,
|
||||
beatTime: 0,
|
||||
measureTime: 0,
|
||||
bpm: song.meta.bpm,
|
||||
beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4),
|
||||
stepsPerBeat: CoolUtil.floorInt(song.meta.stepsPerBeat.getDefault(4))
|
||||
};
|
||||
curChangeIndex = 0;
|
||||
bpmChangeMap = [curChange];
|
||||
invalidEvents = [];
|
||||
if (song.events == null) return;
|
||||
|
||||
var curBPM:Float = song.meta.bpm;
|
||||
var songTime:Float = 0;
|
||||
var stepTime:Float = 0;
|
||||
// fix the sort first...
|
||||
var events:Array<ChartEvent> = [for (e in song.events) if (e.params != null && validEventNames.contains(e.name)) e];
|
||||
events.sort((a, b) -> {
|
||||
if (MathUtil.equal(a.time, b.time)) return a.name == "Continuous BPM Change" ? 1 : -1;
|
||||
return Std.int(a.time - b.time);
|
||||
});
|
||||
|
||||
for(e in song.events) if (e.name == "BPM Change" && e.params != null && e.params[0] is Float) {
|
||||
if (e.params[0] == curBPM) continue;
|
||||
var steps = (e.time - songTime) / ((60 / curBPM) * 1000 / 4);
|
||||
stepTime += steps;
|
||||
songTime = e.time;
|
||||
curBPM = e.params[0];
|
||||
for (e in events) curChange = mapEvent(e, curChange);
|
||||
}
|
||||
|
||||
bpmChangeMap.push({
|
||||
stepTime: stepTime,
|
||||
songTime: songTime,
|
||||
bpm: curBPM
|
||||
private static function mapEvent(e:ChartEvent, curChange:BPMChangeEvent) {
|
||||
var name = e.name, params = e.params, time = e.time;
|
||||
if (curChange.continuous && MathUtil.lessThan(time, curChange.endSongTime)) { //ensure that you cannot place any conductor events during a continuous change
|
||||
invalidEvents.push(e);
|
||||
Logs.trace('Invalid Conductor event "${e.name}" at ${e.time} (Intersecting continuous change!)', WARNING);
|
||||
return curChange;
|
||||
}
|
||||
|
||||
if (name == "BPM Change" && params[0] is Float && curChange.bpm != params[0])
|
||||
curChange = mapBPMChange(curChange, time, params[0]);
|
||||
else if (name == "Time Signature Change") {
|
||||
//if (beatsPerMeasure == curChange.beatsPerMeasure && stepsPerBeat == curChange.stepsPerBeat) continue;
|
||||
/* TODO: make so time sigs doesnt stop the bpm change if its in the duration of bpm change */
|
||||
|
||||
if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm);
|
||||
curChange.beatsPerMeasure = params[0];
|
||||
|
||||
if (params[2]) curChange.stepsPerBeat = params[1];
|
||||
else curChange.stepsPerBeat = Math.floor(16 / params[1]); // convert from denominator to stepsPerBeat
|
||||
|
||||
curChange.stepTime = CoolUtil.floorInt(curChange.stepTime + .99998);
|
||||
curChange.beatTime = CoolUtil.floorInt(curChange.beatTime + .99998);
|
||||
curChange.measureTime = CoolUtil.floorInt(curChange.measureTime + .99998);
|
||||
}
|
||||
else if (name == "Continuous BPM Change") {
|
||||
var prevBPM = curChange.bpm;
|
||||
if (curChange.bpm == params[0]) {
|
||||
invalidEvents.push(e);
|
||||
return curChange; //DO NOT!!!!
|
||||
}
|
||||
|
||||
curChange = mapBPMChange(curChange, time, params[0]);
|
||||
curChange.endSongTime = time + (params[1]) / (curChange.bpm - prevBPM) * Math.log(curChange.bpm / prevBPM) * 15000;
|
||||
curChange.endStepTime = curChange.stepTime + params[1];
|
||||
curChange.continuous = true;
|
||||
}
|
||||
return curChange;
|
||||
}
|
||||
|
||||
public static function mapCharterBPMChanges(song:ChartData) {
|
||||
var curChange:BPMChangeEvent = {
|
||||
songTime: 0,
|
||||
stepTime: 0,
|
||||
beatTime: 0,
|
||||
measureTime: 0,
|
||||
bpm: song.meta.bpm,
|
||||
beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4),
|
||||
stepsPerBeat: CoolUtil.floorInt(song.meta.stepsPerBeat.getDefault(4))
|
||||
};
|
||||
curChangeIndex = 0;
|
||||
bpmChangeMap = [curChange];
|
||||
invalidEvents = [];
|
||||
|
||||
for (event in Charter.instance.eventsGroup.members) {
|
||||
event.events.sort((a, b) -> {
|
||||
if (MathUtil.equal(a.time, b.time)) return a.name == "Continuous BPM Change" ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
var eventTime = Conductor.getStepsInTime(event.step);
|
||||
for (e in event.events) {
|
||||
if (!Math.isNaN(eventTime)) e.time = eventTime;
|
||||
if (validEventNames.contains(e.name)) curChange = mapEvent(e, curChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,12 +337,10 @@ class Conductor
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastSongPos != (lastSongPos = FlxG.sound.music.time - songOffset)) {
|
||||
// update conductor
|
||||
if (lastSongPos != (lastSongPos = FlxG.sound.music.time - songOffset))
|
||||
songPosition = lastSongPos;
|
||||
} else {
|
||||
else
|
||||
songPosition += songOffset + elapsed * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
private static function onStateSwitch(newState:FlxState) {
|
||||
@@ -177,137 +348,247 @@ class Conductor
|
||||
reset();
|
||||
}
|
||||
private static var __lastChange:BPMChangeEvent;
|
||||
private static var __updateStep:Bool;
|
||||
private static var __updateBeat:Bool;
|
||||
private static var __updateMeasure:Bool;
|
||||
|
||||
private static function update() {
|
||||
if (FlxG.state != null && FlxG.state is MusicBeatState) {
|
||||
var state:MusicBeatState = cast FlxG.state;
|
||||
if(state.cancelConductorUpdate)
|
||||
return;
|
||||
}
|
||||
if (FlxG.state != null && FlxG.state is MusicBeatState && cast(FlxG.state, MusicBeatState).cancelConductorUpdate) return;
|
||||
|
||||
__updateSongPos(FlxG.elapsed);
|
||||
|
||||
if (bpm > 0) {
|
||||
// updates curBeat and stuff
|
||||
__lastChange = {
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: 0
|
||||
};
|
||||
for (change in Conductor.bpmChangeMap)
|
||||
{
|
||||
if (Conductor.songPosition >= change.songTime)
|
||||
__lastChange = change;
|
||||
var oldStep = curStep, oldBeat = curBeat, oldMeasure = curMeasure, oldChangeIndex = curChangeIndex;
|
||||
|
||||
if ((curChangeIndex = getTimeInChangeIndex(songPosition, curChangeIndex)) > 0) {
|
||||
var change = curChange;
|
||||
curStepFloat = getTimeWithBPMInSteps(songPosition, curChangeIndex, getTimeWithIndexInBPM(songPosition, curChangeIndex));
|
||||
curBeatFloat = change.beatTime + (curStepFloat - change.stepTime) / change.stepsPerBeat;
|
||||
curMeasureFloat = change.measureTime + (curBeatFloat - change.beatTime) / change.beatsPerMeasure;
|
||||
}
|
||||
else
|
||||
curMeasureFloat = (curBeatFloat = (curStepFloat = songPosition / stepCrochet) / stepsPerBeat) / beatsPerMeasure;
|
||||
|
||||
if (curChangeIndex != oldChangeIndex) {
|
||||
var prev = bpmChangeMap[oldChangeIndex];
|
||||
if (prev != null) {
|
||||
if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat)
|
||||
onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat);
|
||||
|
||||
if (curChange.bpm != prev.bpm) onBPMChange.dispatch(curChange.bpm, curChange.endSongTime);
|
||||
}
|
||||
else {
|
||||
onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat);
|
||||
onBPMChange.dispatch(curChange.bpm, curChange.endSongTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (__lastChange.bpm > 0 && bpm != __lastChange.bpm) changeBPM(__lastChange.bpm);
|
||||
if (__updateStep = (curStep != (curStep = CoolUtil.floorInt(curStepFloat)))) {
|
||||
if (curStep > oldStep) for (i in oldStep...curStep) onStepHit.dispatch(i + 1);
|
||||
else onStepHit.dispatch(curStep);
|
||||
}
|
||||
|
||||
curStepFloat = __lastChange.stepTime + ((Conductor.songPosition - __lastChange.songTime) / Conductor.stepCrochet);
|
||||
curBeatFloat = curStepFloat / stepsPerBeat;
|
||||
curMeasureFloat = curBeatFloat / beatsPerMeasure;
|
||||
if (__updateBeat = (curBeat != (curBeat = CoolUtil.floorInt(curBeatFloat)))) {
|
||||
if (curBeat > oldBeat) for (i in oldBeat...curBeat) onBeatHit.dispatch(i + 1);
|
||||
else onBeatHit.dispatch(curBeat);
|
||||
}
|
||||
|
||||
var oldStep = curStep;
|
||||
var oldBeat = curBeat;
|
||||
var oldMeasure = curMeasure;
|
||||
if (curStep != (curStep = CoolUtil.floorInt(curStepFloat))) {
|
||||
if (curStep < oldStep && oldStep - curStep < 2) return;
|
||||
// updates step
|
||||
__updateBeat = curBeat != (curBeat = CoolUtil.floorInt(curBeatFloat));
|
||||
__updateMeasure = __updateBeat && (curMeasure != (curMeasure = CoolUtil.floorInt(curMeasureFloat)));
|
||||
if (__updateMeasure = (curMeasure != (curMeasure = CoolUtil.floorInt(curMeasureFloat)))) {
|
||||
if (curMeasure > oldMeasure) for (i in oldMeasure...curMeasure) onMeasureHit.dispatch(i + 1);
|
||||
else onMeasureHit.dispatch(curMeasure);
|
||||
}
|
||||
|
||||
if (curStep > oldStep) {
|
||||
for(i in oldStep...curStep) {
|
||||
onStepHit.dispatch(i+1);
|
||||
if (__updateStep || __updateBeat || __updateMeasure) {
|
||||
var state = FlxG.state;
|
||||
while (state != null) {
|
||||
if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) {
|
||||
var st = cast(state, IBeatReceiver);
|
||||
|
||||
if (__updateStep) {
|
||||
if (curStep > oldStep) for (i in oldStep...curStep) st.stepHit(i + 1);
|
||||
else st.stepHit(curStep);
|
||||
}
|
||||
|
||||
if (__updateBeat) {
|
||||
if (curBeat > oldBeat) for (i in oldBeat...curBeat) st.beatHit(i + 1);
|
||||
else st.beatHit(curBeat);
|
||||
}
|
||||
|
||||
if (__updateMeasure) {
|
||||
if (curMeasure > oldMeasure) for (i in oldMeasure...curMeasure) st.measureHit(i + 1);
|
||||
else st.measureHit(curMeasure);
|
||||
}
|
||||
}
|
||||
if (__updateBeat && curBeat > oldBeat) {
|
||||
for(i in oldBeat...curBeat) {
|
||||
onBeatHit.dispatch(i+1);
|
||||
}
|
||||
}
|
||||
if (__updateMeasure && curMeasure > oldMeasure) {
|
||||
for(i in oldMeasure...curMeasure) {
|
||||
onMeasureHit.dispatch(i+1);
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.state is IBeatReceiver) {
|
||||
var state = FlxG.state;
|
||||
while(state != null) {
|
||||
if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) {
|
||||
var st:IBeatReceiver = cast state;
|
||||
if (curStep > oldStep) {
|
||||
for(i in oldStep...curStep) {
|
||||
st.stepHit(i+1);
|
||||
}
|
||||
}
|
||||
if (__updateBeat && curBeat > oldBeat) {
|
||||
for(i in oldBeat...curBeat) {
|
||||
st.beatHit(i+1);
|
||||
}
|
||||
}
|
||||
if (__updateMeasure && curMeasure > oldMeasure) {
|
||||
for(i in oldMeasure...curMeasure) {
|
||||
st.measureHit(i+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
state = state.subState;
|
||||
}
|
||||
}
|
||||
|
||||
state = state.subState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function changeBPM(newBpm:Float, beatsPerMeasure:Float = 4, stepsPerBeat:Int = 4)
|
||||
{
|
||||
bpm = newBpm;
|
||||
|
||||
crochet = ((60 / bpm) * 1000);
|
||||
stepCrochet = crochet / stepsPerBeat;
|
||||
|
||||
Conductor.beatsPerMeasure = beatsPerMeasure;
|
||||
Conductor.stepsPerBeat = stepsPerBeat;
|
||||
|
||||
onBPMChange.dispatch(bpm);
|
||||
public static function getTimeInChangeIndex(time:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].songTime > time) {
|
||||
while (--index > 0) if (time > bpmChangeMap[index].songTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].songTime > time) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeForStep(step:Float) {
|
||||
var bpmChange:BPMChangeEvent = {
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: bpm
|
||||
};
|
||||
|
||||
for(change in bpmChangeMap)
|
||||
if (change.stepTime < step && change.stepTime >= bpmChange.stepTime)
|
||||
bpmChange = change;
|
||||
|
||||
return bpmChange.songTime + ((step - bpmChange.stepTime) * ((60 / bpmChange.bpm) * (1000/stepsPerBeat)));
|
||||
public static function getStepsInChangeIndex(stepTime:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].stepTime > stepTime) {
|
||||
while (--index > 0) if (stepTime > bpmChangeMap[index].stepTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].stepTime > stepTime) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getStepForTime(time:Float) {
|
||||
var bpmChange:BPMChangeEvent = {
|
||||
stepTime: 0,
|
||||
songTime: 0,
|
||||
bpm: bpm
|
||||
};
|
||||
|
||||
for(change in bpmChangeMap)
|
||||
if (change.songTime < time && change.songTime >= bpmChange.songTime)
|
||||
bpmChange = change;
|
||||
|
||||
return bpmChange.stepTime + ((time - bpmChange.songTime) / ((60 / bpmChange.bpm) * (1000/stepsPerBeat)));
|
||||
public static function getBeatsInChangeIndex(beatTime:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].beatTime > beatTime) {
|
||||
while (--index > 0) if (beatTime > bpmChangeMap[index].beatTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].beatTime > beatTime) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMeasuresInChangeIndex(measureTime:Float, index:Int = 0):Int {
|
||||
if (bpmChangeMap.length < 2) return bpmChangeMap.length - 1;
|
||||
else if (bpmChangeMap[index = CoolUtil.boundInt(index, 0, bpmChangeMap.length - 1)].measureTime > measureTime) {
|
||||
while (--index > 0) if (measureTime > bpmChangeMap[index].measureTime) return index;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].measureTime > measureTime) return i - 1;
|
||||
return bpmChangeMap.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeWithIndexInBPM(time:Float, index:Int):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && time < bpmChange.endSongTime && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
if (time <= bpmChange.songTime) return prevBPM;
|
||||
|
||||
var ratio = (time - bpmChange.songTime) / (bpmChange.endSongTime - bpmChange.songTime);
|
||||
return Math.pow(prevBPM, 1 - ratio) * Math.pow(bpmChange.bpm, ratio);
|
||||
}
|
||||
return bpmChange.bpm;
|
||||
}
|
||||
|
||||
public static function getStepsWithIndexInBPM(stepTime:Float, index:Int):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
if (stepTime <= bpmChange.stepTime) return prevBPM;
|
||||
|
||||
var endStepTime = bpmChange.stepTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM) / Math.log(bpmChange.bpm / prevBPM) / 15000;
|
||||
if (stepTime < endStepTime) return FlxMath.remapToRange(stepTime, bpmChange.stepTime, endStepTime, prevBPM, bpmChange.bpm);
|
||||
}
|
||||
return bpmChange.bpm;
|
||||
}
|
||||
|
||||
public static function getTimeInBPM(time:Float):Float {
|
||||
if (bpmChangeMap.length == 0) return dummyChange.bpm;
|
||||
return getTimeWithIndexInBPM(time, getTimeInChangeIndex(time));
|
||||
}
|
||||
|
||||
public static function getTimeWithBPMInSteps(time:Float, index:Int, bpm:Float):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && time > bpmChange.songTime && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
if (time > bpmChange.endSongTime)
|
||||
return bpmChange.stepTime + (((bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM))
|
||||
/ Math.log(bpmChange.bpm / prevBPM) + (time - bpmChange.endSongTime) * bpm) / 15000;
|
||||
else
|
||||
return bpmChange.stepTime + (time - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 15000;
|
||||
}
|
||||
else {
|
||||
return bpmChange.stepTime + (time - bpmChange.songTime) / (15000 / bpm);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeInBeats(time:Float, from:Int = 0):Float {
|
||||
var index = getTimeInChangeIndex(time, from);
|
||||
if (index == -1) return time / (60000 / dummyChange.bpm);
|
||||
else if (index == 0) return time / (15000 / bpmChangeMap[index].bpm) / bpmChangeMap[index].stepsPerBeat;
|
||||
else {
|
||||
var change = bpmChangeMap[index];
|
||||
return change.beatTime + (getTimeWithBPMInSteps(time, index, getTimeWithIndexInBPM(time, index)) - change.stepTime) / change.stepsPerBeat;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTimeInSteps(time:Float, from:Int = 0):Float {
|
||||
var index = getTimeInChangeIndex(time, from);
|
||||
return index < 1 ? time / (15000 / getTimeInBPM(time)) : getTimeWithBPMInSteps(time, index, getTimeWithIndexInBPM(time, index));
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
@:haxe.warning("-WDeprecated")
|
||||
public static inline function getStepForTime(time:Float):Float return getTimeInSteps(time);
|
||||
|
||||
public static function getStepsWithBPMInTime(stepTime:Float, index:Int, bpm:Float):Float {
|
||||
var bpmChange = bpmChangeMap[index];
|
||||
if (bpmChange.continuous && stepTime > bpmChange.stepTime && index > 0) {
|
||||
var prevBPM = bpmChangeMap[index - 1].bpm;
|
||||
var time = bpmChange.songTime + (stepTime - bpmChange.stepTime) / (bpm - prevBPM) * Math.log(bpm / prevBPM) * 15000;
|
||||
if (time > bpmChange.endSongTime)
|
||||
return (15000 * (stepTime - bpmChange.stepTime) - ((bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM))
|
||||
/ Math.log(bpm / prevBPM)) / bpm + bpmChange.endSongTime;
|
||||
else
|
||||
return time;
|
||||
}
|
||||
else {
|
||||
return bpmChange.songTime + (stepTime - bpmChange.stepTime) * (15000 / bpm);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMeasuresInTime(measureTime:Float, from:Int = 0):Float {
|
||||
var index = getMeasuresInChangeIndex(measureTime, from);
|
||||
if (index == -1) return measureTime * (15000 / dummyChange.bpm);
|
||||
else if (index == 0) return measureTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat * bpmChangeMap[index].beatsPerMeasure;
|
||||
else {
|
||||
var change = bpmChangeMap[index];
|
||||
var stepTime = change.stepTime + (measureTime - change.measureTime) * change.stepsPerBeat * change.beatsPerMeasure;
|
||||
return getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index));
|
||||
}
|
||||
}
|
||||
|
||||
public static function getBeatsInTime(beatTime:Float, from:Int = 0):Float {
|
||||
var index = getBeatsInChangeIndex(beatTime, from);
|
||||
if (index == -1) return beatTime * (15000 / dummyChange.bpm);
|
||||
else if (index == 0) return beatTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat;
|
||||
else {
|
||||
var change = bpmChangeMap[index];
|
||||
var stepTime = change.stepTime + (beatTime - change.beatTime) * change.stepsPerBeat;
|
||||
return getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index));
|
||||
}
|
||||
}
|
||||
|
||||
public static function getStepsInTime(stepTime:Float, from:Int = 0):Float {
|
||||
var index = getStepsInChangeIndex(stepTime, from);
|
||||
return index < 1 ? stepTime * (15000 / bpmChangeMap[index].bpm) : getStepsWithBPMInTime(stepTime, index, getStepsWithIndexInBPM(stepTime, index));
|
||||
}
|
||||
|
||||
@:noCompletion
|
||||
@:haxe.warning("-WDeprecated")
|
||||
public static inline function getTimeForStep(steps:Float):Float return getStepsInTime(steps);
|
||||
|
||||
public static inline function getMeasureLength()
|
||||
return stepsPerBeat * beatsPerMeasure;
|
||||
|
||||
public static inline function getMeasuresLength() {
|
||||
if (FlxG.sound.music == null) return 0.0;
|
||||
return getStepForTime(FlxG.sound.music.length) / getMeasureLength();
|
||||
var length = FlxG.sound.music.length;
|
||||
var index = getTimeInChangeIndex(length, bpmChangeMap.length - 1);
|
||||
var change = bpmChangeMap[index];
|
||||
return change.measureTime + (getTimeInBeats(length, index) - change.beatTime) / change.beatsPerMeasure;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class ConductorInfo extends FramerateCategory {
|
||||
_text += '\n - ${Conductor.curStep} steps';
|
||||
_text += '\n - ${Conductor.curMeasure} measures';
|
||||
_text += '\nCurrent BPM: ${Conductor.bpm}';
|
||||
_text += '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.stepsPerBeat}';
|
||||
_text += '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.denominator}';
|
||||
|
||||
this.text.text = _text;
|
||||
super.__enterFrame(t);
|
||||
|
||||
Reference in New Issue
Block a user