From 8da7faa0b48accf542df0c4675cbfc6f37b17d3c Mon Sep 17 00:00:00 2001 From: NyxieFemboy Date: Sun, 6 Oct 2024 15:10:51 -0400 Subject: [PATCH 01/39] time Signature functionality wip --- assets/fonts/timeSig.ttf | Bin 0 -> 6927 bytes .../event-icons/Time Signature Change.png | Bin 0 -> 222 bytes source/funkin/backend/chart/Chart.hx | 1 + source/funkin/backend/chart/EventsData.hx | 3 +- .../funkin/backend/chart/FNFLegacyParser.hx | 15 ++- source/funkin/backend/system/Conductor.hx | 125 +++++++++++++----- source/funkin/editors/charter/CharterEvent.hx | 2 +- source/funkin/game/PlayState.hx | 1 + 8 files changed, 112 insertions(+), 35 deletions(-) create mode 100644 assets/fonts/timeSig.ttf create mode 100644 assets/images/editors/charter/event-icons/Time Signature Change.png diff --git a/assets/fonts/timeSig.ttf b/assets/fonts/timeSig.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9729308fcb613c44fb13cff7b9d9aace314d2469 GIT binary patch literal 6927 zcmeHLOK%%h6#i!Hq)v*{f?I-$L`OVIRUma9O%nlXp;fCuQBa&l4M@*6ZckYdAsY~>I!D}?nj&3{FNBuUhC-cSH(oN@t>srcLbUigDC z>;SI3_{(!EWvtbofE|no#>CKMNUXtIBNp_i5n)RPkx5MBV9;lW9#L%jmh}O;5Vx5Y zXf|4s`Y;qax)uq)D`#K2Fo4sneq7hkDoU+InnR`0x-3dnC2MR+vDc9dQKhKR1H7w@ zZ?TM5C=>iszNDJBsg|y^bhFvK-TbL}vsD6nH)`9LY0WIwS1dbC%=nyiliNfd=sn8^ z^BWd=8R+{{JzrcEagM14#;}J^?LMT?k4JHsPtDVK2FHp0Nj#5JID_+e6_X@(yIa6a zvq63usv1HWNb(fOJ7Q}oaU>(~|E=T1?$jUV*Lh@q$)m_;buv4Z2)nVvesli)YCfAR zMsm1i?gSH&f|YO;+8V7W`;Nw(8!13dY{_rRj=3eLRZ;vkX#gZ(1UyI?E(Mz9xK z{1R_#uEZ9``e9|JpTd?P0Yj=4x;vha3oyF{*&>NPo%`lSs9go z4TplAiLGf%oJiAe+7c(y^qaQCrPMRh!Fy}VC*R7KUWlr(BLwBpug?uZJ<1=o!({xL zQ&rcohP8I+35uQiHyaQ2o3A6jCXv~JUm7G)xM?S4M+q10xRdc=yykkxcVIj2Hp`9V zGo_i&v?U&Rumf(lXCG>;g@bTE=zSoZ$bOMH;U+x&bRIKK>@bhcEAvRc$os@#l3tI1{{+`x*s9)E5 zFJ8x|T2EpCe5iw#!U5}9{e8wl(mJhm8+)wRv>s#swASM|Xt`SN!d`1p>)q7f)p`&0 z_qE=Oqt=I7Pom%YQtK)7THo`3bSvJ&o=f>qmL@*Q8PFgB>L$vb8CK?J%eS&)=^>Hk)S#ci@3UU7~%8E^4o)thtX z%f3^~dyb?ixwX3QIoZ5h%6ZO2wp^;tmg`R5UG##uisu&#Rh2kBI5a$RV)W$Lc!=&U gznkKhCxd!PsWz{^hp*eLb$fR^cM;e{pdAAL05Fqxl>h($ literal 0 HcmV?d00001 diff --git a/assets/images/editors/charter/event-icons/Time Signature Change.png b/assets/images/editors/charter/event-icons/Time Signature Change.png new file mode 100644 index 0000000000000000000000000000000000000000..f7cc46c7805274d13ea1e4b09a9c678fe0285633 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G}c0G|+7ApQUUe;`?Wtho?KF_#4S1p@`HpW+3oW#BCEh%9Dc;5!Jyj5{V~ zzXb~Vd%8G=Xav{xd-E|EayVW5Eng)tqt489<23P$Vs8^#-!W<|$U4GyN_|1=gbP0 Hl+XkKO;<(} literal 0 HcmV?d00001 diff --git a/source/funkin/backend/chart/Chart.hx b/source/funkin/backend/chart/Chart.hx index bd13fd503..e2a22d40a 100644 --- a/source/funkin/backend/chart/Chart.hx +++ b/source/funkin/backend/chart/Chart.hx @@ -129,6 +129,7 @@ class Chart { 1 => "Camera Movement", 2 => "BPM Change", 3 => "Alt Animation Toggle", + 4 => "Time Signature Change", ]; if (data.events == null) data.events = []; diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index d443218ab..e0d173313 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -10,7 +10,7 @@ import funkin.backend.assets.Paths; using StringTools; class EventsData { - public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; + public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation", "Time Signature Change"]; public static var defaultEventsParams:Map> = [ "HScript Call" => [ {name: "Function Name", type: TString, defValue: "myFunc"}, @@ -49,6 +49,7 @@ class EventsData { ], "Alt Animation Toggle" => [{name: "Enable On Sing Poses", type: TBool, defValue: true}, {name: "Enable On Idle", type: TBool, defValue: true}, {name: "Strumline", type: TStrumLine, defValue: 0}], "Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}], + "Time Signature Change" => [{name: "Target Beat Count", type: TFloat(1), defValue: 4}, {name: "Target Step Count", type: TFloat(1), defValue: 4}], ]; public static var eventsList:Array = defaultEventsList.copy(); diff --git a/source/funkin/backend/chart/FNFLegacyParser.hx b/source/funkin/backend/chart/FNFLegacyParser.hx index ede54e183..d75602286 100644 --- a/source/funkin/backend/chart/FNFLegacyParser.hx +++ b/source/funkin/backend/chart/FNFLegacyParser.hx @@ -109,6 +109,16 @@ class FNFLegacyParser { }); } + if (section.sectionBeats != beatsPerMeasure) { + beatsPerMeasure = section.sectionBeats != null ? section.sectionBeats : data.beatsPerMeasure.getDefault(4); + + result.events.push({ + time: curTime, + name: "Time Signature Change", + params: [section.sectionBeats, 4] + }); + } + curTime += curCrochet * beatsPerMeasure; } } @@ -179,7 +189,8 @@ class FNFLegacyParser { mustHitSection: notes[section-1] != null ? notes[section-1].mustHitSection : false, bpm: notes[section-1] != null ? notes[section-1].bpm : chart.meta.bpm, changeBPM: false, - altAnim: notes[section-1] != null ? notes[section-1].altAnim : false + altAnim: notes[section-1] != null ? notes[section-1].altAnim : false, + sectionBeats: notes[section-1] != null ? notes[section-1].sectionBeats : chart.meta.beatsPerMeasure.getDefault(4) }; var sectionEndTime:Float = Conductor.getTimeForStep(Conductor.getMeasureLength() * (section+1)); @@ -193,6 +204,8 @@ class FNFLegacyParser { case "BPM Change": baseSection.changeBPM = true; baseSection.bpm = event.params[0]; + case "Time Signature Change": + baseSection.sectionBeats = event.params[0]; } } notes[section] = baseSection; diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index bd5fca4cf..4af8ab8ac 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -10,6 +10,8 @@ typedef BPMChangeEvent = var stepTime:Float; var songTime:Float; var bpm:Float; + var beatsPerMeasure:Float; + var stepsPerBeat:Float; } class Conductor @@ -21,6 +23,8 @@ class Conductor public static var onBeatHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onStepHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onBPMChange:FlxTypedSignalVoid> = new FlxTypedSignal(); + public static var onBeatsPerMeasureChange:FlxTypedSignalVoid> = new FlxTypedSignal(); + public static var onStepsPerBeatChange:FlxTypedSignalVoid> = new FlxTypedSignal(); /** * Current BPM @@ -108,45 +112,76 @@ class Conductor songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0; bpmChangeMap = []; changeBPM(0); + changeTimeSignature(4, 4); } 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)); + changeBPM(SONG.meta.bpm); + changeTimeSignature(cast SONG.meta.beatsPerMeasure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4)); } /** * Maps BPM changes from a song. * @param song Song to map BPM changes from. */ - public static function mapBPMChanges(song:ChartData) - { + public static function mapBPMChanges(song:ChartData) { bpmChangeMap = [ { stepTime: 0, songTime: 0, - bpm: song.meta.bpm + bpm: song.meta.bpm, + beatsPerMeasure: song.meta.beatsPerMeasure.getDefault(4), + stepsPerBeat: song.meta.stepsPerBeat.getDefault(4) } ]; - + if (song.events == null) return; - + var curBPM:Float = song.meta.bpm; + var curBeatsPerMeasure:Float = song.meta.beatsPerMeasure.getDefault(4); + var curStepsPerBeat:Float = song.meta.stepsPerBeat.getDefault(4); var songTime:Float = 0; var stepTime:Float = 0; - - 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]; - - bpmChangeMap.push({ - stepTime: stepTime, - songTime: songTime, - bpm: curBPM - }); + + 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 / stepsPerBeat); + stepTime += steps; + songTime = e.time; + curBPM = e.params[0]; + + bpmChangeMap.push({ + stepTime: stepTime, + songTime: songTime, + bpm: curBPM, + beatsPerMeasure: curBeatsPerMeasure, // keep old beatsPerMeasure and stepsPerMeasure so shit doesnt break + stepsPerBeat: curStepsPerBeat + }); + } + + if (e.name == "Time Signature Change" && e.params != null) { + var newBeatsPerMeasure = e.params[0]; + var newStepsPerBeat = e.params[1]; + + if (newBeatsPerMeasure == curBeatsPerMeasure && newStepsPerBeat == curStepsPerBeat) continue; + + var steps = (e.time - songTime) / ((60 / curBPM) * 1000 / stepsPerBeat); + stepTime += steps; + songTime = e.time; + + curBeatsPerMeasure = newBeatsPerMeasure; + curStepsPerBeat = newStepsPerBeat; + + bpmChangeMap.push({ + stepTime: stepTime, + songTime: songTime, + bpm: curBPM, // keep old bpm so shit doesnt break + beatsPerMeasure: curBeatsPerMeasure, + stepsPerBeat: curStepsPerBeat + }); + } } } @@ -184,22 +219,32 @@ class Conductor if (FlxG.state != null && FlxG.state is MusicBeatState && cast(FlxG.state, MusicBeatState).cancelConductorUpdate) return; __updateSongPos(FlxG.elapsed); - + if (bpm > 0) { - // updates curbeat and stuff + // Check for BPM change __lastChange = { stepTime: 0, songTime: 0, - bpm: 0 + bpm: 0, + beatsPerMeasure: beatsPerMeasure, + stepsPerBeat: stepsPerBeat }; - for (change in Conductor.bpmChangeMap) - { + + for (change in Conductor.bpmChangeMap) { if (Conductor.songPosition >= change.songTime) __lastChange = change; } + + // Change BPM if necessary + if (__lastChange.bpm > 0 && bpm != __lastChange.bpm) { + changeBPM(__lastChange.bpm); + } - if (__lastChange.bpm > 0 && bpm != __lastChange.bpm) changeBPM(__lastChange.bpm); - + // Check for time signature change + if (__lastChange.beatsPerMeasure != beatsPerMeasure || __lastChange.stepsPerBeat != stepsPerBeat) { + changeTimeSignature(__lastChange.beatsPerMeasure, __lastChange.stepsPerBeat); + } + curStepFloat = __lastChange.stepTime + ((Conductor.songPosition - __lastChange.songTime) / Conductor.stepCrochet); curBeatFloat = curStepFloat / stepsPerBeat; curMeasureFloat = curBeatFloat / beatsPerMeasure; @@ -270,34 +315,50 @@ class Conductor onBPMChange.dispatch(bpm); } + + public static function changeTimeSignature(newBeatsPerMeasure:Float, newStepsPerBeat:Float) { + beatsPerMeasure = newBeatsPerMeasure; + stepsPerBeat = newStepsPerBeat; + + crochet = ((60 / bpm) * 1000); + stepCrochet = crochet / stepsPerBeat; + + onBeatsPerMeasureChange.dispatch(beatsPerMeasure); + onStepsPerBeatChange.dispatch(stepsPerBeat); + } public static function getTimeForStep(step:Float) { var bpmChange:BPMChangeEvent = { stepTime: 0, songTime: 0, - bpm: bpm + bpm: bpm, + beatsPerMeasure: beatsPerMeasure, + stepsPerBeat: stepsPerBeat }; 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))); + return bpmChange.songTime + ((step - bpmChange.stepTime) * ((60 / bpmChange.bpm) * (1000 / bpmChange.stepsPerBeat))); } public static function getStepForTime(time:Float) { var bpmChange:BPMChangeEvent = { stepTime: 0, songTime: 0, - bpm: bpm + bpm: bpm, + beatsPerMeasure: beatsPerMeasure, + stepsPerBeat: stepsPerBeat }; - + 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))); + + return bpmChange.stepTime + ((time - bpmChange.songTime) / ((60 / bpmChange.bpm) * (1000 / bpmChange.stepsPerBeat))); } + public static inline function getMeasureLength() return stepsPerBeat * beatsPerMeasure; diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 89a5d0d9e..b624ee24b 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -109,7 +109,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { draggable = true; for (event in events) - if (event.name == "BPM Change") { + if (event.name == "BPM Change" || event.name == "Time Signature Change") { draggable = false; break; } diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 4e1257241..1806fee2b 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1388,6 +1388,7 @@ class PlayState extends MusicBeatState if (strumLines.members[event.params[0]] != null && strumLines.members[event.params[0]].characters != null) for (char in strumLines.members[event.params[0]].characters) if (char != null) char.playAnim(event.params[1], event.params[2], null); + case "Time Signature Change": // automatically handled by conductor case "Unknown": // nothing } } From 4269a64c489e1669b245f501a98b4290f032be32 Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Mon, 7 Oct 2024 09:37:44 +0200 Subject: [PATCH 02/39] Remove the code for the backwards compat --- source/funkin/backend/chart/Chart.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/source/funkin/backend/chart/Chart.hx b/source/funkin/backend/chart/Chart.hx index e2a22d40a..bd13fd503 100644 --- a/source/funkin/backend/chart/Chart.hx +++ b/source/funkin/backend/chart/Chart.hx @@ -129,7 +129,6 @@ class Chart { 1 => "Camera Movement", 2 => "BPM Change", 3 => "Alt Animation Toggle", - 4 => "Time Signature Change", ]; if (data.events == null) data.events = []; From 638030e35ecc6e12480b358b0054c00c1c5ecbfe Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Thu, 10 Oct 2024 01:48:55 +0200 Subject: [PATCH 03/39] Small optimizations + cleanup --- source/funkin/backend/system/Conductor.hx | 126 +++++++++++----------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 4af8ab8ac..307e07cb3 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -5,16 +5,16 @@ import flixel.FlxState; import funkin.backend.system.interfaces.IBeatReceiver; import flixel.util.FlxSignal.FlxTypedSignal; -typedef BPMChangeEvent = -{ - var stepTime:Float; - var songTime:Float; - var bpm:Float; - var beatsPerMeasure:Float; - var stepsPerBeat:Float; +@:structInit +class BPMChangeEvent { + public var stepTime:Float; + public var songTime:Float; + public var bpm:Float; + public var beatsPerMeasure:Float; + public var stepsPerBeat:Float; } -class Conductor +final class Conductor { /** * FlxSignals @@ -23,8 +23,7 @@ class Conductor public static var onBeatHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onStepHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onBPMChange:FlxTypedSignalVoid> = new FlxTypedSignal(); - public static var onBeatsPerMeasureChange:FlxTypedSignalVoid> = new FlxTypedSignal(); - public static var onStepsPerBeatChange:FlxTypedSignalVoid> = new FlxTypedSignal(); + public static var onTimeSignatureChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); /** * Current BPM @@ -111,15 +110,13 @@ class Conductor public static function reset() { songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0; bpmChangeMap = []; - changeBPM(0); - changeTimeSignature(4, 4); + changeBPM(0, 4, 4); } public static function setupSong(SONG:ChartData) { reset(); mapBPMChanges(SONG); - changeBPM(SONG.meta.bpm); - changeTimeSignature(cast SONG.meta.beatsPerMeasure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4)); + changeBPM(SONG.meta.bpm, cast SONG.meta.beatsPerMeasure.getDefault(4), cast SONG.meta.stepsPerBeat.getDefault(4)); } /** * Maps BPM changes from a song. @@ -135,23 +132,28 @@ class Conductor stepsPerBeat: song.meta.stepsPerBeat.getDefault(4) } ]; - + if (song.events == null) return; - + var curBPM:Float = song.meta.bpm; var curBeatsPerMeasure:Float = song.meta.beatsPerMeasure.getDefault(4); var curStepsPerBeat:Float = song.meta.stepsPerBeat.getDefault(4); var songTime:Float = 0; var stepTime:Float = 0; - + 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 / stepsPerBeat); + var name = e.name; + var params = e.params; + var eventTime = e.time; + if(params == null) continue; + + if (name == "BPM Change" && params[0] is Float) { + if (params[0] == curBPM) continue; + var steps = (eventTime - songTime) / ((60 / curBPM) * 1000 / stepsPerBeat); stepTime += steps; - songTime = e.time; - curBPM = e.params[0]; - + songTime = eventTime; + curBPM = params[0]; + bpmChangeMap.push({ stepTime: stepTime, songTime: songTime, @@ -159,21 +161,19 @@ class Conductor beatsPerMeasure: curBeatsPerMeasure, // keep old beatsPerMeasure and stepsPerMeasure so shit doesnt break stepsPerBeat: curStepsPerBeat }); - } - - if (e.name == "Time Signature Change" && e.params != null) { - var newBeatsPerMeasure = e.params[0]; - var newStepsPerBeat = e.params[1]; - + } else if (name == "Time Signature Change") { + var newBeatsPerMeasure = params[0]; + var newStepsPerBeat = params[1]; + if (newBeatsPerMeasure == curBeatsPerMeasure && newStepsPerBeat == curStepsPerBeat) continue; - - var steps = (e.time - songTime) / ((60 / curBPM) * 1000 / stepsPerBeat); + + var steps = (eventTime - songTime) / ((60 / curBPM) * 1000 / stepsPerBeat); stepTime += steps; - songTime = e.time; - + songTime = eventTime; + curBeatsPerMeasure = newBeatsPerMeasure; curStepsPerBeat = newStepsPerBeat; - + bpmChangeMap.push({ stepTime: stepTime, songTime: songTime, @@ -183,6 +183,9 @@ class Conductor }); } } + + // sort from early to last + bpmChangeMap.sort(function(a, b) return Std.int(a.songTime - b.songTime)); } private static var elapsed:Float; @@ -219,7 +222,7 @@ class Conductor if (FlxG.state != null && FlxG.state is MusicBeatState && cast(FlxG.state, MusicBeatState).cancelConductorUpdate) return; __updateSongPos(FlxG.elapsed); - + if (bpm > 0) { // Check for BPM change __lastChange = { @@ -229,23 +232,22 @@ class Conductor beatsPerMeasure: beatsPerMeasure, stepsPerBeat: stepsPerBeat }; - + + var currentPos = Conductor.songPosition; + for (change in Conductor.bpmChangeMap) { - if (Conductor.songPosition >= change.songTime) + if (currentPos >= change.songTime) __lastChange = change; - } - - // Change BPM if necessary - if (__lastChange.bpm > 0 && bpm != __lastChange.bpm) { - changeBPM(__lastChange.bpm); + else + break; } - // Check for time signature change - if (__lastChange.beatsPerMeasure != beatsPerMeasure || __lastChange.stepsPerBeat != stepsPerBeat) { - changeTimeSignature(__lastChange.beatsPerMeasure, __lastChange.stepsPerBeat); + // 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 + ((Conductor.songPosition - __lastChange.songTime) / Conductor.stepCrochet); + + curStepFloat = __lastChange.stepTime + ((currentPos - __lastChange.songTime) / Conductor.stepCrochet); curBeatFloat = curStepFloat / stepsPerBeat; curMeasureFloat = curBeatFloat / beatsPerMeasure; @@ -303,28 +305,20 @@ class Conductor } } - public static function changeBPM(newBpm:Float, beatsPerMeasure:Float = 4, stepsPerBeat:Float = 4) + public static function changeBPM(newBpm:Float, newBeatsPerMeasure:Float = 4, newStepsPerBeat:Float = 4) { - bpm = newBpm; - - crochet = ((60 / bpm) * 1000); - stepCrochet = crochet / stepsPerBeat; + var timesignChange = (beatsPerMeasure != newBeatsPerMeasure || stepsPerBeat != newStepsPerBeat); + var bpmChange = (bpm != newBpm); - Conductor.beatsPerMeasure = beatsPerMeasure; - Conductor.stepsPerBeat = stepsPerBeat; - - onBPMChange.dispatch(bpm); - } - - public static function changeTimeSignature(newBeatsPerMeasure:Float, newStepsPerBeat:Float) { beatsPerMeasure = newBeatsPerMeasure; stepsPerBeat = newStepsPerBeat; + bpm = newBpm; - crochet = ((60 / bpm) * 1000); + crochet = (60 / bpm) * 1000; stepCrochet = crochet / stepsPerBeat; - onBeatsPerMeasureChange.dispatch(beatsPerMeasure); - onStepsPerBeatChange.dispatch(stepsPerBeat); + if (timesignChange) onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); + if (bpmChange) onBPMChange.dispatch(bpm); } public static function getTimeForStep(step:Float) { @@ -339,6 +333,7 @@ class Conductor 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))); } @@ -351,14 +346,15 @@ class Conductor 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 inline function getMeasureLength() return stepsPerBeat * beatsPerMeasure; From 85c38b266af0f369c09d57cdbffa42ae43468a72 Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Sat, 12 Oct 2024 23:06:55 +0200 Subject: [PATCH 04/39] Made the time signature event icon dynamic --- assets/fonts/timeSig.ttf | Bin 6927 -> 0 bytes .../event-icons/Time Signature Change.png | Bin 222 -> 164 bytes .../event-icons/components/eventNums.png | Bin 0 -> 168 bytes source/funkin/editors/charter/CharterEvent.hx | 88 ++++++++++++++++-- 4 files changed, 81 insertions(+), 7 deletions(-) delete mode 100644 assets/fonts/timeSig.ttf create mode 100644 assets/images/editors/charter/event-icons/components/eventNums.png diff --git a/assets/fonts/timeSig.ttf b/assets/fonts/timeSig.ttf deleted file mode 100644 index 9729308fcb613c44fb13cff7b9d9aace314d2469..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6927 zcmeHLOK%%h6#i!Hq)v*{f?I-$L`OVIRUma9O%nlXp;fCuQBa&l4M@*6ZckYdAsY~>I!D}?nj&3{FNBuUhC-cSH(oN@t>srcLbUigDC z>;SI3_{(!EWvtbofE|no#>CKMNUXtIBNp_i5n)RPkx5MBV9;lW9#L%jmh}O;5Vx5Y zXf|4s`Y;qax)uq)D`#K2Fo4sneq7hkDoU+InnR`0x-3dnC2MR+vDc9dQKhKR1H7w@ zZ?TM5C=>iszNDJBsg|y^bhFvK-TbL}vsD6nH)`9LY0WIwS1dbC%=nyiliNfd=sn8^ z^BWd=8R+{{JzrcEagM14#;}J^?LMT?k4JHsPtDVK2FHp0Nj#5JID_+e6_X@(yIa6a zvq63usv1HWNb(fOJ7Q}oaU>(~|E=T1?$jUV*Lh@q$)m_;buv4Z2)nVvesli)YCfAR zMsm1i?gSH&f|YO;+8V7W`;Nw(8!13dY{_rRj=3eLRZ;vkX#gZ(1UyI?E(Mz9xK z{1R_#uEZ9``e9|JpTd?P0Yj=4x;vha3oyF{*&>NPo%`lSs9go z4TplAiLGf%oJiAe+7c(y^qaQCrPMRh!Fy}VC*R7KUWlr(BLwBpug?uZJ<1=o!({xL zQ&rcohP8I+35uQiHyaQ2o3A6jCXv~JUm7G)xM?S4M+q10xRdc=yykkxcVIj2Hp`9V zGo_i&v?U&Rumf(lXCG>;g@bTE=zSoZ$bOMH;U+x&bRIKK>@bhcEAvRc$os@#l3tI1{{+`x*s9)E5 zFJ8x|T2EpCe5iw#!U5}9{e8wl(mJhm8+)wRv>s#swASM|Xt`SN!d`1p>)q7f)p`&0 z_qE=Oqt=I7Pom%YQtK)7THo`3bSvJ&o=f>qmL@*Q8PFgB>L$vb8CK?J%eS&)=^>Hk)S#ci@3UU7~%8E^4o)thtX z%f3^~dyb?ixwX3QIoZ5h%6ZO2wp^;tmg`R5UG##uisu&#Rh2kBI5a$RV)W$Lc!=&U gznkKhCxd!PsWz{^hp*eLb$fR^cM;e{pdAAL05Fqxl>h($ diff --git a/assets/images/editors/charter/event-icons/Time Signature Change.png b/assets/images/editors/charter/event-icons/Time Signature Change.png index f7cc46c7805274d13ea1e4b09a9c678fe0285633..aaf5b721e06bd62d3e9de47203babd967983eea3 100644 GIT binary patch delta 89 zcmcb|xP)zopr0I0Vb-T(jq delta 145 zcmV;C0B--J0p0*}=F#@Bw^m zkgf%+1ElO%Y9m=@j1>6a5n$f}tP+{wpEJ5Yw{ihZXA|jW00000NkvXXu0mjfkt8;D diff --git a/assets/images/editors/charter/event-icons/components/eventNums.png b/assets/images/editors/charter/event-icons/components/eventNums.png new file mode 100644 index 0000000000000000000000000000000000000000..53e7437d22595840590e6f15b29ef6976451dff5 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^HbBhI1SA-&XJsY=DW;MjzhDMN#wmZ-0eOj@E{-7; zjH2FK8JP`vrZCUl9scctzJQ;ofqssJN9l{B1q}OgYVX~<@FV95Tl*WeW#0-fyXP%1 z4Bmc)Cv|4nf9_8UKPH-6|9SD>p<2XK-b9B(EhXfDd_ Q_kx_@>FVdQ&MBb@0CY(_LjV8( literal 0 HcmV?d00001 diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index b624ee24b..e83b6a043 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -1,5 +1,6 @@ package funkin.editors.charter; +import flixel.group.FlxSpriteGroup; import funkin.editors.charter.Charter.ICharterSelectable; import flixel.math.FlxPoint; import funkin.game.Character; @@ -57,10 +58,26 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return spr; } - public static function generateEventIcon(event:ChartEvent) { - return switch(event.name) { - default: - generateDefaultIcon(event.name); + public static function generateEventIcon(event:ChartEvent):FlxSprite { + switch(event.name) { + case "Time Signature Change": + if(event.params[0] >= 0 || event.params[1] >= 0) { + var group = new FlxSpriteGroup(); + group.add(generateDefaultIcon(event.name)); + group.add({ // top + var num = new EventNumber(9, -1, event.params[0], EventNumber.ALIGN_CENTER); + num.scrollFactor.set(1, 1); + num.active = false; + num; + }); + group.add({ // bottom + var num = new EventNumber(9, 10, event.params[1], EventNumber.ALIGN_CENTER); + num.scrollFactor.set(1, 1); + num.active = false; + num; + }); + return group; + } case "Camera Movement": // custom icon for camera movement var state = cast(FlxG.state, Charter); @@ -71,10 +88,10 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { healthIcon.setUnstretchedGraphicSize(32, 32, false); healthIcon.scrollFactor.set(1, 1); healthIcon.active = false; - healthIcon; - } else - generateDefaultIcon(event.name); + return healthIcon; + } } + return generateDefaultIcon(event.name); } public override function onHovered() { @@ -116,4 +133,61 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { x = (snappedToGrid && eventsBackdrop != null ? eventsBackdrop.x : 0) - (bWidth = 37 + (icons.length * 22)); } +} + +class EventNumber extends FlxSprite { + public static inline final ALIGN_NORMAL:Int = 0; + public static inline final ALIGN_CENTER:Int = 1; + + public var digits:Array = []; + + public var align:Int = ALIGN_NORMAL; + + public function new(x:Float, y:Float, number:Int, ?align:Int = ALIGN_NORMAL) { + super(x, y); + this.digits = []; + this.align = align; + while (number > 0) { + this.digits.insert(0, number % 10); + number = Std.int(number / 10); + } + loadGraphic(Paths.image('editors/charter/event-icons/components/eventNums'), true, 6, 7); + } + + override function update(elapsed:Float) { + super.update(elapsed); + } + + override function draw() { + var baseX = x; + var offsetX = 0.0; + if(align == ALIGN_CENTER) offsetX = -(digits.length - 1) * frameWidth * Math.abs(scale.x) / 2; + + x = baseX + offsetX; + for (i in 0...digits.length) { + frame = frames.frames[digits[i]]; + super.draw(); + x += frameWidth * Math.abs(scale.x); + } + x = baseX; + } + + public var numWidth(get, never):Float; + private function get_numWidth():Float { + return Math.abs(scale.x) * frameWidth * digits.length; + } + public var numHeight(get, never):Float; + private function get_numHeight():Float { + return Math.abs(scale.y) * frameHeight; + } + + public override function updateHitbox():Void + { + var numWidth = this.numWidth; + var numHeight = this.numHeight; + width = numWidth; + height = numHeight; + offset.set(-0.5 * (numWidth - frameWidth * digits.length), -0.5 * (numHeight - frameHeight)); + centerOrigin(); + } } \ No newline at end of file From f115a6c797b570068e279051df4593618c93fb2b Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Sat, 12 Oct 2024 23:21:12 +0200 Subject: [PATCH 05/39] Small change --- source/funkin/editors/charter/CharterEvent.hx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index e83b6a043..16d0b4db0 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -61,7 +61,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { public static function generateEventIcon(event:ChartEvent):FlxSprite { switch(event.name) { case "Time Signature Change": - if(event.params[0] >= 0 || event.params[1] >= 0) { + if(event.params != null && (event.params[0] >= 0 || event.params[1] >= 0)) { var group = new FlxSpriteGroup(); group.add(generateDefaultIcon(event.name)); group.add({ // top @@ -181,8 +181,7 @@ class EventNumber extends FlxSprite { return Math.abs(scale.y) * frameHeight; } - public override function updateHitbox():Void - { + public override function updateHitbox():Void { var numWidth = this.numWidth; var numHeight = this.numHeight; width = numWidth; From cc83f26e1eefb0c9e812efe4c42c9dfa61d17ed8 Mon Sep 17 00:00:00 2001 From: NyxieFemboy Date: Thu, 17 Oct 2024 18:08:29 -0400 Subject: [PATCH 06/39] chart parsing fix maybe? Changing stepsPerBeat to an Int --- .gitignore | 2 +- source/funkin/backend/chart/FNFLegacyParser.hx | 2 +- source/funkin/backend/system/Conductor.hx | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 59fb2c287..ad720ba24 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ latest/windows/haxe/ .haxelib/ pages/ docs/doc.xml -mods/Baldi's Basics in Funkin'/ \ No newline at end of file +mods/ \ No newline at end of file diff --git a/source/funkin/backend/chart/FNFLegacyParser.hx b/source/funkin/backend/chart/FNFLegacyParser.hx index d75602286..1a16068f4 100644 --- a/source/funkin/backend/chart/FNFLegacyParser.hx +++ b/source/funkin/backend/chart/FNFLegacyParser.hx @@ -109,7 +109,7 @@ class FNFLegacyParser { }); } - if (section.sectionBeats != beatsPerMeasure) { + if (section.sectionBeats != null && section.sectionBeats != beatsPerMeasure) { beatsPerMeasure = section.sectionBeats != null ? section.sectionBeats : data.beatsPerMeasure.getDefault(4); result.events.push({ diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 307e07cb3..7ca9858f7 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -11,7 +11,7 @@ class BPMChangeEvent { public var songTime:Float; public var bpm:Float; public var beatsPerMeasure:Float; - public var stepsPerBeat:Float; + public var stepsPerBeat:Int; } final class Conductor @@ -48,7 +48,7 @@ final class Conductor /** * Number of steps per beat (bottom number in time signature). Defaults to 4. */ - public static var stepsPerBeat:Float = 4; + public static var stepsPerBeat:Int = 4; /** @@ -137,7 +137,7 @@ final class Conductor var curBPM:Float = song.meta.bpm; var curBeatsPerMeasure:Float = song.meta.beatsPerMeasure.getDefault(4); - var curStepsPerBeat:Float = song.meta.stepsPerBeat.getDefault(4); + var curStepsPerBeat:Int = song.meta.stepsPerBeat.getDefault(4); var songTime:Float = 0; var stepTime:Float = 0; @@ -305,7 +305,7 @@ final class Conductor } } - public static function changeBPM(newBpm:Float, newBeatsPerMeasure:Float = 4, newStepsPerBeat:Float = 4) + public static function changeBPM(newBpm:Float, newBeatsPerMeasure:Float = 4, newStepsPerBeat:Int = 4) { var timesignChange = (beatsPerMeasure != newBeatsPerMeasure || stepsPerBeat != newStepsPerBeat); var bpmChange = (bpm != newBpm); From 2cde5bf8fcfd52b1ffc81351ea39cbd45348dbf0 Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Fri, 18 Oct 2024 00:36:55 +0200 Subject: [PATCH 07/39] Compiling fixes --- source/funkin/backend/chart/ChartData.hx | 2 +- source/funkin/backend/utils/CoolUtil.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/backend/chart/ChartData.hx b/source/funkin/backend/chart/ChartData.hx index c46164f54..93f18ae4b 100644 --- a/source/funkin/backend/chart/ChartData.hx +++ b/source/funkin/backend/chart/ChartData.hx @@ -19,7 +19,7 @@ typedef ChartMetaData = { public var ?bpm:Float; public var ?displayName:String; public var ?beatsPerMeasure:Float; - public var ?stepsPerBeat:Float; + public var ?stepsPerBeat:Int; public var ?needsVoices:Bool; public var ?icon:String; public var ?color:Dynamic; diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index 9fd7f3c52..aa659968a 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -394,7 +394,7 @@ class CoolUtil var timeSignParsed:Array> = musicInfo["TimeSignature"] == null ? [] : [for(s in musicInfo["TimeSignature"].split("/")) Std.parseFloat(s)]; var beatsPerMeasure:Float = 4; - var stepsPerBeat:Float = 4; + var stepsPerBeat:Int = 4; // Check later, i dont think timeSignParsed can contain null, only nan if (timeSignParsed.length == 2 && !timeSignParsed.contains(null)) { 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 08/39] Continuous Tempo Change --- source/funkin/backend/system/Conductor.hx | 515 ++++++++++++++-------- source/funkin/backend/utils/CoolUtil.hx | 2 +- 2 files changed, 323 insertions(+), 194 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 7ca9858f7..1a0a5111f 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,48 +29,76 @@ 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 + * Current position of the song, in milliseconds. */ - public static var bpm:Float = 100; + public static var songPosition(get, default):Float; + private static function get_songPosition() { + if (songOffset != Options.songOffset) songOffset = Options.songOffset; + return songPosition - songOffset; + } /** - * Current Crochet (time per beat), in milliseconds. + * Offset of the song */ - public static var crochet:Float = ((60 / bpm) * 1000); // beats in milliseconds + public static var songOffset:Float = 0; + /** - * Current StepCrochet (time per step), in milliseconds. + * Current bpmChangeMap index */ - public static var stepCrochet:Float = crochet / 4; // steps in milliseconds + public static var curChangeIndex:Int = 0; /** - * Number of beats per mesure (top number in time signature). Defaults to 4. + * Current bpmChangeMap */ - public static var beatsPerMeasure:Float = 4; + public static var curChange(get, never):Null; + private static function get_curChange() + return bpmChangeMap[curChangeIndex]; /** - * Number of steps per beat (bottom number in time signature). Defaults to 4. + * Current BPM */ - public static var stepsPerBeat:Int = 4; + 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 position of the song, in milliseconds. + * Current Crochet (time per beat), in milliseconds. + * It should be crotchet but ehhh, now it's there for backward compatibility. */ - public static var songPosition(get, default):Float; - private static function get_songPosition() { - if (songOffset != Options.songOffset) trace(songOffset = Options.songOffset); - return songPosition - songOffset; - } + public static var crochet(get, never):Float; + private static function get_crochet() return 60000 / bpm; /** - * Offset of the song + * Current StepCrochet (time per step), in milliseconds. */ - public static var songOffset:Float = 0; + 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; - - for(e in song.events) { - var name = e.name; - var params = e.params; - var eventTime = e.time; - if(params == null) continue; - - 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 - }); + // 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 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 (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 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); - } + var oldStep = curStep, oldBeat = curBeat, oldMeasure = curMeasure, oldChangeIndex = curChangeIndex; - 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 (curStep > oldStep) { - for(i in oldStep...curStep) { - onStepHit.dispatch(i+1); - } + if (curChangeIndex != oldChangeIndex) { + var prev = bpmChangeMap[oldChangeIndex]; + if (beatsPerMeasure != prev.beatsPerMeasure || stepsPerBeat != prev.stepsPerBeat) + onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); + + if (curChange.bpm != prev.bpm) onBPMChange.dispatch(curChange.bpm, curChange.endSongTime); + } + + 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 (__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 (__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 (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); + 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; + } + } - beatsPerMeasure = newBeatsPerMeasure; - stepsPerBeat = newStepsPerBeat; - bpm = newBpm; + 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; + } + } - crochet = (60 / bpm) * 1000; - stepCrochet = crochet / 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; + } + } - if (timesignChange) onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat); - if (bpmChange) onBPMChange.dispatch(bpm); + 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 getTimeForStep(step:Float) { - var bpmChange:BPMChangeEvent = { - stepTime: 0, - songTime: 0, - bpm: bpm, - beatsPerMeasure: beatsPerMeasure, - stepsPerBeat: stepsPerBeat - }; + 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; + } - for(change in bpmChangeMap) - if (change.stepTime < step && change.stepTime >= bpmChange.stepTime) - bpmChange = change; - // possible break here + 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; - return bpmChange.songTime + ((step - bpmChange.stepTime) * ((60 / bpmChange.bpm) * (1000 / bpmChange.stepsPerBeat))); + 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 getStepForTime(time:Float) { - var bpmChange:BPMChangeEvent = { - stepTime: 0, - songTime: 0, - bpm: bpm, - beatsPerMeasure: beatsPerMeasure, - stepsPerBeat: stepsPerBeat - }; + public static function getTimeInBPM(time:Float):Float { + if (bpmChangeMap.length == 0) return 100; + return getTimeWithIndexInBPM(time, getTimeInChangeIndex(time)); + } - for(change in bpmChangeMap) - if (change.songTime < time && change.songTime >= bpmChange.songTime) - bpmChange = change; - // possible break here + 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); + } + } - return bpmChange.stepTime + ((time - bpmChange.songTime) / ((60 / bpmChange.bpm) * (1000 / bpmChange.stepsPerBeat))); + 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 aa659968a..a9de75ca9 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 09/39] 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 1a0a5111f..c722c511f 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 17ed9bc0c..c19c3ec14 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 10/39] 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 c722c511f..8a9fe6db8 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 a9de75ca9..e6af43932 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 11/39] 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 8a9fe6db8..b869ec86c 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 From 79dae9ed61b7722a9905e54c487ca5e62b212711 Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Tue, 19 Nov 2024 20:20:42 +0100 Subject: [PATCH 12/39] Added Event UI Scripts --- .../event-icons/components/arrow-right.png | Bin 0 -> 104 bytes .../charter/event-icons/components/cross.png | Bin 0 -> 123 bytes .../charter/event-icons/components/flash.png | Bin 0 -> 106 bytes .../charter/event-icons/components/plus.png | Bin 0 -> 119 bytes source/funkin/backend/chart/EventsData.hx | 7 +- source/funkin/editors/charter/CharterEvent.hx | 191 ++++++++++++++++-- .../editors/charter/CharterEventScreen.hx | 157 ++++++++------ 7 files changed, 267 insertions(+), 88 deletions(-) create mode 100644 assets/images/editors/charter/event-icons/components/arrow-right.png create mode 100644 assets/images/editors/charter/event-icons/components/cross.png create mode 100644 assets/images/editors/charter/event-icons/components/flash.png create mode 100644 assets/images/editors/charter/event-icons/components/plus.png diff --git a/assets/images/editors/charter/event-icons/components/arrow-right.png b/assets/images/editors/charter/event-icons/components/arrow-right.png new file mode 100644 index 0000000000000000000000000000000000000000..a5da3a70a8a63157a781a223040058e9e5bf1aa5 GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^+(691!2%>(4bL(IDSb~D$B>F!$qvl_9a-8Q{Qu9< z`Jdy5yxvE7)&r{l_B%8wd|+B8$CucY!JyZ1xRJrt+wIlDzjiBtS{XcD{an^LB{Ts5 DeBmG| literal 0 HcmV?d00001 diff --git a/assets/images/editors/charter/event-icons/components/cross.png b/assets/images/editors/charter/event-icons/components/cross.png new file mode 100644 index 0000000000000000000000000000000000000000..a3dee356fc3296c16115179398e2da39c3192b71 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VPM$7~Ar-fJCmiHtP~bUYRK2|L zG)IyA#zn0)P79W%@Gz{syo&X~SxW~+BgUMIf?T)eHyl!4aVf1-x`0zw@lip1{Vb!v Wv?EPh<~9ONWAJqKb6Mw<&;$Sjzopr E0DYPtJ^%m! literal 0 HcmV?d00001 diff --git a/assets/images/editors/charter/event-icons/components/plus.png b/assets/images/editors/charter/event-icons/components/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..939d4c46fbd5ffc428ccf4ff36656189a831c6a6 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4*?GD+hEy;nA7J{|Y{>Xw|LgZD z0%|LM*q=^wG+^6U7}dT`pi%JJK?5JfO%gGN9!&CWnkqS_mh%+XOnAY-&d_WZVQJ%6 RvIuArgQu&X%Q~loCIEC#BWM5s literal 0 HcmV?d00001 diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index e0d173313..9380723db 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -76,11 +76,12 @@ class EventsData { hscriptParser.allowJSON = hscriptParser.allowMetadata = false; for (file in Paths.getFolderContent('data/events/', true, BOTH)) { - if (Path.extension(file) != "json" && Path.extension(file) != "pack") continue; - var eventName:String = Path.withoutExtension(Path.withoutDirectory(file)); + var ext = Path.extension(file); + if (ext != "json" && ext != "pack") continue; + var eventName:String = CoolUtil.getFilename(file); var fileTxt:String = Assets.getText(file); - if (Path.extension(file) == "pack") { + if (ext == "pack") { var arr = fileTxt.split("________PACKSEP________"); eventName = Path.withoutExtension(arr[0]); fileTxt = arr[2]; diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 16d0b4db0..a16cd7227 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -1,12 +1,15 @@ package funkin.editors.charter; import flixel.group.FlxSpriteGroup; -import funkin.editors.charter.Charter.ICharterSelectable; import flixel.math.FlxPoint; +import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.scripting.DummyScript; +import funkin.backend.scripting.Script; +import funkin.editors.charter.Charter.ICharterSelectable; +import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; import funkin.game.Character; import funkin.game.HealthIcon; -import funkin.editors.charter.CharterBackdropGroup.EventBackdrop; -import funkin.backend.chart.ChartData.ChartEvent; +import openfl.display.BitmapData; class CharterEvent extends UISliceSprite implements ICharterSelectable { public var events:Array; @@ -45,50 +48,148 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { sprite.colorTransform = colorTransform; } + /** + * Pack data is a list of 4 strings separated by `________PACKSEP________` + * [0] Event Name + * [1] Event Script + * [2] Event JSON Info + * [3] Event Icon + * [4] Event UI Script / Icon Script + **/ + @:dox(hide) public static function getPackData(name:String):Array { + var packFile = Paths.pack('events/${name}'); + if (Assets.exists(packFile)) { + return Assets.getText(packFile).split('________PACKSEP________'); + } + return null; + } + + @:dox(hide) public static function getUIScript(event:ChartEvent, caller:String):Script { + var uiScript = Paths.script('data/events/${event.name}.ui'); + var script:Script = null; + if(Assets.exists(uiScript)) { + script = Script.create(uiScript); + } else { + var packData = getPackData(event.name); + if(packData != null) { + var scriptFile = packData[4]; + if(scriptFile != null) { + script = Script.fromString(scriptFile, uiScript); + } + } + } + + if(script != null && !(script is DummyScript)) { + // classes and functions + script.set("EventIconGroup", EventIconGroup); // automatically imported + script.set("EventNumber", EventNumber); // automatically imported + script.set("getIconFromStrumline", getIconFromStrumline); + script.set("getIconFromCharName", getIconFromCharName); + script.set("generateDefaultIcon", generateDefaultIcon); + script.set("getPackData", getPackData); + script.set("getEventComponent", getEventComponent); + // data + script.set("event", event); + script.set("caller", caller); + + script.load(); + } + + return script; + } + + /** + * Generates the default event icon for the wanted event + * @param name The name of the event + * @return The icon + **/ private static function generateDefaultIcon(name:String) { var isBase64:Bool = false; var path:String = Paths.image('editors/charter/event-icons/$name'); - if (!Assets.exists(path)) path = Paths.image('editors/charter/event-icons/Unknown'); - if (Assets.exists(Paths.pack('events/$name'))) { - var packimg = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; - if (isBase64 = (packimg != null)) - path = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; + if(!Assets.exists(path)) { + path = Paths.image('editors/charter/event-icons/Unknown'); + + var packData = getPackData(name); + if(packData != null && packData[3] != null) { + isBase64 = true; + path = packData[3]; + } } - var spr = new FlxSprite().loadGraphic(isBase64 ? openfl.display.BitmapData.fromBase64(path.trim(), 'UTF8') : path); + + var spr = new FlxSprite().loadGraphic(isBase64 ? BitmapData.fromBase64(path.trim(), 'UTF8') : path); return spr; } + /** + * Gets a component sprite from the editors/charter/event-icons/components folder + * If you wanna use a number, please use the EventNumber class instead + * @param type The type of component to get + * @param x The x position of the sprite (optional) + * @param y The y position of the sprite (optional) + * @return The component sprite + **/ + public static function getEventComponent(type:String, x:Float = 0.0, y:Float = 0.0) { + var componentPath = Paths.image("editors/charter/event-icons/components/" + type); + if(Assets.exists(componentPath)) + return new FlxSprite(x, y).loadGraphic(componentPath); + + Logs.trace('Could not find component $type', WARNING); + return null; + } + + /** + * Expected to be called from inside of a ui script, + * calling this elsewhere might cause unexpected results or crashes + **/ + public static function getIconFromStrumline(index:Null) { + var state = cast(FlxG.state, Charter); + if (index != null && index >= 0 && index < state.strumLines.length) { + return getIconFromCharName(state.strumLines.members[index].strumLine.characters[0]); + } + return null; + } + + public static function getIconFromCharName(name:String) { + var icon = Character.getIconFromCharName(name); + var healthIcon = new HealthIcon(icon); + CoolUtil.setUnstretchedGraphicSize(healthIcon, 32, 32, false); + healthIcon.scrollFactor.set(1, 1); + healthIcon.active = false; + return healthIcon; + } + public static function generateEventIcon(event:ChartEvent):FlxSprite { + var script = getUIScript(event, "event-icon"); + if(script != null && !(script is DummyScript)) { + if(script.get("generateIcon") != null) { + var res:FlxSprite = script.call("generateIcon"); + if(res != null) + return res; + } + } + switch(event.name) { case "Time Signature Change": if(event.params != null && (event.params[0] >= 0 || event.params[1] >= 0)) { - var group = new FlxSpriteGroup(); + var group = new EventIconGroup(); group.add(generateDefaultIcon(event.name)); group.add({ // top var num = new EventNumber(9, -1, event.params[0], EventNumber.ALIGN_CENTER); - num.scrollFactor.set(1, 1); num.active = false; num; }); group.add({ // bottom var num = new EventNumber(9, 10, event.params[1], EventNumber.ALIGN_CENTER); - num.scrollFactor.set(1, 1); num.active = false; num; }); return group; } case "Camera Movement": - // custom icon for camera movement - var state = cast(FlxG.state, Charter); - if (event.params != null && event.params[0] != null && event.params[0] >= 0 && event.params[0] < state.strumLines.length) { - // camera movement, use health icon - var icon = Character.getIconFromCharName(state.strumLines.members[event.params[0]].strumLine.characters[0]); - var healthIcon = new HealthIcon(icon); - healthIcon.setUnstretchedGraphicSize(32, 32, false); - healthIcon.scrollFactor.set(1, 1); - healthIcon.active = false; - return healthIcon; + // camera movement, use health icon + if(event.params != null) { + var icon = getIconFromStrumline(event.params[0]); + if(icon != null) return icon; } } return generateDefaultIcon(event.name); @@ -135,6 +236,52 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } } +class EventIconGroup extends FlxSpriteGroup { + public var forceWidth:Float = 32; + public var forceHeight:Float = 32; + public var dontTransformChildren:Bool = true; + + public function new() { + super(); + scrollFactor.set(1, 1); + } + + override function preAdd(sprite:FlxSprite):Void + { + super.preAdd(sprite); + sprite.scrollFactor.set(1, 1); + } + + public override function transformChildren(Function:FlxSprite->V->Void, Value:V):Void + { + if (dontTransformChildren) + return; + + super.transformChildren(Function, Value); + } + + override function set_x(Value:Float):Float + { + if (exists && x != Value) + transformChildren(xTransform, Value - x); // offset + return x = Value; + } + + override function set_y(Value:Float):Float + { + if (exists && y != Value) + transformChildren(yTransform, Value - y); // offset + return y = Value; + } + + override function get_width() { + return forceWidth; + } + override function get_height() { + return forceHeight; + } +} + class EventNumber extends FlxSprite { public static inline final ALIGN_NORMAL:Int = 0; public static inline final ALIGN_CENTER:Int = 1; diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index 31d625800..c16333031 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -1,10 +1,13 @@ package funkin.editors.charter; -import funkin.backend.chart.ChartData.ChartEvent; -import funkin.backend.system.Conductor; import flixel.group.FlxGroup; -import funkin.backend.chart.EventsData; import flixel.util.FlxColor; +import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.chart.EventsData; +import funkin.backend.scripting.DummyScript; +import funkin.backend.scripting.Script; +import funkin.backend.system.Conductor; +import funkin.editors.charter.CharterEvent.EventNumber; using StringTools; @@ -109,83 +112,111 @@ class CharterEventScreen extends UISubstateWindow { // destroy old elements paramsFields = []; - for(e in paramsPanel) { - e.destroy(); - paramsPanel.remove(e); + while(paramsPanel.members.length > 0) { + var e = paramsPanel.members.pop(); + if(e != null) { + e.destroy(); + } } if (id >= 0 && id < events.length) { curEvent = id; var curEvent = events[curEvent]; eventName.text = curEvent.name; - // add new elements - var y:Float = eventName.y + eventName.height + 10; - for(k=>param in EventsData.getEventParams(curEvent.name)) { - function addLabel() { - var label:UIText = new UIText(eventName.x, y, 0, param.name); - y += label.height + 4; - paramsPanel.add(label); - }; - - var value:Dynamic = CoolUtil.getDefault(curEvent.params[k], param.defValue); - var lastAdded = switch(param.type) { - case TString: - addLabel(); - var textBox:UITextBox = new UITextBox(eventName.x, y, cast value); - paramsPanel.add(textBox); paramsFields.push(textBox); - textBox; - case TBool: - var checkbox = new UICheckbox(eventName.x, y, param.name, cast value); - paramsPanel.add(checkbox); paramsFields.push(checkbox); - checkbox; - case TInt(min, max, step): - addLabel(); - var numericStepper = new UINumericStepper(eventName.x, y, cast value, step.getDefault(1), 0, min, max); - paramsPanel.add(numericStepper); paramsFields.push(numericStepper); - numericStepper; - case TFloat(min, max, step, precision): - addLabel(); - var numericStepper = new UINumericStepper(eventName.x, y, cast value, step.getDefault(1), precision, min, max); - paramsPanel.add(numericStepper); paramsFields.push(numericStepper); - numericStepper; - case TStrumLine: - addLabel(); - var dropdown = new UIDropDown(eventName.x, y, 320, 32, [for(k=>s in cast(FlxG.state, Charter).strumLines.members) 'Strumline #${k+1} (${s.strumLine.characters[0]})'], cast value); - paramsPanel.add(dropdown); paramsFields.push(dropdown); - dropdown; - case TColorWheel: - addLabel(); - var colorWheel = new UIColorwheel(eventName.x, y, value is String ? FlxColor.fromString(value) : Std.int(value)); - paramsPanel.add(colorWheel); paramsFields.push(colorWheel); - colorWheel; - case TDropDown(options): - addLabel(); - var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value)))); - paramsPanel.add(dropdown); paramsFields.push(dropdown); - dropdown; - default: - paramsFields.push(null); - null; - } - if (lastAdded is UISliceSprite) - y += cast(lastAdded, UISliceSprite).bHeight + 4; - else if (lastAdded is FlxSprite) - y += cast(lastAdded, FlxSprite).height + 6; - } + + generateEventUI(curEvent); } else { eventName.text = "No event"; curEvent = -1; } } + function generateEventUI(event:ChartEvent):Void { + var script = CharterEvent.getUIScript(event, "event-ui"); + if(script != null && !(script is DummyScript)) { + script.set("paramsPanel", paramsPanel); + script.set("paramsFields", paramsFields); + if(script.get("generateUI") != null) { + if(script.call("generateUI") == false) + return; + } + } + + // add new elements + var y:Float = eventName.y + eventName.height + 10; + var params = EventsData.getEventParams(event.name); + for(k=>param in params) { + function addLabel() { + var label:UIText = new UIText(eventName.x, y, 0, param.name); + y += label.height + 4; + paramsPanel.add(label); + }; + + var value:Dynamic = CoolUtil.getDefault(event.params[k], param.defValue); + var lastAdded = switch(param.type) { + case TString: + addLabel(); + var textBox:UITextBox = new UITextBox(eventName.x, y, cast value); + paramsPanel.add(textBox); paramsFields.push(textBox); + textBox; + case TBool: + var checkbox = new UICheckbox(eventName.x, y, param.name, cast value); + paramsPanel.add(checkbox); paramsFields.push(checkbox); + checkbox; + case TInt(min, max, step): + addLabel(); + var numericStepper = new UINumericStepper(eventName.x, y, cast value, CoolUtil.getDefault(step, 1), 0, min, max); + paramsPanel.add(numericStepper); paramsFields.push(numericStepper); + numericStepper; + case TFloat(min, max, step, precision): + addLabel(); + var numericStepper = new UINumericStepper(eventName.x, y, cast value, CoolUtil.getDefault(step, 1), precision, min, max); + paramsPanel.add(numericStepper); paramsFields.push(numericStepper); + numericStepper; + case TStrumLine: + addLabel(); + var dropdown = new UIDropDown(eventName.x, y, 320, 32, [ + for(k=>s in cast(FlxG.state, Charter).strumLines.members) + 'Strumline #${k+1} (${s.strumLine.characters[0]})' + ], cast value); + paramsPanel.add(dropdown); paramsFields.push(dropdown); + dropdown; + case TColorWheel: + addLabel(); + var colorWheel = new UIColorwheel(eventName.x, y, CoolUtil.getColorFromDynamic(value)); + paramsPanel.add(colorWheel); paramsFields.push(colorWheel); + colorWheel; + case TDropDown(options): + addLabel(); + var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value)))); + paramsPanel.add(dropdown); paramsFields.push(dropdown); + dropdown; + default: + paramsFields.push(null); + null; + } + if (lastAdded is UISliceSprite) + y += cast(lastAdded, UISliceSprite).bHeight + 4; + else if (lastAdded is FlxSprite) + y += cast(lastAdded, FlxSprite).height + 6; + } + + if(script != null && !(script is DummyScript)) { + if(script.get("postGenerateUI") != null) { + script.set("params", params); + script.call("postGenerateUI"); + } + } + } + public function saveCurTab() { if (curEvent < 0) return; + var dataParams = EventsData.getEventParams(events[curEvent].name); events[curEvent].params = [ - for(p in paramsFields) { + for(i=>p in paramsFields) { if (p is UIDropDown) { - var dataParams = EventsData.getEventParams(events[curEvent].name); - if (dataParams[paramsFields.indexOf(p)].type == TStrumLine) cast(p, UIDropDown).index; + if (dataParams[i].type == TStrumLine) cast(p, UIDropDown).index; else cast(p, UIDropDown).label.text; } else if (p is UINumericStepper) { 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 13/39] 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 b869ec86c..2fbc6dfea 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 4ec2f299e..3760b25a9 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 21eeb90fb..ac58a4446 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 From 3e13bf50c68d0ef6cce5aef726883beeebb6d1ef Mon Sep 17 00:00:00 2001 From: TheZoroForce240 <86524550+TheZoroForce240@users.noreply.github.com> Date: Sat, 1 Feb 2025 21:13:46 +0000 Subject: [PATCH 14/39] Continuous bpm change event, lots of other reworks and fixes --- .../event-icons/components/arrow-down.png | Bin 0 -> 6292 bytes .../event-icons/components/arrow-down.xml | 6 + .../event-icons/components/warning.png | Bin 0 -> 7951 bytes source/funkin/backend/chart/EventsData.hx | 9 +- source/funkin/backend/system/Conductor.hx | 115 ++++++++++++--- source/funkin/editors/charter/Charter.hx | 39 +++-- .../editors/charter/CharterBackdropGroup.hx | 6 +- source/funkin/editors/charter/CharterEvent.hx | 138 ++++++++++++++++-- .../editors/charter/CharterEventScreen.hx | 4 +- .../charter/CharterEventTypeSelection.hx | 21 ++- 10 files changed, 278 insertions(+), 60 deletions(-) create mode 100644 assets/images/editors/charter/event-icons/components/arrow-down.png create mode 100644 assets/images/editors/charter/event-icons/components/arrow-down.xml create mode 100644 assets/images/editors/charter/event-icons/components/warning.png diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.png b/assets/images/editors/charter/event-icons/components/arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e3ce87f66d9765527b40e8750cf14da1c5bcaf GIT binary patch literal 6292 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 R^$O$-GuAiPE86cC{(m8k8dLxP literal 0 HcmV?d00001 diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.xml b/assets/images/editors/charter/event-icons/components/arrow-down.xml new file mode 100644 index 000000000..1e58cc38e --- /dev/null +++ b/assets/images/editors/charter/event-icons/components/arrow-down.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/images/editors/charter/event-icons/components/warning.png b/assets/images/editors/charter/event-icons/components/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..3558e0a35fd630d2df4465e4af4d6460c861001f GIT binary patch literal 7951 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 literal 0 HcmV?d00001 diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index d1cac6152..e9f5e79b7 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -10,7 +10,7 @@ import funkin.backend.assets.Paths; using StringTools; class EventsData { - public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation", "Time Signature Change"]; + public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Continuous BPM Change", "Time Signature Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; public static var defaultEventsParams:Map> = [ "HScript Call" => [ {name: "Function Name", type: TString, defValue: "myFunc"}, @@ -31,7 +31,9 @@ class EventsData { {name: "Time (Steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}, {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camHUD"} ], - "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, null, 0.001, 3), defValue: 100}], + "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}], + "Continuous BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}, {name: "Time (steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}], + "Time Signature Change" => [{name: "Target Beat Count", type: TFloat(1), defValue: 4}, {name: "Target Step Count", type: TFloat(1), defValue: 4}], "Scroll Speed Change" => [ {name: "Tween Speed?", type: TBool, defValue: true}, {name: "New Speed", type: TFloat(0.01, 99, 0.01, 2), defValue: 1.}, @@ -48,8 +50,7 @@ class EventsData { } ], "Alt Animation Toggle" => [{name: "Enable On Sing Poses", type: TBool, defValue: true}, {name: "Enable On Idle", type: TBool, defValue: true}, {name: "Strumline", type: TStrumLine, defValue: 0}], - "Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}], - "Time Signature Change" => [{name: "Target Beat Count", type: TFloat(1), defValue: 4}, {name: "Target Step Count", type: TFloat(1), defValue: 4}], + "Play Animation" => [{name: "Character", type: TStrumLine, defValue: 0}, {name: "Animation", type: TString, defValue: "animation"}, {name: "Is forced?", type: TBool, defValue: true}] ]; public static var eventsList:Array = defaultEventsList.copy(); diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 2fbc6dfea..01281ef85 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -4,6 +4,7 @@ import flixel.FlxState; import flixel.util.FlxSignal.FlxTypedSignal; import funkin.backend.chart.ChartData; import funkin.backend.system.interfaces.IBeatReceiver; +import funkin.editors.charter.Charter; @:structInit class BPMChangeEvent @@ -14,6 +15,7 @@ class BPMChangeEvent public var stepsPerBeat:Int = 4; public var endSongTime:Float = 0; + public var endStepTime:Float = 0; public var continuous:Bool = false; public var stepTime:Float; @@ -139,6 +141,11 @@ final class Conductor */ public static var bpmChangeMap:Array; + /** + * Array of all events that have been rejected by the Conductor. + */ + public static var invalidEvents:Array = []; + @:dox(hide) public function new() {} public static function reset() { @@ -154,13 +161,9 @@ final class Conductor mapBPMChanges(SONG); } - private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float, ?endTime:Float, ?prevChange:BPMChangeEvent):BPMChangeEvent { + private static function mapBPMChange(curChange:BPMChangeEvent, time:Float, bpm:Float):BPMChangeEvent { var beatTime:Float, measureTime:Float, stepTime:Float; - 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); + 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; @@ -174,7 +177,6 @@ final class Conductor beatsPerMeasure: curChange.beatsPerMeasure, stepsPerBeat: curChange.stepsPerBeat }); - if (curChange.continuous = (endTime is Float && endTime != 0)) curChange.endSongTime = endTime; return curChange; } @@ -194,32 +196,97 @@ final class Conductor }; curChangeIndex = 0; bpmChangeMap = [curChange]; + invalidEvents = []; if (song.events == null) return; // 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) if (e.params != null && (e.name == "BPM Change" || e.name == "Time Signature Change" || e.name == "Continuous BPM Change")) events.push(e); + events.sort(function(a, b) { + if (a.time == b.time) { + if (a.name == "Continuous BPM Change") return 1; + if (b.name == "Continuous BPM Change") return -1; + } + 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.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(curChange, time, curChange.bpm, null, prevChange); - curChange.beatsPerMeasure = params[0]; - curChange.stepsPerBeat = params[1]; + curChange = mapEvent(e, curChange); + } + } + + private static function mapEvent(e:ChartEvent, curChange:BPMChangeEvent) { + var name = e.name, params = e.params, time = e.time; + if (curChange.continuous && 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]; + curChange.stepsPerBeat = params[1]; + + 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]); + var endTime = time + (params[1]) / (curChange.bpm - prevBPM) * Math.log(curChange.bpm / prevBPM) * 15000; + curChange.endStepTime = curChange.stepTime + params[1]; + curChange.continuous = true; + curChange.endSongTime = endTime; + } + 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(function(a, b) { + if (a.time == b.time) { + if (a.name == "Continuous BPM Change") return 1; + if (b.name == "Continuous BPM Change") return -1; + } + return 0; + }); + + var eventTime = Conductor.getTimeForStep(event.step); + for (e in event.events) { + e.time = eventTime; - curChange.stepTime = CoolUtil.floorInt(curChange.stepTime + .99998); - curChange.beatTime = CoolUtil.floorInt(curChange.beatTime + .99998); - curChange.measureTime = CoolUtil.floorInt(curChange.measureTime + .99998); + if ((e.name == "BPM Change" || e.name == "Time Signature Change" || e.name == "Continuous BPM Change")) { + curChange = mapEvent(e, curChange); + } + } } + + } private static var elapsed:Float; diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 3760b25a9..55f74e707 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -843,6 +843,7 @@ class Charter extends UIState { if (s is CharterNote) cast(s, CharterNote).snappedToStrumline = true; if (s is UISprite) cast(s, UISprite).cursor = BUTTON; } + checkSelectionForBPMUpdates(); if (!(verticalChange == 0 && horizontalChange == 0)) { notesGroup.sortNotes(); eventsGroup.sortEvents(); undos.addToUndo(CSelectionDrag(undoDrags)); @@ -1031,11 +1032,7 @@ class Charter extends UIState { notesGroup.sortNotes(); notesGroup.autoSort = true; - for (s in selection) - if (s is CharterEvent) { - Charter.instance.updateBPMEvents(); - break; - } + checkSelectionForBPMUpdates(); if (addToUndo) undos.addToUndo(CCreateSelection(selection)); @@ -1059,11 +1056,7 @@ class Charter extends UIState { notesGroup.sortNotes(); notesGroup.autoSort = true; - for (s in selection) - if (s is CharterEvent) { - Charter.instance.updateBPMEvents(); - break; - } + checkSelectionForBPMUpdates(); if (addToUndo) undos.addToUndo(CDeleteSelection(selection)); @@ -1432,6 +1425,8 @@ class Charter extends UIState { selection = sObjects; _edit_copy(_); // to fix stupid bugs + checkSelectionForBPMUpdates(); + undos.addToUndo(CCreateSelection(sObjects.copy())); } @@ -1477,6 +1472,7 @@ class Charter extends UIState { if (s.selectable.draggable) s.selectable.handleDrag(s.change * -1); selection = [for (s in selectionDrags) s.selectable]; + Charter.instance.updateBPMEvents(); case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.before, n.note.type); @@ -1522,6 +1518,7 @@ class Charter extends UIState { for (s in selectionDrags) if (s.selectable.draggable) s.selectable.handleDrag(s.change); //this.selection = selection; + Charter.instance.updateBPMEvents(); case CEditSustains(changes): for(n in changes) n.note.updatePos(n.note.step, n.note.id, n.after, n.note.type); @@ -1836,14 +1833,30 @@ class Charter extends UIState { } public function updateBPMEvents() { + eventsGroup.sortEvents(); + Conductor.mapCharterBPMChanges(PlayState.SONG); buildEvents(); - Conductor.changeBPM(PlayState.SONG.meta.bpm, cast PlayState.SONG.meta.beatsPerMeasure.getDefault(4), cast PlayState.SONG.meta.stepsPerBeat.getDefault(4)); - Conductor.mapBPMChanges(PlayState.SONG); - + for(e in eventsGroup.members) { + for(event in e.events) { + if (event.name == "BPM Change" || event.name == "Time Signature Change" || event.name == "Continuous BPM Change") { + e.refreshEventIcons(); + break; + } + } + } + refreshBPMSensitive(); } + public inline function checkSelectionForBPMUpdates() { + for (s in selection) + if (s is CharterEvent) { + Charter.instance.updateBPMEvents(); + break; + } + } + public inline function hitsoundsEnabled(id:Int) return strumLines.members[id] != null && strumLines.members[id].hitsounds; diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx index ac58a4446..484b03802 100644 --- a/source/funkin/editors/charter/CharterBackdropGroup.hx +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -294,10 +294,10 @@ class CharterGridSeperatorBase extends FlxSprite { 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)); + var index:Int = CoolUtil.boundInt(i-1, 0, Conductor.bpmChangeMap.length - 1); + var step:Float = Conductor.getTimeWithBPMInSteps(change.songTime, index, Conductor.getTimeWithIndexInBPM(change.songTime, index)); - if (Math.ceil(step) - step > 0) { //mid step change + if (Math.ceil(step) - step > 0 && (step - Math.floor(step)) > FlxMath.EPSILON) { //mid step change timeSignatureChangeGaps.push(step); } } diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 0eb0fc814..0e2dc0029 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -1,5 +1,7 @@ package funkin.editors.charter; +import funkin.backend.shaders.CustomShader; +import funkin.backend.system.Conductor; import flixel.group.FlxSpriteGroup; import flixel.math.FlxPoint; import flixel.system.FlxAssets.FlxGraphicAsset; @@ -176,7 +178,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return healthIcon; } - public static function generateEventIcon(event:ChartEvent):FlxSprite { + public static function generateEventIcon(event:ChartEvent, inMenu:Bool = true):FlxSprite { var script = getUIScript(event, "event-icon"); if(script != null && !(script is DummyScript)) { if(script.get("generateIcon") != null) { @@ -201,8 +203,43 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { num.active = false; num; }); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); return group; } + case "Continuous BPM Change": + if(event.params != null && event.params[1] != null) { + if (inMenu) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon("BPM Change Start")); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + + var group = generateEventIconWithDuration(event.params[1], "BPM Change Start"); + group.members[0].y -= 2; + generateEventIconNumbers(group, Std.int(event.params[0]), 15); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } else { + return generateDefaultIcon("BPM Change Start"); + } + case "BPM Change": + if(event.params != null && event.params[0] != null) { + if (inMenu) { + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + group.members[0].y -= 2; + generateEventIconNumbers(group, Std.int(event.params[0]), 15); + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + case "Camera Movement": // camera movement, use health icon if(event.params != null) { @@ -213,6 +250,76 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return generateDefaultIcon(event.name); } + private static function generateEventIconNumbers(group:EventIconGroup, number:Int, y:Float) { + var numString = Std.string(number); + var offset = (16 - (numString.length*5))/2; + for (num in 0...numString.length) { + group.add({ + var num = new EventNumber(offset + num*5, y, Std.parseInt(numString.charAt(num)), EventNumber.ALIGN_CENTER); + num.active = false; + num; + }); + } + } + + private static function generateEventIconWithDuration(duration:Float, startIcon:String, endIcon:String = "") { + var group = new EventIconGroup(); + group.add(generateDefaultIcon(startIcon)); + + var xOffset = 4; + var yGap = 24; + var endGap = 7; + if (endIcon == "") endGap = -2; + + if (duration >= 0.65) { //min time for showing arrow + var tail = new FlxSprite(xOffset, yGap); + var arrow = new FlxSprite(xOffset, (duration * 40) + endGap); + var arrowSegment = new FlxSprite(xOffset, yGap); + tail.frames = arrow.frames = arrowSegment.frames = Paths.getSparrowAtlas("editors/charter/event-icons/components/arrow-down"); + + group.add({ + tail.animation.addByPrefix("tail", "tail"); + tail.animation.play("tail"); + tail; + }); + + group.add({ + arrowSegment.animation.addByPrefix("segment", "segment"); + arrowSegment.animation.play("segment"); + arrowSegment.scale.y = (duration * 40) - (tail.height + endGap + yGap); + arrowSegment.updateHitbox(); + arrowSegment.y += tail.height; + arrowSegment; + }); + + group.add({ + arrow.animation.addByPrefix("arrow", "arrow"); + arrow.animation.play("arrow"); + arrow; + }); + } + + if (endIcon != "") { + group.add({ + var icon = generateDefaultIcon(endIcon); + icon.y = duration * 40; + icon; + }); + } + + + return group; + } + + private static function generateEventIconWarning(group:EventIconGroup) { + for (spr in group) { + spr.colorTransform.redMultiplier = spr.colorTransform.greenMultiplier = spr.colorTransform.blueMultiplier = 0.5; + spr.colorTransform.redOffset = 100; + } + group.add(getEventComponent("warning", 16, -8)); + group.copyColorTransformToChildren = false; + } + public override function onHovered() { super.onHovered(); /* @@ -238,26 +345,22 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } for(event in events) { - var spr = generateEventIcon(event); + var spr = generateEventIcon(event, false); icons.push(spr); members.push(spr); } draggable = true; - for (event in events) - if (event.name == "BPM Change" || event.name == "Time Signature Change") { - draggable = false; - break; - } x = (snappedToGrid && eventsBackdrop != null ? eventsBackdrop.x : 0) - (bWidth = 37 + (icons.length * 22)); } } class EventIconGroup extends FlxSpriteGroup { - public var forceWidth:Float = 32; - public var forceHeight:Float = 32; + public var forceWidth:Float = 16; + public var forceHeight:Float = 16; public var dontTransformChildren:Bool = true; + public var copyColorTransformToChildren:Bool = true; public function new() { super(); @@ -298,6 +401,12 @@ class EventIconGroup extends FlxSpriteGroup { override function get_height() { return forceHeight; } + + override public function draw() { + @:privateAccess + if (copyColorTransformToChildren && colorTransform != null) for (child in members) child.colorTransform.__copyFrom(colorTransform); + super.draw(); + } } class EventNumber extends FlxSprite { @@ -312,10 +421,15 @@ class EventNumber extends FlxSprite { super(x, y); this.digits = []; this.align = align; - while (number > 0) { - this.digits.insert(0, number % 10); - number = Std.int(number / 10); + if (number == 0) { + this.digits.insert(0, 0); + } else { + while (number > 0) { + this.digits.insert(0, number % 10); + number = Std.int(number / 10); + } } + loadGraphic(Paths.image('editors/charter/event-icons/components/eventNums'), true, 6, 7); } diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index 307aa9079..3c2ad340b 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -58,7 +58,7 @@ class CharterEventScreen extends UISubstateWindow { }); eventsList.add(new EventButton(events[events.length-1], CharterEvent.generateEventIcon(events[events.length-1]), events.length-1, this, eventsList)); changeTab(events.length-1); - })); + }, chartEvent.step)); for (k=>i in events) eventsList.add(new EventButton(i, CharterEvent.generateEventIcon(i), k, this, eventsList)); add(eventsList); @@ -83,12 +83,12 @@ class CharterEventScreen extends UISubstateWindow { else { chartEvent.events = [for (i in eventsList.buttons.members) i.event]; chartEvent.refreshEventIcons(); - Charter.instance.updateBPMEvents(); Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)])); } } + Charter.instance.updateBPMEvents(); close(); }); saveButton.x -= saveButton.bWidth; diff --git a/source/funkin/editors/charter/CharterEventTypeSelection.hx b/source/funkin/editors/charter/CharterEventTypeSelection.hx index bfa6cfe78..0e836e771 100644 --- a/source/funkin/editors/charter/CharterEventTypeSelection.hx +++ b/source/funkin/editors/charter/CharterEventTypeSelection.hx @@ -1,9 +1,11 @@ package funkin.editors.charter; +import funkin.backend.system.Conductor; import funkin.backend.chart.EventsData; class CharterEventTypeSelection extends UISubstateWindow { var callback:String->Void; + var eventStepTime:Float; var buttons:Array = []; @@ -13,9 +15,10 @@ class CharterEventTypeSelection extends UISubstateWindow { var upIndicator:UIText; var downIndicator:UIText; - public function new(callback:String->Void) { + public function new(callback:String->Void, eventStepTime:Float) { super(); this.callback = callback; + this.eventStepTime = eventStepTime; } public override function create() { @@ -32,6 +35,15 @@ class CharterEventTypeSelection extends UISubstateWindow { buttonsBG.frames = Paths.getFrames('editors/ui/inputbox'); add(buttonsBG); + var disableConductorEvents:Bool = false; + var disableOnlyContinuousChanges:Bool = false; + for (change in Conductor.bpmChangeMap) { + if (change.continuous && eventStepTime >= change.stepTime && eventStepTime < change.endStepTime) { + disableOnlyContinuousChanges = eventStepTime == change.stepTime; //allow time sig and instant bpm changes on the same event + disableConductorEvents = !disableOnlyContinuousChanges; + } + } + for(k=>eventName in EventsData.eventsList) { var button = new UIButton(0, (32 * k), eventName, function() { close(); @@ -52,6 +64,11 @@ class CharterEventTypeSelection extends UISubstateWindow { icon.x = button.x + 8; icon.y = button.y + Math.abs(button.bHeight - icon.height) / 2; add(icon); + + if (disableConductorEvents && (eventName == "Time Signature Change" || eventName == "Continuous BPM Change" || eventName == "BPM Change") || (disableOnlyContinuousChanges && eventName == "Continuous BPM Change")) { + button.selectable = button.shouldPress = false; + button.autoAlpha = true; + } } windowSpr.bHeight = 61 + (32 * (17)); @@ -79,7 +96,7 @@ class CharterEventTypeSelection extends UISubstateWindow { sinner += elapsed; for (button in buttons) - button.selectable = buttonsBG.hovered; + if (button.shouldPress) button.selectable = buttonsBG.hovered; buttonCameras.zoom = subCam.zoom; From cb4a95678c1d90ec67a6f779b2cd71709c6beb79 Mon Sep 17 00:00:00 2001 From: TheZoroForce240 <86524550+TheZoroForce240@users.noreply.github.com> Date: Sun, 2 Feb 2025 09:48:11 +0000 Subject: [PATCH 15/39] Negative and decimal support for event numbers (and some other changes) --- .../event-icons/components/arrow-down.png | Bin 6292 -> 122 bytes .../event-icons/components/eventNums.png | Bin 168 -> 216 bytes .../event-icons/components/warning.png | Bin 7951 -> 125 bytes source/funkin/editors/charter/CharterEvent.hx | 56 +++++++++++------- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.png b/assets/images/editors/charter/event-icons/components/arrow-down.png index b5e3ce87f66d9765527b40e8750cf14da1c5bcaf..749279c56fe110b1ddd2e5da07052ee783167a4e 100644 GIT binary patch delta 104 zcmbPYST#W+iHVtkfkEkm2p^E*3GfMV1=2w9|Ns9NFJ45QK0gu2VJr#q3ubV5b|VeQ zk@0kK45^4qPAEx8nBUNMWVu1e+LQ^=Oi9L83=9>g*-d2M+8zfP;pyt_f1 BA-n(p literal 6292 zcmeHKXH-*J*A6O*2%{8*7j%e0PzF;8B@slVOD|Fdxk+wf2uUCbMS2-WnxZ0LK|q>x zML-4_l%_Zq6cCWEh#*xIDT=^%1M2Ad-aoGO&OftqlY7qD``LRx=ecL?8)a#3C@L%? z41>W$jg9oIp?@Lh5)oPh{Z1$0w?N}Ee_IEhHQ)>PIuR@KROi#^Xn`; zNB7yG{L8VWl4#fcm`H#i(x2R`gTAdTMo+pzOBzpdOb82;>~}pe{61uH;v&-NlF|jo(`GhF)>S>1TlF!hP`TX6)fITX5E!_0%e^fBt=NOp z3%du#`*K5j#TCgy?oZzRhg7vWa^~=%F%iww?W@*h_}o4HSb9oV0`@`Z%8oW!(XPN+#_r9nRs7la%!qv;~unJ2`4SYi<1{;<)?LZ>V}$cl}_K{o2rq$ zzJpt|z_i9V6D^llvCq^sYEKsZ()#Y;96EB>P>-K|tSCV zA}r|Lk$ifr7XJHsx|$<-J@R24<$8JFhRK!eyShol<0)ovES`z!pIvQBfN59t-HL4&iXiQ}7=sVpRvx*1 z(PBsB@z?hZUL38AL62^SMQS)%-ZhjCRh7F#3rW}%JqvT(o|EApsU#vji2yEBjOIkT zpSl@NCGlD3Pz28gKlg*HGFkb31Fm%3vGw zebk4CcyQ2>dWku++3rw%T!Ou$cUfzza(1S%e#w@?&Qi81)3CfH@se+ccX>~$fe9~qx*=p8ouy<-kjPanqM>Dp^^+=kl*c-A7 zV`G`~^?+I4Qt4#RP(%2QvsFl@RFA7=ymX@9$*M^Cu6LX`=b$cfXWIa}Jh0>b6A`6R z%c)v_{bQ}gTzc0~$CZ6jm#5#w=Vp^?@mr$6$I_!kE!+QGyIrWHbcQ8VU~*p=`=HH= z87vYemr3i_au+XVU{wvHX+f*iqCfPirw5VZS*y5DH#ImUvCLF6n~oQ#CAt#sWgSo$ zIDZS#R55VN@TDTkT{1N{rYS_7lqKG}qu(4I29b`EhWzL5&5rGrpbp zz@RXYmm_}Bm13*gC=N99lDWbQwDYz_169s_G2v9#fd>Pqkc({|G_FFFs84(jV|ac< z-gWdDD&}R;GWDZ3cvF>8RC^hP{%P2_+s=+>UG$|4neKi{)GSm@BfQ`Z={JQM}ZFJYBFy z*~XVLiB?J2+cL4bJ79PI@ty}V7nF1kO%&caL%$nPu>WuOgZZxOBF2{7CO5B{;EMR9 zF;64a8D|SDVaiID{WXIZ8p1S9n~Ub33Ma_(=1H!0*HcJYBW5RZRd*>(?K@yK;3}zy zwa|jsAB$-;K#Oy(P~}d)!y08YH=Z7B{w;Bn*SOaFxpSTK-NlypB(lY$J;F`J3BBrR z9xi36>CEy2Nuu_JQcR7cSi;DG>f1Tb16_-e8}B`RaGn#Kn=!ezS7%xV`~K#Oitg<5 zZLkFAg`DX*w=9Q}X4C48+IMr$&vbk2Id#HCV#IH^K>YDUV?9FXYOxV#LE{d^3TMW( zfE3#}yg)=A(fVbX{$X=&cdn(%uFd`?)oC@|o6iZ-pI+*SynHXh?wL`RC?n9fcHxiC zTREwl#kSo>s@hECKasEv&H0e$iE}pEY%iILl2xCtLEIQ_`#?9Fj3N6lYsM<<9-Ylt zSE$@DT2v$`qFyMJdD zkY$v?%r^GA=JK&w?f$CwCoN9e9^qJTSe-nlmKgTPz-jBbgZh@nse_A`T`KG=r-v1` zHApw)s@Dj}yDKfVpk`^Z?ahm)Zr<0<7Ww4kw6(nQF|R;bVkz0%uJ>c{16cb#VS`@S z^Sg%i`$<_3ROAa^T#Z<&j>wjKznPkm;(DV+tBpL~9<^bE_W48)&k{}g$H{kRhV_la z)`+sB!nfL96Ev;Qml26EcQ<)`+~Q~{=GhamfGx#`ob7z=9F-3yu77qX{MX>_XZkr7 zR~OG@uG{ZKK-!o{%R40%)~(MEe(n@rT+rH(USl}sWmS7*j2aP4ujaIY<0+{DoAySM z>fduau-USV7YvfREnVX3!^9VJvj;!?4aU`+F^&>2pARDAh6M1lCttPX7EWdSDrRNh zf6=f`&mnzbM@VLvEN2b#wiDXds_pgexgCin^BqR}gLfA9(Q7`*TFl~iF&sS=W91C) z!CDX+A3mAeHw9L-wMfKk=jn(1_B+{I%GYbA?&4{Mm)`nyb{qFK1qTSce{${DYMo~% zeFOKo?;)uKq*vC}Fg@oQMX4@!D7R+6!dK;cs>uZ|a=QT$WFx0GTHHTRwLxib-buxm zx%q}Fyoa)if3=5kcC?2@{bhQ!?g5wO%@J27GzM2*(nc?JqvsKgI)d0Lx9PY@k)YI`in)lI%a$!d23PRL z#pmizWlFxO-qRE_3>WH+$PX`hBeiYo!~U?kqZ-Lmw}$4^z@328jam4kyjrh~3nokV zUH$7w5x+Q>MqGP1UuP3{cP_BCv_FecdSPfW_RcPpZfgZ@!&LgJ(S6+)4^{vQqva_9 zyMs4%SzXZY-}#nx*5HpvMHdxYCJkE-mB`SfbX)8vo_iXk3aH6vn;PZ3&ZCYbKOrA( zy$pYza>i11yB$0Ge6*~UlEQKM(>t40MT5J_`u3sZ>86?B)(N95`X|iNeASGXE8FSf zl;*DRn`LPpCmJRfg&2EzDZs0nHH;6lT{2Yv>4(~VbAru{ZP$W}dp>5xS8- z2ek1wwu{HVe+j1W0Ueo)!WDt;4_M$MiHa`wGwE zer5sU1MLTRqOm9pn#n|e>%rye`#>O{6Z)?nTwADZMq7hiwl{|i>id8!p8U5E6!KSp zPj8OLayb+-8uS2}kSQ06iv2F6p|P3eR}Ve~G&<9B*$X23J4+s&`VU#(<;GuGF6Y}s zAos7h-&udDec2eYGBYFUvB}>2^o;ei5d8Q=3Y$!)5SMQO3?7FiP*jju4K+Lx4{E3* z2>^+L1T{c45|sjg6gAQ}P{u4S4`7i&J`@CwqC+?uWRQwi$AL&S5{Zh$1E4xm1H@wa zcK}sI6^|pUt9%1t!J$K{1U$aYiVsDBpeO(d1E^z2NF1O-LE=@_Nk|P93JFQUtCC0* z5)MZtt1UxO$V3A+hY3L2NoN8y5bepLEidp1C+b)lYawtb%s(@h9srLDIY7?C`ltFEGs*HFh}3D6g(vXb>Oq8-TLLR#dbVlgP3#OJX8%)mjePkHpiCD_RvD`A>e$^Wr4#rSGL87&V?+F@-_ci^){f_%4p>w@Sra* z!QsooB?9D?AY8x)q$~%5{8pC8M*tQLgr4xvlKL`F|1aA`#p4Kgycz}CXR-nB}5#J~?3|5Vrij4t7S-sV6S)ZqC-?`ljl-9MrCyj3I< zLp_*LbM(k5Xtdtb$dLh+=)3je6n!*|D>DUUj zlifl?mqeDfizo`s3f6_Jjoc)$S|%;AIz;1*cK!V&p-{!&E$5d6g{AHc!yNDJj49cE R^$O$-GuAiPE86cC{(m8k8dLxP diff --git a/assets/images/editors/charter/event-icons/components/eventNums.png b/assets/images/editors/charter/event-icons/components/eventNums.png index 53e7437d22595840590e6f15b29ef6976451dff5..3dd37e472fb7eacc0d67c53913e8558da0a06e51 100644 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^9ze{_!VDxM&x)M|Qv3lvA+A6=G&J=8|NlS+kaXyq zPy%Ezmjw9*1Lv#ZmOuL_ za755^L4wAFH6hzvD}F4?47~haT7BN` zRQzopr0DAsZ)Bpeg literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^HbBhI1SA-&XJsY=DW;MjzhDMN#wmZ-0eOj@E{-7; zjH2FK8JP`vrZCUl9scctzJQ;ofqssJN9l{B1q}OgYVX~<@FV95Tl*WeW#0-fyXP%1 z4Bmc)Cv|4nf9_8UKPH-6|9SD>p<2XK-b9B(EhXfDd_ Q_kx_@>FVdQ&MBb@0CY(_LjV8( diff --git a/assets/images/editors/charter/event-icons/components/warning.png b/assets/images/editors/charter/event-icons/components/warning.png index 3558e0a35fd630d2df4465e4af4d6460c861001f..d55cf52f2c198e41816277648df12bedf3b732f8 100644 GIT binary patch delta 107 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 diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 0e2dc0029..939082a68 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -217,7 +217,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { var group = generateEventIconWithDuration(event.params[1], "BPM Change Start"); group.members[0].y -= 2; - generateEventIconNumbers(group, Std.int(event.params[0]), 15); + generateEventIconNumbers(group, event.params[0], 15); if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); return group; } else { @@ -235,7 +235,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { var group = new EventIconGroup(); group.add(generateDefaultIcon(event.name)); group.members[0].y -= 2; - generateEventIconNumbers(group, Std.int(event.params[0]), 15); + generateEventIconNumbers(group, event.params[0], 15); if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); return group; } @@ -250,16 +250,12 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return generateDefaultIcon(event.name); } - private static function generateEventIconNumbers(group:EventIconGroup, number:Int, y:Float) { - var numString = Std.string(number); - var offset = (16 - (numString.length*5))/2; - for (num in 0...numString.length) { - group.add({ - var num = new EventNumber(offset + num*5, y, Std.parseInt(numString.charAt(num)), EventNumber.ALIGN_CENTER); - num.active = false; - num; - }); - } + private static function generateEventIconNumbers(group:EventIconGroup, number:Float, y:Float) { + group.add({ + var num = new EventNumber(3, y, number, EventNumber.ALIGN_CENTER, 5); + num.active = false; + num; + }); } private static function generateEventIconWithDuration(duration:Float, startIcon:String, endIcon:String = "") { @@ -414,19 +410,39 @@ class EventNumber extends FlxSprite { public static inline final ALIGN_CENTER:Int = 1; public var digits:Array = []; + public static inline final FRAME_POINT:Int = 10; + public static inline final FRAME_NEGATIVE:Int = 11; public var align:Int = ALIGN_NORMAL; + public var spacing:Float = 6; - public function new(x:Float, y:Float, number:Int, ?align:Int = ALIGN_NORMAL) { + public function new(x:Float, y:Float, number:Float, ?align:Int = ALIGN_NORMAL, spacing:Float = 6, precision:Int = 3) { super(x, y); this.digits = []; this.align = align; + this.spacing = spacing; + + + if (number == 0) { this.digits.insert(0, 0); } else { - while (number > 0) { - this.digits.insert(0, number % 10); - number = Std.int(number / 10); + + var decimals:Float = FlxMath.roundDecimal(Math.abs(number % 1), precision); + if (decimals > 0) this.digits.insert(0, FRAME_POINT); + while(decimals > 0) { + this.digits.push(Math.floor(decimals * 10)); + decimals = FlxMath.roundDecimal((decimals * 10) % 1, precision); + } + + var ints = Std.int(Math.abs(number)); + while (ints > 0) { + this.digits.insert(0, ints % 10); + ints = Std.int(ints / 10); + } + + if (number < 0) { + this.digits.insert(0, FRAME_NEGATIVE); } } @@ -440,20 +456,20 @@ class EventNumber extends FlxSprite { override function draw() { var baseX = x; var offsetX = 0.0; - if(align == ALIGN_CENTER) offsetX = -(digits.length - 1) * frameWidth * Math.abs(scale.x) / 2; + if(align == ALIGN_CENTER) offsetX = -(digits.length - 1) * spacing * Math.abs(scale.x) / 2; x = baseX + offsetX; for (i in 0...digits.length) { frame = frames.frames[digits[i]]; super.draw(); - x += frameWidth * Math.abs(scale.x); + x += spacing * Math.abs(scale.x); } x = baseX; } public var numWidth(get, never):Float; private function get_numWidth():Float { - return Math.abs(scale.x) * frameWidth * digits.length; + return Math.abs(scale.x) * spacing * digits.length; } public var numHeight(get, never):Float; private function get_numHeight():Float { @@ -465,7 +481,7 @@ class EventNumber extends FlxSprite { var numHeight = this.numHeight; width = numWidth; height = numHeight; - offset.set(-0.5 * (numWidth - frameWidth * digits.length), -0.5 * (numHeight - frameHeight)); + offset.set(-0.5 * (numWidth - spacing * digits.length), -0.5 * (numHeight - frameHeight)); centerOrigin(); } } \ No newline at end of file From 0f0d116fb2a082452614eaaa3fe2671eaf5b06c7 Mon Sep 17 00:00:00 2001 From: TheZoroForce240 <86524550+TheZoroForce240@users.noreply.github.com> Date: Sun, 2 Feb 2025 12:29:42 +0000 Subject: [PATCH 16/39] more event icon stuff --- source/funkin/editors/charter/CharterEvent.hx | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 939082a68..9dcee4b1e 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -89,6 +89,9 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { script.set("getIconFromStrumline", getIconFromStrumline); script.set("getIconFromCharName", getIconFromCharName); script.set("generateDefaultIcon", generateDefaultIcon); + script.set("generateEventIconDurationArrow", generateEventIconDurationArrow); + script.set("generateEventIconNumbers", generateEventIconNumbers); + script.set("generateEventIconWarning", generateEventIconWarning); script.set("getPackData", getPackData); script.set("getEventComponent", getEventComponent); // data @@ -181,6 +184,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { public static function generateEventIcon(event:ChartEvent, inMenu:Bool = true):FlxSprite { var script = getUIScript(event, "event-icon"); if(script != null && !(script is DummyScript)) { + script.set("inMenu", inMenu); if(script.get("generateIcon") != null) { var res:FlxSprite = script.call("generateIcon"); if(res != null) @@ -208,16 +212,13 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } case "Continuous BPM Change": if(event.params != null && event.params[1] != null) { - if (inMenu) { - var group = new EventIconGroup(); - group.add(generateDefaultIcon("BPM Change Start")); - if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); - return group; + var group = new EventIconGroup(); + group.add(generateDefaultIcon("BPM Change Start")); + if (!inMenu) { + generateEventIconDurationArrow(group, event.params[1]); + group.members[0].y -= 2; + generateEventIconNumbers(group, event.params[0], 3); } - - var group = generateEventIconWithDuration(event.params[1], "BPM Change Start"); - group.members[0].y -= 2; - generateEventIconNumbers(group, event.params[0], 15); if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); return group; } else { @@ -225,18 +226,23 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { } case "BPM Change": if(event.params != null && event.params[0] != null) { - if (inMenu) { - var group = new EventIconGroup(); - group.add(generateDefaultIcon(event.name)); - if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); - return group; + var group = new EventIconGroup(); + group.add(generateDefaultIcon(event.name)); + if (!inMenu) { + group.members[0].y -= 2; + generateEventIconNumbers(group, event.params[0], 3); } + if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + return group; + } + case "Scroll Speed Change": + if(event.params != null && !inMenu) { var group = new EventIconGroup(); group.add(generateDefaultIcon(event.name)); + if (event.params[0]) generateEventIconDurationArrow(group, event.params[2]); group.members[0].y -= 2; - generateEventIconNumbers(group, event.params[0], 15); - if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group); + generateEventIconNumbers(group, event.params[1]); return group; } @@ -250,26 +256,28 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { return generateDefaultIcon(event.name); } - private static function generateEventIconNumbers(group:EventIconGroup, number:Float, y:Float) { + private static function generateEventIconNumbers(group:EventIconGroup, number:Float, x:Float = 4, y:Float = 15, spacing:Float = 5, precision:Int = 3) { group.add({ - var num = new EventNumber(3, y, number, EventNumber.ALIGN_CENTER, 5); + var num = new EventNumber(x, y, number, EventNumber.ALIGN_CENTER, spacing, precision); + if (num.numWidth > 20) { + num.scale.x = num.scale.y = 20 / num.numWidth; + } num.active = false; num; }); } - private static function generateEventIconWithDuration(duration:Float, startIcon:String, endIcon:String = "") { - var group = new EventIconGroup(); - group.add(generateDefaultIcon(startIcon)); + private static function generateEventIconDurationArrow(group:EventIconGroup, stepDuration:Float) { + //var group = new EventIconGroup(); + //group.add(generateDefaultIcon(startIcon)); var xOffset = 4; var yGap = 24; - var endGap = 7; - if (endIcon == "") endGap = -2; + var endGap = 2; - if (duration >= 0.65) { //min time for showing arrow + if (stepDuration >= 0.55) { //min time for showing arrow var tail = new FlxSprite(xOffset, yGap); - var arrow = new FlxSprite(xOffset, (duration * 40) + endGap); + var arrow = new FlxSprite(xOffset, (stepDuration * 40) + endGap); var arrowSegment = new FlxSprite(xOffset, yGap); tail.frames = arrow.frames = arrowSegment.frames = Paths.getSparrowAtlas("editors/charter/event-icons/components/arrow-down"); @@ -282,7 +290,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { group.add({ arrowSegment.animation.addByPrefix("segment", "segment"); arrowSegment.animation.play("segment"); - arrowSegment.scale.y = (duration * 40) - (tail.height + endGap + yGap); + arrowSegment.scale.y = endGap + (stepDuration * 40) - (tail.height + yGap); arrowSegment.updateHitbox(); arrowSegment.y += tail.height; arrowSegment; @@ -294,17 +302,6 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { arrow; }); } - - if (endIcon != "") { - group.add({ - var icon = generateDefaultIcon(endIcon); - icon.y = duration * 40; - icon; - }); - } - - - return group; } private static function generateEventIconWarning(group:EventIconGroup) { @@ -403,6 +400,10 @@ class EventIconGroup extends FlxSpriteGroup { if (copyColorTransformToChildren && colorTransform != null) for (child in members) child.colorTransform.__copyFrom(colorTransform); super.draw(); } + + override function update(elapsed:Float) { + super.update(elapsed); + } } class EventNumber extends FlxSprite { @@ -436,6 +437,7 @@ class EventNumber extends FlxSprite { } var ints = Std.int(Math.abs(number)); + if (ints == 0) this.digits.insert(0, 0); while (ints > 0) { this.digits.insert(0, ints % 10); ints = Std.int(ints / 10); From 3250cdb4366d93190f7d861dc80ce5fe79ec0f0e Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+NyxieFemboy@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:37:49 -0500 Subject: [PATCH 17/39] I think this should be everything? --- .../charter/event-icons/BPM Change End.png | Bin 215 -> 461 bytes .../charter/event-icons/BPM Change Start.png | Bin 204 -> 461 bytes .../charter/event-icons/components/end-plus.png | Bin 0 -> 427 bytes .../charter/event-icons/components/note.png | Bin 0 -> 402 bytes .../event-icons/components/start-plus.png | Bin 0 -> 427 bytes source/funkin/editors/charter/Charter.hx | 2 +- 6 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/images/editors/charter/event-icons/components/end-plus.png create mode 100644 assets/images/editors/charter/event-icons/components/note.png create mode 100644 assets/images/editors/charter/event-icons/components/start-plus.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 index c8e19949b03b6e8b7506d5667a6adf0e807bd3e6..28248791e3df48d2c3a33773ce5de83c4a78d803 100644 GIT binary patch literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G|^0G|+7Af06<`v3p`zeoBuC$IpymWIWeK#Hv-$S)Wu{{KJ2=KV|d14TFs zJR*x382Ao>Fr%o3R|8N`qQo_#Bsf2?|U7@5K1OhC3FBNNy+Q9yQPI}2Dm3&;k6 zPi2e@FMyte(P)-30Hr3dGl0BeU}S8-xBy}*$VS!$5R;|=*&x6KG?xjiGRV>b$b#xJ zG%x_k_E{eczbwAT56JZKba4#P2<|->$k$-N!F-`i_xt`$2OAHb;s2Rfo5FHL&t<~b z9k-d5PGS7UV$_(kKqXp8I_7FYjpg*Z1FsEKu4EiHRea3#1%;Y|Np=5@ASV%`Zg!9uxu90 zG7|*~@?B$Q15%77L4Lsu4$p3+0XY$#E{-7;w@UjxxsEvSFyHPD`Tzgpfs&5e&I8k% zCSBaw_}`b~L`t*6Ertma-Djp0>8RY4;x3pvUC3m|?bTPxw*OTM4=oS1W%+(~t3r~t ki;K@o@dtA%8MA-#C(dA+xA5jsf1o7{p00i_>zopr0K|eyQUCw| diff --git a/assets/images/editors/charter/event-icons/BPM Change Start.png b/assets/images/editors/charter/event-icons/BPM Change Start.png index 4011f562a415895872efde5165c1de32876780d0..166e71ed8bd76656c5024738ecb38a05bb835b48 100644 GIT binary patch literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G|^0G|+7AgyPW`~Uy{j=ld`sy73<3Af4(fD~IvkY6xR{QrN3&HI<^2a0eO zctjR6Fz_7$VMb96uLhu?M2TxeNpOBzNqJ&XDnmeGW?qS&pKFMMsh**p(eFLX+kk4e zrABzBd3tIwZ~!^13{s4&42(dQ7Z6KB*&x?zFfxP1nSg9VMkcUtqJZqob{4RB7LW}B zpUM~+UI0A_qtPs707^|@XJ7%UG%zwYU|aw(6=Wmp0*FadfNT(80-DPNRvBbz0c1gS z85)2BfT7R&VEAS6J$^u@kEe@ch(>Vlxj?=K0}kd3WxC(@Z#vj`@C^UY#M%^=BYG|q zzV5iqv~&vNHx{GDoCPY;Leeo;3u-K<*By9mpmHVSxT)e}{ueRUAD--Bc*Vv%J-Im4 P8Dxj2tDnm{r-UW|c>HfZ delta 176 zcmX@he1>s?NUx>sjRj zMT{lc{{kt-k|4ie28U-i(tw-*PZ!6Kid(h)o?HhQIF4K?{PbU4eAj}v3mNq%HXLJ} zV>}_?s95J-)`(9bAv7(-Y9;msZ__hvEl1a ZM%9%}jImSdD}jbHc)I$ztaD0e0swPoLy!Of diff --git a/assets/images/editors/charter/event-icons/components/end-plus.png b/assets/images/editors/charter/event-icons/components/end-plus.png new file mode 100644 index 0000000000000000000000000000000000000000..540f2ba05abdeb4c3fec4128f87688107931c810 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&F%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}23GfMV%`y}Hd!%o30t-;Ub{T6DNL@*gU-19`{}~*#R(=8UISV`@iy0XB z4ude`@%$AjKtYKT*NBqf{Irtt#G+J&fW*wa5@05M2E zj7GDR0qE}u>GIB8}@G#l_{XhS3XaMte=P8<=OKysuS^1_nD3J94+p$~5 f|9JOo_9BQ!3HF6HKu+5QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKtYKT*NBqf{Irtt#G+J&fW*wa5@05M2Ej7GDRfti6}0y_f>P=$e!u>s=(h^ZhQtP3C}O#!k&fC*?0 z6If-Cr3H`$)n#a40Fr%q^_#`1TRBxgrlY5eV~9rZ-3bSI85DUAzqwui`~$;6rnwLP ztQ7LD6scnRa94MN3d74M&yE|JMx6dSQ&Z&hZoVt&-xf@LWutw6Hxo~2o`5)O$I~>B N!Je*uF6*2UngB!1Spon6 literal 0 HcmV?d00001 diff --git a/assets/images/editors/charter/event-icons/components/start-plus.png b/assets/images/editors/charter/event-icons/components/start-plus.png new file mode 100644 index 0000000000000000000000000000000000000000..36dc91d13c0dd2cde7a8f7404f5c814d353cab05 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&F%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}23GfMV)w9a&*!!QQdNWYKNb^@KNL@*gU-19`{}~*#R(=8UISV`@iy0XB z4ude`@%$AjKtYKT*NBqf{Irtt#G+J&fW*wa5@05M2E zj7GDR0qE}u>gCZIV?V3k3Z7C;tMm!W|HNcQE` zZx*L+mdKI;Vst0C|O5{Qv*} literal 0 HcmV?d00001 diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 55f74e707..f4d7c87e2 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -1247,7 +1247,7 @@ class Charter extends UIState { + '\nStep: ${curStep}' + '\nBeat: ${curBeat}' + '\nMeasure: ${curMeasure}' - + '\nBPM: ${Conductor.bpm}' + + '\nBPM: ${Math.floor(Conductor.bpm*1000)/1000}' + '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.stepsPerBeat}'; if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, __firstFrame ? 1 : 0.125))) From 8e88a1545ffb8c8444b417e6dd7b75689cc3fb4a Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+NyxieFemboy@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:38:05 -0500 Subject: [PATCH 18/39] fixed chart parsing --- .../funkin/backend/chart/FNFLegacyParser.hx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/source/funkin/backend/chart/FNFLegacyParser.hx b/source/funkin/backend/chart/FNFLegacyParser.hx index 94fa14eba..6bef760e8 100644 --- a/source/funkin/backend/chart/FNFLegacyParser.hx +++ b/source/funkin/backend/chart/FNFLegacyParser.hx @@ -51,6 +51,18 @@ class FNFLegacyParser { continue; // Yoshi Engine charts crash fix } + var newBeatsPerMeasure:Float = section.sectionBeats != null ? section.sectionBeats : data.beatsPerMeasure.getDefault(4); // Default to 4 if sectionBeats is null or undefined (oops :3) + + if (newBeatsPerMeasure != beatsPerMeasure) { + beatsPerMeasure = newBeatsPerMeasure; + + result.events.push({ + time: curTime, + name: "Time Signature Change", + params: [newBeatsPerMeasure, 4] + }); + } + if (camFocusedBF != (camFocusedBF = section.mustHitSection)) { result.events.push({ time: curTime, @@ -104,16 +116,6 @@ class FNFLegacyParser { }); } - if (section.sectionBeats != null && section.sectionBeats != beatsPerMeasure) { - beatsPerMeasure = section.sectionBeats != null ? section.sectionBeats : data.beatsPerMeasure.getDefault(4); - - result.events.push({ - time: curTime, - name: "Time Signature Change", - params: [section.sectionBeats, 4] - }); - } - curTime += curCrochet * beatsPerMeasure; } } From c81aaf171a213490a727204bd8542ce6cae45b81 Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Sat, 1 Mar 2025 19:24:49 +0100 Subject: [PATCH 19/39] Optimize images --- .../charter/event-icons/BPM Change End.png | Bin 461 -> 158 bytes .../charter/event-icons/BPM Change Start.png | Bin 461 -> 158 bytes .../editors/charter/event-icons/BPM Change.png | Bin 186 -> 149 bytes .../event-icons/Time Signature Change.png | Bin 164 -> 112 bytes .../event-icons/components/arrow-down.png | Bin 122 -> 96 bytes .../charter/event-icons/components/end-plus.png | Bin 427 -> 124 bytes .../event-icons/components/eventNums.png | Bin 216 -> 181 bytes .../charter/event-icons/components/note.png | Bin 402 -> 94 bytes .../event-icons/components/start-plus.png | Bin 427 -> 124 bytes .../charter/event-icons/components/warning.png | Bin 125 -> 104 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/images/editors/charter/event-icons/BPM Change End.png b/assets/images/editors/charter/event-icons/BPM Change End.png index 28248791e3df48d2c3a33773ce5de83c4a78d803..6d5dd527c260401d8dc76e2491dc34bc39837b82 100644 GIT binary patch delta 141 zcmV;80CNA$1D*kp8Gi-<001BJ|6u?C0A)!;K~#7FV;}(j!x&j+qW?jdfdud$1b{SH z%g;>92}6nTN-K%}1mNS%G|^0G|+7Af06<`v3p`zeoBuC$IpymWIWeK#Hv-$S)Wu{{KJ2=KV|d14TFs zJR*x382Ao>Fr%o3R|8N`qQo_#Bsf2?|U7@5K1OhC3FBNNy+Q9yQPI}2Dm3&;k6 zPi2e@FMyte(P)-30Hr3dGl0BeU}S8-xBy}*$VS!$5R;|=*&x6KG?xjiGRV>b$b#xJ zG%x_k_E{eczbwAT56JZKba4#P2<|->$k$-N!F-`i_xt`$2OAHb;s2Rfo5FHL&t<~b z9k-d5PGS7UV$_(kKqXp8I_7FYjpg*Z1FsEKu4EiHRea3%g;>92}6nTN-K%}1mjXBX literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~mUKs7M+SzC z{oH>NS%G|^0G|+7AgyPW`~Uy{j=ld`sy73<3Af4(fD~IvkY6xR{QrN3&HI<^2a0eO zctjR6Fz_7$VMb96uLhu?M2TxeNpOBzNqJ&XDnmeGW?qS&pKFMMsh**p(eFLX+kk4e zrABzBd3tIwZ~!^13{s4&42(dQ7Z6KB*&x?zFfxP1nSg9VMkcUtqJZqob{4RB7LW}B zpUM~+UI0A_qtPs707^|@XJ7%UG%zwYU|aw(6=Wmp0*FadfNT(80-DPNRvBbz0c1gS z85)2BfT7R&VEAS6J$^u@kEe@ch(>Vlxj?=K0}kd3WxC(@Z#vj`@C^UY#M%^=BYG|q zzV5iqv~&vNHx{GDoCPY;Leeo;3u-K<*By9mpmHVSxT)e}{ueRUAD--Bc*Vv%J-Im4 P8Dxj2tDnm{r-UW|c>HfZ diff --git a/assets/images/editors/charter/event-icons/BPM Change.png b/assets/images/editors/charter/event-icons/BPM Change.png index 7157da8ee7dee831c79da6eb0532c5391a7ed97d..4eefebc3966547c5b651718196d4d1107f487d57 100644 GIT binary patch delta 132 zcmV-~0DJ$s0hIxe7<~u^0000V^Z#K0003M`L_t(|0nLvA27pi$1Vb0-;sJD!&X1jGG0WE3euWIZ>T1#k$Uf!~${xTTFSY9;X96GRh${{kW# mkAZLq(N|%fKr)7UXt@Di8Pxn*E7NlT0000@Kd#&f$DS;!r`MVHxA* zIa7M3hB)w~CnqNh?+|ej4>4?d$Y40(=tX5lFK#DQ7DGuT9syH@6)BUAtY delta 146 zcmXR|!Z<;)o|%DxK|_~67)UV|2e~^jtUD+363F2U@Ck7R(*OVe2a?6dnhSvxb4ie2 zFi_z7DPBh)*UHnyF+^f&azYaWn_37PvoiBm7KsZi4l0`%S{yhWLJzVE9Bc4s$z$V~ qE_gFaU^)k*$bBOQ#-8&m3^&d&c0@)rmGA(KVeoYIb6Mw<&;$UD$SLLk diff --git a/assets/images/editors/charter/event-icons/components/arrow-down.png b/assets/images/editors/charter/event-icons/components/arrow-down.png index 749279c56fe110b1ddd2e5da07052ee783167a4e..f44a87114aad1cf5d7137480278fe8ed54bd22a0 100644 GIT binary patch delta 77 zcmb}W7!NS16@O8S07?4u;ba4!+xRu<{^xvU_<=_4v|K&AYf*b!AH2x2Wv}oW~ g+%Y39!9tcnD%|I+`~H19fLa(lUHx3vIVCg!05T;UY5)KL delta 103 zcmYeOnjn$H#LU3Jp!7k64@mI@_=LCuX(0Ij|Nn~@FQQJLp9thImIV0)GdMiEkp|?* zc)B=-RKz7Glq4j~Z|FO++#qCa%7kd9Bx5TEhKkedCbDmBkAsZxboFyt=akR{0CLVD AR{#J2 diff --git a/assets/images/editors/charter/event-icons/components/end-plus.png b/assets/images/editors/charter/event-icons/components/end-plus.png index 540f2ba05abdeb4c3fec4128f87688107931c810..7629e754eceac23199c22d9224220253ce92d3ad 100644 GIT binary patch delta 105 zcmZ3@Tr)v3nT>&gq1WjAULfV{>EaktaVyz@Ju+?4e@6*liQtR(m1`4tk{*Zduxw=Q zam<->ZJ}mMa?0xjiDd$(+%1^XWbJsuJf>WAscutgbz!$)SUjQlrlWpd70@^aPgg&e IbxsLQ0J+;GS^xk5 literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&F%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}23GfMV%`y}Hd!%o30t-;Ub{T6DNL@*gU-19`{}~*#R(=8UISV`@iy0XB z4ude`@%$AjKtYKT*NBqf{Irtt#G+J&fW*wa5@05M2E zj7GDR0qE}u>GIB8}@G#l_{XhS3XaMte=P8<=OKysuS^1_nD3J94+p$~5 f|9JOoaR&rcp7LYsk&cisvEF-OvH(p R4M3|IJYD@<);T3K0RSqXJ{14} delta 200 zcmdnWc!P0*L_G^L0|P_kS+TP~ia)?7#1%+~hKBzC{~yQzk`8?nN`Nfpk|4ieApNWC z$UPuc>gnPbQW59df06Tu0gv<9Ql^EhUg{ki%?rNIe>vsF@@F3fjtF`#NYI$DCS;py z#gAo~ftTNF+*X=*;|0T9qpceQ+cy6=T6^_VkYmuCrh9%{UaQaBovL3r$#mJ%#-3Rb x%TJo$VayBWNhv&AH@QB1%@U?Q`y*Uu<^Mlb=d3hU3IMu*!PC{xWt~$(697a)RG$C< diff --git a/assets/images/editors/charter/event-icons/components/note.png b/assets/images/editors/charter/event-icons/components/note.png index 8f5a165babd92cbccea9145db3d0196eb74faee9..4b08e4f5ba594fc6105ac24029040d372c8c22bf 100644 GIT binary patch delta 75 zcmbQl95+GIm4$(Up`c@JHIP#Eba4!+xRva{^gqzz|9@sywi&_9BQ!3HF6HKu+5QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKtYKT*NBqf{Irtt#G+J&fW*wa5@05M2Ej7GDRfti6}0y_f>P=$e!u>s=(h^ZhQtP3C}O#!k&fC*?0 z6If-Cr3H`$)n#a40Fr%q^_#`1TRBxgrlY5eV~9rZ-3bSI85DUAzqwui`~$;6rnwLP ztQ7LD6scnRa94MN3d74M&yE|JMx6dSQ&Z&hZoVt&-xf@LWutw6Hxo~2o`5)O$I~>B N!Je*uF6*2UngB!1Spon6 diff --git a/assets/images/editors/charter/event-icons/components/start-plus.png b/assets/images/editors/charter/event-icons/components/start-plus.png index 36dc91d13c0dd2cde7a8f7404f5c814d353cab05..84b42f13615faa30f2b4dd4ab457ec23a0b2e55b 100644 GIT binary patch delta 105 zcmZ3@Tr)v3nT>&gq1WjAULfV{>EaktaVyz@y((mFBs~s25wusb z$1!KlwS}53$tkZBB$f%Ba<^bkleOas^O$ngrMgX})rH-HAzopr0Q!g}!~g&Q literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&F%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}23GfMV)w9a&*!!QQdNWYKNb^@KNL@*gU-19`{}~*#R(=8UISV`@iy0XB z4ude`@%$AjKtYKT*NBqf{Irtt#G+J&fW*wa5@05M2E zj7GDR0qE}u>gCZIV?V3k3Z7C;tMm!W|HNcQE` zZx*L+mdKI;Vst0C|O5{Qv*} diff --git a/assets/images/editors/charter/event-icons/components/warning.png b/assets/images/editors/charter/event-icons/components/warning.png index d55cf52f2c198e41816277648df12bedf3b732f8..03a423dbf4b312884afed7eab7c6469c2683da50 100644 GIT binary patch delta 85 zcmb=;m>?0r!N$PA@L(dF0Fcu6ba4!+U`#&1^v_s;;s5?W(|+?WIvL&)(6GQWoMo!R o;Tc>HgmdKI;Vst0N(!|TmS$7 delta 106 zcmd0(ogk6Q#LU3JF!%qvW+25G;1l8sq=DeeZ#kQn<~M;X#*!evUGggFccb2J5XwhDC^OG<1CvovUFvtnQfW@O!68l<)!WQwP&pUXO@geCxK C>K^t0 From 098f7474622b3ffd67ab97fea07067d177a2fdc5 Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+SenDoesStuff@users.noreply.github.com> Date: Tue, 4 Mar 2025 01:39:06 -0500 Subject: [PATCH 20/39] working on fixing camera beat timing, only issue is time sig changes on curBeat 0 now --- source/funkin/game/PlayState.hx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index e26e2e3a4..4c9834aeb 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -232,6 +232,14 @@ class PlayState extends MusicBeatState * How strong the cam zooms should be (defaults to 1) */ public var camZoomingStrength:Float = 1; + /** + * Number of Beats since the last Time Signature Change + */ + public var beatsSinceChange:Float = 0; + /** + * The curBeat position of the last Time Signature Change that occured + */ + public var lastTimeSigBeat:Float = 0; /** * Maximum amount of zoom for the camera. */ @@ -1405,7 +1413,9 @@ class PlayState extends MusicBeatState if (strumLines.members[event.params[0]] != null && strumLines.members[event.params[0]].characters != null) for (char in strumLines.members[event.params[0]].characters) if (char != null) char.playAnim(event.params[1], event.params[2], null); - case "Time Signature Change": // automatically handled by conductor + case "Time Signature Change": + lastTimeSigBeat = Conductor.curBeat; + // the rest is automatically handled by conductor case "Unknown": // nothing } } @@ -1817,10 +1827,10 @@ class PlayState extends MusicBeatState override function beatHit(curBeat:Int) { super.beatHit(curBeat); + + beatsSinceChange = curBeat - lastTimeSigBeat; - if (camZoomingInterval < 1) camZoomingInterval = 1; - if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom && curBeat % camZoomingInterval == 0) - { + if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom && beatsSinceChange % camZoomingInterval == 0) { FlxG.camera.zoom += 0.015 * camZoomingStrength; camHUD.zoom += 0.03 * camZoomingStrength; } From 68f3a64466d5fc027ce08c5d3b507b164632755b Mon Sep 17 00:00:00 2001 From: Ne_Eo Date: Sat, 8 Mar 2025 19:14:24 +0100 Subject: [PATCH 21/39] Allow setting the positions for events --- source/funkin/backend/chart/EventsData.hx | 12 ++++++++- .../editors/charter/CharterEventScreen.hx | 26 +++++++++++-------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index e9f5e79b7..97eefd7f1 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -100,7 +100,14 @@ class EventsData { var finalParams:Array = []; for (paramData in cast(data.params, Array)) { try { - finalParams.push({name: paramData.name, type: hscriptInterp.expr(hscriptParser.parseString(paramData.type)), defValue: paramData.defaultValue}); + finalParams.push({ + name: paramData.name, + type: hscriptInterp.expr(hscriptParser.parseString(paramData.type)), + defValue: paramData.defaultValue, + + x: paramData.x, + y: paramData.y + }); } catch (e) {trace('Error parsing event param ${paramData.name} - ${eventName}: $e'); finalParams.push(null);} } eventsParams.set(eventName, finalParams); @@ -120,6 +127,9 @@ typedef EventParamInfo = { var name:String; var type:EventParamType; var defValue:Dynamic; + + @:optional var x:Float; + @:optional var y:Float; } enum EventParamType { diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index 3c2ad340b..b2feccca9 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -144,11 +144,15 @@ class CharterEventScreen extends UISubstateWindow { } // add new elements - var y:Float = eventName.y + eventName.height + 10; + var _y:Float = eventName.y + eventName.height + 10; var params = EventsData.getEventParams(event.name); for(k=>param in params) { + var x = eventName.x + (param.x == null ? 0 : param.x); + var y = (param.y == null ? _y : param.y); + function addLabel() { - var label:UIText = new UIText(eventName.x, y, 0, param.name); + var label:UIText = new UIText(x, y, 0, param.name); + _y += label.height + 4; y += label.height + 4; paramsPanel.add(label); }; @@ -157,26 +161,26 @@ class CharterEventScreen extends UISubstateWindow { var lastAdded = switch(param.type) { case TString: addLabel(); - var textBox:UITextBox = new UITextBox(eventName.x, y, cast value); + var textBox:UITextBox = new UITextBox(x, y, cast value); paramsPanel.add(textBox); paramsFields.push(textBox); textBox; case TBool: - var checkbox = new UICheckbox(eventName.x, y, param.name, cast value); + var checkbox = new UICheckbox(x, y, param.name, cast value); paramsPanel.add(checkbox); paramsFields.push(checkbox); checkbox; case TInt(min, max, step): addLabel(); - var numericStepper = new UINumericStepper(eventName.x, y, cast value, CoolUtil.getDefault(step, 1), 0, min, max); + var numericStepper = new UINumericStepper(x, y, cast value, CoolUtil.getDefault(step, 1), 0, min, max); paramsPanel.add(numericStepper); paramsFields.push(numericStepper); numericStepper; case TFloat(min, max, step, precision): addLabel(); - var numericStepper = new UINumericStepper(eventName.x, y, cast value, CoolUtil.getDefault(step, 1), precision, min, max); + var numericStepper = new UINumericStepper(x, y, cast value, CoolUtil.getDefault(step, 1), precision, min, max); paramsPanel.add(numericStepper); paramsFields.push(numericStepper); numericStepper; case TStrumLine: addLabel(); - var dropdown = new UIDropDown(eventName.x, y, 320, 32, [ + var dropdown = new UIDropDown(x, y, 320, 32, [ for(k=>s in cast(FlxG.state, Charter).strumLines.members) 'Strumline #${k+1} (${s.strumLine.characters[0]})' ], cast value); @@ -184,12 +188,12 @@ class CharterEventScreen extends UISubstateWindow { dropdown; case TColorWheel: addLabel(); - var colorWheel = new UIColorwheel(eventName.x, y, CoolUtil.getColorFromDynamic(value)); + var colorWheel = new UIColorwheel(x, y, CoolUtil.getColorFromDynamic(value)); paramsPanel.add(colorWheel); paramsFields.push(colorWheel); colorWheel; case TDropDown(options): addLabel(); - var dropdown = new UIDropDown(eventName.x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value)))); + var dropdown = new UIDropDown(x, y, 320, 32, options, Std.int(Math.abs(options.indexOf(cast value)))); paramsPanel.add(dropdown); paramsFields.push(dropdown); dropdown; default: @@ -197,9 +201,9 @@ class CharterEventScreen extends UISubstateWindow { null; } if (lastAdded is UISliceSprite) - y += cast(lastAdded, UISliceSprite).bHeight + 4; + _y += cast(lastAdded, UISliceSprite).bHeight + 4; else if (lastAdded is FlxSprite) - y += cast(lastAdded, FlxSprite).height + 6; + _y += cast(lastAdded, FlxSprite).height + 6; } if(script != null && !(script is DummyScript)) { From d43c043dce1eba55a936c1d3d7ab401d130a0b14 Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+SenDoesStuff@users.noreply.github.com> Date: Sat, 8 Mar 2025 16:13:47 -0500 Subject: [PATCH 22/39] bandaid fix for beat 0 i guess --- source/funkin/game/PlayState.hx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 4c9834aeb..c6fde0f21 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1414,7 +1414,8 @@ class PlayState extends MusicBeatState for (char in strumLines.members[event.params[0]].characters) if (char != null) char.playAnim(event.params[1], event.params[2], null); case "Time Signature Change": - lastTimeSigBeat = Conductor.curBeat; + if (Conductor.curBeat <= 0) lastTimeSigBeat = 0; + else lastTimeSigBeat = Conductor.curBeat; // the rest is automatically handled by conductor case "Unknown": // nothing } From 26ac15e17327a065d11ff303cc4649e24776815d Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+SenDoesStuff@users.noreply.github.com> Date: Sat, 8 Mar 2025 16:59:29 -0500 Subject: [PATCH 23/39] added a comment --- source/funkin/game/PlayState.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index c6fde0f21..b9c02d7f8 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1414,8 +1414,8 @@ class PlayState extends MusicBeatState for (char in strumLines.members[event.params[0]].characters) if (char != null) char.playAnim(event.params[1], event.params[2], null); case "Time Signature Change": - if (Conductor.curBeat <= 0) lastTimeSigBeat = 0; - else lastTimeSigBeat = Conductor.curBeat; + if (Conductor.curBeat <= 0) lastTimeSigBeat = 0; // figure out a real solution instead of this failsafe eventually -sen + else lastTimeSigBeat = Conductor.curBeat; // the rest is automatically handled by conductor case "Unknown": // nothing } From 62d57df27e837b13c9f2ec3cc58d6a032c28712b Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+SenDoesStuff@users.noreply.github.com> Date: Sat, 8 Mar 2025 19:01:01 -0500 Subject: [PATCH 24/39] i might be stupoid --- source/funkin/game/PlayState.hx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index b9c02d7f8..44387d4bc 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1414,8 +1414,7 @@ class PlayState extends MusicBeatState for (char in strumLines.members[event.params[0]].characters) if (char != null) char.playAnim(event.params[1], event.params[2], null); case "Time Signature Change": - if (Conductor.curBeat <= 0) lastTimeSigBeat = 0; // figure out a real solution instead of this failsafe eventually -sen - else lastTimeSigBeat = Conductor.curBeat; + lastTimeSigBeat = Conductor.getTimeInBeats(event.time); // the rest is automatically handled by conductor case "Unknown": // nothing } From 733428e7ee14bbcf29ca1b055d0f95d93f17746d Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+SenDoesStuff@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:32:55 -0400 Subject: [PATCH 25/39] i hope i put this in the right place --- source/funkin/backend/system/Conductor.hx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 01281ef85..ab00f1a1c 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -32,6 +32,8 @@ final class Conductor public static var onBeatHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onStepHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onBPMChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); + public static var onContinuousBPMChangeStart:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); + public static var onContinuousBPMChangeEnd:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); public static var onTimeSignatureChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); /** @@ -248,6 +250,7 @@ final class Conductor curChange.endStepTime = curChange.stepTime + params[1]; curChange.continuous = true; curChange.endSongTime = endTime; + onContinuousBPMChangeStart.dispatch(curChange.bpm, curChange.endSongTime); } return curChange; } @@ -329,6 +332,12 @@ final class Conductor if ((curChangeIndex = getTimeInChangeIndex(songPosition, curChangeIndex)) > 0) { var change = curChange; + + if (change.continuous && songPosition >= change.endSongTime) { + onContinuousBPMChangeEnd.dispatch(change.bpm, change.endSongTime); + change.continuous = false; //used to mark the current continuous change as 'ended' + } + curStepFloat = getTimeWithBPMInSteps(songPosition, curChangeIndex, getTimeWithIndexInBPM(songPosition, curChangeIndex)); curBeatFloat = change.beatTime + (curStepFloat - change.stepTime) / change.stepsPerBeat; curMeasureFloat = change.measureTime + (curBeatFloat - change.beatTime) / change.beatsPerMeasure; From 04fcedfe9f5c1ed0bb68cc812489f5b98169e615 Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+SenDoesStuff@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:50:43 -0400 Subject: [PATCH 26/39] Revert "i hope i put this in the right place" This reverts commit 733428e7ee14bbcf29ca1b055d0f95d93f17746d. --- source/funkin/backend/system/Conductor.hx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index ab00f1a1c..01281ef85 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -32,8 +32,6 @@ final class Conductor public static var onBeatHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onStepHit:FlxTypedSignalVoid> = new FlxTypedSignal(); public static var onBPMChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); - public static var onContinuousBPMChangeStart:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); - public static var onContinuousBPMChangeEnd:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); public static var onTimeSignatureChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal(); /** @@ -250,7 +248,6 @@ final class Conductor curChange.endStepTime = curChange.stepTime + params[1]; curChange.continuous = true; curChange.endSongTime = endTime; - onContinuousBPMChangeStart.dispatch(curChange.bpm, curChange.endSongTime); } return curChange; } @@ -332,12 +329,6 @@ final class Conductor if ((curChangeIndex = getTimeInChangeIndex(songPosition, curChangeIndex)) > 0) { var change = curChange; - - if (change.continuous && songPosition >= change.endSongTime) { - onContinuousBPMChangeEnd.dispatch(change.bpm, change.endSongTime); - change.continuous = false; //used to mark the current continuous change as 'ended' - } - curStepFloat = getTimeWithBPMInSteps(songPosition, curChangeIndex, getTimeWithIndexInBPM(songPosition, curChangeIndex)); curBeatFloat = change.beatTime + (curStepFloat - change.stepTime) / change.stepsPerBeat; curMeasureFloat = change.measureTime + (curBeatFloat - change.beatTime) / change.beatsPerMeasure; From 20efd35eef1a74983b01f5ef7bf8f392c2c6ce13 Mon Sep 17 00:00:00 2001 From: NyxieFemboy <132237065+SenDoesStuff@users.noreply.github.com> Date: Thu, 13 Mar 2025 18:42:39 -0400 Subject: [PATCH 27/39] MathUtil --- source/funkin/backend/utils/MathUtil.hx | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 source/funkin/backend/utils/MathUtil.hx diff --git a/source/funkin/backend/utils/MathUtil.hx b/source/funkin/backend/utils/MathUtil.hx new file mode 100644 index 000000000..833f7328e --- /dev/null +++ b/source/funkin/backend/utils/MathUtil.hx @@ -0,0 +1,28 @@ +package funkin.backend.utils; + +final class MathUtil { + //Remind me to add the descriptions for the Wiki later - sen + public static inline function lessThan(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool { + return aVal < bVal - diff; + } + + public static inline function lessThanEqual(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool { + return aVal <= bVal + diff; + } + + public static inline function greaterThan(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool { + return aVal > bVal + diff; + } + + public static inline function greaterThanEqual(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool { + return aVal >= bVal - diff; + } + + public static inline function equal(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool { + return Math.abs(aVal - bVal) <= diff; + } + + public static inline function notEqual(aVal:Float, bVal:Float, diff:Float = FlxMath.EPSILON):Bool { + return Math.abs(aVal - bVal) > diff; + } +} \ No newline at end of file From 4d7288bf4ea1e25c8f984d42f7aed4cc84fb49ee Mon Sep 17 00:00:00 2001 From: SenDoesStuff <132237065+SenDoesStuff@users.noreply.github.com> Date: Mon, 5 May 2025 09:24:05 -0400 Subject: [PATCH 28/39] variable name change --- source/funkin/game/PlayState.hx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 44387d4bc..810120dd1 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -233,9 +233,10 @@ class PlayState extends MusicBeatState */ public var camZoomingStrength:Float = 1; /** - * Number of Beats since the last Time Signature Change + * Number of Beats to offset camZooming by. + * Will automatically be set when a Time Signature Change Occurs. */ - public var beatsSinceChange:Float = 0; + public var camZoomingOffset:Float = 0; /** * The curBeat position of the last Time Signature Change that occured */ @@ -1828,9 +1829,9 @@ class PlayState extends MusicBeatState { super.beatHit(curBeat); - beatsSinceChange = curBeat - lastTimeSigBeat; + camZoomingOffset = curBeat - lastTimeSigBeat; - if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom && beatsSinceChange % camZoomingInterval == 0) { + if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom && camZoomingOffset % camZoomingInterval == 0) { FlxG.camera.zoom += 0.015 * camZoomingStrength; camHUD.zoom += 0.03 * camZoomingStrength; } From 2b087dc96ef31a5b76a7225972bd0c35d33c00a9 Mon Sep 17 00:00:00 2001 From: TheZoroForce240 <86524550+TheZoroForce240@users.noreply.github.com> Date: Mon, 5 May 2025 17:53:43 +0100 Subject: [PATCH 29/39] floating point fixes (hopefully) --- source/funkin/backend/system/Conductor.hx | 6 +++--- source/funkin/editors/charter/CharterEventTypeSelection.hx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 01281ef85..10bc8a42e 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -203,7 +203,7 @@ final class Conductor var events:Array = []; for (e in song.events) if (e.params != null && (e.name == "BPM Change" || e.name == "Time Signature Change" || e.name == "Continuous BPM Change")) events.push(e); events.sort(function(a, b) { - if (a.time == b.time) { + if (MathUtil.equal(a.time, b.time)) { if (a.name == "Continuous BPM Change") return 1; if (b.name == "Continuous BPM Change") return -1; } @@ -217,7 +217,7 @@ final class Conductor private static function mapEvent(e:ChartEvent, curChange:BPMChangeEvent) { var name = e.name, params = e.params, time = e.time; - if (curChange.continuous && time < curChange.endSongTime) { //ensure that you cannot place any conductor events during a continuous change + 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; @@ -268,7 +268,7 @@ final class Conductor for(event in Charter.instance.eventsGroup.members) { event.events.sort(function(a, b) { - if (a.time == b.time) { + if (MathUtil.equal(a.time, b.time)) { if (a.name == "Continuous BPM Change") return 1; if (b.name == "Continuous BPM Change") return -1; } diff --git a/source/funkin/editors/charter/CharterEventTypeSelection.hx b/source/funkin/editors/charter/CharterEventTypeSelection.hx index 0e836e771..1b1754c66 100644 --- a/source/funkin/editors/charter/CharterEventTypeSelection.hx +++ b/source/funkin/editors/charter/CharterEventTypeSelection.hx @@ -38,8 +38,8 @@ class CharterEventTypeSelection extends UISubstateWindow { var disableConductorEvents:Bool = false; var disableOnlyContinuousChanges:Bool = false; for (change in Conductor.bpmChangeMap) { - if (change.continuous && eventStepTime >= change.stepTime && eventStepTime < change.endStepTime) { - disableOnlyContinuousChanges = eventStepTime == change.stepTime; //allow time sig and instant bpm changes on the same event + if (change.continuous && MathUtil.greaterThanEqual(eventStepTime, change.stepTime) && MathUtil.lessThan(eventStepTime, change.endStepTime)) { + disableOnlyContinuousChanges = MathUtil.equal(eventStepTime, change.stepTime); //allow time sig and instant bpm changes on the same event disableConductorEvents = !disableOnlyContinuousChanges; } } From 241b87ad5880ea3e33b71c10f76ee71a8554d7c1 Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:49:38 +0700 Subject: [PATCH 30/39] Change event params a bit and fix cam zooming --- source/funkin/backend/chart/EventsData.hx | 2 +- source/funkin/backend/system/Conductor.hx | 48 ++++++++---- .../backend/system/framerate/ConductorInfo.hx | 2 +- source/funkin/editors/charter/Charter.hx | 13 ++-- source/funkin/editors/charter/CharterEvent.hx | 10 +-- .../editors/charter/CharterMetaDataScreen.hx | 12 +-- .../editors/charter/SongCreationScreen.hx | 12 +-- source/funkin/game/PlayState.hx | 76 ++++++++++++------- .../options/categories/GameplayOptions.hx | 2 +- 9 files changed, 107 insertions(+), 70 deletions(-) diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index 97eefd7f1..a46de5548 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -33,7 +33,7 @@ class EventsData { ], "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}], "Continuous BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}, {name: "Time (steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}], - "Time Signature Change" => [{name: "Target Beat Count", type: TFloat(1), defValue: 4}, {name: "Target Step Count", type: TFloat(1), defValue: 4}], + "Time Signature Change" => [{name: "Target Numerator", type: TFloat(1), defValue: 4}, {name: "Target Denominator", type: TFloat(1), defValue: 4}], "Scroll Speed Change" => [ {name: "Tween Speed?", type: TBool, defValue: true}, {name: "New Speed", type: TFloat(0.01, 99, 0.01, 2), defValue: 1.}, diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 10bc8a42e..d535a9a9d 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -6,6 +6,12 @@ import funkin.backend.chart.ChartData; import funkin.backend.system.interfaces.IBeatReceiver; import funkin.editors.charter.Charter; +enum BeatType { + STEP; + BEAT; + MEASURE; +} + @:structInit class BPMChangeEvent { @@ -25,6 +31,16 @@ class BPMChangeEvent 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 */ @@ -96,12 +112,21 @@ final class Conductor return bpmChangeMap.length == 0 ? 4 : bpmChangeMap[curChangeIndex].beatsPerMeasure; /** - * Number of steps per beat (bottom number in time signature). Defaults to 4. + * 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):Float; + public static var stepsPerBeat(get, never):Int; private static function get_stepsPerBeat() return bpmChangeMap.length == 0 ? 4 : 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):Int; + private static function get_denominator() return Math.floor(16 / stepsPerBeat); + /** * Current step */ @@ -231,13 +256,13 @@ final class Conductor if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm); curChange.beatsPerMeasure = params[0]; - curChange.stepsPerBeat = params[1]; - + 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") { - + } + else if (name == "Continuous BPM Change") { var prevBPM = curChange.bpm; if (curChange.bpm == params[0]) { invalidEvents.push(e); @@ -266,7 +291,7 @@ final class Conductor bpmChangeMap = [curChange]; invalidEvents = []; - for(event in Charter.instance.eventsGroup.members) { + for (event in Charter.instance.eventsGroup.members) { event.events.sort(function(a, b) { if (MathUtil.equal(a.time, b.time)) { if (a.name == "Continuous BPM Change") return 1; @@ -282,11 +307,8 @@ final class Conductor if ((e.name == "BPM Change" || e.name == "Time Signature Change" || e.name == "Continuous BPM Change")) { curChange = mapEvent(e, curChange); } - } } - - } private static var elapsed:Float; @@ -303,12 +325,10 @@ final 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) { diff --git a/source/funkin/backend/system/framerate/ConductorInfo.hx b/source/funkin/backend/system/framerate/ConductorInfo.hx index 96f4e39d8..1afb043b4 100644 --- a/source/funkin/backend/system/framerate/ConductorInfo.hx +++ b/source/funkin/backend/system/framerate/ConductorInfo.hx @@ -13,7 +13,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); diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 96855473f..301e23067 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -1243,12 +1243,13 @@ class Charter extends UIState { for (strumLine in strumLines.members) strumLine.vocals.pause(); } - songPosInfo.text = '${CoolUtil.timeToStr(Conductor.songPosition)} / ${CoolUtil.timeToStr(songLength)}' - + '\nStep: ${curStep}' - + '\nBeat: ${curBeat}' - + '\nMeasure: ${curMeasure}' - + '\nBPM: ${Math.floor(Conductor.bpm*1000)/1000}' - + '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.stepsPerBeat}'; + var curChange = Conductor.curChange; + songPosInfo.text = '${CoolUtil.timeToStr(songPos)} / ${CoolUtil.timeToStr(songLength)}' + + '\nStep: $curStep' + + '\nBeat: $curBeat' + + '\nMeasure: $curMeasure' + + '\nBPM: ${(curChange != null && curChange.continuous && curChange.endSongTime > songPos) ? CoolUtil.quantize(Conductor.bpm, 2) : Conductor.bpm}' + + '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.denominator}'; if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, __firstFrame ? 1 : 0.125))) updateDisplaySprites(); diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index 9dcee4b1e..8386f5e38 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -140,7 +140,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { graphic = defaultPath; } - return new FlxSprite().loadGraphic(graphic); + return new FlxSprite(graphic); } /** @@ -154,7 +154,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { public static function getEventComponent(type:String, x:Float = 0.0, y:Float = 0.0) { var componentPath = Paths.image("editors/charter/event-icons/components/" + type); if(Assets.exists(componentPath)) - return new FlxSprite(x, y).loadGraphic(componentPath); + return new FlxSprite(x, y, componentPath); Logs.trace('Could not find component $type', WARNING); return null; @@ -423,12 +423,10 @@ class EventNumber extends FlxSprite { this.align = align; this.spacing = spacing; - - if (number == 0) { this.digits.insert(0, 0); - } else { - + } + else { var decimals:Float = FlxMath.roundDecimal(Math.abs(number % 1), precision); if (decimals > 0) this.digits.insert(0, FRAME_POINT); while(decimals > 0) { diff --git a/source/funkin/editors/charter/CharterMetaDataScreen.hx b/source/funkin/editors/charter/CharterMetaDataScreen.hx index e58898c08..3266b3a28 100644 --- a/source/funkin/editors/charter/CharterMetaDataScreen.hx +++ b/source/funkin/editors/charter/CharterMetaDataScreen.hx @@ -15,7 +15,7 @@ class CharterMetaDataScreen extends UISubstateWindow { public var songNameTextBox:UITextBox; public var bpmStepper:UINumericStepper; public var beatsPerMeasureStepper:UINumericStepper; - public var stepsPerBeatStepper :UINumericStepper; + public var denominatorStepper :UINumericStepper; public var needsVoicesCheckbox:UICheckbox; public var customPropertiesButtonList:UIButtonList; @@ -63,10 +63,10 @@ class CharterMetaDataScreen extends UISubstateWindow { add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22)); - stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, metadata.stepsPerBeat, 1, 0, 1, null, 54); - add(stepsPerBeatStepper); + denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, Math.floor(16 / metadata.stepsPerBeat), 1, 0, 1, null, 54); + add(denominatorStepper); - needsVoicesCheckbox = new UICheckbox(stepsPerBeatStepper.x + 80 + 26, stepsPerBeatStepper.y, "Voices", metadata.needsVoices); + needsVoicesCheckbox = new UICheckbox(denominatorStepper.x + 80 + 26, denominatorStepper.y, "Voices", metadata.needsVoices); add(needsVoicesCheckbox); addLabelOn(needsVoicesCheckbox, "Needs Voices"); needsVoicesCheckbox.y += 6; needsVoicesCheckbox.x += 4; @@ -142,7 +142,7 @@ class CharterMetaDataScreen extends UISubstateWindow { } public function saveMeta() { - for (stepper in [bpmStepper, beatsPerMeasureStepper, stepsPerBeatStepper]) + for (stepper in [bpmStepper, beatsPerMeasureStepper, denominatorStepper]) @:privateAccess stepper.__onChange(stepper.label.text); var customVals = {}; @@ -154,7 +154,7 @@ class CharterMetaDataScreen extends UISubstateWindow { name: songNameTextBox.label.text, bpm: bpmStepper.value, beatsPerMeasure: Std.int(beatsPerMeasureStepper.value), - stepsPerBeat: Std.int(stepsPerBeatStepper.value), + stepsPerBeat: Std.int(16 / denominatorStepper.value), needsVoices: needsVoicesCheckbox.checked, displayName: displayNameTextBox.label.text, icon: iconTextBox.label.text, diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index 007c8f6c0..d3c1b0bc0 100644 --- a/source/funkin/editors/charter/SongCreationScreen.hx +++ b/source/funkin/editors/charter/SongCreationScreen.hx @@ -19,7 +19,7 @@ class SongCreationScreen extends UISubstateWindow { public var songNameTextBox:UITextBox; public var bpmStepper:UINumericStepper; public var beatsPerMeasureStepper:UINumericStepper; - public var stepsPerBeatStepper :UINumericStepper; + public var denominatorStepper :UINumericStepper; public var needsVoicesCheckbox:UICheckbox; public var instExplorer:UIFileExplorer; public var voicesExplorer:UIFileExplorer; @@ -79,12 +79,12 @@ class SongCreationScreen extends UISubstateWindow { songDataGroup.add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22)); - stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54); - songDataGroup.add(stepsPerBeatStepper); + denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54); + songDataGroup.add(denominatorStepper); var voicesUIText:UIText = null; - needsVoicesCheckbox = new UICheckbox(stepsPerBeatStepper.x + 80 + 26, stepsPerBeatStepper.y, "Voices", true); + needsVoicesCheckbox = new UICheckbox(denominatorStepper.x + 80 + 26, denominatorStepper.y, "Voices", true); needsVoicesCheckbox.onChecked = function(checked) { if (voicesExplorer == null) return; @@ -243,14 +243,14 @@ class SongCreationScreen extends UISubstateWindow { } function saveSongInfo() { - for (stepper in [bpmStepper, beatsPerMeasureStepper, stepsPerBeatStepper]) + for (stepper in [bpmStepper, beatsPerMeasureStepper, denominatorStepper]) @:privateAccess stepper.__onChange(stepper.label.text); var meta:ChartMetaData = { name: songNameTextBox.label.text, bpm: bpmStepper.value, beatsPerMeasure: Std.int(beatsPerMeasureStepper.value), - stepsPerBeat: Std.int(stepsPerBeatStepper.value), + stepsPerBeat: Std.int(16 / denominatorStepper.value), needsVoices: needsVoicesCheckbox.checked, displayName: displayNameTextBox.label.text, icon: iconTextBox.label.text, diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 1744c3acf..bbb95c494 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -224,23 +224,29 @@ class PlayState extends MusicBeatState */ public var camZooming:Bool = false; /** - * Interval of cam zooming (beats). - * For example: if set to 4, the camera will zoom every 4 beats. + * Interval of cam zooming (in conductor values). + * Example: If Interval is 1 and Beat Type is on MEASURE, it'll zoom every a measure. + * NOTE: Will set to 4 if not found any other time signatures unlike 4/4 */ - public var camZoomingInterval:Int = 4; + public var camZoomingInterval:Float = 1; /** - * How strong the cam zooms should be (defaults to 1) + * Number of Conductor values to offset camZooming by. */ - public var camZoomingStrength:Float = 1; + public var camZoomingOffset:Float = 0; /** - * Number of Beats to offset camZooming by. - * Will automatically be set when a Time Signature Change Occurs. + * Beat type for interval of cam zooming. + * Example: If Beat Type is on STEP and Interval is 2, it'll zoom every 2 steps. + * NOTE: Will set to BEAT if not found any other time signatures unlike 4/4 */ - public var camZoomingOffset:Float = 0; + public var camZoomingEvery:BeatType = MEASURE; /** - * The curBeat position of the last Time Signature Change that occured + * Stores what was the last beat for the cam zooming intervals. */ - public var lastTimeSigBeat:Float = 0; + public var camZoomingLastBeat:Float; + /** + * How strong the cam zooms should be (defaults to 1) + */ + public var camZoomingStrength:Float = 1; /** * Maximum amount of zoom for the camera. */ @@ -1020,21 +1026,34 @@ class PlayState extends MusicBeatState { if (songData == null) songData = SONG; - events = songData.events != null ? [for(e in songData.events) e] : []; - // get first camera focus - for(e in events) { - if (e.time > 10) break; - if (e.name == "Camera Movement") { - executeEvent(e); - break; + var foundCam = false; + var foundSigs = songData.meta.beatsPerMeasure.getDefault(4) != 4 || songData.meta.stepsPerBeat.getDefault(4) != 4; + + if (events == null) events = []; + else events = [ + for (e in songData.events) { + switch (e.name) { + case "Camera Movement": if (!foundCam && e.time < 10) { + foundCam = true; + executeEvent(e); + } + case "Time Signature Change": if (!foundSigs && (e.params[0] != 4 || e.params[1] != 4)) { + foundSigs = true; + } + } + e; } + ]; + + if (!foundSigs) { + camZoomingInterval = 4; + camZoomingEvery = BEAT; } + events.sort(function(p1, p2) { return FlxSort.byValues(FlxSort.DESCENDING, p1.time, p2.time); }); - camZoomingInterval = cast songData.meta.beatsPerMeasure.getDefault(4); - curSong = songData.meta.name.toLowerCase(); inst = FlxG.sound.load(Paths.inst(SONG.meta.name, difficulty)); @@ -1284,6 +1303,15 @@ class PlayState extends MusicBeatState } } + if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom) { + var beat = Conductor.getBeats(camZoomingEvery, camZoomingInterval, camZoomingOffset); + if (camZoomingLastBeat != beat) { + camZoomingLastBeat = beat; + FlxG.camera.zoom += 0.015 * camZoomingStrength; + camHUD.zoom += 0.03 * camZoomingStrength; + } + } + if (doIconBop) for (icon in iconArray) if (icon.updateBump != null) @@ -1470,9 +1498,6 @@ class PlayState extends MusicBeatState if (strumLines.members[event.params[0]] != null && strumLines.members[event.params[0]].characters != null) for (char in strumLines.members[event.params[0]].characters) if (char != null) char.playAnim(event.params[1], event.params[2], null); - case "Time Signature Change": - lastTimeSigBeat = Conductor.getTimeInBeats(event.time); - // the rest is automatically handled by conductor case "Unknown": // nothing } } @@ -1885,13 +1910,6 @@ class PlayState extends MusicBeatState override function beatHit(curBeat:Int) { super.beatHit(curBeat); - - camZoomingOffset = curBeat - lastTimeSigBeat; - - if (Options.camZoomOnBeat && camZooming && FlxG.camera.zoom < maxCamZoom && camZoomingOffset % camZoomingInterval == 0) { - FlxG.camera.zoom += 0.015 * camZoomingStrength; - camHUD.zoom += 0.03 * camZoomingStrength; - } if (doIconBop) for (icon in iconArray) diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 15c0ba603..8bd3c49bb 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -53,7 +53,7 @@ class GameplayOptions extends OptionsScreen { __lastBeat = Conductor.curBeat; } - var beat = Math.floor(Conductor.getStepForTime(FlxG.sound.music.time) / Conductor.stepsPerBeat); + var beat = Math.floor(Conductor.getTimeInBeats(FlxG.sound.music.time)); if (__lastSongBeat != beat) { __metronome.replay(); __lastSongBeat = beat; From 9982236321afd36adb0d7dca13918b39f9fa1c0b Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Mon, 30 Jun 2025 22:29:21 +0700 Subject: [PATCH 31/39] Whoops! --- source/funkin/backend/chart/EventsData.hx | 6 ++++-- source/funkin/backend/system/Conductor.hx | 4 ++-- source/funkin/game/PlayState.hx | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index a46de5548..7dd011970 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -22,8 +22,10 @@ class EventsData { {name: "Camera", type: TDropDown(['camGame', 'camHUD']), defValue: "camGame"} ], "Camera Modulo Change" => [ - {name: "Modulo Interval (Beats)", type: TInt(1, 9999999, 1), defValue: 4}, - {name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1} + {name: "Modulo Interval", type: TInt(1, 9999999, 1), defValue: 4}, + {name: "Bump Strength", type: TFloat(0.1, 10, 0.01, 2), defValue: 1}, + {name: "Every Beat Type", type: TDropDown(['BEAT', 'MEASURE', 'STEP']), defValue: 'BEAT'}, + {name: "Beat Offset", type: TFloat(-10, 10, 0.25, 2), defValue: 0} ], "Camera Flash" => [ {name: "Reversed?", type: TBool, defValue: false}, diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index d535a9a9d..c7a2de632 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -7,9 +7,9 @@ import funkin.backend.system.interfaces.IBeatReceiver; import funkin.editors.charter.Charter; enum BeatType { - STEP; BEAT; MEASURE; + STEP; } @:structInit @@ -38,7 +38,7 @@ final class Conductor default: curBeatFloat; } if (interval <= 0) return beat - offset; - else return Math.floor((beat - offset) * interval) / interval; + else return Math.floor((beat - offset) / interval) * interval; } /** diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index bbb95c494..ba920a5e3 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1468,6 +1468,12 @@ class PlayState extends MusicBeatState case "Camera Modulo Change": camZoomingInterval = event.params[0]; camZoomingStrength = event.params[1]; + camZoomingEvery = switch (event.params[2].toUpperCase()) { + case "STEP": STEP; + case "MEASURE": MEASURE; + default: BEAT; + } + camZoomingOffset = event.params[2]; case "Camera Flash": var camera:FlxCamera = event.params[3] == "camHUD" ? camHUD : camGame; From 1a8646f4e89806fe9ebda718f16bbb7b75e238f7 Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Mon, 30 Jun 2025 22:39:15 +0700 Subject: [PATCH 32/39] Update PlayState.hx --- source/funkin/game/PlayState.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index ba920a5e3..0c2a8798c 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1468,12 +1468,12 @@ class PlayState extends MusicBeatState case "Camera Modulo Change": camZoomingInterval = event.params[0]; camZoomingStrength = event.params[1]; - camZoomingEvery = switch (event.params[2].toUpperCase()) { + if (event.params[2] != null) camZoomingEvery = switch (event.params[2].toUpperCase()) { case "STEP": STEP; case "MEASURE": MEASURE; default: BEAT; } - camZoomingOffset = event.params[2]; + if (event.params[3] != null) camZoomingOffset = event.params[3]; case "Camera Flash": var camera:FlxCamera = event.params[3] == "camHUD" ? camHUD : camGame; From 7003a98c9dc0336208a94e18b58a2fd22b90b660 Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 5 Jul 2025 14:17:50 +0700 Subject: [PATCH 33/39] Update Charter.hx --- source/funkin/editors/charter/Charter.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 301e23067..7154939ce 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -1248,7 +1248,7 @@ class Charter extends UIState { + '\nStep: $curStep' + '\nBeat: $curBeat' + '\nMeasure: $curMeasure' - + '\nBPM: ${(curChange != null && curChange.continuous && curChange.endSongTime > songPos) ? CoolUtil.quantize(Conductor.bpm, 2) : Conductor.bpm}' + + '\nBPM: ${(curChange != null && curChange.continuous && curChange.endSongTime > songPos) ? FlxMath.roundDecimal(Conductor.bpm, 3) : Conductor.bpm}' + '\nTime Signature: ${Conductor.beatsPerMeasure}/${Conductor.denominator}'; if (charterCamera.zoom != (charterCamera.zoom = lerp(charterCamera.zoom, __camZoom, __firstFrame ? 1 : 0.125))) From 2b533d62f571414f1e6740c9f1bc16d25cbc6557 Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 5 Jul 2025 18:19:49 +0700 Subject: [PATCH 34/39] dummyChange & lastBeatChange --- source/funkin/backend/system/Conductor.hx | 37 +++++++++++++++++------ 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index c7a2de632..3282dd8e3 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -73,9 +73,10 @@ final class Conductor /** * Current bpmChangeMap */ - public static var curChange(get, never):Null; + 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[curChangeIndex]; + return bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]; /** * Current BPM @@ -89,7 +90,7 @@ final class Conductor */ public static var startingBPM(get, never):Float; private static function get_startingBPM() - return bpmChangeMap.length == 0 ? 100 : bpmChangeMap[0].bpm; + return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[0]).bpm; /** * Current Crochet (time per beat), in milliseconds. @@ -109,7 +110,7 @@ final class Conductor */ public static var beatsPerMeasure(get, never):Float; private static function get_beatsPerMeasure() - return bpmChangeMap.length == 0 ? 4 : bpmChangeMap[curChangeIndex].beatsPerMeasure; + return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).beatsPerMeasure; /** * Number of steps per beat. Defaults to 4. @@ -118,7 +119,7 @@ final class Conductor */ public static var stepsPerBeat(get, never):Int; private static function get_stepsPerBeat() - return bpmChangeMap.length == 0 ? 4 : bpmChangeMap[curChangeIndex].stepsPerBeat; + return (bpmChangeMap.length == 0 ? dummyChange : bpmChangeMap[curChangeIndex]).stepsPerBeat; /** * How much value notes to divide for beat (bottom or divisor number in time signature). @@ -127,6 +128,24 @@ final class Conductor public static var denominator(get, never):Int; private static function get_denominator() return Math.floor(16 / stepsPerBeat); + /** + * 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 */ @@ -484,7 +503,7 @@ final class Conductor } public static function getTimeInBPM(time:Float):Float { - if (bpmChangeMap.length == 0) return 100; + if (bpmChangeMap.length == 0) return dummyChange.bpm; return getTimeWithIndexInBPM(time, getTimeInChangeIndex(time)); } @@ -505,7 +524,7 @@ final class Conductor public static function getTimeInBeats(time:Float, from:Int = 0):Float { var index = getTimeInChangeIndex(time, from); - if (index == -1) return time / (60000 / 100); + 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]; @@ -540,7 +559,7 @@ 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); + 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]; @@ -551,7 +570,7 @@ final class Conductor public static function getBeatsInTime(beatTime:Float, from:Int = 0):Float { var index = getBeatsInChangeIndex(beatTime, from); - if (index == -1) return beatTime * (60000 / 100); + 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]; From a5c208f4d311a25f637e7ae4ad58529e138218d8 Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 5 Jul 2025 18:20:21 +0700 Subject: [PATCH 35/39] Try to fix the characters dancing fast? --- source/funkin/backend/FunkinSprite.hx | 5 +++-- source/funkin/game/Character.hx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index 2f999e0dc..d8e2af507 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -15,6 +15,7 @@ import flixel.math.FlxRect; import flixel.math.FlxPoint; import flixel.util.typeLimit.OneOfTwo; import funkin.backend.system.interfaces.IBeatReceiver; +import funkin.backend.system.Conductor; enum abstract XMLAnimType(Int) { @@ -140,9 +141,9 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I private var countedBeat = 0; public function beatHit(curBeat:Int) { - if (beatAnims.length > 0 && (curBeat + beatOffset) % beatInterval == 0) + if (Conductor.curBeat != curBeat || (skipNegativeBeats && curBeat < 0)) return; + if (beatAnims.length > 0 && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.minInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0) { - if(skipNegativeBeats && curBeat < 0) return; // TODO: find a solution without countedBeat var anim = beatAnims[FlxMath.wrap(countedBeat++, 0, beatAnims.length - 1)]; if (anim.name != null && anim.name != "null" && anim.name != "none") diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index 456dcae3c..5d9643ce6 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -167,13 +167,14 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset } /** - * Whenever the character should dance on beat or not. Set to false for `gf`, since the dance animation is automatically handled by PlayState. + * Whenever the character should dance on beat or not. */ public var danceOnBeat:Bool = true; public override function beatHit(curBeat:Int) { scripts.call("beatHit", [curBeat]); - if (danceOnBeat && (curBeat + beatOffset) % beatInterval == 0 && !__lockAnimThisFrame) + if (Conductor.curBeat != curBeat || (skipNegativeBeats && curBeat < 0)) return; + if (danceOnBeat && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.minInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0 && !__lockAnimThisFrame) tryDance(); } From e2ff493bdf38de51134d3e4b67a36d5f5c33c02c Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 5 Jul 2025 18:29:39 +0700 Subject: [PATCH 36/39] Try to fix the characters dancing fast? 2 god damn it i got confused max with min sometime --- source/funkin/backend/FunkinSprite.hx | 2 +- source/funkin/game/Character.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index d8e2af507..60e131e3c 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -142,7 +142,7 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I public function beatHit(curBeat:Int) { if (Conductor.curBeat != curBeat || (skipNegativeBeats && curBeat < 0)) return; - if (beatAnims.length > 0 && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.minInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0) + if (beatAnims.length > 0 && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0) { // TODO: find a solution without countedBeat var anim = beatAnims[FlxMath.wrap(countedBeat++, 0, beatAnims.length - 1)]; diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index 5d9643ce6..ff1341b98 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -174,7 +174,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset scripts.call("beatHit", [curBeat]); if (Conductor.curBeat != curBeat || (skipNegativeBeats && curBeat < 0)) return; - if (danceOnBeat && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.minInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0 && !__lockAnimThisFrame) + if (danceOnBeat && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0 && !__lockAnimThisFrame) tryDance(); } From bdfb1d31b48b44e1bbe5a705ff63aebb1c5d841c Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 5 Jul 2025 19:06:48 +0700 Subject: [PATCH 37/39] Update Conductor.hx --- source/funkin/backend/system/Conductor.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 3282dd8e3..8e9755229 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -83,7 +83,7 @@ final class Conductor */ public static var bpm(get, never):Float; private static function get_bpm() - return curChangeIndex == 0 ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex); + return (curChangeIndex == 0 || bpmChange.length == 0) ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex); /** * Starting BPM From 5ee4ff2a108ca8fe9565f0103952caf7a57c951a Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sat, 5 Jul 2025 19:07:57 +0700 Subject: [PATCH 38/39] can you please lock in --- source/funkin/backend/system/Conductor.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index 8e9755229..fed6dd93a 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -83,7 +83,7 @@ final class Conductor */ public static var bpm(get, never):Float; private static function get_bpm() - return (curChangeIndex == 0 || bpmChange.length == 0) ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex); + return (curChangeIndex == 0 || bpmChangeMap.length == 0) ? startingBPM : getTimeWithIndexInBPM(songPosition, curChangeIndex); /** * Starting BPM From 34b4de31fc7d7a76dc7c86968d0fc18309167d39 Mon Sep 17 00:00:00 2001 From: Ralty <78720179+Raltyro@users.noreply.github.com> Date: Sun, 6 Jul 2025 10:33:25 +0700 Subject: [PATCH 39/39] Should be done --- source/funkin/backend/FunkinSprite.hx | 4 +- source/funkin/backend/chart/EventsData.hx | 2 +- source/funkin/backend/system/Conductor.hx | 45 ++++++++++------------- source/funkin/game/Character.hx | 4 +- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index 60e131e3c..cb1fea563 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -141,8 +141,8 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I private var countedBeat = 0; public function beatHit(curBeat:Int) { - if (Conductor.curBeat != curBeat || (skipNegativeBeats && curBeat < 0)) return; - if (beatAnims.length > 0 && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0) + if (skipNegativeBeats && curBeat < 0) return; + if (beatAnims.length > 0 && (curBeat + beatOffset) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0) { // TODO: find a solution without countedBeat var anim = beatAnims[FlxMath.wrap(countedBeat++, 0, beatAnims.length - 1)]; diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index 7dd011970..efc7d16a1 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -35,7 +35,7 @@ class EventsData { ], "BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}], "Continuous BPM Change" => [{name: "Target BPM", type: TFloat(1.00, 9999, 0.001, 3), defValue: 100}, {name: "Time (steps)", type: TFloat(0.25, 9999, 0.25, 2), defValue: 4}], - "Time Signature Change" => [{name: "Target Numerator", type: TFloat(1), defValue: 4}, {name: "Target Denominator", type: TFloat(1), defValue: 4}], + "Time Signature Change" => [{name: "Target Numerator", type: TFloat(1), defValue: 4}, {name: "Target Denominator", type: TFloat(1), defValue: 4}, {name: "Denominator is Steps Per Beat", type: TBool, defValue: false}], "Scroll Speed Change" => [ {name: "Tween Speed?", type: TBool, defValue: true}, {name: "New Speed", type: TFloat(0.01, 99, 0.01, 2), defValue: 1.}, diff --git a/source/funkin/backend/system/Conductor.hx b/source/funkin/backend/system/Conductor.hx index fed6dd93a..22fb26bfd 100644 --- a/source/funkin/backend/system/Conductor.hx +++ b/source/funkin/backend/system/Conductor.hx @@ -125,8 +125,8 @@ final class Conductor * 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):Int; - private static function get_denominator() return Math.floor(16 / stepsPerBeat); + public static var denominator(get, never):Float; + private static function get_denominator() return FlxMath.roundDecimal(16 / stepsPerBeat, 2); /** * Last step from BPM Change @@ -190,10 +190,13 @@ final class Conductor */ public static var invalidEvents:Array = []; + private static var validEventNames:Array = ["BPM Change", "Time Signature Change", "Continuous BPM Change"]; + @:dox(hide) public function new() {} public static function reset() { songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0; + curChangeIndex = 0; changeBPM(); } @@ -244,19 +247,13 @@ final class Conductor if (song.events == null) return; // 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" || e.name == "Continuous BPM Change")) events.push(e); - events.sort(function(a, b) { - if (MathUtil.equal(a.time, b.time)) { - if (a.name == "Continuous BPM Change") return 1; - if (b.name == "Continuous BPM Change") return -1; - } + var events:Array = [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 events) { - curChange = mapEvent(e, curChange); - } + for (e in events) curChange = mapEvent(e, curChange); } private static function mapEvent(e:ChartEvent, curChange:BPMChangeEvent) { @@ -275,7 +272,9 @@ final class Conductor if (curChange.songTime != time) curChange = mapBPMChange(curChange, time, curChange.bpm); curChange.beatsPerMeasure = params[0]; - curChange.stepsPerBeat = Math.floor(16 / params[1]); // convert from denominator to stepsPerBeat + + 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); @@ -287,11 +286,11 @@ final class Conductor invalidEvents.push(e); return curChange; //DO NOT!!!! } + curChange = mapBPMChange(curChange, time, params[0]); - var endTime = time + (params[1]) / (curChange.bpm - prevBPM) * Math.log(curChange.bpm / prevBPM) * 15000; + curChange.endSongTime = time + (params[1]) / (curChange.bpm - prevBPM) * Math.log(curChange.bpm / prevBPM) * 15000; curChange.endStepTime = curChange.stepTime + params[1]; curChange.continuous = true; - curChange.endSongTime = endTime; } return curChange; } @@ -311,21 +310,15 @@ final class Conductor invalidEvents = []; for (event in Charter.instance.eventsGroup.members) { - event.events.sort(function(a, b) { - if (MathUtil.equal(a.time, b.time)) { - if (a.name == "Continuous BPM Change") return 1; - if (b.name == "Continuous BPM Change") return -1; - } + 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.getTimeForStep(event.step); + var eventTime = Conductor.getStepsInTime(event.step); for (e in event.events) { - e.time = eventTime; - - if ((e.name == "BPM Change" || e.name == "Time Signature Change" || e.name == "Continuous BPM Change")) { - curChange = mapEvent(e, curChange); - } + if (!Math.isNaN(eventTime)) e.time = eventTime; + if (validEventNames.contains(e.name)) curChange = mapEvent(e, curChange); } } } diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index ff1341b98..597f2b472 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -173,8 +173,8 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public override function beatHit(curBeat:Int) { scripts.call("beatHit", [curBeat]); - if (Conductor.curBeat != curBeat || (skipNegativeBeats && curBeat < 0)) return; - if (danceOnBeat && (curBeat + beatOffset - Conductor.lastBeatChange) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0 && !__lockAnimThisFrame) + if (skipNegativeBeats && curBeat < 0) return; + if (danceOnBeat && (curBeat + beatOffset) % (beatInterval * CoolUtil.maxInt(Math.floor(4 / Conductor.stepsPerBeat), 1)) == 0 && !__lockAnimThisFrame) tryDance(); }