From 3d99cd19855d20ff0251c6472125fd7ecb77cdfb Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:19:22 +0700 Subject: [PATCH 1/4] Continuous Tempo Change --- source/funkin/backend/system/Conductor.hx | 547 +++++++++++++--------- source/funkin/backend/utils/CoolUtil.hx | 2 +- 2 files changed, 339 insertions(+), 210 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 7ca9858f..1a0a5111 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -1,17 +1,24 @@ package funkin.backend.system; -import funkin.backend.chart.ChartData; import flixel.FlxState; -import funkin.backend.system.interfaces.IBeatReceiver; import flixel.util.FlxSignal.FlxTypedSignal; +import funkin.backend.chart.ChartData; +import funkin.backend.system.interfaces.IBeatReceiver; @:structInit -class BPMChangeEvent { - public var stepTime:Float; +class BPMChangeEvent +{ public var songTime:Float; public var bpm:Float; - public var beatsPerMeasure:Float; - public var stepsPerBeat:Int; + public var beatsPerMeasure:Float = 4; + public var stepsPerBeat:Int = 4; + + public var endSongTime:Float = 0; + public var continuous:Bool = false; + + public var stepTime:Float; + public var beatTime:Float; + public var measureTime:Float; } final class Conductor @@ -22,41 +29,15 @@ final class Conductor public static var onMeasureHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onBeatHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onStepHit:FlxTypedSignalVoid> = new FlxTypedSignal(); - public static var onBPMChange:FlxTypedSignalVoid> = new FlxTypedSignal(); + public static var onBPMChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); public static var onTimeSignatureChange:FlxTypedSignal<(Float,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 mesure (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; - - /** * 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; } @@ -65,6 +46,60 @@ final 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):Null; + private static function get_curChange() + return bpmChangeMap[curChangeIndex]; + + /** + * Current BPM + */ + public static var bpm(get, never):Float; + private static function get_bpm() + return curChangeIndex == 0 ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex); + + /** + * Starting BPM + */ + public static var startingBPM(get, never):Float; + private static function get_startingBPM() + return bpmChangeMap.length == 0 ? 100 : 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 60000 / bpm; + + /** + * Current StepCrochet (time per step), in milliseconds. + */ + public static var stepCrochet(get, never):Float; + private static function get_stepCrochet() return crochet / stepsPerBeat; + + /** + * 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 ? 4 : bpmChangeMap[curChangeIndex].beatsPerMeasure; + + /** + * Number of steps per beat (bottom number in time signature). Defaults to 4. + */ + public static var stepsPerBeat(get, never):Float; + private static function get_stepsPerBeat() + return bpmChangeMap.length == 0 ? 4 : bpmChangeMap[curChangeIndex].stepsPerBeat; + /** * Current step */ @@ -80,7 +115,6 @@ final class Conductor */ public static var curMeasure:Int = 0; - /** * Current step, as a `Float` (ex: 4.94, instead of 4) */ @@ -103,89 +137,97 @@ final class Conductor /** * Array of all BPM changes that have been mapped. */ - public static var bpmChangeMap:Array = []; + public static var bpmChangeMap:Array; @:dox(hide) public function new() {} public static function reset() { songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0; - bpmChangeMap = []; - changeBPM(0, 4, 4); + 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, ?endTime:Null):BPMChangeEvent { + if (bpm == curChange.bpm) return curChange; + + var beatTime:Float, measureTime:Float, stepTime:Float; + if (curChange.continuous) { + beatTime = curChange.beatTime + (curChange.endSongTime - curChange.songTime) * (bpm - curChange.bpm) / Math.log(bpm / curChange.bpm) / 60000 + + (time - curChange.endSongTime) / (60000 / curChange.bpm); + + measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / beatsPerMeasure; + stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * stepsPerBeat; + } + else { + beatTime = curChange.beatTime + (time - curChange.songTime) / (60000 / curChange.bpm); + measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / beatsPerMeasure; + stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * stepsPerBeat; + } + + bpmChangeMap.push(curChange = { + songTime: time, + stepTime: stepTime, + beatTime: beatTime, + measureTime: measureTime, + bpm: bpm, + continuous: endTime is Float, + beatsPerMeasure: curChange.beatsPerMeasure, + stepsPerBeat: curChange.stepsPerBeat + }); + if (curChange.continuous) curChange.endSongTime = endTime; + 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, - beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4), - stepsPerBeat: song.meta.stepsPerBeat.getDefault(4) - } - ]; - + 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)) + }; + bpmChangeMap = [curChange]; if (song.events == null) return; - var curBPM:Float = song.meta.bpm; - var curBeatsPerMeasure:Float = song.meta.beatsPerMeasure.getDefault(4); - var curStepsPerBeat:Int = song.meta.stepsPerBeat.getDefault(4); - var songTime:Float = 0; - var stepTime:Float = 0; + // fix the sort first... + var events:Array = []; + for (e in song.events) if (e.params != null && (e.name == "BPM Change" || e.name == "Time Signature Change")) events.push(e); + events.sort(function(a, b) return Std.int(a.time - b.time)); - for(e in song.events) { - var name = e.name; - var params = e.params; - var eventTime = e.time; - if(params == null) continue; + for (e in events) { + var name = e.name, params = e.params, time = e.time; + if (name == "BPM Change" && params[0] is Float) + curChange = mapBPMChange(curChange, time, params[0], params[1]); + else if (name == "Time Signature Change") { + var beatsPerMeasure = params[0], stepsPerBeat = params[1]; + //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 (name == "BPM Change" && params[0] is Float) { - if (params[0] == curBPM) continue; - var steps = (eventTime - songTime) / ((60 / curBPM) * 1000 / stepsPerBeat); - stepTime += steps; - songTime = eventTime; - curBPM = params[0]; - - bpmChangeMap.push({ - stepTime: stepTime, - songTime: songTime, - bpm: curBPM, - beatsPerMeasure: curBeatsPerMeasure, // keep old beatsPerMeasure and stepsPerMeasure so shit doesnt break - stepsPerBeat: curStepsPerBeat - }); - } else if (name == "Time Signature Change") { - var newBeatsPerMeasure = params[0]; - var newStepsPerBeat = params[1]; - - if (newBeatsPerMeasure == curBeatsPerMeasure && newStepsPerBeat == curStepsPerBeat) continue; - - var steps = (eventTime - songTime) / ((60 / curBPM) * 1000 / stepsPerBeat); - stepTime += steps; - songTime = eventTime; - - curBeatsPerMeasure = newBeatsPerMeasure; - curStepsPerBeat = newStepsPerBeat; - - bpmChangeMap.push({ - stepTime: stepTime, - songTime: songTime, - bpm: curBPM, // keep old bpm so shit doesnt break - beatsPerMeasure: curBeatsPerMeasure, - stepsPerBeat: curStepsPerBeat - }); + if (curChange.songTime == time) { + curChange.beatsPerMeasure = beatsPerMeasure; + curChange.stepsPerBeat = stepsPerBeat; + } + else + curChange = mapBPMChange(curChange, time, curChange.bpm); + + curChange.stepTime = CoolUtil.floorInt(curChange.stepTime + .99998); + curChange.beatTime = CoolUtil.floorInt(curChange.beatTime + .99998); + curChange.measureTime = CoolUtil.floorInt(curChange.measureTime + .99998); } } - - // sort from early to last - bpmChangeMap.sort(function(a, b) return Std.int(a.songTime - b.songTime)); } private static var elapsed:Float; @@ -223,144 +265,231 @@ final class Conductor __updateSongPos(FlxG.elapsed); - if (bpm > 0) { - // Check for BPM change - __lastChange = { - stepTime: 0, - songTime: 0, - bpm: 0, - beatsPerMeasure: beatsPerMeasure, - stepsPerBeat: stepsPerBeat - }; + var oldStep = curStep, oldBeat = curBeat, oldMeasure = curMeasure, oldChangeIndex = curChangeIndex; - var currentPos = Conductor.songPosition; - - for (change in Conductor.bpmChangeMap) { - if (currentPos >= change.songTime) - __lastChange = change; - else - break; - } - - // Change BPM if necessary and check for time signature change - if ((__lastChange.bpm > 0 && bpm != __lastChange.bpm) || (__lastChange.beatsPerMeasure != beatsPerMeasure || __lastChange.stepsPerBeat != stepsPerBeat)) { - changeBPM(__lastChange.bpm, __lastChange.beatsPerMeasure, __lastChange.stepsPerBeat); - } - - curStepFloat = __lastChange.stepTime + ((currentPos - __lastChange.songTime) / Conductor.stepCrochet); - curBeatFloat = curStepFloat / stepsPerBeat; + if ((curChangeIndex = getTimeInChangeIndex(songPosition, curChangeIndex)) > 0) { + var change = curChange; + curBeatFloat = getTimeWithBPMInBeats(songPosition, curChangeIndex, getTimeWithIndexInBPM(songPosition, curChangeIndex)); + curMeasureFloat = change.measureTime + (curBeatFloat - change.beatTime) / beatsPerMeasure; + curStepFloat = change.stepTime + (curBeatFloat - change.beatTime) * stepsPerBeat; + } + else { + curBeatFloat = songPosition / (60000 / bpm); curMeasureFloat = curBeatFloat / beatsPerMeasure; + curStepFloat = curBeatFloat * stepsPerBeat; + } - 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 (curChangeIndex != oldChangeIndex) { + var prev = bpmChangeMap[oldChangeIndex]; + if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat) + onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); - if (curStep > oldStep) { - for(i in oldStep...curStep) { - onStepHit.dispatch(i+1); - } - } - 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 (curChange.bpm != prev.bpm) onBPMChange.dispatch(curChange.bpm, curChange.endSongTime); + } - if (FlxG.state is IBeatReceiver) { - var state = FlxG.state; - while(state != null) { - if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) { - var st = cast(state, IBeatReceiver); - if (curStep > oldStep) { - for(i in oldStep...curStep) { - st.stepHit(i+1); - } + 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 (curStep > oldStep) { + for(i in oldStep...curStep) { + onStepHit.dispatch(i+1); + } + } + 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 = cast(state, IBeatReceiver); + 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 (__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); - } + } + 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, newBeatsPerMeasure:Float = 4, newStepsPerBeat:Int = 4) - { - var timesignChange = (beatsPerMeasure != newBeatsPerMeasure || stepsPerBeat != newStepsPerBeat); - var bpmChange = (bpm != newBpm); - - beatsPerMeasure = newBeatsPerMeasure; - stepsPerBeat = newStepsPerBeat; - bpm = newBpm; - - crochet = (60 / bpm) * 1000; - stepCrochet = crochet / stepsPerBeat; - - if (timesignChange) onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); - if (bpmChange) 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; + return bpmChangeMap.length - 1; + } } - public static function getTimeForStep(step:Float) { - var bpmChange:BPMChangeEvent = { - stepTime: 0, - songTime: 0, - bpm: bpm, - beatsPerMeasure: beatsPerMeasure, - stepsPerBeat: stepsPerBeat - }; - - for(change in bpmChangeMap) - if (change.stepTime < step && change.stepTime >= bpmChange.stepTime) - bpmChange = change; - // possible break here - - return bpmChange.songTime + ((step - bpmChange.stepTime) * ((60 / bpmChange.bpm) * (1000 / bpmChange.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; + return bpmChangeMap.length - 1; + } } - public static function getStepForTime(time:Float) { - var bpmChange:BPMChangeEvent = { - stepTime: 0, - songTime: 0, - bpm: bpm, - beatsPerMeasure: beatsPerMeasure, - stepsPerBeat: stepsPerBeat - }; - - for(change in bpmChangeMap) - if (change.songTime < time && change.songTime >= bpmChange.songTime) - bpmChange = change; - // possible break here - - return bpmChange.stepTime + ((time - bpmChange.songTime) / ((60 / bpmChange.bpm) * (1000 / bpmChange.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; + 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; + 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].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 getBeatsWithIndexInBPM(beatTime:Float, index:Int):Float { + var bpmChange = bpmChangeMap[index]; + if (bpmChange.continuous && index > 0) { + var prevBPM = bpmChangeMap[index].bpm; + if (beatTime <= bpmChange.beatTime) return prevBPM; + + var endBeatTime = bpmChange.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM) / Math.log(bpmChange.bpm / prevBPM) / 60000; + if (beatTime < endBeatTime) return FlxMath.remapToRange(beatTime, bpmChange.beatTime, endBeatTime, prevBPM, bpmChange.bpm); + } + return bpmChange.bpm; + } + + public static function getTimeInBPM(time:Float):Float { + if (bpmChangeMap.length == 0) return 100; + return getTimeWithIndexInBPM(time, getTimeInChangeIndex(time)); + } + + public static function getTimeWithBPMInBeats(time:Float, index:Int, bpm:Float):Float { + var bpmChange = bpmChangeMap[index]; + if (bpmChange.continuous && time > bpmChange.songTime && index > 0) { + var prevBPM = bpmChangeMap[index].bpm; + if (time > bpmChange.endSongTime) + return bpmChange.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 60000 + + (time - bpmChange.endSongTime) / (60000 / bpm); + else + return bpmChange.beatTime + (time - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 60000; + } + else { + return bpmChange.beatTime + (time - bpmChange.songTime) / (60000 / bpm); + } + } + + public static function getTimeInSteps(time:Float):Float { + var index = getTimeInChangeIndex(time); + if (index == -1) return time / (15000 / 100); + else if (index == 0) return time / (60000 / getTimeInBPM(time)) * bpmChangeMap[index].stepsPerBeat; + else { + var change = bpmChangeMap[index]; + return change.stepTime + (getTimeWithBPMInBeats(time, index, getTimeWithIndexInBPM(time, index)) - change.beatTime) * change.stepsPerBeat; + } + } + + @:haxe.warning("-WDeprecated") + public static inline function getStepForTime(time:Float):Float return getTimeInSteps(time); + + public static function getTimeInBeats(time:Float):Float { + var index = getTimeInChangeIndex(time); + return index < 1 ? time / (60000 / getTimeInBPM(time)) : getTimeWithBPMInBeats(time, index, getTimeWithIndexInBPM(time, index)); + } + + public static function getBeatsWithBPMInTime(beatTime:Float, index:Int, bpm:Float):Float { + var bpmChange = bpmChangeMap[index]; + if (bpmChange.continuous && beatTime > bpmChange.beatTime && index > 0) { + var prevBPM = bpmChangeMap[index].bpm; + var time = bpmChange.songTime + (beatTime - bpmChange.beatTime) / (bpm - prevBPM) * Math.log(bpm / prevBPM) * 60000; + if (time > bpmChange.endSongTime) + return bpmChange.endSongTime + (beatTime - ( + bpmChange.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 60000 + )) * (60000 / bpm); + else + return time; + } + else { + return bpmChange.songTime + (beatTime - bpmChange.beatTime) * (60000 / bpm); + } + } + + public static function getStepsInTime(stepTime:Float):Float { + var index = getStepsInChangeIndex(stepTime); + if (index == -1) return stepTime * (15000 / 100); + else if (index == 0) return stepTime * (60000 / getTimeInBPM(0)) / bpmChangeMap[index].stepsPerBeat; + else { + var change = bpmChangeMap[index]; + var beatTime = change.beatTime + (stepTime - change.stepTime) / change.stepsPerBeat; + return getBeatsWithBPMInTime(beatTime, index, getBeatsWithIndexInBPM(beatTime, index)); + } + } + + @:haxe.warning("-WDeprecated") + public static inline function getTimeForStep(steps:Float):Float return getStepsInTime(steps); + + public static function getBeatsInTime(beatTime:Float):Float { + var index = getBeatsInChangeIndex(beatTime); + return index < 1 ? beatTime * (60000 / getTimeInBPM(0)) : getBeatsWithBPMInTime(beatTime, index, getBeatsWithIndexInBPM(beatTime, index)); + } 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 + (getTimeWithBPMInBeats(length, index, getTimeWithIndexInBPM(length, index)) - change.beatTime) / change.beatsPerMeasure; } -} +} \ No newline at end of file diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index aa659968..a9de75ca 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -403,7 +403,7 @@ class CoolUtil } var bpm:Null = Std.parseFloat(musicInfo["BPM"]).getDefault(DefaultBPM); - Conductor.changeBPM(bpm, beatsPerMeasure, stepsPerBeat); + Conductor.changeBPM(bpm, beatsPerMeasure, floorInt(stepsPerBeat)); } else Conductor.changeBPM(DefaultBPM); } From 56076db7323d66ef45db88bde169e97523d838ef Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Thu, 24 Oct 2024 06:05:55 +0700 Subject: [PATCH 2/4] Fix Conductor --- source/funkin/backend/system/Conductor.hx | 55 +++++++++++------------ source/funkin/game/PlayState.hx | 2 - 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 1a0a5111..c722c511 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -154,21 +154,21 @@ final class Conductor mapBPMChanges(SONG); } - private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float, ?endTime:Null):BPMChangeEvent { + private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float, ?endTime:Float, ?prevChange:BPMChangeEvent):BPMChangeEvent { if (bpm == curChange.bpm) return curChange; var beatTime:Float, measureTime:Float, stepTime:Float; if (curChange.continuous) { - beatTime = curChange.beatTime + (curChange.endSongTime - curChange.songTime) * (bpm - curChange.bpm) / Math.log(bpm / curChange.bpm) / 60000 + + beatTime = curChange.beatTime + (curChange.endSongTime - curChange.songTime) * (curChange.bpm - prevChange.bpm) / Math.log(curChange.bpm / prevChange.bpm) / 60000 + (time - curChange.endSongTime) / (60000 / curChange.bpm); - measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / beatsPerMeasure; - stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * stepsPerBeat; + measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / curChange.beatsPerMeasure; + stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * curChange.stepsPerBeat; } else { beatTime = curChange.beatTime + (time - curChange.songTime) / (60000 / curChange.bpm); - measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / beatsPerMeasure; - stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * stepsPerBeat; + measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / curChange.beatsPerMeasure; + stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * curChange.stepsPerBeat; } bpmChangeMap.push(curChange = { @@ -177,11 +177,10 @@ final class Conductor beatTime: beatTime, measureTime: measureTime, bpm: bpm, - continuous: endTime is Float, beatsPerMeasure: curChange.beatsPerMeasure, stepsPerBeat: curChange.stepsPerBeat }); - if (curChange.continuous) curChange.endSongTime = endTime; + if (curChange.continuous = (endTime is Float && endTime != 0)) curChange.endSongTime = endTime; return curChange; } @@ -207,21 +206,19 @@ final class Conductor for (e in song.events) if (e.params != null && (e.name == "BPM Change" || e.name == "Time Signature Change")) events.push(e); events.sort(function(a, b) return Std.int(a.time - b.time)); + var prevChange:BPMChangeEvent = null; for (e in events) { + if (bpmChangeMap.length > 3) prevChange = bpmChangeMap[bpmChangeMap.length - 2]; var name = e.name, params = e.params, time = e.time; if (name == "BPM Change" && params[0] is Float) - curChange = mapBPMChange(curChange, time, params[0], params[1]); + curChange = mapBPMChange(curChange, time, params[0], params[1], prevChange); else if (name == "Time Signature Change") { - var beatsPerMeasure = params[0], stepsPerBeat = params[1]; //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.beatsPerMeasure = beatsPerMeasure; - curChange.stepsPerBeat = stepsPerBeat; - } - else - curChange = mapBPMChange(curChange, time, curChange.bpm); + if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm, null, prevChange); + curChange.beatsPerMeasure = params[0]; + curChange.stepsPerBeat = params[1]; curChange.stepTime = CoolUtil.floorInt(curChange.stepTime + .99998); curChange.beatTime = CoolUtil.floorInt(curChange.beatTime + .99998); @@ -339,11 +336,11 @@ final class Conductor 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; + 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; + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].songTime > time) return i - 1; return bpmChangeMap.length - 1; } } @@ -351,11 +348,11 @@ final class Conductor 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; + 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; + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].stepTime > stepTime) return i - 1; return bpmChangeMap.length - 1; } } @@ -363,11 +360,11 @@ final class Conductor 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; + 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; + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].beatTime > beatTime) return i - 1; return bpmChangeMap.length - 1; } } @@ -375,11 +372,11 @@ final class Conductor 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; + 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; + for (i in index...bpmChangeMap.length) if (bpmChangeMap[i].measureTime > measureTime) return i - 1; return bpmChangeMap.length - 1; } } @@ -387,7 +384,7 @@ final class Conductor 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].bpm; + var prevBPM = bpmChangeMap[index - 1].bpm; if (time <= bpmChange.songTime) return prevBPM; var ratio = (time - bpmChange.songTime) / (bpmChange.endSongTime - bpmChange.songTime); @@ -399,7 +396,7 @@ final class Conductor public static function getBeatsWithIndexInBPM(beatTime:Float, index:Int):Float { var bpmChange = bpmChangeMap[index]; if (bpmChange.continuous && index > 0) { - var prevBPM = bpmChangeMap[index].bpm; + var prevBPM = bpmChangeMap[index - 1].bpm; if (beatTime <= bpmChange.beatTime) return prevBPM; var endBeatTime = bpmChange.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM) / Math.log(bpmChange.bpm / prevBPM) / 60000; @@ -416,7 +413,7 @@ final class Conductor public static function getTimeWithBPMInBeats(time:Float, index:Int, bpm:Float):Float { var bpmChange = bpmChangeMap[index]; if (bpmChange.continuous && time > bpmChange.songTime && index > 0) { - var prevBPM = bpmChangeMap[index].bpm; + var prevBPM = bpmChangeMap[index - 1].bpm; if (time > bpmChange.endSongTime) return bpmChange.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 60000 + (time - bpmChange.endSongTime) / (60000 / bpm); @@ -438,6 +435,7 @@ final class Conductor } } + @:noCompletion @:haxe.warning("-WDeprecated") public static inline function getStepForTime(time:Float):Float return getTimeInSteps(time); @@ -449,7 +447,7 @@ final class Conductor public static function getBeatsWithBPMInTime(beatTime:Float, index:Int, bpm:Float):Float { var bpmChange = bpmChangeMap[index]; if (bpmChange.continuous && beatTime > bpmChange.beatTime && index > 0) { - var prevBPM = bpmChangeMap[index].bpm; + var prevBPM = bpmChangeMap[index - 1].bpm; var time = bpmChange.songTime + (beatTime - bpmChange.beatTime) / (bpm - prevBPM) * Math.log(bpm / prevBPM) * 60000; if (time > bpmChange.endSongTime) return bpmChange.endSongTime + (beatTime - ( @@ -474,6 +472,7 @@ final class Conductor } } + @:noCompletion @:haxe.warning("-WDeprecated") public static inline function getTimeForStep(steps:Float):Float return getStepsInTime(steps); diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 17ed9bc0..c19c3ec1 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1002,8 +1002,6 @@ class PlayState extends MusicBeatState camZoomingInterval = cast songData.meta.beatsPerMeasure.getDefault(4); - Conductor.changeBPM(songData.meta.bpm, cast songData.meta.beatsPerMeasure.getDefault(4), cast songData.meta.stepsPerBeat.getDefault(4)); - curSong = songData.meta.name.toLowerCase(); inst = FlxG.sound.load(Paths.inst(SONG.meta.name, difficulty)); From daf3179f79278d69cf333318b71b81bda79b15fa Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:14:56 +0700 Subject: [PATCH 3/4] Conductor Final Continuous Tempo & Time Sigs --- .../charter/event-icons/BPM Change End.png | Bin 0 -> 215 bytes .../charter/event-icons/BPM Change Start.png | Bin 0 -> 204 bytes .../charter/event-icons/BPM Change.png | Bin 185 -> 186 bytes source/funkin/backend/system/Conductor.hx | 14 +++++-- source/funkin/backend/utils/CoolUtil.hx | 39 ++++++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 assets/images/editors/charter/event-icons/BPM Change End.png create mode 100644 assets/images/editors/charter/event-icons/BPM Change Start.png diff --git a/assets/images/editors/charter/event-icons/BPM Change End.png b/assets/images/editors/charter/event-icons/BPM Change End.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e19949b03b6e8b7506d5667a6adf0e807bd3e6 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJXMsm#F#`kNVGw3Kp1&dmC@2x& z6XFV_|NsBr_jmf=BYm3_SXed-W|@fs1^KQqvjHi_k|4ie28U-i(tw-@PZ!6Kid&`q zo?J&9c$jZ@hy4Hl@jyvOZRdgMO_MI}Z2a%baU!MJ;TFRLiS9E~igZ+NN^uuVoi4QF z_UbET+y5$shn9!hvV1?gRUt{+#l`2P_=7o>jM+c=6K62ZTX^%RKhO>aPgg&ebxsLQ E0CR9l0ssI2 literal 0 HcmV?d00001 diff --git a/assets/images/editors/charter/event-icons/BPM Change Start.png b/assets/images/editors/charter/event-icons/BPM Change Start.png new file mode 100644 index 0000000000000000000000000000000000000000..4011f562a415895872efde5165c1de32876780d0 GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJXMsm#F#`kNVGw3Kp1&dmC@31> z6XFV_|NsBr_jh{7-v2DsoAs=6fg;9|?0-(U6n^@zF1~BQ+l7q!6B~}P&M}@4a8#^wFKfi7kdU3XgA;e(4vq}ku42C^|BvrA t*?FZb`HV9?Zxlb(RH|d$*zolyqv}c~#@H$Kl|Zu@JYD@<);T3K0RSURL{R!~g&P`~FU6*(?ZTyRCER0#b}6L4Lsu4$p3+0XZ(7E{-7;w~`r{ z#1&YW-NjYeIh>D394aU@EMwd}XG+i15C@+0Jy~yar?WD?L hD5=CFU@CB!;lXB}=YjDsKP) delta 130 zcmdnRxRY^$UG;x3==(dJWwRgy14G2@v)_RfV@Z%-FoVOh8)-m}v!{z=NX4yW1|~5! zaW;d3lE9({qaH;baq(nvgVUZTBRpA;6qo@)qxV74#YQp;Y?~~s95NkOv?!=Kw=gbZ bOOjx?lFTzb+)Y0WXdHv5tDnm{r-UW|jwLKL diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index c722c511..8a9fe6db 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -216,7 +216,7 @@ final class Conductor //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, null, prevChange); + if (curChange.songTime != time) curChange = mapBPMChange(prevChange = curChange, time, curChange.bpm, null, prevChange); curChange.beatsPerMeasure = params[0]; curChange.stepsPerBeat = params[1]; @@ -278,10 +278,16 @@ final class Conductor if (curChangeIndex != oldChangeIndex) { var prev = bpmChangeMap[oldChangeIndex]; - if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat) - onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); + if (prev) { + if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat) + onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); - if (curChange.bpm != prev.bpm) onBPMChange.dispatch(curChange.bpm, curChange.endSongTime); + if (curChange.bpm != prev.bpm) onBPMChange.dispatch(curChange.bpm, curChange.endSongTime); + } + else { + onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); + onBPMChange.dispatch(curChange.bpm, curChange.endSongTime); + } } if (curStep != (curStep = CoolUtil.floorInt(curStepFloat))) { diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index a9de75ca..e6af4393 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -737,6 +737,15 @@ class CoolUtil @:noUsing public static inline function maxInt(p1:Int, p2:Int) return p1 < p2 ? p2 : p1; + /** + * Equivalent of `Math.min`, except doesn't require a Int -> Float -> Int conversion. + * @param p1 + * @param p2 + * @return return p1 > p2 ? p2 : p1 + */ + @:noUsing public static inline function minInt(p1:Int, p2:Int) + return p1 > p2 ? p2 : p1; + /** * Equivalent of `Math.floor`, except doesn't require a Int -> Float -> Int conversion. * @param e Value to get the floor of. @@ -778,6 +787,36 @@ class CoolUtil return file.file; } + public static inline function bound(Value:Float, Min:Float, Max:Float):Float { + #if cpp + var _hx_tmp1:Float = Value; + var _hx_tmp2:Float = Min; + var _hx_tmp3:Float = Max; + return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3); + #else + return (Value < Min) ? Min : (Value > Max) ? Max : Value; + #end + } + + public static inline function boundInt(Value:Int, Min:Int, Max:Int):Int { + #if cpp + var _hx_tmp1:Int = Value; + var _hx_tmp2:Int = Min; + var _hx_tmp3:Int = Max; + return untyped __cpp__("((({0}) < ({1})) ? ({1}) : (({0}) > ({2})) ? ({2}) : ({0}))", _hx_tmp1, _hx_tmp2, _hx_tmp3); + #else + return (Value < Min) ? Min : (Value > Max) ? Max : Value; + #end + } + + public static inline function boolToInt(b:Bool):Int { + #if cpp + return untyped __cpp__("(({0}) ? 1 : 0)", b); + #else + return b ? 1 : 0; + #end + } + /** * Converts a string of "1..3,5,7..9,8..5" into an array of numbers like [1,2,3,5,7,8,9,8,7,6,5] * @param input String to parse From b1e1ab18011b2078182963534b4e6d72620cfb8a Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Fri, 25 Oct 2024 21:25:48 +0700 Subject: [PATCH 4/4] Make Conductor relies on steps instead --- source/funkin/backend/system/Conductor.hx | 195 ++++++++++------------ 1 file changed, 89 insertions(+), 106 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 8a9fe6db..b869ec86 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -78,13 +78,13 @@ final class Conductor * 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 60000 / bpm; + 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 crochet / stepsPerBeat; + private static function get_stepCrochet() return 15000 / bpm; /** * Number of beats per mesure (top number in time signature). Defaults to 4. @@ -155,21 +155,15 @@ final class Conductor } private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float, ?endTime:Float, ?prevChange:BPMChangeEvent):BPMChangeEvent { - if (bpm == curChange.bpm) return curChange; - var beatTime:Float, measureTime:Float, stepTime:Float; - if (curChange.continuous) { - beatTime = curChange.beatTime + (curChange.endSongTime - curChange.songTime) * (curChange.bpm - prevChange.bpm) / Math.log(curChange.bpm / prevChange.bpm) / 60000 + - (time - curChange.endSongTime) / (60000 / curChange.bpm); - - measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / curChange.beatsPerMeasure; - stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * curChange.stepsPerBeat; - } - else { - beatTime = curChange.beatTime + (time - curChange.songTime) / (60000 / curChange.bpm); - measureTime = curChange.measureTime + (beatTime - curChange.beatTime) / curChange.beatsPerMeasure; - stepTime = curChange.stepTime + (beatTime - curChange.beatTime) * curChange.stepsPerBeat; - } + if (curChange.continuous) + stepTime = curChange.stepTime + (((curChange.endSongTime - curChange.songTime) * (curChange.bpm - prevChange.bpm)) + / Math.log(curChange.bpm / prevChange.bpm) + (time - curChange.endSongTime) * curChange.bpm) / 15000; + else + stepTime = curChange.stepTime + (time - 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, @@ -210,13 +204,13 @@ final class Conductor for (e in events) { if (bpmChangeMap.length > 3) prevChange = bpmChangeMap[bpmChangeMap.length - 2]; var name = e.name, params = e.params, time = e.time; - if (name == "BPM Change" && params[0] is Float) + if (name == "BPM Change" && params[0] is Float && curChange.bpm != params[0]) curChange = mapBPMChange(curChange, time, params[0], params[1], prevChange); 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(prevChange = curChange, time, curChange.bpm, null, prevChange); + if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm, null, prevChange); curChange.beatsPerMeasure = params[0]; curChange.stepsPerBeat = params[1]; @@ -254,6 +248,7 @@ final class Conductor reset(); } private static var __lastChange:BPMChangeEvent; + private static var __updateStep:Bool; private static var __updateBeat:Bool; private static var __updateMeasure:Bool; @@ -266,19 +261,16 @@ final class Conductor if ((curChangeIndex = getTimeInChangeIndex(songPosition, curChangeIndex)) > 0) { var change = curChange; - curBeatFloat = getTimeWithBPMInBeats(songPosition, curChangeIndex, getTimeWithIndexInBPM(songPosition, curChangeIndex)); - curMeasureFloat = change.measureTime + (curBeatFloat - change.beatTime) / beatsPerMeasure; - curStepFloat = change.stepTime + (curBeatFloat - change.beatTime) * stepsPerBeat; - } - else { - curBeatFloat = songPosition / (60000 / bpm); - curMeasureFloat = curBeatFloat / beatsPerMeasure; - curStepFloat = curBeatFloat * stepsPerBeat; + 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) { + if (prev != null) { if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat) onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); @@ -290,51 +282,43 @@ final class Conductor } } - 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 (__updateStep = (curStep != (curStep = CoolUtil.floorInt(curStepFloat)))) { + if (curStep > oldStep) for (i in oldStep...curStep) onStepHit.dispatch(i + 1); + else onStepHit.dispatch(curStep); + } - if (curStep > oldStep) { - for(i in oldStep...curStep) { - onStepHit.dispatch(i+1); - } - } - 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 (__updateBeat = (curBeat != (curBeat = CoolUtil.floorInt(curBeatFloat)))) { + if (curBeat > oldBeat) for (i in oldBeat...curBeat) onBeatHit.dispatch(i + 1); + else onBeatHit.dispatch(curBeat); + } - if (FlxG.state is IBeatReceiver) { - var state = FlxG.state; - while(state != null) { - if (state is IBeatReceiver && (state.subState == null || state.persistentUpdate)) { - var st = cast(state, IBeatReceiver); - 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); - } - } + if (__updateMeasure = (curMeasure != (curMeasure = CoolUtil.floorInt(curMeasureFloat)))) { + if (curMeasure > oldMeasure) for (i in oldMeasure...curMeasure) onMeasureHit.dispatch(i + 1); + else onMeasureHit.dispatch(curMeasure); + } + + 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); } - state = state.subState; } + state = state.subState; } } } @@ -399,14 +383,14 @@ final class Conductor return bpmChange.bpm; } - public static function getBeatsWithIndexInBPM(beatTime:Float, index:Int):Float { + 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 (beatTime <= bpmChange.beatTime) return prevBPM; + if (stepTime <= bpmChange.stepTime) return prevBPM; - var endBeatTime = bpmChange.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM) / Math.log(bpmChange.bpm / prevBPM) / 60000; - if (beatTime < endBeatTime) return FlxMath.remapToRange(beatTime, bpmChange.beatTime, endBeatTime, prevBPM, bpmChange.bpm); + 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; } @@ -416,77 +400,76 @@ final class Conductor return getTimeWithIndexInBPM(time, getTimeInChangeIndex(time)); } - public static function getTimeWithBPMInBeats(time:Float, index:Int, bpm:Float):Float { + 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.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 60000 + - (time - bpmChange.endSongTime) / (60000 / bpm); + return bpmChange.stepTime + (((bpmChange.endSongTime - bpmChange.songTime) * (bpmChange.bpm - prevBPM)) + / Math.log(bpmChange.bpm / prevBPM) + (time - bpmChange.endSongTime) * bpm) / 15000; else - return bpmChange.beatTime + (time - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 60000; + return bpmChange.stepTime + (time - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 15000; } else { - return bpmChange.beatTime + (time - bpmChange.songTime) / (60000 / bpm); + return bpmChange.stepTime + (time - bpmChange.songTime) / (15000 / bpm); } } - public static function getTimeInSteps(time:Float):Float { - var index = getTimeInChangeIndex(time); - if (index == -1) return time / (15000 / 100); - else if (index == 0) return time / (60000 / getTimeInBPM(time)) * bpmChangeMap[index].stepsPerBeat; + public static function getTimeInBeats(time:Float, from:Int = 0):Float { + var index = getTimeInChangeIndex(time, from); + if (index == -1) return time / (60000 / 100); + else if (index == 0) return time / (15000 / bpmChangeMap[index].bpm) / bpmChangeMap[index].stepsPerBeat; else { var change = bpmChangeMap[index]; - return change.stepTime + (getTimeWithBPMInBeats(time, index, getTimeWithIndexInBPM(time, index)) - change.beatTime) * change.stepsPerBeat; + 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 getTimeInBeats(time:Float):Float { - var index = getTimeInChangeIndex(time); - return index < 1 ? time / (60000 / getTimeInBPM(time)) : getTimeWithBPMInBeats(time, index, getTimeWithIndexInBPM(time, index)); - } - - public static function getBeatsWithBPMInTime(beatTime:Float, index:Int, bpm:Float):Float { + public static function getStepsWithBPMInTime(stepTime:Float, index:Int, bpm:Float):Float { var bpmChange = bpmChangeMap[index]; - if (bpmChange.continuous && beatTime > bpmChange.beatTime && index > 0) { + if (bpmChange.continuous && stepTime > bpmChange.stepTime && index > 0) { var prevBPM = bpmChangeMap[index - 1].bpm; - var time = bpmChange.songTime + (beatTime - bpmChange.beatTime) / (bpm - prevBPM) * Math.log(bpm / prevBPM) * 60000; + var time = bpmChange.songTime + (stepTime - bpmChange.stepTime) / (bpm - prevBPM) * Math.log(bpm / prevBPM) * 15000; if (time > bpmChange.endSongTime) - return bpmChange.endSongTime + (beatTime - ( - bpmChange.beatTime + (bpmChange.endSongTime - bpmChange.songTime) * (bpm - prevBPM) / Math.log(bpm / prevBPM) / 60000 - )) * (60000 / bpm); + 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 + (beatTime - bpmChange.beatTime) * (60000 / bpm); + return bpmChange.songTime + (stepTime - bpmChange.stepTime) * (15000 / bpm); } } - public static function getStepsInTime(stepTime:Float):Float { - var index = getStepsInChangeIndex(stepTime); - if (index == -1) return stepTime * (15000 / 100); - else if (index == 0) return stepTime * (60000 / getTimeInBPM(0)) / bpmChangeMap[index].stepsPerBeat; + public static function getBeatsInTime(beatTime:Float, from:Int = 0):Float { + var index = getStepsInChangeIndex(beatTime, from); + if (index == -1) return beatTime * (60000 / 100); + else if (index == 0) return beatTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat; else { var change = bpmChangeMap[index]; - var beatTime = change.beatTime + (stepTime - change.stepTime) / change.stepsPerBeat; - return getBeatsWithBPMInTime(beatTime, index, getBeatsWithIndexInBPM(beatTime, 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 function getBeatsInTime(beatTime:Float):Float { - var index = getBeatsInChangeIndex(beatTime); - return index < 1 ? beatTime * (60000 / getTimeInBPM(0)) : getBeatsWithBPMInTime(beatTime, index, getBeatsWithIndexInBPM(beatTime, index)); - } - public static inline function getMeasureLength() return stepsPerBeat * beatsPerMeasure; @@ -495,6 +478,6 @@ final class Conductor var length = FlxG.sound.music.length; var index = getTimeInChangeIndex(length, bpmChangeMap.length - 1); var change = bpmChangeMap[index]; - return change.measureTime + (getTimeWithBPMInBeats(length, index, getTimeWithIndexInBPM(length, index)) - change.beatTime) / change.beatsPerMeasure; + return change.measureTime + (getTimeInBeats(length, index) - change.beatTime) / change.beatsPerMeasure; } } \ No newline at end of file