From 2f6207b11bc72c9522b467e537907243e429515f Mon Sep 17 00:00:00 2001 From: TheZoroForce240 <86524550+TheZoroForce240@users.noreply.github.com> Date: Sat, 11 Jan 2025 19:16:46 +0000 Subject: [PATCH] fixing beat and measure lines with time sig changes (#2) --- source/funkin/backend/system/Conductor.hx | 14 +- source/funkin/editors/charter/Charter.hx | 7 +- .../editors/charter/CharterBackdropGroup.hx | 243 ++++++++++++++---- 3 files changed, 203 insertions(+), 61 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index b869ec86..2fbc6dfe 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -192,6 +192,7 @@ final class Conductor beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4), stepsPerBeat: CoolUtil.floorInt(song.meta.stepsPerBeat.getDefault(4)) }; + curChangeIndex = 0; bpmChangeMap = [curChange]; if (song.events == null) return; @@ -450,8 +451,19 @@ final class Conductor } } + public static function getMeasuresInTime(measureTime:Float, from:Int = 0):Float { + var index = getMeasuresInChangeIndex(measureTime, from); + if (index == -1) return measureTime * (60000 / 100 / 4); + 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 = getStepsInChangeIndex(beatTime, from); + var index = getBeatsInChangeIndex(beatTime, from); if (index == -1) return beatTime * (60000 / 100); else if (index == 0) return beatTime * (15000 / bpmChangeMap[index].bpm) * bpmChangeMap[index].stepsPerBeat; else { diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 4ec2f299..3760b25a 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -16,6 +16,7 @@ import flixel.input.keyboard.FlxKey; import flixel.sound.FlxSound; import flixel.math.FlxPoint; import funkin.editors.charter.CharterBackdropGroup.CharterBackdropDummy; +import funkin.editors.charter.CharterBackdropGroup.CharterGridSeperatorBase; import funkin.backend.system.Conductor; import funkin.backend.chart.*; import funkin.backend.chart.ChartData; @@ -622,6 +623,8 @@ class Charter extends UIState { gridBackdrops.bottomLimitY = __endStep * 40; eventsBackdrop.bottomSeparator.y = gridBackdrops.bottomLimitY-2; + CharterGridSeperatorBase.lastConductorSprY = Math.NEGATIVE_INFINITY; + updateWaveforms(); } @@ -1631,11 +1634,9 @@ class Charter extends UIState { } function _view_showeventSecSeparator(t) { t.icon = (Options.charterShowSections = !Options.charterShowSections) ? 1 : 0; - eventsBackdrop.eventSecSeparator.visible = gridBackdrops.sectionsVisible = Options.charterShowSections; } function _view_showeventBeatSeparator(t) { t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0; - eventsBackdrop.eventBeatSeparator.visible = gridBackdrops.beatsVisible = Options.charterShowBeats; } function _view_switchWaveformDetail(t) { t.icon = (Options.charterLowDetailWaveforms = !Options.charterLowDetailWaveforms) ? 1 : 0; @@ -1837,8 +1838,8 @@ class Charter extends UIState { public function updateBPMEvents() { buildEvents(); - Conductor.mapBPMChanges(PlayState.SONG); Conductor.changeBPM(PlayState.SONG.meta.bpm, cast PlayState.SONG.meta.beatsPerMeasure.getDefault(4), cast PlayState.SONG.meta.stepsPerBeat.getDefault(4)); + Conductor.mapBPMChanges(PlayState.SONG); refreshBPMSensitive(); } diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx index 21eeb90f..ac58a444 100644 --- a/source/funkin/editors/charter/CharterBackdropGroup.hx +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -12,8 +12,6 @@ class CharterBackdropGroup extends FlxTypedGroup { public var conductorSprY:Float = 0; public var bottomLimitY:Float = 0; - public var sectionsVisible:Bool = true; - public var beatsVisible:Bool = true; // Just here so you can update display sprites all dat and above public var strumlinesAmount:Int = 0; @@ -61,8 +59,6 @@ class CharterBackdropGroup extends FlxTypedGroup { grid.conductorFollowerSpr.y = conductorSprY; grid.bottomSeparator.y = (grid.bottomLimit.y = bottomLimitY)-2; - grid.sectionSeparator.visible = sectionsVisible; - grid.beatSeparator.visible = beatsVisible; grid.waveformSprite.shader = strumLine.waveformShader; @@ -119,8 +115,7 @@ class CharterBackdrop extends FlxTypedGroup { public var waveformSprite:FlxSprite; public var conductorFollowerSpr:FlxSprite; - public var beatSeparator:FlxBackdrop; - public var sectionSeparator:FlxBackdrop; + public var beatSeparator:CharterGridSeperator; public var notesGroup:FlxTypedGroup = new FlxTypedGroup(); public var strumLine:CharterStrumline; @@ -136,23 +131,14 @@ class CharterBackdrop extends FlxTypedGroup { waveformSprite.updateHitbox(); add(waveformSprite); - sectionSeparator = new FlxBackdrop(null, Y, 0, 0); - sectionSeparator.y = -2; - sectionSeparator.visible = Options.charterShowSections; - - beatSeparator = new FlxBackdrop(null, Y, 0, 0); - beatSeparator.y = -1; - beatSeparator.visible = Options.charterShowBeats; - - for(sep in [sectionSeparator, beatSeparator]) { - sep.makeSolid(1, 1, -1); - sep.alpha = 0.5; - sep.scrollFactor.set(1, 1); - sep.scale.set((4 * 40), sep == sectionSeparator ? 4 : 2); - sep.updateHitbox(); - } + beatSeparator = new CharterGridSeperator(); + beatSeparator.makeSolid(1, 1, -1); + beatSeparator.alpha = 0.5; + beatSeparator.scrollFactor.set(1, 1); + beatSeparator.scale.set((4 * 40), 2); + beatSeparator.updateHitbox(); add(beatSeparator); - add(sectionSeparator); + add(notesGroup); bottomSeparator = new FlxSprite(0,-2); @@ -200,15 +186,12 @@ class CharterBackdrop extends FlxTypedGroup { alpha = strumLine.strumLine.visible ? 0.9 : 0.4; } else alpha = 0.9; - for (spr in [gridBackDrop, sectionSeparator, beatSeparator, topLimit, bottomLimit, + for (spr in [gridBackDrop, beatSeparator, topLimit, bottomLimit, topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite]) { spr.x = x; if (spr != waveformSprite) spr.alpha = alpha; spr.cameras = this.cameras; } - sectionSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1; - beatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; - topLimit.scale.set(4 * 40, Math.ceil(FlxG.height / cameras[0].zoom)); topLimit.updateHitbox(); topLimit.y = -topLimit.height; @@ -237,6 +220,159 @@ class CharterBackdrop extends FlxTypedGroup { } } +class CharterGridSeperatorBase extends FlxSprite { + + private static var minStep:Float = 0; + private static var maxStep:Float = 0; + + private static var minBeat:Float = 0; + private static var maxBeat:Float = 0; + + private static var minMeasure:Float = 0; + private static var maxMeasure:Float = 0; + + private static var lastMinBeat:Float = -1; + private static var lastMaxBeat:Float = -1; + + private static var lastMinMeasure:Float = -1; + private static var lastMaxMeasure:Float = -1; + + public static var lastConductorSprY:Float = Math.NEGATIVE_INFINITY; + + private static var beatStepTimes:Array = []; + private static var measureStepTimes:Array = []; + private static var timeSignatureChangeGaps:Array = []; + + private function recalculateBeats() { + var conductorSprY = Charter.instance.gridBackdrops.conductorSprY; + if (conductorSprY == lastConductorSprY) return; + + var zoomOffset = ((FlxG.height * (1/cameras[0].zoom)) * 0.5); + + minStep = (conductorSprY - zoomOffset)/40; + maxStep = (conductorSprY + zoomOffset)/40; + + var minTime:Float = Conductor.getStepsInTime(minStep); + var maxTime:Float = Conductor.getStepsInTime(maxStep); + + var minBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(minTime)]; + var maxBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(maxTime)]; + + minBeat = Conductor.getTimeInBeats(minTime); + maxBeat = Conductor.getTimeInBeats(maxTime); + + minMeasure = minBpmChange.measureTime + (minBeat - minBpmChange.beatTime) / minBpmChange.beatsPerMeasure; + maxMeasure = maxBpmChange.measureTime + (maxBeat - maxBpmChange.beatTime) / maxBpmChange.beatsPerMeasure; + + //cap out the beats/measures at the end of the song + var endTime = Conductor.getStepsInTime(Charter.instance.__endStep); + var endBeat = Conductor.getTimeInBeats(endTime); + var endBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(endTime)]; + var endMeasure = endBpmChange.measureTime + (endBeat - endBpmChange.beatTime) / endBpmChange.beatsPerMeasure; + + if (maxBeat > endBeat) maxBeat = endBeat; + if (maxMeasure > endMeasure) maxMeasure = endMeasure; + if (minMeasure < 0) minMeasure = 0; + if (minBeat < 0) minBeat = 0; + + //only calculate if needed + if ((minBeat != lastMinBeat) || (maxBeat != lastMaxBeat) || (minMeasure != lastMinMeasure) || (maxMeasure != lastMaxMeasure) || lastConductorSprY == Math.NEGATIVE_INFINITY) { + calculateTimeSignatureGaps(); + calculateStepTimes(); + lastMinBeat = minBeat; + lastMaxBeat = maxBeat; + lastMinMeasure = minMeasure; + lastMaxMeasure = maxMeasure; + } + + lastConductorSprY = conductorSprY; + } + + private inline function calculateTimeSignatureGaps() { + //for time signatures that start mid step + timeSignatureChangeGaps.splice(0, timeSignatureChangeGaps.length); + for (i => change in Conductor.bpmChangeMap) { + if (change.stepTime >= minStep && change.stepTime <= maxStep) { + //get step while ignoring the current change + var index = CoolUtil.boundInt(i-1, 0, Conductor.bpmChangeMap.length - 1); + var step = Conductor.getTimeWithBPMInSteps(change.songTime, index, Conductor.getTimeWithIndexInBPM(change.songTime, index)); + + if (Math.ceil(step) - step > 0) { //mid step change + timeSignatureChangeGaps.push(step); + } + } + } + } + + private inline function calculateStepTimes() { + beatStepTimes.splice(0, beatStepTimes.length); + for (i in Math.floor(minBeat)...Math.ceil(maxBeat)) { + beatStepTimes.push(Conductor.getTimeInSteps(Conductor.getBeatsInTime(i))); + } + measureStepTimes.splice(0, measureStepTimes.length); + for (i in Math.floor(minMeasure)...Math.ceil(maxMeasure)) { + measureStepTimes.push(Conductor.getTimeInSteps(Conductor.getMeasuresInTime(i))); + } + } + + override public function draw() { + + //should only need to recalculate once per frame and will be shared across each instance + recalculateBeats(); + + drawTimeSignatureChangeGaps(); + + if (Options.charterShowBeats) drawBeats(); + if (Options.charterShowSections) drawMeasures(); + } + + private function drawBeats(offset:Float = 0.0) { + for (i in beatStepTimes) { + y = (i*40)+offset; + super.draw(); + } + } + private function drawMeasures(offset:Float = 0.0) { + for (i in measureStepTimes) { + y = (i*40)+offset; + super.draw(); + } + } + private function drawTimeSignatureChangeGaps() { + if (timeSignatureChangeGaps.length == 0) return; + var prevColor = color; + var prevBlend = blend; + + color = 0xFF888888; + blend = MULTIPLY; + + for (step in timeSignatureChangeGaps) { + y = step*40; + var diff = Math.ceil(step) - step; + scale.y = diff*40; + updateHitbox(); + + super.draw(); + } + + color = prevColor; + blend = prevBlend; + } +} + +class CharterGridSeperator extends CharterGridSeperatorBase { + override private function drawBeats(offset:Float = 0.0) { + scale.y = 2; + updateHitbox(); + super.drawBeats(-2); + } + override private function drawMeasures(offset:Float = 0.0) { + scale.y = 4; + updateHitbox(); + super.drawMeasures(-3); + } +} + class CharterBackdropDummy extends UISprite { var parent:CharterBackdropGroup; public function new(parent:CharterBackdropGroup) { @@ -258,8 +394,7 @@ class CharterBackdropDummy extends UISprite { } class EventBackdrop extends FlxBackdrop { - public var eventBeatSeparator:FlxBackdrop; - public var eventSecSeparator:FlxBackdrop; + public var eventBeatSeparator:CharterEventGridSeperator; public var topSeparator:FlxSprite; public var bottomSeparator:FlxSprite; @@ -269,25 +404,10 @@ class EventBackdrop extends FlxBackdrop { alpha = 0.9; // Separators - eventSecSeparator = new FlxBackdrop(null, Y, 0, 0); - eventSecSeparator.y = -2; - eventSecSeparator.visible = Options.charterShowSections; - - eventBeatSeparator = new FlxBackdrop(null, Y, 0, 0); - eventBeatSeparator.y = -1; - eventBeatSeparator.visible = Options.charterShowBeats; - - for(sep in [eventSecSeparator, eventBeatSeparator]) { - sep.makeSolid(1, 1, -1); - sep.alpha = 0.5; - sep.scrollFactor.set(1, 1); - } - - eventSecSeparator.scale.set(20, 4); - eventSecSeparator.updateHitbox(); - - eventBeatSeparator.scale.set(10, 2); - eventBeatSeparator.updateHitbox(); + eventBeatSeparator = new CharterEventGridSeperator(); + eventBeatSeparator.makeSolid(1, 1, -1); + eventBeatSeparator.alpha = 0.5; + eventBeatSeparator.scrollFactor.set(1, 1); bottomSeparator = new FlxSprite(0,-2); bottomSeparator.makeSolid(1, 1, -1); @@ -308,23 +428,32 @@ class EventBackdrop extends FlxBackdrop { public override function draw() { super.draw(); - eventSecSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1; - eventBeatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1; - - eventSecSeparator.cameras = cameras; - eventSecSeparator.x = (x+width) - 20; - if (eventSecSeparator.visible) eventSecSeparator.draw(); - eventBeatSeparator.cameras = cameras; - eventBeatSeparator.x = (x+width) - 10; - if (eventBeatSeparator.visible) eventBeatSeparator.draw(); + eventBeatSeparator.xPos = x+width; + eventBeatSeparator.draw(); topSeparator.x = (x+width) - 20; topSeparator.cameras = this.cameras; - if (!eventSecSeparator.visible) topSeparator.draw(); + if (!Options.charterShowSections) topSeparator.draw(); bottomSeparator.x = (x+width) - 20; bottomSeparator.cameras = this.cameras; bottomSeparator.draw(); } +} +class CharterEventGridSeperator extends CharterGridSeperatorBase { + public var xPos:Float = 0.0; + override private function drawBeats(offset:Float = 0.0) { + scale.set(10, 2); + updateHitbox(); + x = xPos-10; + super.drawBeats(-2); + } + override private function drawMeasures(offset:Float = 0.0) { + scale.set(20, 4); + updateHitbox(); + x = xPos-20; + super.drawMeasures(-3); + } + override private function drawTimeSignatureChangeGaps() {} } \ No newline at end of file