More chart editor changes

This commit is contained in:
EliteMasterEric 2023-06-08 17:07:35 -04:00
parent c3577b32ef
commit 26998c9164
7 changed files with 350 additions and 140 deletions

View file

@ -324,7 +324,7 @@ class SongDifficulty
/**
* Build a list of vocal files for the given character.
* Automatically resolves suffixed character IDs (so bf-car will resolve to bf if needed).
*
*
* @param id The character we are about to play.
*/
public function buildVoiceList(?id:String = 'bf'):Array<String>

View file

@ -1,5 +1,7 @@
package funkin.play.song;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.modding.events.ScriptEvent;
import flixel.util.typeLimit.OneOfTwo;
import funkin.play.song.ScriptedSong;
import funkin.util.assets.DataAssets;
@ -24,7 +26,7 @@ class SongDataParser
/**
* Parses and preloads the game's song metadata and scripts when the game starts.
*
*
* If you want to force song metadata to be reloaded, you can just call this function again.
*/
public static function loadSongCache():Void
@ -95,6 +97,9 @@ class SongDataParser
{
var song:Song = songCache.get(songId);
trace('Successfully fetch song: ${songId}');
var event:ScriptEvent = new ScriptEvent(ScriptEvent.CREATE, false);
ScriptEventDispatcher.callEvent(song, event);
return song;
}
else
@ -112,12 +117,21 @@ class SongDataParser
}
}
/**
* A list of all the song IDs available to the game.
* @return The list of song IDs.
*/
public static function listSongIds():Array<String>
{
return songCache.keys().array();
}
public static function parseSongMetadata(songId:String):Array<SongMetadata>
/**
* Loads the song metadata for a particular song.
* @param songId The ID of the song to load.
* @return The song metadata for each variation, or an empty array if the song was not found.
*/
public static function loadSongMetadata(songId:String):Array<SongMetadata>
{
var result:Array<SongMetadata> = [];
@ -139,19 +153,13 @@ class SongDataParser
result.push(songMetadata);
var variations = songMetadata.playData.songVariations;
var variations:Array<String> = songMetadata.playData.songVariations;
for (variation in variations)
{
var variationJsonStr:String = loadSongMetadataFile(songId, variation);
var variationJsonData:Dynamic = null;
try
{
variationJsonData = Json.parse(variationJsonStr);
}
catch (e) {}
var variationSongMetadata:SongMetadata = SongMigrator.migrateSongMetadata(variationJsonData, '${songId}-${variation}');
variationSongMetadata = SongValidator.validateSongMetadata(variationSongMetadata, '${songId}-${variation}');
var variationRawJson:String = loadSongMetadataFile(songId, variation);
var variationSongMetadata:SongMetadata = SongMigrator.migrateSongMetadata(variationRawJson, '${songId}_${variation}');
variationSongMetadata = SongValidator.validateSongMetadata(variationSongMetadata, '${songId}_${variation}');
if (variationSongMetadata != null)
{
variationSongMetadata.variation = variation;
@ -168,7 +176,7 @@ class SongDataParser
var rawJson:String = Assets.getText(songMetadataFilePath).trim();
while (!rawJson.endsWith("}"))
while (!rawJson.endsWith('}') && rawJson.length > 0)
{
rawJson = rawJson.substr(0, rawJson.length - 1);
}
@ -176,7 +184,7 @@ class SongDataParser
return rawJson;
}
public static function parseSongChartData(songId:String, variation:String = ""):SongChartData
public static function parseSongChartData(songId:String, variation:String = ''):SongChartData
{
var rawJson:String = loadSongChartDataFile(songId, variation);
var jsonData:Dynamic = null;
@ -184,7 +192,11 @@ class SongDataParser
{
jsonData = Json.parse(rawJson);
}
catch (e) {}
catch (e)
{
trace('Failed to parse song chart data: ${songId} (${variation})');
trace(e);
}
var songChartData:SongChartData = SongMigrator.migrateSongChartData(jsonData, songId);
songChartData = SongValidator.validateSongChartData(songChartData, songId);
@ -204,7 +216,7 @@ class SongDataParser
var rawJson:String = Assets.getText(songChartDataFilePath).trim();
while (!rawJson.endsWith("}"))
while (!rawJson.endsWith('}') && rawJson.length > 0)
{
rawJson = rawJson.substr(0, rawJson.length - 1);
}
@ -217,7 +229,7 @@ typedef RawSongMetadata =
{
/**
* A semantic versioning string for the song data format.
*
*
*/
var version:Version;
@ -272,7 +284,7 @@ abstract SongMetadata(RawSongMetadata)
public function clone(?newVariation:String = null):SongMetadata
{
var result = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
var result:SongMetadata = new SongMetadata(this.songName, this.artist, newVariation == null ? this.variation : newVariation);
result.version = this.version;
result.timeFormat = this.timeFormat;
result.divisions = this.divisions;
@ -350,22 +362,22 @@ abstract SongNoteData(RawSongNoteData)
public var time(get, set):Float;
public function get_time():Float
function get_time():Float
{
return this.t;
}
public function set_time(value:Float):Float
function set_time(value:Float):Float
{
return this.t = value;
}
public var stepTime(get, never):Float;
public function get_stepTime():Float
function get_stepTime():Float
{
// TODO: Account for changes in BPM.
return this.t / Conductor.stepCrochet;
return this.t / Conductor.stepLengthMs;
}
/**
@ -373,12 +385,12 @@ abstract SongNoteData(RawSongNoteData)
*/
public var data(get, set):Int;
public function get_data():Int
function get_data():Int
{
return this.d;
}
public function set_data(value:Int):Int
function set_data(value:Int):Int
{
return this.d = value;
}
@ -414,7 +426,7 @@ abstract SongNoteData(RawSongNoteData)
/**
* The strumline index of the note, if applicable.
* Strips the direction from the data.
*
*
* 0 = player, 1 = opponent, etc.
*/
public inline function getStrumlineIndex(strumlineSize:Int = 4):Int
@ -429,26 +441,26 @@ abstract SongNoteData(RawSongNoteData)
public var length(get, set):Float;
public function get_length():Float
function get_length():Float
{
return this.l;
}
public function set_length(value:Float):Float
function set_length(value:Float):Float
{
return this.l = value;
}
public var kind(get, set):String;
public function get_kind():String
function get_kind():String
{
if (this.k == null || this.k == '') return 'normal';
return this.k;
}
public function set_kind(value:String):String
function set_kind(value:String):String
{
if (value == 'normal' || value == '') value = null;
return this.k = value;
@ -536,56 +548,56 @@ abstract SongEventData(RawSongEventData)
public var time(get, set):Float;
public function get_time():Float
function get_time():Float
{
return this.t;
}
public function set_time(value:Float):Float
function set_time(value:Float):Float
{
return this.t = value;
}
public var stepTime(get, never):Float;
public function get_stepTime():Float
function get_stepTime():Float
{
// TODO: Account for changes in BPM.
return this.t / Conductor.stepCrochet;
return this.t / Conductor.stepLengthMs;
}
public var event(get, set):String;
public function get_event():String
function get_event():String
{
return this.e;
}
public function set_event(value:String):String
function set_event(value:String):String
{
return this.e = value;
}
public var value(get, set):Dynamic;
public function get_value():Dynamic
function get_value():Dynamic
{
return this.v;
}
public function set_value(value:Dynamic):Dynamic
function set_value(value:Dynamic):Dynamic
{
return this.v = value;
}
public var activated(get, set):Bool;
public function get_activated():Bool
function get_activated():Bool
{
return this.a;
}
public function set_activated(value:Bool):Bool
function set_activated(value:Bool):Bool
{
return this.a = value;
}
@ -664,7 +676,7 @@ abstract SongEventData(RawSongEventData)
abstract SongPlayableChar(RawSongPlayableChar)
{
public function new(girlfriend:String, opponent:String, inst:String = "")
public function new(girlfriend:String, opponent:String, inst:String = '')
{
this =
{
@ -676,36 +688,36 @@ abstract SongPlayableChar(RawSongPlayableChar)
public var girlfriend(get, set):String;
public function get_girlfriend():String
function get_girlfriend():String
{
return this.g;
}
public function set_girlfriend(value:String):String
function set_girlfriend(value:String):String
{
return this.g = value;
}
public var opponent(get, set):String;
public function get_opponent():String
function get_opponent():String
{
return this.o;
}
public function set_opponent(value:String):String
function set_opponent(value:String):String
{
return this.o = value;
}
public var inst(get, set):String;
public function get_inst():String
function get_inst():String
{
return this.i;
}
public function set_inst(value:String):String
function set_inst(value:String):String
{
return this.i = value;
}
@ -751,6 +763,35 @@ abstract SongChartData(RawSongChartData)
return (result == 0.0) ? 1.0 : result;
}
public function setScrollSpeed(value:Float, diff:String = 'default'):Float
{
return this.scrollSpeed.set(diff, value);
}
public function getNotes(diff:String):Array<SongNoteData>
{
var result:Array<SongNoteData> = this.notes.get(diff);
if (result == null && diff != 'normal') return getNotes('normal');
return (result == null) ? [] : result;
}
public function setNotes(value:Array<SongNoteData>, diff:String):Array<SongNoteData>
{
return this.notes.set(diff, value);
}
public function getEvents():Array<SongEventData>
{
return this.events;
}
public function setEvents(value:Array<SongEventData>):Array<SongEventData>
{
return this.events = value;
}
}
typedef RawSongTimeChange =
@ -811,67 +852,67 @@ abstract SongTimeChange(RawSongTimeChange)
public var timeStamp(get, set):Float;
public function get_timeStamp():Float
function get_timeStamp():Float
{
return this.t;
}
public function set_timeStamp(value:Float):Float
function set_timeStamp(value:Float):Float
{
return this.t = value;
}
public var beatTime(get, set):Int;
public function get_beatTime():Int
function get_beatTime():Int
{
return this.b;
}
public function set_beatTime(value:Int):Int
function set_beatTime(value:Int):Int
{
return this.b = value;
}
public var bpm(get, set):Float;
public function get_bpm():Float
function get_bpm():Float
{
return this.bpm;
}
public function set_bpm(value:Float):Float
function set_bpm(value:Float):Float
{
return this.bpm = value;
}
public var timeSignatureNum(get, set):Int;
public function get_timeSignatureNum():Int
function get_timeSignatureNum():Int
{
return this.n;
}
public function set_timeSignatureNum(value:Int):Int
function set_timeSignatureNum(value:Int):Int
{
return this.n = value;
}
public var timeSignatureDen(get, set):Int;
public function get_timeSignatureDen():Int
function get_timeSignatureDen():Int
{
return this.d;
}
public function set_timeSignatureDen(value:Int):Int
function set_timeSignatureDen(value:Int):Int
{
return this.d = value;
}
public var beatTuplets(get, set):Array<Int>;
public function get_beatTuplets():Array<Int>
function get_beatTuplets():Array<Int>
{
if (Std.isOfType(this.bt, Int))
{
@ -883,7 +924,7 @@ abstract SongTimeChange(RawSongTimeChange)
}
}
public function set_beatTuplets(value:Array<Int>):Array<Int>
function set_beatTuplets(value:Array<Int>):Array<Int>
{
return this.bt = value;
}
@ -891,7 +932,7 @@ abstract SongTimeChange(RawSongTimeChange)
enum abstract SongTimeFormat(String) from String to String
{
var TICKS = "ticks";
var FLOAT = "float";
var MILLISECONDS = "ms";
var TICKS = 'ticks';
var FLOAT = 'float';
var MILLISECONDS = 'ms';
}

View file

@ -14,14 +14,13 @@ class SongDataUtils
* Given an array of SongNoteData objects, return a new array of SongNoteData objects
* whose timestamps are shifted by the given amount.
* Does not mutate the original array.
*
*
* @param notes The notes to modify.
* @param offset The time difference to apply in milliseconds.
*/
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Int):Array<SongNoteData>
{
return notes.map(function(note:SongNoteData):SongNoteData
{
return notes.map(function(note:SongNoteData):SongNoteData {
return new SongNoteData(note.time + offset, note.data, note.length, note.kind);
});
}
@ -30,14 +29,13 @@ class SongDataUtils
* Given an array of SongEventData objects, return a new array of SongEventData objects
* whose timestamps are shifted by the given amount.
* Does not mutate the original array.
*
*
* @param events The events to modify.
* @param offset The time difference to apply in milliseconds.
*/
public static function offsetSongEventData(events:Array<SongEventData>, offset:Int):Array<SongEventData>
{
return events.map(function(event:SongEventData):SongEventData
{
return events.map(function(event:SongEventData):SongEventData {
return new SongEventData(event.time + offset, event.event, event.value);
});
}
@ -45,7 +43,7 @@ class SongDataUtils
/**
* Return a new array without a certain subset of notes from an array of SongNoteData objects.
* Does not mutate the original array.
*
*
* @param notes The array of notes to be subtracted from.
* @param subtrahend The notes to remove from the `notes` array. Yes, subtrahend is a real word.
*/
@ -53,8 +51,7 @@ class SongDataUtils
{
if (notes.length == 0 || subtrahend.length == 0) return notes;
var result = notes.filter(function(note:SongNoteData):Bool
{
var result = notes.filter(function(note:SongNoteData):Bool {
for (x in subtrahend)
// SongNoteData's == operation has been overridden so that this will work.
if (x == note) return false;
@ -68,7 +65,7 @@ class SongDataUtils
/**
* Return a new array without a certain subset of events from an array of SongEventData objects.
* Does not mutate the original array.
*
*
* @param events The array of events to be subtracted from.
* @param subtrahend The events to remove from the `events` array. Yes, subtrahend is a real word.
*/
@ -76,8 +73,7 @@ class SongDataUtils
{
if (events.length == 0 || subtrahend.length == 0) return events;
return events.filter(function(event:SongEventData):Bool
{
return events.filter(function(event:SongEventData):Bool {
// SongEventData's == operation has been overridden so that this will work.
return !subtrahend.has(event);
});
@ -89,8 +85,7 @@ class SongDataUtils
*/
public static function flipNotes(notes:Array<SongNoteData>, ?strumlineSize:Int = 4):Array<SongNoteData>
{
return notes.map(function(note:SongNoteData):SongNoteData
{
return notes.map(function(note:SongNoteData):SongNoteData {
var newData = note.data;
if (newData < strumlineSize) newData += strumlineSize;
@ -103,22 +98,26 @@ class SongDataUtils
/**
* Prepare an array of notes to be used as the clipboard data.
*
*
* Offset the provided array of notes such that the first note is at 0 milliseconds.
*/
public static function buildNoteClipboard(notes:Array<SongNoteData>):Array<SongNoteData>
public static function buildNoteClipboard(notes:Array<SongNoteData>, ?timeOffset:Int = null):Array<SongNoteData>
{
return offsetSongNoteData(sortNotes(notes), -Std.int(notes[0].time));
if (notes.length == 0) return notes;
if (timeOffset == null) timeOffset = -Std.int(notes[0].time);
return offsetSongNoteData(sortNotes(notes), timeOffset);
}
/**
* Prepare an array of events to be used as the clipboard data.
*
*
* Offset the provided array of events such that the first event is at 0 milliseconds.
*/
public static function buildEventClipboard(events:Array<SongEventData>):Array<SongEventData>
public static function buildEventClipboard(events:Array<SongEventData>, ?timeOffset:Int = null):Array<SongEventData>
{
return offsetSongEventData(sortEvents(events), -Std.int(events[0].time));
if (events.length == 0) return events;
if (timeOffset == null) timeOffset = -Std.int(events[0].time);
return offsetSongEventData(sortEvents(events), timeOffset);
}
/**
@ -127,8 +126,7 @@ class SongDataUtils
public static function sortNotes(notes:Array<SongNoteData>, ?desc:Bool = false):Array<SongNoteData>
{
// TODO: Modifies the array in place. Is this okay?
notes.sort(function(a:SongNoteData, b:SongNoteData):Int
{
notes.sort(function(a:SongNoteData, b:SongNoteData):Int {
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
});
return notes;
@ -140,8 +138,7 @@ class SongDataUtils
public static function sortEvents(events:Array<SongEventData>, ?desc:Bool = false):Array<SongEventData>
{
// TODO: Modifies the array in place. Is this okay?
events.sort(function(a:SongEventData, b:SongEventData):Int
{
events.sort(function(a:SongEventData, b:SongEventData):Int {
return FlxSort.byValues(desc ? FlxSort.DESCENDING : FlxSort.ASCENDING, a.time, b.time);
});
return events;
@ -192,8 +189,7 @@ class SongDataUtils
*/
public static function getNotesInTimeRange(notes:Array<SongNoteData>, start:Float, end:Float):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool
{
return notes.filter(function(note:SongNoteData):Bool {
return note.time >= start && note.time <= end;
});
}
@ -203,8 +199,7 @@ class SongDataUtils
*/
public static function getEventsInTimeRange(events:Array<SongEventData>, start:Float, end:Float):Array<SongEventData>
{
return events.filter(function(event:SongEventData):Bool
{
return events.filter(function(event:SongEventData):Bool {
return event.time >= start && event.time <= end;
});
}
@ -214,8 +209,7 @@ class SongDataUtils
*/
public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool
{
return notes.filter(function(note:SongNoteData):Bool {
return note.data >= start && note.data <= end;
});
}
@ -225,8 +219,7 @@ class SongDataUtils
*/
public static function getNotesWithData(notes:Array<SongNoteData>, data:Array<Int>):Array<SongNoteData>
{
return notes.filter(function(note:SongNoteData):Bool
{
return notes.filter(function(note:SongNoteData):Bool {
return data.indexOf(note.data) != -1;
});
}

View file

@ -1,7 +1,11 @@
package funkin.play.song;
import funkin.play.song.formats.FNFLegacy;
import funkin.play.song.SongData.SongChartData;
import funkin.play.song.SongData.SongEventData;
import funkin.play.song.SongData.SongMetadata;
import funkin.play.song.SongData.SongNoteData;
import funkin.play.song.SongData.SongPlayableChar;
import funkin.util.VersionUtil;
class SongMigrator
@ -11,13 +15,22 @@ class SongMigrator
* Handle breaking changes by incrementing this value
* and adding migration to the SongMigrator class.
*/
public static final CHART_VERSION:String = "2.0.0";
public static final CHART_VERSION:String = '2.0.0';
public static final CHART_VERSION_RULE:String = "2.0.x";
/**
* Version rule for which chart versions are compatible with the current version.
*/
public static final CHART_VERSION_RULE:String = '2.0.x';
/**
* Migrate song data from an older chart version to the current version.
* @param jsonData The song metadata to migrate.
* @param songId The ID of the song (only used for error reporting).
* @return The migrated song metadata, or null if the migration failed.
*/
public static function migrateSongMetadata(jsonData:Dynamic, songId:String):SongMetadata
{
if (jsonData.version)
if (jsonData.version != null)
{
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
{
@ -32,10 +45,11 @@ class SongMigrator
trace('Song (${songId}) metadata version (${jsonData.version}) is outdated.');
switch (jsonData.version)
{
// TODO: Add migration functions as cases here.
case '1.0.0':
return migrateSongMetadataFromLegacy(jsonData);
default:
// Unknown version.
trace('Song (${songId}) unknown metadata version: ${jsonData.version}');
trace('Song (${songId}) has unknown metadata version (${jsonData.version}), assuming FNF Legacy.');
return migrateSongMetadataFromLegacy(jsonData);
}
}
}
@ -46,6 +60,12 @@ class SongMigrator
return null;
}
/**
* Migrate song chart data from an older chart version to the current version.
* @param jsonData The song chart data to migrate.
* @param songId The ID of the song (only used for error reporting).
* @return The migrated song chart data, or null if the migration failed.
*/
public static function migrateSongChartData(jsonData:Dynamic, songId:String):SongChartData
{
if (jsonData.version)
@ -76,4 +96,167 @@ class SongMigrator
}
return null;
}
/**
* Migrate song metadata from FNF Legacy chart version to the current version.
* @param jsonData The song metadata to migrate.
* @param songId The ID of the song (only used for error reporting).
* @return The migrated song metadata, or null if the migration failed.
*/
public static function migrateSongMetadataFromLegacy(jsonData:Dynamic):SongMetadata
{
trace('Migrating song metadata from FNF Legacy.');
var songData:FNFLegacy = cast jsonData;
var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default');
var hadError:Bool = false;
// Set generatedBy string for debugging.
songMetadata.generatedBy = 'Chart Editor Import (FNF Legacy)';
try
{
// Set the song's BPM.
songMetadata.timeChanges[0].bpm = songData.song.bpm;
}
catch (e)
{
trace("Couldn't parse BPM!");
hadError = true;
}
try
{
// Set the song's stage.
songMetadata.playData.stage = songData.song.stageDefault;
}
catch (e)
{
trace("Couldn't parse stage!");
hadError = true;
}
try
{
// Set's the song's name.
songMetadata.songName = songData.song.song;
}
catch (e)
{
trace("Couldn't parse song name!");
hadError = true;
}
songMetadata.playData.difficulties = [];
if (songData.song != null && songData.song.notes != null)
{
if (songData.song.notes.easy != null) songMetadata.playData.difficulties.push('easy');
if (songData.song.notes.normal != null) songMetadata.playData.difficulties.push('normal');
if (songData.song.notes.hard != null) songMetadata.playData.difficulties.push('hard');
}
else
{
trace("Couldn't parse difficulties!");
hadError = true;
}
songMetadata.playData.songVariations = [];
// Set the song's song variations.
songMetadata.playData.playableChars = {};
try
{
Reflect.setField(songMetadata.playData.playableChars, songData.song.player1, new SongPlayableChar('', songData.song.player2));
}
catch (e)
{
trace("Couldn't parse characters!");
hadError = true;
}
return songMetadata;
}
/**
* Migrate song chart data from FNF Legacy chart version to the current version.
* @param jsonData The song data to migrate.
* @param songId The ID of the song (only used for error reporting).
* @param difficulty The difficulty to migrate.
* @return The migrated song chart data, or null if the migration failed.
*/
public static function migrateSongChartDataFromLegacy(jsonData:Dynamic):SongChartData
{
trace('Migrating song chart data from FNF Legacy.');
var songData:FNFLegacy = cast jsonData;
var songChartData:SongChartData = new SongChartData(1.0, [], []);
if (songData.song.notes.normal != null)
{
var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes.normal));
songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes.normal), 'normal');
songChartData.setScrollSpeed(songData.song.speed.normal, 'normal');
}
if (songData.song.notes.easy != null)
{
var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes.easy));
songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes.easy), 'easy');
songChartData.setScrollSpeed(songData.song.speed.easy, 'easy');
}
if (songData.song.notes.hard != null)
{
var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes.hard));
songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes.hard), 'hard');
songChartData.setScrollSpeed(songData.song.speed.hard, 'hard');
}
return songChartData;
}
static function migrateSongNoteDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongNoteData>
{
var songNotes:Array<SongNoteData> = [];
for (section in sections)
{
// Skip empty sections.
if (section.sectionNotes.length == 0) continue;
for (note in section.sectionNotes)
{
songNotes.push(new SongNoteData(note.time, note.getData(section.mustHitSection), note.length, note.kind));
}
}
return songNotes;
}
static function migrateSongEventDataFromLegacy(sections:Array<LegacyNoteSection>):Array<SongEventData>
{
var songEvents:Array<SongEventData> = [];
var lastSectionWasMustHit:Null<Bool> = null;
for (section in sections)
{
// Skip empty sections.
if (section.sectionNotes.length == 0) continue;
if (section.mustHitSection != lastSectionWasMustHit)
{
lastSectionWasMustHit = section.mustHitSection;
var firstNote:LegacyNote = section.sectionNotes[0];
songEvents.push(new SongEventData(firstNote.time, 'FocusCamera', {char: section.mustHitSection ? 0 : 1}));
}
}
return songEvents;
}
}

View file

@ -50,8 +50,7 @@ class SongSerializer
*/
public static function importSongChartDataAsync(callback:SongChartData->Void):Void
{
browseFileReference(function(fileReference:FileReference)
{
browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
@ -68,8 +67,7 @@ class SongSerializer
*/
public static function importSongMetadataAsync(callback:SongMetadata->Void):Void
{
browseFileReference(function(fileReference:FileReference)
{
browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
@ -103,7 +101,7 @@ class SongSerializer
/**
* Save a SongChartData object as a JSON file to a specified path.
* Works great on HTML5 and desktop.
*
*
* @param path The file path to save to.
*/
public static function exportSongChartDataAs(path:String, data:SongChartData)
@ -116,7 +114,7 @@ class SongSerializer
/**
* Save a SongMetadata object as a JSON file to a specified path.
* Works great on HTML5 and desktop.
*
*
* @param path The file path to save to.
*/
public static function exportSongMetadataAs(path:String, data:SongMetadata)
@ -163,19 +161,17 @@ class SongSerializer
/**
* Browse for a file to read and execute a callback once we have a file reference.
* Works great on HTML5 or desktop.
*
*
* @param callback The function to call when the file is loaded.
*/
static function browseFileReference(callback:FileReference->Void)
{
var file = new FileReference();
file.addEventListener(Event.SELECT, function(e)
{
file.addEventListener(Event.SELECT, function(e) {
var selectedFileRef:FileReference = e.target;
trace('Selected file: ' + selectedFileRef.name);
selectedFileRef.addEventListener(Event.COMPLETE, function(e)
{
selectedFileRef.addEventListener(Event.COMPLETE, function(e) {
var loadedFileRef:FileReference = e.target;
trace('Loaded file: ' + loadedFileRef.name);
callback(loadedFileRef);
@ -192,16 +188,13 @@ class SongSerializer
static function writeFileReference(path:String, data:String)
{
var file = new FileReference();
file.addEventListener(Event.COMPLETE, function(e:Event)
{
file.addEventListener(Event.COMPLETE, function(e:Event) {
trace('Successfully wrote file.');
});
file.addEventListener(Event.CANCEL, function(e:Event)
{
file.addEventListener(Event.CANCEL, function(e:Event) {
trace('Cancelled writing file.');
});
file.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent)
{
file.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent) {
trace('IO error writing file.');
});
file.save(data, path);

View file

@ -30,7 +30,7 @@ class SongValidator
/**
* Validates the fields of a SongMetadata object (excluding the version field).
*
*
* @param input The SongMetadata object to validate.
* @param songId The ID of the song being validated. Only used for error messages.
* @return The validated SongMetadata object.
@ -73,7 +73,7 @@ class SongValidator
/**
* Validates the fields of a SongPlayData object.
*
*
* @param input The SongPlayData object to validate.
* @param songId The ID of the song being validated. Only used for error messages.
* @return The validated SongPlayData object.
@ -85,7 +85,7 @@ class SongValidator
/**
* Validates the fields of a TimeChange object.
*
*
* @param input The TimeChange object to validate.
* @param songId The ID of the song being validated. Only used for error messages.
* @return The validated TimeChange object.
@ -113,7 +113,7 @@ class SongValidator
/**
* Validates the fields of a SongChartData object (excluding the version field).
*
*
* @param input The SongChartData object to validate.
* @param songId The ID of the song being validated. Only used for error messages.
* @return The validated SongChartData object.

View file

@ -20,7 +20,7 @@ import flixel.util.FlxColor;
import flixel.util.FlxSort;
import flixel.util.FlxTimer;
import funkin.audio.visualize.PolygonSpectogram;
import funkin.audio.VocalGroup;
import funkin.audio.VoicesGroup;
import funkin.input.Cursor;
import funkin.input.TurboKeyHandler;
import funkin.modding.events.ScriptEvent;
@ -197,12 +197,12 @@ class ChartEditorState extends HaxeUIState
function get_scrollPositionInMs():Float
{
return scrollPositionInSteps * Conductor.stepLengthMs;
return scrollPositionInSteps * Conductor.stepCrochet;
}
function set_scrollPositionInMs(value:Float):Float
{
scrollPositionInPixels = value / Conductor.stepLengthMs;
scrollPositionInPixels = value / Conductor.stepCrochet;
return value;
}
@ -231,7 +231,7 @@ class ChartEditorState extends HaxeUIState
function get_playheadPositionInMs():Float
{
return playheadPositionInSteps * Conductor.stepLengthMs;
return playheadPositionInSteps * Conductor.stepCrochet;
}
/**
@ -271,7 +271,7 @@ class ChartEditorState extends HaxeUIState
function get_songLengthInMs():Float
{
return songLengthInSteps * Conductor.stepLengthMs;
return songLengthInSteps * Conductor.stepCrochet;
}
function set_songLengthInMs(value:Float):Float
@ -642,7 +642,7 @@ class ChartEditorState extends HaxeUIState
/**
* The audio track for the vocals.
*/
var audioVocalTrackGroup:VocalGroup;
var audioVocalTrackGroup:VoicesGroup;
/**
* The raw byte data for the vocal audio tracks.
@ -1053,7 +1053,7 @@ class ChartEditorState extends HaxeUIState
// Initialize the song chart data.
songChartData = new Map<String, SongChartData>();
audioVocalTrackGroup = new VocalGroup();
audioVocalTrackGroup = new VoicesGroup();
}
/**
@ -1811,7 +1811,7 @@ class ChartEditorState extends HaxeUIState
// The song position of the cursor, in steps.
var cursorFractionalStep:Float = cursorY / GRID_SIZE / (16 / noteSnapQuant);
var cursorStep:Int = Std.int(Math.floor(cursorFractionalStep));
var cursorMs:Float = cursorStep * Conductor.stepLengthMs * (16 / noteSnapQuant);
var cursorMs:Float = cursorStep * Conductor.stepCrochet * (16 / noteSnapQuant);
// The direction value for the column at the cursor.
var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE);
if (cursorColumn < 0) cursorColumn = 0;
@ -1849,7 +1849,7 @@ class ChartEditorState extends HaxeUIState
// We released the mouse. Select the notes in the box.
var cursorFractionalStepStart:Float = cursorYStart / GRID_SIZE;
var cursorStepStart:Int = Math.floor(cursorFractionalStepStart);
var cursorMsStart:Float = cursorStepStart * Conductor.stepLengthMs;
var cursorMsStart:Float = cursorStepStart * Conductor.stepCrochet;
var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE);
var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE);
@ -2053,12 +2053,12 @@ class ChartEditorState extends HaxeUIState
{
// Handle extending the note as you drag.
// Since use Math.floor and stepLengthMs here, the hold notes will be beat snapped.
var dragLengthSteps:Float = Math.floor((cursorMs - currentPlaceNoteData.time) / Conductor.stepLengthMs);
// Since use Math.floor and stepCrochet here, the hold notes will be beat snapped.
var dragLengthSteps:Float = Math.floor((cursorMs - currentPlaceNoteData.time) / Conductor.stepCrochet);
// Without this, the newly placed note feels too short compared to the user's input.
var INCREMENT:Float = 1.0;
var dragLengthMs:Float = (dragLengthSteps + INCREMENT) * Conductor.stepLengthMs;
var dragLengthMs:Float = (dragLengthSteps + INCREMENT) * Conductor.stepCrochet;
// TODO: Add and update some sort of preview?
@ -2363,7 +2363,7 @@ class ChartEditorState extends HaxeUIState
}
// Get the position the note should be at.
var noteTimePixels:Float = noteData.time / Conductor.stepLengthMs * GRID_SIZE;
var noteTimePixels:Float = noteData.time / Conductor.stepCrochet * GRID_SIZE;
// Make sure the note appears when scrolling up.
var modifiedViewAreaTop:Float = viewAreaTop - GRID_SIZE;
@ -2389,7 +2389,7 @@ class ChartEditorState extends HaxeUIState
{
// If the note is a hold, we need to make sure it's long enough.
var noteLengthMs:Float = noteSprite.noteData.length;
var noteLengthSteps:Float = (noteLengthMs / Conductor.stepLengthMs);
var noteLengthSteps:Float = (noteLengthMs / Conductor.stepCrochet);
var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
while (noteLengthSteps > 0)
@ -2413,7 +2413,7 @@ class ChartEditorState extends HaxeUIState
// Make sure the last note sprite shows the end cap properly.
lastNoteSprite.childNoteSprite = null;
// var noteLengthPixels:Float = (noteLengthMs / Conductor.stepLengthMs + 1) * GRID_SIZE;
// var noteLengthPixels:Float = (noteLengthMs / Conductor.stepCrochet + 1) * GRID_SIZE;
// add(new FlxSprite(noteSprite.x, noteSprite.y - renderedNotes.y + noteLengthPixels).makeGraphic(40, 2, 0xFFFF0000));
}
}
@ -2428,7 +2428,7 @@ class ChartEditorState extends HaxeUIState
}
// Get the position the event should be at.
var eventTimePixels:Float = eventData.time / Conductor.stepLengthMs * GRID_SIZE;
var eventTimePixels:Float = eventData.time / Conductor.stepCrochet * GRID_SIZE;
// Make sure the event appears when scrolling up.
var modifiedViewAreaTop:Float = viewAreaTop - GRID_SIZE;
@ -3115,7 +3115,7 @@ class ChartEditorState extends HaxeUIState
var playheadPos:Float = scrollPositionInPixels + playheadPositionInPixels;
var playheadPosFractionalStep:Float = playheadPos / GRID_SIZE / (16 / noteSnapQuant);
var playheadPosStep:Int = Std.int(Math.floor(playheadPosFractionalStep));
var playheadPosMs:Float = playheadPosStep * Conductor.stepLengthMs * (16 / noteSnapQuant);
var playheadPosMs:Float = playheadPosStep * Conductor.stepCrochet * (16 / noteSnapQuant);
var newNoteData:SongNoteData = new SongNoteData(playheadPosMs, column, 0, selectedNoteKind);
performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL));
@ -3363,10 +3363,10 @@ class ChartEditorState extends HaxeUIState
audioVocalTrackGroup.clear();
}
// Add player vocals.
if (currentSongCharacterPlayer != null) audioVocalTrackGroup.setPlayerVocals(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
if (currentSongCharacterPlayer != null) audioVocalTrackGroup.addPlayerVocals(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
'-$currentSongCharacterPlayer'))));
// Add opponent vocals.
if (currentSongCharacterOpponent != null) audioVocalTrackGroup.setOpponentVocals(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
if (currentSongCharacterOpponent != null) audioVocalTrackGroup.addOpponentVocals(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
'-$currentSongCharacterOpponent'))));
postLoadInstrumental();