Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time Signature Change Functionality #397

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ latest/windows/haxe/
.haxelib/
pages/
docs/doc.xml
mods/Baldi's Basics in Funkin'/
mods/
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion source/funkin/backend/chart/ChartData.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 6 additions & 4 deletions source/funkin/backend/chart/EventsData.hx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import funkin.backend.assets.Paths;
using StringTools;

class EventsData {
public static var defaultEventsList:Array<String> = ["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<String> = ["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<String, Array<EventParamInfo>> = [
"HScript Call" => [
{name: "Function Name", type: TString, defValue: "myFunc"},
Expand Down Expand Up @@ -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<String> = defaultEventsList.copy();
Expand All @@ -75,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];
Expand Down
15 changes: 14 additions & 1 deletion source/funkin/backend/chart/FNFLegacyParser.hx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ 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;
}
}
Expand Down Expand Up @@ -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));
Expand All @@ -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;
Expand Down
137 changes: 97 additions & 40 deletions source/funkin/backend/system/Conductor.hx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +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;
@:structInit
class BPMChangeEvent {
public var stepTime:Float;
public var songTime:Float;
public var bpm:Float;
public var beatsPerMeasure:Float;
public var stepsPerBeat:Int;
}

class Conductor
final class Conductor
{
/**
* FlxSignals
Expand All @@ -21,6 +23,7 @@ class Conductor
public static var onBeatHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
public static var onStepHit:FlxTypedSignal<Int->Void> = new FlxTypedSignal();
public static var onBPMChange:FlxTypedSignal<Float->Void> = new FlxTypedSignal();
public static var onTimeSignatureChange:FlxTypedSignal<(Float,Float)->Void> = new FlxTypedSignal();

/**
* Current BPM
Expand All @@ -45,7 +48,7 @@ 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;


/**
Expand Down Expand Up @@ -107,7 +110,7 @@ class Conductor
public static function reset() {
songPosition = lastSongPos = curBeatFloat = curStepFloat = curBeat = curStep = 0;
bpmChangeMap = [];
changeBPM(0);
changeBPM(0, 4, 4);
}

public static function setupSong(SONG:ChartData) {
Expand All @@ -119,35 +122,70 @@ class Conductor
* 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:Int = 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) {
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
});
}
}

// sort from early to last
bpmChangeMap.sort(function(a, b) return Std.int(a.songTime - b.songTime));
}

private static var elapsed:Float;
Expand Down Expand Up @@ -186,21 +224,30 @@ class Conductor
__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)
{
if (Conductor.songPosition >= change.songTime)

var currentPos = Conductor.songPosition;

for (change in Conductor.bpmChangeMap) {
if (currentPos >= change.songTime)
__lastChange = change;
else
break;
}

if (__lastChange.bpm > 0 && bpm != __lastChange.bpm) changeBPM(__lastChange.bpm);
// 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;

Expand Down Expand Up @@ -258,47 +305,57 @@ class Conductor
}
}

public static function changeBPM(newBpm:Float, beatsPerMeasure:Float = 4, stepsPerBeat:Float = 4)
public static function changeBPM(newBpm:Float, newBeatsPerMeasure:Float = 4, newStepsPerBeat:Int = 4)
{
var timesignChange = (beatsPerMeasure != newBeatsPerMeasure || stepsPerBeat != newStepsPerBeat);
var bpmChange = (bpm != newBpm);

beatsPerMeasure = newBeatsPerMeasure;
stepsPerBeat = newStepsPerBeat;
bpm = newBpm;

crochet = ((60 / bpm) * 1000);
crochet = (60 / bpm) * 1000;
stepCrochet = crochet / stepsPerBeat;

Conductor.beatsPerMeasure = beatsPerMeasure;
Conductor.stepsPerBeat = stepsPerBeat;

onBPMChange.dispatch(bpm);
if (timesignChange) onTimeSignatureChange.dispatch(beatsPerMeasure, stepsPerBeat);
if (bpmChange) onBPMChange.dispatch(bpm);
}

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;
// possible break here

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;
// possible break here

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;

Expand Down
2 changes: 1 addition & 1 deletion source/funkin/backend/utils/CoolUtil.hx
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ class CoolUtil

var timeSignParsed:Array<Null<Float>> = 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)) {
Expand Down
Loading