mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-12-03 12:56:59 -05:00
More chart editor changes
This commit is contained in:
parent
c3577b32ef
commit
26998c9164
7 changed files with 350 additions and 140 deletions
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue