mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2025-03-27 12:29:39 +00:00
Merge pull request #120 from FunkinCrew/feature/chart-editor-bpm
Chart Editor: BPM Changes
This commit is contained in:
commit
13c5297ccc
4
hmm.json
4
hmm.json
|
@ -42,7 +42,7 @@
|
||||||
"name": "haxeui-core",
|
"name": "haxeui-core",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"dir": null,
|
"dir": null,
|
||||||
"ref": "1e1151094f2ca0987025f1a9d33401e44dce3f28",
|
"ref": "3590c94858fc6dbcf9b4d522cd644ad571269677",
|
||||||
"url": "https://github.com/haxeui/haxeui-core/"
|
"url": "https://github.com/haxeui/haxeui-core/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -116,4 +116,4 @@
|
||||||
"version": "0.11.0"
|
"version": "0.11.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,5 +98,6 @@ class Main extends Sprite
|
||||||
// - It scans the class path and registers any HaxeUI components.
|
// - It scans the class path and registers any HaxeUI components.
|
||||||
Toolkit.init();
|
Toolkit.init();
|
||||||
Toolkit.theme = 'dark'; // don't be cringe
|
Toolkit.theme = 'dark'; // don't be cringe
|
||||||
|
Toolkit.autoScale = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,17 +75,18 @@ class Conductor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of a beat in milliseconds. Calculated based on bpm.
|
* Duration of a beat (quarter note) in milliseconds. Calculated based on bpm.
|
||||||
*/
|
*/
|
||||||
public static var beatLengthMs(get, null):Float;
|
public static var beatLengthMs(get, null):Float;
|
||||||
|
|
||||||
static function get_beatLengthMs():Float
|
static function get_beatLengthMs():Float
|
||||||
{
|
{
|
||||||
|
// Tied directly to BPM.
|
||||||
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC);
|
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration of a step (quarter) in milliseconds. Calculated based on bpm.
|
* Duration of a step (sixtennth note) in milliseconds. Calculated based on bpm.
|
||||||
*/
|
*/
|
||||||
public static var stepLengthMs(get, null):Float;
|
public static var stepLengthMs(get, null):Float;
|
||||||
|
|
||||||
|
@ -272,7 +273,8 @@ class Conductor
|
||||||
{
|
{
|
||||||
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
|
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
|
||||||
currentTimeChange.beatTime = prevTimeChange.beatTime
|
currentTimeChange.beatTime = prevTimeChange.beatTime
|
||||||
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC);
|
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC)
|
||||||
|
+ 0.01;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,12 +317,86 @@ class Conductor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultStep += Math.floor((ms - lastTimeChange.timeStamp) / stepLengthMs);
|
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||||
|
var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs;
|
||||||
|
resultStep += resultFractionalStep; // Math.floor();
|
||||||
|
|
||||||
return resultStep;
|
return resultStep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a time in steps and fractional steps, return a time in milliseconds.
|
||||||
|
*/
|
||||||
|
public static function getStepTimeInMs(stepTime:Float):Float
|
||||||
|
{
|
||||||
|
if (timeChanges.length == 0)
|
||||||
|
{
|
||||||
|
// Assume a constant BPM equal to the forced value.
|
||||||
|
return stepTime * stepLengthMs;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var resultMs:Float = 0;
|
||||||
|
|
||||||
|
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||||
|
for (timeChange in timeChanges)
|
||||||
|
{
|
||||||
|
if (stepTime >= timeChange.beatTime * 4)
|
||||||
|
{
|
||||||
|
lastTimeChange = timeChange;
|
||||||
|
resultMs = lastTimeChange.timeStamp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This time change is after the requested time.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||||
|
resultMs += (stepTime - lastTimeChange.beatTime * 4) * lastStepLengthMs;
|
||||||
|
|
||||||
|
return resultMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a time in beats and fractional beats, return a time in milliseconds.
|
||||||
|
*/
|
||||||
|
public static function getBeatTimeInMs(beatTime:Float):Float
|
||||||
|
{
|
||||||
|
if (timeChanges.length == 0)
|
||||||
|
{
|
||||||
|
// Assume a constant BPM equal to the forced value.
|
||||||
|
return beatTime * stepLengthMs * Constants.STEPS_PER_BEAT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var resultMs:Float = 0;
|
||||||
|
|
||||||
|
var lastTimeChange:SongTimeChange = timeChanges[0];
|
||||||
|
for (timeChange in timeChanges)
|
||||||
|
{
|
||||||
|
if (beatTime >= timeChange.beatTime)
|
||||||
|
{
|
||||||
|
lastTimeChange = timeChange;
|
||||||
|
resultMs = lastTimeChange.timeStamp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This time change is after the requested time.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
|
||||||
|
resultMs += (beatTime - lastTimeChange.beatTime) * lastStepLengthMs * Constants.STEPS_PER_BEAT;
|
||||||
|
|
||||||
|
return resultMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function reset():Void
|
public static function reset():Void
|
||||||
{
|
{
|
||||||
beatHit.removeAll();
|
beatHit.removeAll();
|
||||||
|
|
|
@ -127,7 +127,7 @@ class FreeplayState extends MusicBeatSubState
|
||||||
|
|
||||||
if (FlxG.sound.music != null)
|
if (FlxG.sound.music != null)
|
||||||
{
|
{
|
||||||
if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu'));
|
if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (StoryMenuState.weekUnlocked[2] || isDebug)
|
// if (StoryMenuState.weekUnlocked[2] || isDebug)
|
||||||
|
|
|
@ -261,7 +261,7 @@ class InitState extends FlxTransitionableState
|
||||||
*/
|
*/
|
||||||
function startGameNormally():Void
|
function startGameNormally():Void
|
||||||
{
|
{
|
||||||
FlxG.sound.cache(Paths.music('freakyMenu'));
|
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||||
FlxG.switchState(new TitleState());
|
FlxG.switchState(new TitleState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ class MainMenuState extends MusicBeatState
|
||||||
|
|
||||||
if (!FlxG.sound.music.playing)
|
if (!FlxG.sound.music.playing)
|
||||||
{
|
{
|
||||||
FlxG.sound.playMusic(Paths.music('freakyMenu'));
|
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||||
}
|
}
|
||||||
|
|
||||||
persistentUpdate = persistentDraw = true;
|
persistentUpdate = persistentDraw = true;
|
||||||
|
|
|
@ -49,7 +49,7 @@ class TitleState extends MusicBeatState
|
||||||
swagShader = new ColorSwap();
|
swagShader = new ColorSwap();
|
||||||
|
|
||||||
curWacky = FlxG.random.getObject(getIntroTextShit());
|
curWacky = FlxG.random.getObject(getIntroTextShit());
|
||||||
FlxG.sound.cache(Paths.music('freakyMenu'));
|
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||||
|
|
||||||
// DEBUG BULLSHIT
|
// DEBUG BULLSHIT
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.modding.module;
|
package funkin.modding.module;
|
||||||
|
|
||||||
|
import funkin.util.SortUtil;
|
||||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.modding.events.ScriptEventDispatcher;
|
import funkin.modding.events.ScriptEventDispatcher;
|
||||||
|
@ -76,8 +77,7 @@ class ModuleHandler
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Sort alphabetically. Yes that's how this works.
|
return SortUtil.alphabetically(a, b);
|
||||||
return a > b ? 1 : -1;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2374,7 +2374,7 @@ class PlayState extends MusicBeatState
|
||||||
|
|
||||||
if (targetSongId == null)
|
if (targetSongId == null)
|
||||||
{
|
{
|
||||||
FlxG.sound.playMusic(Paths.music('freakyMenu'));
|
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||||
|
|
||||||
transIn = FlxTransitionableState.defaultTransIn;
|
transIn = FlxTransitionableState.defaultTransIn;
|
||||||
transOut = FlxTransitionableState.defaultTransOut;
|
transOut = FlxTransitionableState.defaultTransOut;
|
||||||
|
|
|
@ -592,7 +592,7 @@ class BaseCharacter extends Bopper
|
||||||
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reversed:Bool = false):Void
|
public override function playAnimation(name:String, restart:Bool = false, ?ignoreOther:Bool = false, ?reversed:Bool = false):Void
|
||||||
{
|
{
|
||||||
FlxG.watch.addQuick('playAnim(${characterName})', name);
|
FlxG.watch.addQuick('playAnim(${characterName})', name);
|
||||||
trace('playAnim(${characterName}): ${name}');
|
// trace('playAnim(${characterName}): ${name}');
|
||||||
super.playAnimation(name, restart, ignoreOther, reversed);
|
super.playAnimation(name, restart, ignoreOther, reversed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,15 @@ class SongEvent
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the asset path to the icon this event type should use in the chart editor.
|
||||||
|
* To customize this, override getIconPath().
|
||||||
|
*/
|
||||||
|
public function getIconPath():String
|
||||||
|
{
|
||||||
|
return 'ui/chart-editor/events/default';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the human readable title of this song event type.
|
* Retrieves the human readable title of this song event type.
|
||||||
* Used for the chart editor.
|
* Used for the chart editor.
|
||||||
|
|
|
@ -770,7 +770,7 @@ class Strumline extends FlxSpriteGroup
|
||||||
{
|
{
|
||||||
// The note sprite pool is full and all note splashes are active.
|
// The note sprite pool is full and all note splashes are active.
|
||||||
// We have to create a new note.
|
// We have to create a new note.
|
||||||
result = new SustainTrail(0, 100, noteStyle.getHoldNoteAssetPath(), noteStyle);
|
result = new SustainTrail(0, 100, noteStyle);
|
||||||
this.holdNotes.add(result);
|
this.holdNotes.add(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,9 +88,9 @@ class SustainTrail extends FlxSprite
|
||||||
* @param SustainLength Length in milliseconds.
|
* @param SustainLength Length in milliseconds.
|
||||||
* @param fileName
|
* @param fileName
|
||||||
*/
|
*/
|
||||||
public function new(noteDirection:NoteDirection, sustainLength:Float, fileName:String, noteStyle:NoteStyle)
|
public function new(noteDirection:NoteDirection, sustainLength:Float, noteStyle:NoteStyle)
|
||||||
{
|
{
|
||||||
super(0, 0, fileName);
|
super(0, 0, noteStyle.getHoldNoteAssetPath());
|
||||||
|
|
||||||
antialiasing = true;
|
antialiasing = true;
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ class SustainTrail extends FlxSprite
|
||||||
|
|
||||||
// CALCULATE SIZE
|
// CALCULATE SIZE
|
||||||
width = graphic.width / 8 * zoom; // amount of notes * 2
|
width = graphic.width / 8 * zoom; // amount of notes * 2
|
||||||
height = sustainHeight(sustainLength, PlayState.instance.currentChart.scrollSpeed);
|
height = sustainHeight(sustainLength, getScrollSpeed());
|
||||||
// instead of scrollSpeed, PlayState.SONG.speed
|
// instead of scrollSpeed, PlayState.SONG.speed
|
||||||
|
|
||||||
flipY = PreferencesMenu.getPref('downscroll');
|
flipY = PreferencesMenu.getPref('downscroll');
|
||||||
|
@ -123,6 +123,13 @@ class SustainTrail extends FlxSprite
|
||||||
|
|
||||||
updateClipping();
|
updateClipping();
|
||||||
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
|
||||||
|
|
||||||
|
this.active = true; // This NEEDS to be true for the note to be drawn!
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScrollSpeed():Float
|
||||||
|
{
|
||||||
|
return PlayState?.instance?.currentChart?.scrollSpeed ?? 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -139,7 +146,7 @@ class SustainTrail extends FlxSprite
|
||||||
{
|
{
|
||||||
if (s < 0) s = 0;
|
if (s < 0) s = 0;
|
||||||
|
|
||||||
height = sustainHeight(s, PlayState.instance.currentChart.scrollSpeed);
|
height = sustainHeight(s, getScrollSpeed());
|
||||||
updateColorTransform();
|
updateColorTransform();
|
||||||
updateClipping();
|
updateClipping();
|
||||||
return sustainLength = s;
|
return sustainLength = s;
|
||||||
|
@ -152,7 +159,7 @@ class SustainTrail extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function updateClipping(songTime:Float = 0):Void
|
public function updateClipping(songTime:Float = 0):Void
|
||||||
{
|
{
|
||||||
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), PlayState.instance.currentChart.scrollSpeed), 0, height);
|
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, height);
|
||||||
if (clipHeight == 0)
|
if (clipHeight == 0)
|
||||||
{
|
{
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
|
@ -393,6 +393,9 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of the note, in milliseconds.
|
||||||
|
*/
|
||||||
public var time(get, set):Float;
|
public var time(get, set):Float;
|
||||||
|
|
||||||
function get_time():Float
|
function get_time():Float
|
||||||
|
@ -405,11 +408,14 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
return this.t = value;
|
return this.t = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of the note, in steps.
|
||||||
|
*/
|
||||||
public var stepTime(get, never):Float;
|
public var stepTime(get, never):Float;
|
||||||
|
|
||||||
function get_stepTime():Float
|
function get_stepTime():Float
|
||||||
{
|
{
|
||||||
return Conductor.getTimeInSteps(this.t);
|
return Conductor.getTimeInSteps(abstract.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -435,12 +441,12 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
*/
|
*/
|
||||||
public inline function getDirection(strumlineSize:Int = 4):Int
|
public inline function getDirection(strumlineSize:Int = 4):Int
|
||||||
{
|
{
|
||||||
return this.d % strumlineSize;
|
return abstract.data % strumlineSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDirectionName(strumlineSize:Int = 4):String
|
public function getDirectionName(strumlineSize:Int = 4):String
|
||||||
{
|
{
|
||||||
switch (this.d % strumlineSize)
|
switch (abstract.data % strumlineSize)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
return 'Left';
|
return 'Left';
|
||||||
|
@ -463,7 +469,7 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
*/
|
*/
|
||||||
public inline function getStrumlineIndex(strumlineSize:Int = 4):Int
|
public inline function getStrumlineIndex(strumlineSize:Int = 4):Int
|
||||||
{
|
{
|
||||||
return Math.floor(this.d / strumlineSize);
|
return Math.floor(abstract.data / strumlineSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -477,6 +483,10 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
return getStrumlineIndex(strumlineSize) == 0;
|
return getStrumlineIndex(strumlineSize) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a hold note, this is the length of the hold note in milliseconds.
|
||||||
|
* @default 0 (not a hold note)
|
||||||
|
*/
|
||||||
public var length(get, set):Float;
|
public var length(get, set):Float;
|
||||||
|
|
||||||
function get_length():Float
|
function get_length():Float
|
||||||
|
@ -489,6 +499,22 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
return this.l = value;
|
return this.l = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a hold note, this is the length of the hold note in steps.
|
||||||
|
* @default 0 (not a hold note)
|
||||||
|
*/
|
||||||
|
public var stepLength(get, set):Float;
|
||||||
|
|
||||||
|
function get_stepLength():Float
|
||||||
|
{
|
||||||
|
return Conductor.getTimeInSteps(abstract.time + abstract.length) - abstract.stepTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_stepLength(value:Float):Float
|
||||||
|
{
|
||||||
|
return abstract.length = Conductor.getStepTimeInMs(value) - abstract.time;
|
||||||
|
}
|
||||||
|
|
||||||
public var isHoldNote(get, never):Bool;
|
public var isHoldNote(get, never):Bool;
|
||||||
|
|
||||||
public function get_isHoldNote():Bool
|
public function get_isHoldNote():Bool
|
||||||
|
@ -514,21 +540,37 @@ abstract SongNoteData(RawSongNoteData)
|
||||||
@:op(A == B)
|
@:op(A == B)
|
||||||
public function op_equals(other:SongNoteData):Bool
|
public function op_equals(other:SongNoteData):Bool
|
||||||
{
|
{
|
||||||
if (this.k == '') if (other.kind != '' && other.kind != 'normal') return false;
|
if (abstract.kind == '')
|
||||||
|
{
|
||||||
|
if (other.kind != '' && other.kind != 'normal') return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (other.kind == '' || other.kind != abstract.kind) return false;
|
||||||
|
}
|
||||||
|
|
||||||
return this.t == other.time && this.d == other.data && this.l == other.length;
|
return abstract.time == other.time && abstract.data == other.data && abstract.length == other.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:op(A != B)
|
@:op(A != B)
|
||||||
public function op_notEquals(other:SongNoteData):Bool
|
public function op_notEquals(other:SongNoteData):Bool
|
||||||
{
|
{
|
||||||
return this.t != other.time || this.d != other.data || this.l != other.length || this.k != other.kind;
|
if (abstract.kind == '')
|
||||||
|
{
|
||||||
|
if (other.kind != '' && other.kind != 'normal') return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (other.kind == '' || other.kind != abstract.kind) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return abstract.time != other.time || abstract.data != other.data || abstract.length != other.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:op(A > B)
|
@:op(A > B)
|
||||||
public function op_greaterThan(other:SongNoteData):Bool
|
public function op_greaterThan(other:SongNoteData):Bool
|
||||||
{
|
{
|
||||||
return this.t > other.time;
|
return abstract.time > other.time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@:op(A < B)
|
@:op(A < B)
|
||||||
|
@ -607,7 +649,7 @@ abstract SongEventData(RawSongEventData)
|
||||||
|
|
||||||
function get_stepTime():Float
|
function get_stepTime():Float
|
||||||
{
|
{
|
||||||
return Conductor.getTimeInSteps(this.t);
|
return Conductor.getTimeInSteps(abstract.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public var event(get, set):String;
|
public var event(get, set):String;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import funkin.util.SerializerUtil;
|
||||||
import funkin.play.song.SongData.SongChartData;
|
import funkin.play.song.SongData.SongChartData;
|
||||||
import funkin.play.song.SongData.SongMetadata;
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
|
import funkin.util.SortUtil;
|
||||||
import funkin.input.Cursor;
|
import funkin.input.Cursor;
|
||||||
import funkin.play.character.BaseCharacter;
|
import funkin.play.character.BaseCharacter;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
|
@ -106,8 +107,7 @@ class ChartEditorDialogHandler
|
||||||
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
||||||
|
|
||||||
var songList:Array<String> = SongDataParser.listSongIds();
|
var songList:Array<String> = SongDataParser.listSongIds();
|
||||||
// Sort alphabetically
|
songList.sort(SortUtil.alphabetically);
|
||||||
songList.sort((a, b) -> a > b ? 1 : -1);
|
|
||||||
|
|
||||||
for (targetSongId in songList)
|
for (targetSongId in songList)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import funkin.play.event.SongEventData.SongEventParser;
|
||||||
|
import flixel.graphics.frames.FlxAtlasFrames;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.utils.Assets;
|
import openfl.utils.Assets;
|
||||||
import flixel.FlxObject;
|
import flixel.FlxObject;
|
||||||
|
@ -16,6 +18,8 @@ import funkin.play.song.SongData.SongEventData;
|
||||||
*/
|
*/
|
||||||
class ChartEditorEventSprite extends FlxSprite
|
class ChartEditorEventSprite extends FlxSprite
|
||||||
{
|
{
|
||||||
|
public static final DEFAULT_EVENT = 'Default';
|
||||||
|
|
||||||
public var parentState:ChartEditorState;
|
public var parentState:ChartEditorState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,17 +39,75 @@ class ChartEditorEventSprite extends FlxSprite
|
||||||
|
|
||||||
this.parentState = parent;
|
this.parentState = parent;
|
||||||
|
|
||||||
buildGraphic();
|
this.frames = buildFrames();
|
||||||
|
|
||||||
|
buildAnimations();
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGraphic():Void
|
/**
|
||||||
|
* Build a set of animations to allow displaying different types of chart events.
|
||||||
|
* @param force `true` to force rebuilding the frames.
|
||||||
|
*/
|
||||||
|
static function buildFrames(?force:Bool = false):FlxFramesCollection
|
||||||
{
|
{
|
||||||
if (eventSpriteBasic == null)
|
static var eventFrames:FlxFramesCollection = null;
|
||||||
|
|
||||||
|
if (eventFrames != null && !force) return eventFrames;
|
||||||
|
eventFrames = new FlxAtlasFrames(null);
|
||||||
|
|
||||||
|
// Push the default event as a frame.
|
||||||
|
var defaultFrames:FlxAtlasFrames = Paths.getSparrowAtlas('ui/chart-editor/events/$DEFAULT_EVENT');
|
||||||
|
defaultFrames.parent.persist = true;
|
||||||
|
for (frame in defaultFrames.frames)
|
||||||
{
|
{
|
||||||
eventSpriteBasic = Assets.getBitmapData(Paths.image('ui/chart-editor/event'));
|
eventFrames.pushFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadGraphic(eventSpriteBasic);
|
// Push all the other events as frames.
|
||||||
|
for (eventName in SongEventParser.listEventIds())
|
||||||
|
{
|
||||||
|
var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName'));
|
||||||
|
if (!exists) continue; // No graphic for this event.
|
||||||
|
|
||||||
|
var frames:FlxAtlasFrames = Paths.getSparrowAtlas('ui/chart-editor/events/$eventName');
|
||||||
|
if (frames == null) continue; // Could not load graphic for this event.
|
||||||
|
|
||||||
|
frames.parent.persist = true;
|
||||||
|
for (frame in frames.frames)
|
||||||
|
{
|
||||||
|
eventFrames.pushFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAnimations():Void
|
||||||
|
{
|
||||||
|
var eventNames:Array<String> = [DEFAULT_EVENT].concat(SongEventParser.listEventIds());
|
||||||
|
for (eventName in eventNames)
|
||||||
|
{
|
||||||
|
this.animation.addByPrefix(eventName, '${eventName}0', 24, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function correctAnimationName(name:String):String
|
||||||
|
{
|
||||||
|
if (this.animation.exists(name)) return name;
|
||||||
|
trace('Warning: Invalid animation name "' + name + '" for song event. Using "${DEFAULT_EVENT}"');
|
||||||
|
return DEFAULT_EVENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function playAnimation(name:String):Void
|
||||||
|
{
|
||||||
|
var correctedName = correctAnimationName(name);
|
||||||
|
this.animation.play(correctedName);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh():Void
|
||||||
|
{
|
||||||
setGraphicSize(ChartEditorState.GRID_SIZE);
|
setGraphicSize(ChartEditorState.GRID_SIZE);
|
||||||
this.updateHitbox();
|
this.updateHitbox();
|
||||||
}
|
}
|
||||||
|
@ -56,13 +118,13 @@ class ChartEditorEventSprite extends FlxSprite
|
||||||
|
|
||||||
if (this.eventData == null)
|
if (this.eventData == null)
|
||||||
{
|
{
|
||||||
// Disown parent.
|
// Disown parent. MAKE SURE TO REVIVE BEFORE REUSING
|
||||||
this.kill();
|
this.kill();
|
||||||
return this.eventData;
|
return this.eventData;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
playAnimation(this.eventData.event);
|
||||||
// Update the position to match the note data.
|
// Update the position to match the note data.
|
||||||
updateEventPosition();
|
updateEventPosition();
|
||||||
|
|
||||||
|
@ -82,19 +144,34 @@ class ChartEditorEventSprite extends FlxSprite
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether this note (or its parent) is currently visible.
|
* Return whether this event is currently visible.
|
||||||
*/
|
*/
|
||||||
public function isEventVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
public function isEventVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
||||||
{
|
{
|
||||||
var outsideViewArea = (this.y + this.height < viewAreaTop || this.y > viewAreaBottom);
|
// True if the note is above the view area.
|
||||||
|
var aboveViewArea = (this.y + this.height < viewAreaTop);
|
||||||
|
|
||||||
if (!outsideViewArea)
|
// True if the note is below the view area.
|
||||||
{
|
var belowViewArea = (this.y > viewAreaBottom);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if this note's parent or child is visible.
|
return !aboveViewArea && !belowViewArea;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
/**
|
||||||
|
* Return whether an event, if placed in the scene, would be visible.
|
||||||
|
*/
|
||||||
|
public static function wouldEventBeVisible(viewAreaBottom:Float, viewAreaTop:Float, eventData:SongEventData, ?origin:FlxObject):Bool
|
||||||
|
{
|
||||||
|
var noteHeight:Float = ChartEditorState.GRID_SIZE;
|
||||||
|
var notePosY:Float = eventData.stepTime * ChartEditorState.GRID_SIZE;
|
||||||
|
if (origin != null) notePosY += origin.y;
|
||||||
|
|
||||||
|
// True if the note is above the view area.
|
||||||
|
var aboveViewArea = (notePosY + noteHeight < viewAreaTop);
|
||||||
|
|
||||||
|
// True if the note is below the view area.
|
||||||
|
var belowViewArea = (notePosY > viewAreaBottom);
|
||||||
|
|
||||||
|
return !aboveViewArea && !belowViewArea;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
157
source/funkin/ui/debug/charting/ChartEditorHoldNoteSprite.hx
Normal file
157
source/funkin/ui/debug/charting/ChartEditorHoldNoteSprite.hx
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import funkin.play.notes.Strumline;
|
||||||
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import flixel.FlxObject;
|
||||||
|
import flixel.FlxSprite;
|
||||||
|
import flixel.graphics.frames.FlxFramesCollection;
|
||||||
|
import flixel.graphics.frames.FlxTileFrames;
|
||||||
|
import flixel.math.FlxPoint;
|
||||||
|
import funkin.play.notes.SustainTrail;
|
||||||
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hold note sprite that can be used to display a note in a chart.
|
||||||
|
* Designed to be used and reused efficiently. Has no gameplay functionality.
|
||||||
|
*/
|
||||||
|
class ChartEditorHoldNoteSprite extends SustainTrail
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The ChartEditorState this note belongs to.
|
||||||
|
*/
|
||||||
|
public var parentState:ChartEditorState;
|
||||||
|
|
||||||
|
public function new(parent:ChartEditorState)
|
||||||
|
{
|
||||||
|
var noteStyle = NoteStyleRegistry.instance.fetchDefault();
|
||||||
|
|
||||||
|
super(0, 100, noteStyle);
|
||||||
|
|
||||||
|
this.parentState = parent;
|
||||||
|
|
||||||
|
zoom = 1.0;
|
||||||
|
zoom *= noteStyle.fetchHoldNoteScale();
|
||||||
|
zoom *= 0.7;
|
||||||
|
zoom *= ChartEditorState.GRID_SIZE / Strumline.STRUMLINE_SIZE;
|
||||||
|
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the height directly, to a value in pixels.
|
||||||
|
* @param h The desired height in pixels.
|
||||||
|
*/
|
||||||
|
public function setHeightDirectly(h:Float)
|
||||||
|
{
|
||||||
|
sustainLength = h / (getScrollSpeed() * Constants.PIXELS_PER_MS);
|
||||||
|
fullSustainLength = sustainLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup():Void
|
||||||
|
{
|
||||||
|
strumTime = 999999999;
|
||||||
|
missedNote = false;
|
||||||
|
hitNote = false;
|
||||||
|
active = true;
|
||||||
|
visible = true;
|
||||||
|
alpha = 1.0;
|
||||||
|
width = graphic.width / 8 * zoom; // amount of notes * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function revive():Void
|
||||||
|
{
|
||||||
|
super.revive();
|
||||||
|
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override function kill():Void
|
||||||
|
{
|
||||||
|
super.kill();
|
||||||
|
|
||||||
|
active = false;
|
||||||
|
visible = false;
|
||||||
|
noteData = null;
|
||||||
|
strumTime = 999999999;
|
||||||
|
noteDirection = 0;
|
||||||
|
sustainLength = 0;
|
||||||
|
fullSustainLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether this note is currently visible.
|
||||||
|
*/
|
||||||
|
public function isHoldNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
||||||
|
{
|
||||||
|
// True if the note is above the view area.
|
||||||
|
var aboveViewArea = (this.y + this.height < viewAreaTop);
|
||||||
|
|
||||||
|
// True if the note is below the view area.
|
||||||
|
var belowViewArea = (this.y > viewAreaBottom);
|
||||||
|
|
||||||
|
return !aboveViewArea && !belowViewArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a hold note, if placed in the scene, would be visible.
|
||||||
|
*/
|
||||||
|
public static function wouldHoldNoteBeVisible(viewAreaBottom:Float, viewAreaTop:Float, noteData:SongNoteData, ?origin:FlxObject):Bool
|
||||||
|
{
|
||||||
|
var noteHeight:Float = noteData.stepLength * ChartEditorState.GRID_SIZE;
|
||||||
|
var notePosY:Float = noteData.stepTime * ChartEditorState.GRID_SIZE;
|
||||||
|
if (origin != null) notePosY += origin.y;
|
||||||
|
|
||||||
|
// True if the note is above the view area.
|
||||||
|
var aboveViewArea = (notePosY + noteHeight < viewAreaTop);
|
||||||
|
|
||||||
|
// True if the note is below the view area.
|
||||||
|
var belowViewArea = (notePosY > viewAreaBottom);
|
||||||
|
|
||||||
|
return !aboveViewArea && !belowViewArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateHoldNotePosition(?origin:FlxObject)
|
||||||
|
{
|
||||||
|
var cursorColumn:Int = this.noteData.data;
|
||||||
|
|
||||||
|
if (cursorColumn < 0) cursorColumn = 0;
|
||||||
|
if (cursorColumn >= (ChartEditorState.STRUMLINE_SIZE * 2 + 1))
|
||||||
|
{
|
||||||
|
cursorColumn = (ChartEditorState.STRUMLINE_SIZE * 2 + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invert player and opponent columns.
|
||||||
|
if (cursorColumn >= ChartEditorState.STRUMLINE_SIZE)
|
||||||
|
{
|
||||||
|
cursorColumn -= ChartEditorState.STRUMLINE_SIZE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cursorColumn += ChartEditorState.STRUMLINE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
|
||||||
|
|
||||||
|
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
|
||||||
|
if (this.noteData.stepTime >= 0)
|
||||||
|
{
|
||||||
|
// noteData.stepTime is a calculated value which accounts for BPM changes
|
||||||
|
var stepTime:Float = this.noteData.stepTime;
|
||||||
|
var roundedStepTime:Float = Math.floor(stepTime + 0.01); // Add epsilon to fix rounding issues
|
||||||
|
this.y = roundedStepTime * ChartEditorState.GRID_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.x += ChartEditorState.GRID_SIZE / 2;
|
||||||
|
this.x -= this.width / 2;
|
||||||
|
|
||||||
|
this.y += ChartEditorState.GRID_SIZE / 2;
|
||||||
|
|
||||||
|
if (origin != null)
|
||||||
|
{
|
||||||
|
this.x += origin.x;
|
||||||
|
this.y += origin.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,16 +34,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public var noteStyle(get, null):String;
|
public var noteStyle(get, null):String;
|
||||||
|
|
||||||
/**
|
|
||||||
* This note is the previous sprite in a sustain chain.
|
|
||||||
*/
|
|
||||||
public var parentNoteSprite(default, set):ChartEditorNoteSprite = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This note is the next sprite in a sustain chain.
|
|
||||||
*/
|
|
||||||
public var childNoteSprite(default, set):ChartEditorNoteSprite = null;
|
|
||||||
|
|
||||||
public function new(parent:ChartEditorState)
|
public function new(parent:ChartEditorState)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
@ -124,14 +114,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
|
|
||||||
if (this.noteData == null)
|
if (this.noteData == null)
|
||||||
{
|
{
|
||||||
// Disown parent.
|
|
||||||
this.parentNoteSprite = null;
|
|
||||||
if (this.childNoteSprite != null)
|
|
||||||
{
|
|
||||||
// Kill all children and disown them.
|
|
||||||
this.childNoteSprite.noteData = null;
|
|
||||||
this.childNoteSprite = null;
|
|
||||||
}
|
|
||||||
this.kill();
|
this.kill();
|
||||||
return this.noteData;
|
return this.noteData;
|
||||||
}
|
}
|
||||||
|
@ -170,68 +152,22 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentNoteSprite == null)
|
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
|
||||||
|
|
||||||
|
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
|
||||||
|
if (this.noteData.stepTime >= 0)
|
||||||
{
|
{
|
||||||
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
|
// noteData.stepTime is a calculated value which accounts for BPM changes
|
||||||
|
var stepTime:Float = this.noteData.stepTime;
|
||||||
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
|
var roundedStepTime:Float = Math.floor(stepTime + 0.01); // Add epsilon to fix rounding issues
|
||||||
// TODO: stepTime doesn't account for fluctuating BPMs.
|
this.y = roundedStepTime * ChartEditorState.GRID_SIZE;
|
||||||
if (this.noteData.stepTime >= 0) this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
|
|
||||||
|
|
||||||
if (origin != null)
|
|
||||||
{
|
|
||||||
this.x += origin.x;
|
|
||||||
this.y += origin.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If this is a hold note, we need to adjust the position to be centered.
|
|
||||||
if (parentNoteSprite.parentNoteSprite == null)
|
|
||||||
{
|
|
||||||
this.x = parentNoteSprite.x;
|
|
||||||
this.x += (ChartEditorState.GRID_SIZE / 2);
|
|
||||||
this.x -= this.width / 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.x = parentNoteSprite.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.y = parentNoteSprite.y;
|
|
||||||
if (parentNoteSprite.parentNoteSprite == null)
|
|
||||||
{
|
|
||||||
this.y += parentNoteSprite.height / 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.y += parentNoteSprite.height - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_parentNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
|
|
||||||
{
|
|
||||||
this.parentNoteSprite = value;
|
|
||||||
|
|
||||||
if (this.parentNoteSprite != null)
|
|
||||||
{
|
|
||||||
this.noteData = this.parentNoteSprite.noteData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.parentNoteSprite;
|
if (origin != null)
|
||||||
}
|
|
||||||
|
|
||||||
function set_childNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
|
|
||||||
{
|
|
||||||
this.childNoteSprite = value;
|
|
||||||
|
|
||||||
if (this.parentNoteSprite != null)
|
|
||||||
{
|
{
|
||||||
this.noteData = this.parentNoteSprite.noteData;
|
this.x += origin.x;
|
||||||
|
this.y += origin.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.childNoteSprite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_noteStyle():String
|
function get_noteStyle():String
|
||||||
|
@ -244,7 +180,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
{
|
{
|
||||||
// Decide whether to display a note or a sustain.
|
// Decide whether to display a note or a sustain.
|
||||||
var baseAnimationName:String = 'tap';
|
var baseAnimationName:String = 'tap';
|
||||||
if (this.parentNoteSprite != null) baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd';
|
|
||||||
|
|
||||||
// Play the appropriate animation for the type, direction, and skin.
|
// Play the appropriate animation for the type, direction, and skin.
|
||||||
var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle}';
|
var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle}';
|
||||||
|
@ -257,17 +192,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
{
|
{
|
||||||
case 'tap':
|
case 'tap':
|
||||||
this.setGraphicSize(0, ChartEditorState.GRID_SIZE);
|
this.setGraphicSize(0, ChartEditorState.GRID_SIZE);
|
||||||
case 'hold':
|
|
||||||
if (parentNoteSprite.parentNoteSprite == null)
|
|
||||||
{
|
|
||||||
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), ChartEditorState.GRID_SIZE);
|
|
||||||
}
|
|
||||||
case 'holdEnd':
|
|
||||||
this.setGraphicSize(Std.int(ChartEditorState.GRID_SIZE / 2), Std.int(ChartEditorState.GRID_SIZE / 2));
|
|
||||||
}
|
}
|
||||||
this.updateHitbox();
|
this.updateHitbox();
|
||||||
|
|
||||||
|
@ -280,22 +204,30 @@ class ChartEditorNoteSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public function isNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
public function isNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
|
||||||
{
|
{
|
||||||
var outsideViewArea = (this.y + this.height < viewAreaTop || this.y > viewAreaBottom);
|
// True if the note is above the view area.
|
||||||
|
var aboveViewArea = (this.y + this.height < viewAreaTop);
|
||||||
|
|
||||||
if (!outsideViewArea)
|
// True if the note is below the view area.
|
||||||
{
|
var belowViewArea = (this.y > viewAreaBottom);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if this note's parent or child is visible.
|
return !aboveViewArea && !belowViewArea;
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBaseNoteSprite()
|
/**
|
||||||
|
* Return whether a note, if placed in the scene, would be visible.
|
||||||
|
*/
|
||||||
|
public static function wouldNoteBeVisible(viewAreaBottom:Float, viewAreaTop:Float, noteData:SongNoteData, ?origin:FlxObject):Bool
|
||||||
{
|
{
|
||||||
if (this.parentNoteSprite == null) return this;
|
var noteHeight:Float = ChartEditorState.GRID_SIZE;
|
||||||
else
|
var notePosY:Float = noteData.stepTime * ChartEditorState.GRID_SIZE;
|
||||||
return this.parentNoteSprite;
|
if (origin != null) notePosY += origin.y;
|
||||||
|
|
||||||
|
// True if the note is above the view area.
|
||||||
|
var aboveViewArea = (notePosY + noteHeight < viewAreaTop);
|
||||||
|
|
||||||
|
// True if the note is below the view area.
|
||||||
|
var belowViewArea = (notePosY > viewAreaBottom);
|
||||||
|
|
||||||
|
return !aboveViewArea && !belowViewArea;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
import funkin.data.notestyle.NoteStyleRegistry;
|
|
||||||
import funkin.ui.debug.charting.ChartEditorCommand;
|
|
||||||
import funkin.play.song.SongData.SongPlayableChar;
|
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
|
||||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
|
||||||
import openfl.Assets;
|
|
||||||
import flixel.addons.display.FlxSliceSprite;
|
import flixel.addons.display.FlxSliceSprite;
|
||||||
import flixel.addons.display.FlxTiledSprite;
|
import flixel.addons.display.FlxTiledSprite;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
|
@ -22,32 +16,41 @@ import flixel.util.FlxSort;
|
||||||
import flixel.util.FlxTimer;
|
import flixel.util.FlxTimer;
|
||||||
import funkin.audio.visualize.PolygonSpectogram;
|
import funkin.audio.visualize.PolygonSpectogram;
|
||||||
import funkin.audio.VoicesGroup;
|
import funkin.audio.VoicesGroup;
|
||||||
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
|
import funkin.data.notestyle.NoteStyleRegistry;
|
||||||
import funkin.input.Cursor;
|
import funkin.input.Cursor;
|
||||||
import funkin.input.TurboKeyHandler;
|
import funkin.input.TurboKeyHandler;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
|
import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
import funkin.play.HealthIcon;
|
import funkin.play.HealthIcon;
|
||||||
import funkin.play.notes.NoteSprite;
|
import funkin.play.notes.NoteSprite;
|
||||||
|
import funkin.play.notes.Strumline;
|
||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
import funkin.play.song.SongData.SongChartData;
|
import funkin.play.song.SongData.SongChartData;
|
||||||
import funkin.play.song.SongData.SongDataParser;
|
import funkin.play.song.SongData.SongDataParser;
|
||||||
import funkin.play.song.SongData.SongEventData;
|
import funkin.play.song.SongData.SongEventData;
|
||||||
import funkin.play.song.SongData.SongMetadata;
|
import funkin.play.song.SongData.SongMetadata;
|
||||||
import funkin.play.song.SongData.SongNoteData;
|
import funkin.play.song.SongData.SongNoteData;
|
||||||
|
import funkin.play.song.SongData.SongPlayableChar;
|
||||||
import funkin.play.song.SongDataUtils;
|
import funkin.play.song.SongDataUtils;
|
||||||
|
import funkin.ui.debug.charting.ChartEditorCommand;
|
||||||
|
import funkin.ui.debug.charting.ChartEditorCommand;
|
||||||
import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme;
|
import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme;
|
||||||
import funkin.ui.debug.charting.ChartEditorToolboxHandler.ChartEditorToolMode;
|
import funkin.ui.debug.charting.ChartEditorToolboxHandler.ChartEditorToolMode;
|
||||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||||
import funkin.ui.haxeui.HaxeUIState;
|
import funkin.ui.haxeui.HaxeUIState;
|
||||||
import funkin.util.FileUtil;
|
|
||||||
import funkin.util.Constants;
|
import funkin.util.Constants;
|
||||||
import funkin.util.DateUtil;
|
import funkin.util.DateUtil;
|
||||||
|
import funkin.util.FileUtil;
|
||||||
import funkin.util.SerializerUtil;
|
import funkin.util.SerializerUtil;
|
||||||
|
import funkin.util.SortUtil;
|
||||||
import funkin.util.WindowUtil;
|
import funkin.util.WindowUtil;
|
||||||
import haxe.DynamicAccess;
|
import haxe.DynamicAccess;
|
||||||
import haxe.io.Bytes;
|
import haxe.io.Bytes;
|
||||||
import haxe.io.Path;
|
import haxe.io.Path;
|
||||||
import haxe.ui.components.Label;
|
import haxe.ui.components.Label;
|
||||||
import haxe.ui.components.Slider;
|
import haxe.ui.components.Slider;
|
||||||
|
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||||
import haxe.ui.containers.menus.MenuItem;
|
import haxe.ui.containers.menus.MenuItem;
|
||||||
import haxe.ui.containers.TreeView;
|
import haxe.ui.containers.TreeView;
|
||||||
import haxe.ui.containers.TreeViewNode;
|
import haxe.ui.containers.TreeViewNode;
|
||||||
|
@ -57,6 +60,7 @@ import haxe.ui.events.DragEvent;
|
||||||
import haxe.ui.events.UIEvent;
|
import haxe.ui.events.UIEvent;
|
||||||
import haxe.ui.notifications.NotificationManager;
|
import haxe.ui.notifications.NotificationManager;
|
||||||
import haxe.ui.notifications.NotificationType;
|
import haxe.ui.notifications.NotificationType;
|
||||||
|
import openfl.Assets;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
import openfl.geom.Rectangle;
|
import openfl.geom.Rectangle;
|
||||||
|
|
||||||
|
@ -121,6 +125,11 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
static final MENU_BAR_HEIGHT:Int = 32;
|
static final MENU_BAR_HEIGHT:Int = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The height of the playbar in the layout.
|
||||||
|
*/
|
||||||
|
static final PLAYBAR_HEIGHT:Int = 48;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duration to wait before autosaving the chart.
|
* Duration to wait before autosaving the chart.
|
||||||
*/
|
*/
|
||||||
|
@ -182,29 +191,35 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* scrollPosition, converted to steps.
|
* scrollPosition, converted to steps.
|
||||||
* TODO: Handle BPM changes.
|
* NOT dependant on BPM, because the size of a grid square does not change with BPM.
|
||||||
*/
|
*/
|
||||||
var scrollPositionInSteps(get, null):Float;
|
var scrollPositionInSteps(get, set):Float;
|
||||||
|
|
||||||
function get_scrollPositionInSteps():Float
|
function get_scrollPositionInSteps():Float
|
||||||
{
|
{
|
||||||
return scrollPositionInPixels / GRID_SIZE;
|
return scrollPositionInPixels / GRID_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set_scrollPositionInSteps(value:Float):Float
|
||||||
|
{
|
||||||
|
scrollPositionInPixels = value * GRID_SIZE;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* scrollPosition, converted to milliseconds.
|
* scrollPosition, converted to milliseconds.
|
||||||
* TODO: Handle BPM changes.
|
* DEPENDANT on BPM, because the duration of a grid square changes with BPM.
|
||||||
*/
|
*/
|
||||||
var scrollPositionInMs(get, set):Float;
|
var scrollPositionInMs(get, set):Float;
|
||||||
|
|
||||||
function get_scrollPositionInMs():Float
|
function get_scrollPositionInMs():Float
|
||||||
{
|
{
|
||||||
return scrollPositionInSteps * Conductor.stepLengthMs;
|
return Conductor.getStepTimeInMs(scrollPositionInSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_scrollPositionInMs(value:Float):Float
|
function set_scrollPositionInMs(value:Float):Float
|
||||||
{
|
{
|
||||||
scrollPositionInPixels = value / Conductor.stepLengthMs;
|
scrollPositionInSteps = Conductor.getTimeInSteps(value);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,11 +231,26 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
var playheadPositionInPixels(default, set):Float;
|
var playheadPositionInPixels(default, set):Float;
|
||||||
|
|
||||||
var playheadPositionInSteps(get, null):Float;
|
function set_playheadPositionInPixels(value:Float):Float
|
||||||
|
{
|
||||||
|
// Make sure playhead doesn't go outside the song.
|
||||||
|
if (value + scrollPositionInPixels < 0) value = -scrollPositionInPixels;
|
||||||
|
if (value + scrollPositionInPixels > songLengthInPixels) value = songLengthInPixels - scrollPositionInPixels;
|
||||||
|
|
||||||
|
this.playheadPositionInPixels = value;
|
||||||
|
|
||||||
|
// Move the playhead sprite to the correct position.
|
||||||
|
gridPlayhead.y = this.playheadPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
|
||||||
|
|
||||||
|
return this.playheadPositionInPixels;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* playheadPosition, converted to steps.
|
* playheadPosition, converted to steps.
|
||||||
|
* NOT dependant on BPM, because the size of a grid square does not change with BPM.
|
||||||
*/
|
*/
|
||||||
|
var playheadPositionInSteps(get, null):Float;
|
||||||
|
|
||||||
function get_playheadPositionInSteps():Float
|
function get_playheadPositionInSteps():Float
|
||||||
{
|
{
|
||||||
return playheadPositionInPixels / GRID_SIZE;
|
return playheadPositionInPixels / GRID_SIZE;
|
||||||
|
@ -228,60 +258,76 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* playheadPosition, converted to milliseconds.
|
* playheadPosition, converted to milliseconds.
|
||||||
|
* DEPENDANT on BPM, because the duration of a grid square changes with BPM.
|
||||||
*/
|
*/
|
||||||
var playheadPositionInMs(get, null):Float;
|
var playheadPositionInMs(get, null):Float;
|
||||||
|
|
||||||
function get_playheadPositionInMs():Float
|
function get_playheadPositionInMs():Float
|
||||||
{
|
{
|
||||||
return playheadPositionInSteps * Conductor.stepLengthMs;
|
return Conductor.getStepTimeInMs(playheadPositionInSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the song's length in PIXELS, same format as scrollPosition.
|
* songLength, in milliseconds.
|
||||||
*/
|
*/
|
||||||
var songLengthInPixels(get, default):Int;
|
@:isVar var songLengthInMs(get, set):Float;
|
||||||
|
|
||||||
function get_songLengthInPixels():Int
|
function get_songLengthInMs():Float
|
||||||
{
|
{
|
||||||
if (songLengthInPixels <= 0) return 1000;
|
if (songLengthInMs <= 0) return 1000;
|
||||||
|
return songLengthInMs;
|
||||||
|
}
|
||||||
|
|
||||||
return songLengthInPixels;
|
function set_songLengthInMs(value:Float):Float
|
||||||
|
{
|
||||||
|
this.songLengthInMs = value;
|
||||||
|
|
||||||
|
// Make sure playhead doesn't go outside the song.
|
||||||
|
if (playheadPositionInMs > songLengthInMs) playheadPositionInMs = songLengthInMs;
|
||||||
|
|
||||||
|
return this.songLengthInMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* songLength, converted to steps.
|
* songLength, converted to steps.
|
||||||
* TODO: Handle BPM changes.
|
* Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does.
|
||||||
*/
|
*/
|
||||||
var songLengthInSteps(get, set):Float;
|
var songLengthInSteps(get, set):Float;
|
||||||
|
|
||||||
function get_songLengthInSteps():Float
|
function get_songLengthInSteps():Float
|
||||||
{
|
{
|
||||||
return songLengthInPixels / GRID_SIZE;
|
return Conductor.getTimeInSteps(songLengthInMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_songLengthInSteps(value:Float):Float
|
function set_songLengthInSteps(value:Float):Float
|
||||||
{
|
{
|
||||||
songLengthInPixels = Std.int(value * GRID_SIZE);
|
// Getting a reasonable result from setting songLengthInSteps requires that Conductor.mapBPMChanges be called first.
|
||||||
|
songLengthInMs = Conductor.getStepTimeInMs(value);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* songLength, converted to milliseconds.
|
* This is the song's length in PIXELS, same format as scrollPosition.
|
||||||
* TODO: Handle BPM changes.
|
* Dependant on BPM, because the size of a grid square does not change with BPM but the length of a beat does.
|
||||||
*/
|
*/
|
||||||
var songLengthInMs(get, set):Float;
|
var songLengthInPixels(get, set):Int;
|
||||||
|
|
||||||
function get_songLengthInMs():Float
|
function get_songLengthInPixels():Int
|
||||||
{
|
{
|
||||||
return songLengthInSteps * Conductor.stepLengthMs;
|
return Std.int(songLengthInSteps * GRID_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_songLengthInMs(value:Float):Float
|
function set_songLengthInPixels(value:Int):Int
|
||||||
{
|
{
|
||||||
songLengthInSteps = Conductor.getTimeInSteps(audioInstTrack.length);
|
songLengthInSteps = value / GRID_SIZE;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current theme used by the editor.
|
||||||
|
* Dictates the appearance of many UI elements.
|
||||||
|
* Currently hardcoded to just Light and Dark.
|
||||||
|
*/
|
||||||
var currentTheme(default, set):ChartEditorTheme = null;
|
var currentTheme(default, set):ChartEditorTheme = null;
|
||||||
|
|
||||||
function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme
|
function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme
|
||||||
|
@ -1002,6 +1048,13 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
var renderedNotes:FlxTypedSpriteGroup<ChartEditorNoteSprite>;
|
var renderedNotes:FlxTypedSpriteGroup<ChartEditorNoteSprite>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sprite group containing the hold note graphics.
|
||||||
|
* Only displays a subset of the data from `currentSongChartNoteData`,
|
||||||
|
* and kills notes that are off-screen to be recycled later.
|
||||||
|
*/
|
||||||
|
var renderedHoldNotes:FlxTypedSpriteGroup<ChartEditorHoldNoteSprite>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sprite group containing the song events.
|
* The sprite group containing the song events.
|
||||||
* Only displays a subset of the data from `currentSongChartEventData`,
|
* Only displays a subset of the data from `currentSongChartEventData`,
|
||||||
|
@ -1088,7 +1141,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
gridGhostNote = new ChartEditorNoteSprite(this);
|
gridGhostNote = new ChartEditorNoteSprite(this);
|
||||||
gridGhostNote.alpha = 0.6;
|
gridGhostNote.alpha = 0.6;
|
||||||
gridGhostNote.noteData = new SongNoteData(-1, -1, 0, '');
|
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "");
|
||||||
gridGhostNote.visible = false;
|
gridGhostNote.visible = false;
|
||||||
add(gridGhostNote);
|
add(gridGhostNote);
|
||||||
|
|
||||||
|
@ -1187,6 +1240,10 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
function buildNoteGroup():Void
|
function buildNoteGroup():Void
|
||||||
{
|
{
|
||||||
|
renderedHoldNotes = new FlxTypedSpriteGroup<ChartEditorHoldNoteSprite>();
|
||||||
|
renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||||
|
add(renderedHoldNotes);
|
||||||
|
|
||||||
renderedNotes = new FlxTypedSpriteGroup<ChartEditorNoteSprite>();
|
renderedNotes = new FlxTypedSpriteGroup<ChartEditorNoteSprite>();
|
||||||
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||||
add(renderedNotes);
|
add(renderedNotes);
|
||||||
|
@ -1812,12 +1869,9 @@ class ChartEditorState extends HaxeUIState
|
||||||
moveSongToScrollPosition();
|
moveSongToScrollPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor position snapped to the grid.
|
|
||||||
|
|
||||||
// The song position of the cursor, in steps.
|
// The song position of the cursor, in steps.
|
||||||
var cursorFractionalStep:Float = cursorY / GRID_SIZE / (16 / noteSnapQuant);
|
var cursorFractionalStep:Float = cursorY / GRID_SIZE / (16 / noteSnapQuant);
|
||||||
var cursorStep:Int = Std.int(Math.floor(cursorFractionalStep));
|
var cursorMs:Float = Conductor.getStepTimeInMs(cursorFractionalStep);
|
||||||
var cursorMs:Float = cursorStep * Conductor.stepLengthMs * (16 / noteSnapQuant);
|
|
||||||
// The direction value for the column at the cursor.
|
// The direction value for the column at the cursor.
|
||||||
var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE);
|
var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE);
|
||||||
if (cursorColumn < 0) cursorColumn = 0;
|
if (cursorColumn < 0) cursorColumn = 0;
|
||||||
|
@ -1855,7 +1909,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
// We released the mouse. Select the notes in the box.
|
// We released the mouse. Select the notes in the box.
|
||||||
var cursorFractionalStepStart:Float = cursorYStart / GRID_SIZE;
|
var cursorFractionalStepStart:Float = cursorYStart / GRID_SIZE;
|
||||||
var cursorStepStart:Int = Math.floor(cursorFractionalStepStart);
|
var cursorStepStart:Int = Math.floor(cursorFractionalStepStart);
|
||||||
var cursorMsStart:Float = cursorStepStart * Conductor.stepLengthMs;
|
var cursorMsStart:Float = Conductor.getStepTimeInMs(cursorStepStart);
|
||||||
var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE);
|
var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE);
|
||||||
var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE);
|
var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE);
|
||||||
|
|
||||||
|
@ -1994,8 +2048,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
{
|
{
|
||||||
if (highlightedNote != null)
|
if (highlightedNote != null)
|
||||||
{
|
{
|
||||||
// Handle the case of clicking on a sustain piece.
|
// TODO: Handle the case of clicking on a sustain piece.
|
||||||
highlightedNote = highlightedNote.getBaseNoteSprite();
|
|
||||||
// Control click to select/deselect an individual note.
|
// Control click to select/deselect an individual note.
|
||||||
if (isNoteSelected(highlightedNote.noteData))
|
if (isNoteSelected(highlightedNote.noteData))
|
||||||
{
|
{
|
||||||
|
@ -2059,12 +2112,13 @@ class ChartEditorState extends HaxeUIState
|
||||||
{
|
{
|
||||||
// Handle extending the note as you drag.
|
// Handle extending the note as you drag.
|
||||||
|
|
||||||
// Since use Math.floor and stepLengthMs here, the hold notes will be beat snapped.
|
// TODO: This should be beat snapped?
|
||||||
var dragLengthSteps:Float = Math.floor((cursorMs - currentPlaceNoteData.time) / Conductor.stepLengthMs);
|
var dragLengthSteps:Float = Conductor.getTimeInSteps(cursorMs) - currentPlaceNoteData.stepTime;
|
||||||
|
|
||||||
// Without this, the newly placed note feels too short compared to the user's input.
|
// Without this, the newly placed note feels too short compared to the user's input.
|
||||||
var INCREMENT:Float = 1.0;
|
var INCREMENT:Float = 1.0;
|
||||||
var dragLengthMs:Float = (dragLengthSteps + INCREMENT) * Conductor.stepLengthMs;
|
// TODO: Make this not busted with BPM changes
|
||||||
|
var dragLengthMs:Float = Math.floor(dragLengthSteps + INCREMENT) * Conductor.stepLengthMs;
|
||||||
|
|
||||||
// TODO: Add and update some sort of preview?
|
// TODO: Add and update some sort of preview?
|
||||||
|
|
||||||
|
@ -2197,8 +2251,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
if (highlightedNote != null)
|
if (highlightedNote != null)
|
||||||
{
|
{
|
||||||
// Handle the case of clicking on a sustain piece.
|
// TODO: Handle the case of clicking on a sustain piece.
|
||||||
highlightedNote = highlightedNote.getBaseNoteSprite();
|
|
||||||
// Remove the note.
|
// Remove the note.
|
||||||
performCommand(new RemoveNotesCommand([highlightedNote.noteData]));
|
performCommand(new RemoveNotesCommand([highlightedNote.noteData]));
|
||||||
}
|
}
|
||||||
|
@ -2286,10 +2339,10 @@ class ChartEditorState extends HaxeUIState
|
||||||
// Update for whether downscroll is enabled.
|
// Update for whether downscroll is enabled.
|
||||||
renderedNotes.flipX = (isViewDownscroll);
|
renderedNotes.flipX = (isViewDownscroll);
|
||||||
|
|
||||||
// Calculate the view bounds.
|
// Calculate the top and bottom of the view area.
|
||||||
var viewAreaTop:Float = this.scrollPositionInPixels - GRID_TOP_PAD;
|
var viewAreaTopPixels:Float = MENU_BAR_HEIGHT;
|
||||||
var viewHeight:Float = (FlxG.height - MENU_BAR_HEIGHT);
|
var visibleGridHeightPixels:Float = FlxG.height - MENU_BAR_HEIGHT - PLAYBAR_HEIGHT; // The area underneath the menu bar and playbar is not visible.
|
||||||
var viewAreaBottom:Float = this.scrollPositionInPixels + viewHeight;
|
var viewAreaBottomPixels:Float = viewAreaTopPixels + visibleGridHeightPixels;
|
||||||
|
|
||||||
// Remove notes that are no longer visible and list the ones that are.
|
// Remove notes that are no longer visible and list the ones that are.
|
||||||
var displayedNoteData:Array<SongNoteData> = [];
|
var displayedNoteData:Array<SongNoteData> = [];
|
||||||
|
@ -2297,7 +2350,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
{
|
{
|
||||||
if (noteSprite == null || !noteSprite.exists || !noteSprite.visible) continue;
|
if (noteSprite == null || !noteSprite.exists || !noteSprite.visible) continue;
|
||||||
|
|
||||||
if (!noteSprite.isNoteVisible(viewAreaBottom, viewAreaTop))
|
if (!noteSprite.isNoteVisible(viewAreaBottomPixels, viewAreaTopPixels))
|
||||||
{
|
{
|
||||||
// This sprite is off-screen.
|
// This sprite is off-screen.
|
||||||
// Kill the note sprite and recycle it.
|
// Kill the note sprite and recycle it.
|
||||||
|
@ -2309,18 +2362,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
// Kill the note sprite and recycle it.
|
// Kill the note sprite and recycle it.
|
||||||
noteSprite.noteData = null;
|
noteSprite.noteData = null;
|
||||||
}
|
}
|
||||||
else if (noteSprite.noteData.length > 0 && (noteSprite.parentNoteSprite == null && noteSprite.childNoteSprite == null))
|
|
||||||
{
|
|
||||||
// Note was extended.
|
|
||||||
// Kill the note sprite and recycle it.
|
|
||||||
noteSprite.noteData = null;
|
|
||||||
}
|
|
||||||
else if (noteSprite.noteData.length == 0 && (noteSprite.parentNoteSprite != null || noteSprite.childNoteSprite != null))
|
|
||||||
{
|
|
||||||
// Note was shortened.
|
|
||||||
// Kill the note sprite and recycle it.
|
|
||||||
noteSprite.noteData = null;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Note is already displayed and should remain displayed.
|
// Note is already displayed and should remain displayed.
|
||||||
|
@ -2331,13 +2372,42 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var displayedHoldNoteData:Array<SongNoteData> = [];
|
||||||
|
for (holdNoteSprite in renderedHoldNotes.members)
|
||||||
|
{
|
||||||
|
if (holdNoteSprite == null || !holdNoteSprite.exists || !holdNoteSprite.visible) continue;
|
||||||
|
|
||||||
|
if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD))
|
||||||
|
{
|
||||||
|
holdNoteSprite.kill();
|
||||||
|
}
|
||||||
|
else if (currentSongChartNoteData.indexOf(holdNoteSprite.noteData) == -1 || holdNoteSprite.noteData.length == 0)
|
||||||
|
{
|
||||||
|
// This hold note was deleted.
|
||||||
|
// Kill the hold note sprite and recycle it.
|
||||||
|
holdNoteSprite.kill();
|
||||||
|
}
|
||||||
|
else if (displayedHoldNoteData.indexOf(holdNoteSprite.noteData) != -1)
|
||||||
|
{
|
||||||
|
// This hold note is a duplicate.
|
||||||
|
// Kill the hold note sprite and recycle it.
|
||||||
|
holdNoteSprite.kill();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
displayedHoldNoteData.push(holdNoteSprite.noteData);
|
||||||
|
// Update the event sprite's position.
|
||||||
|
holdNoteSprite.updateHoldNotePosition(renderedNotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove events that are no longer visible and list the ones that are.
|
// Remove events that are no longer visible and list the ones that are.
|
||||||
var displayedEventData:Array<SongEventData> = [];
|
var displayedEventData:Array<SongEventData> = [];
|
||||||
for (eventSprite in renderedEvents.members)
|
for (eventSprite in renderedEvents.members)
|
||||||
{
|
{
|
||||||
if (eventSprite == null || !eventSprite.exists || !eventSprite.visible) continue;
|
if (eventSprite == null || !eventSprite.exists || !eventSprite.visible) continue;
|
||||||
|
|
||||||
if (!eventSprite.isEventVisible(viewAreaBottom, viewAreaTop))
|
if (!eventSprite.isEventVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD))
|
||||||
{
|
{
|
||||||
// This sprite is off-screen.
|
// This sprite is off-screen.
|
||||||
// Kill the event sprite and recycle it.
|
// Kill the event sprite and recycle it.
|
||||||
|
@ -2368,59 +2438,36 @@ class ChartEditorState extends HaxeUIState
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the position the note should be at.
|
if (!ChartEditorNoteSprite.wouldNoteBeVisible(viewAreaBottomPixels, viewAreaTopPixels, noteData,
|
||||||
var noteTimePixels:Float = noteData.time / Conductor.stepLengthMs * GRID_SIZE;
|
renderedNotes)) continue; // Else, this note is visible and we need to render it!
|
||||||
|
|
||||||
// Make sure the note appears when scrolling up.
|
|
||||||
var modifiedViewAreaTop:Float = viewAreaTop - GRID_SIZE;
|
|
||||||
|
|
||||||
if (noteTimePixels < modifiedViewAreaTop || noteTimePixels > viewAreaBottom) continue;
|
|
||||||
|
|
||||||
// Else, this note is visible and we need to render it!
|
|
||||||
|
|
||||||
// Get a note sprite from the pool.
|
// Get a note sprite from the pool.
|
||||||
// If we can reuse a deleted note, do so.
|
// If we can reuse a deleted note, do so.
|
||||||
// If a new note is needed, call buildNoteSprite.
|
// If a new note is needed, call buildNoteSprite.
|
||||||
var noteSprite:ChartEditorNoteSprite = renderedNotes.recycle(() -> new ChartEditorNoteSprite(this));
|
var noteSprite:ChartEditorNoteSprite = renderedNotes.recycle(() -> new ChartEditorNoteSprite(this));
|
||||||
|
trace('Creating new Note... (${renderedNotes.members.length})');
|
||||||
noteSprite.parentState = this;
|
noteSprite.parentState = this;
|
||||||
|
|
||||||
// The note sprite handles animation playback and positioning.
|
// The note sprite handles animation playback and positioning.
|
||||||
noteSprite.noteData = noteData;
|
noteSprite.noteData = noteData;
|
||||||
|
|
||||||
// Setting note data resets position relative to the grid so we fix that.
|
// Setting note data resets position relative to the grid so we fix that.
|
||||||
noteSprite.x += renderedNotes.x;
|
noteSprite.updateNotePosition(renderedNotes);
|
||||||
noteSprite.y += renderedNotes.y;
|
|
||||||
|
|
||||||
if (noteSprite.noteData.length > 0)
|
// Add hold notes that are now visible (and not already displayed).
|
||||||
|
if (noteSprite.noteData.length > 0 && displayedHoldNoteData.indexOf(noteData) == -1)
|
||||||
{
|
{
|
||||||
// If the note is a hold, we need to make sure it's long enough.
|
var holdNoteSprite:ChartEditorHoldNoteSprite = renderedHoldNotes.recycle(() -> new ChartEditorHoldNoteSprite(this));
|
||||||
var noteLengthMs:Float = noteSprite.noteData.length;
|
trace('Creating new HoldNote... (${renderedHoldNotes.members.length})');
|
||||||
var noteLengthSteps:Float = (noteLengthMs / Conductor.stepLengthMs);
|
|
||||||
var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
|
|
||||||
|
|
||||||
while (noteLengthSteps > 0)
|
var noteLengthPixels:Float = noteSprite.noteData.stepLength * GRID_SIZE;
|
||||||
{
|
|
||||||
if (noteLengthSteps <= 1.0)
|
|
||||||
{
|
|
||||||
// Last note in the hold.
|
|
||||||
// TODO: We may need to make it shorter and clip it visually.
|
|
||||||
}
|
|
||||||
|
|
||||||
var nextNoteSprite:ChartEditorNoteSprite = renderedNotes.recycle(ChartEditorNoteSprite);
|
holdNoteSprite.noteData = noteSprite.noteData;
|
||||||
nextNoteSprite.parentState = this;
|
holdNoteSprite.noteDirection = noteSprite.noteData.getDirection();
|
||||||
nextNoteSprite.parentNoteSprite = lastNoteSprite;
|
|
||||||
lastNoteSprite.childNoteSprite = nextNoteSprite;
|
|
||||||
|
|
||||||
lastNoteSprite = nextNoteSprite;
|
holdNoteSprite.setHeightDirectly(noteLengthPixels);
|
||||||
|
|
||||||
noteLengthSteps -= 1;
|
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the last note sprite shows the end cap properly.
|
|
||||||
lastNoteSprite.childNoteSprite = null;
|
|
||||||
|
|
||||||
// var noteLengthPixels:Float = (noteLengthMs / Conductor.stepLengthMs + 1) * GRID_SIZE;
|
|
||||||
// add(new FlxSprite(noteSprite.x, noteSprite.y - renderedNotes.y + noteLengthPixels).makeGraphic(40, 2, 0xFFFF0000));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2433,21 +2480,16 @@ class ChartEditorState extends HaxeUIState
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the position the event should be at.
|
if (!ChartEditorEventSprite.wouldEventBeVisible(viewAreaBottomPixels, viewAreaTopPixels, eventData, renderedNotes)) continue;
|
||||||
var eventTimePixels:Float = eventData.time / Conductor.stepLengthMs * GRID_SIZE;
|
|
||||||
|
|
||||||
// Make sure the event appears when scrolling up.
|
|
||||||
var modifiedViewAreaTop:Float = viewAreaTop - GRID_SIZE;
|
|
||||||
|
|
||||||
if (eventTimePixels < modifiedViewAreaTop || eventTimePixels > viewAreaBottom) continue;
|
|
||||||
|
|
||||||
// Else, this event is visible and we need to render it!
|
// Else, this event is visible and we need to render it!
|
||||||
|
|
||||||
// Get an event sprite from the pool.
|
// Get an event sprite from the pool.
|
||||||
// If we can reuse a deleted event, do so.
|
// If we can reuse a deleted event, do so.
|
||||||
// If a new event is needed, call buildEventSprite.
|
// If a new event is needed, call buildEventSprite.
|
||||||
var eventSprite:ChartEditorEventSprite = renderedEvents.recycle(() -> new ChartEditorEventSprite(this));
|
var eventSprite:ChartEditorEventSprite = renderedEvents.recycle(() -> new ChartEditorEventSprite(this), false, true);
|
||||||
eventSprite.parentState = this;
|
eventSprite.parentState = this;
|
||||||
|
trace('Creating new Event... (${renderedEvents.members.length})');
|
||||||
|
|
||||||
// The event sprite handles animation playback and positioning.
|
// The event sprite handles animation playback and positioning.
|
||||||
eventSprite.eventData = eventData;
|
eventSprite.eventData = eventData;
|
||||||
|
@ -2457,6 +2499,37 @@ class ChartEditorState extends HaxeUIState
|
||||||
eventSprite.y += renderedEvents.y;
|
eventSprite.y += renderedEvents.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add hold notes that have been made visible (but not their parents)
|
||||||
|
for (noteData in currentSongChartNoteData)
|
||||||
|
{
|
||||||
|
// Is the note a hold note?
|
||||||
|
if (noteData.length <= 0) continue;
|
||||||
|
|
||||||
|
// Is the hold note rendered already?
|
||||||
|
if (displayedHoldNoteData.indexOf(noteData) != -1) continue;
|
||||||
|
|
||||||
|
// Is the hold note offscreen?
|
||||||
|
if (!ChartEditorHoldNoteSprite.wouldHoldNoteBeVisible(viewAreaBottomPixels, viewAreaTopPixels, noteData, renderedHoldNotes)) continue;
|
||||||
|
|
||||||
|
// Hold note should be rendered.
|
||||||
|
var holdNoteFactory = function() {
|
||||||
|
// TODO: Print some kind of warning if `renderedHoldNotes.members` is too high?
|
||||||
|
return new ChartEditorHoldNoteSprite(this);
|
||||||
|
}
|
||||||
|
var holdNoteSprite:ChartEditorHoldNoteSprite = renderedHoldNotes.recycle(holdNoteFactory);
|
||||||
|
|
||||||
|
var noteLengthPixels:Float = noteData.stepLength * GRID_SIZE;
|
||||||
|
|
||||||
|
holdNoteSprite.noteData = noteData;
|
||||||
|
holdNoteSprite.noteDirection = noteData.getDirection();
|
||||||
|
|
||||||
|
holdNoteSprite.setHeightDirectly(noteLengthPixels);
|
||||||
|
|
||||||
|
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
|
||||||
|
|
||||||
|
displayedHoldNoteData.push(noteData);
|
||||||
|
}
|
||||||
|
|
||||||
// Destroy all existing selection squares.
|
// Destroy all existing selection squares.
|
||||||
for (member in renderedSelectionSquares.members)
|
for (member in renderedSelectionSquares.members)
|
||||||
{
|
{
|
||||||
|
@ -2468,7 +2541,8 @@ class ChartEditorState extends HaxeUIState
|
||||||
// Recycle selection squares if possible.
|
// Recycle selection squares if possible.
|
||||||
for (noteSprite in renderedNotes.members)
|
for (noteSprite in renderedNotes.members)
|
||||||
{
|
{
|
||||||
if (isNoteSelected(noteSprite.noteData) && noteSprite.parentNoteSprite == null)
|
// TODO: Handle selection of hold notes.
|
||||||
|
if (isNoteSelected(noteSprite.noteData))
|
||||||
{
|
{
|
||||||
var selectionSquare:FlxSprite = renderedSelectionSquares.recycle(buildSelectionSquare);
|
var selectionSquare:FlxSprite = renderedSelectionSquares.recycle(buildSelectionSquare);
|
||||||
|
|
||||||
|
@ -2500,6 +2574,12 @@ class ChartEditorState extends HaxeUIState
|
||||||
// Sort the events DESCENDING. This keeps the sustain behind the associated note.
|
// Sort the events DESCENDING. This keeps the sustain behind the associated note.
|
||||||
renderedEvents.sort(FlxSort.byY, FlxSort.DESCENDING);
|
renderedEvents.sort(FlxSort.byY, FlxSort.DESCENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a debug value which displays the current size of the note pool.
|
||||||
|
// The pool will grow as more notes need to be rendered at once.
|
||||||
|
// If this gets too big, something needs to be optimized somewhere! -Eric
|
||||||
|
FlxG.watch.addQuick("tapNotesRendered", renderedNotes.members.length);
|
||||||
|
FlxG.watch.addQuick("holdNotesRendered", renderedHoldNotes.members.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSelectionSquare():FlxSprite
|
function buildSelectionSquare():FlxSprite
|
||||||
|
@ -2882,7 +2962,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
function handleNotePreview():Void
|
function handleNotePreview():Void
|
||||||
{
|
{
|
||||||
//
|
// TODO: Finish this.
|
||||||
if (notePreviewDirty)
|
if (notePreviewDirty)
|
||||||
{
|
{
|
||||||
notePreviewDirty = false;
|
notePreviewDirty = false;
|
||||||
|
@ -3023,11 +3103,13 @@ class ChartEditorState extends HaxeUIState
|
||||||
// Assume notes are sorted by time.
|
// Assume notes are sorted by time.
|
||||||
for (noteData in currentSongChartNoteData)
|
for (noteData in currentSongChartNoteData)
|
||||||
{
|
{
|
||||||
|
// Check for notes between the old and new song positions.
|
||||||
|
|
||||||
if (noteData.time < oldSongPosition) // Note is in the past.
|
if (noteData.time < oldSongPosition) // Note is in the past.
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (noteData.time >= newSongPosition) // Note is in the future.
|
if (noteData.time > newSongPosition) // Note is in the future.
|
||||||
return;
|
return; // Assume all notes are also in the future.
|
||||||
|
|
||||||
// Note was just hit.
|
// Note was just hit.
|
||||||
|
|
||||||
|
@ -3160,6 +3242,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
// Move the rendered notes to the correct position.
|
// Move the rendered notes to the correct position.
|
||||||
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||||
|
renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||||
renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||||
renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||||
|
|
||||||
|
@ -3169,29 +3252,11 @@ class ChartEditorState extends HaxeUIState
|
||||||
return this.scrollPositionInPixels;
|
return this.scrollPositionInPixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_playheadPositionInPixels():Float
|
|
||||||
{
|
|
||||||
return this.playheadPositionInPixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_playheadPositionInPixels(value:Float):Float
|
|
||||||
{
|
|
||||||
// Make sure playhead doesn't go outside the song.
|
|
||||||
if (value + scrollPositionInPixels < 0) value = -scrollPositionInPixels;
|
|
||||||
if (value + scrollPositionInPixels > songLengthInPixels) value = songLengthInPixels - scrollPositionInPixels;
|
|
||||||
|
|
||||||
this.playheadPositionInPixels = value;
|
|
||||||
|
|
||||||
// Move the playhead sprite to the correct position.
|
|
||||||
gridPlayhead.y = this.playheadPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
|
|
||||||
|
|
||||||
return this.playheadPositionInPixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an instrumental from an absolute file path, replacing the current instrumental.
|
* Loads an instrumental from an absolute file path, replacing the current instrumental.
|
||||||
*
|
*
|
||||||
* @param path The absolute path to the audio file.
|
* @param path The absolute path to the audio file.
|
||||||
|
*
|
||||||
* @return Success or failure.
|
* @return Success or failure.
|
||||||
*/
|
*/
|
||||||
public function loadInstrumentalFromPath(path:Path):Bool
|
public function loadInstrumentalFromPath(path:Path):Bool
|
||||||
|
@ -3295,7 +3360,16 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a vocal track from an OpenFL asset.
|
* Clear the voices group.
|
||||||
|
*/
|
||||||
|
public function clearVocals():Void
|
||||||
|
{
|
||||||
|
audioVocalTrackGroup.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a vocal track for a given song and character and add it to the voices group.
|
||||||
|
*
|
||||||
* @param path ID of the asset.
|
* @param path ID of the asset.
|
||||||
* @param charKey Character to load the vocal track for.
|
* @param charKey Character to load the vocal track for.
|
||||||
* @return Success or failure.
|
* @return Success or failure.
|
||||||
|
@ -3346,9 +3420,8 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
for (metadata in rawSongMetadata)
|
for (metadata in rawSongMetadata)
|
||||||
{
|
{
|
||||||
var variation:String = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation;
|
var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation;
|
||||||
|
songMetadata.set(variation, Reflect.copy(metadata));
|
||||||
songMetadata.set(variation, metadata);
|
|
||||||
songChartData.set(variation, SongDataParser.parseSongChartData(songId, metadata.variation));
|
songChartData.set(variation, SongDataParser.parseSongChartData(songId, metadata.variation));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3359,21 +3432,21 @@ class ChartEditorState extends HaxeUIState
|
||||||
audioInstTrack.stop();
|
audioInstTrack.stop();
|
||||||
audioInstTrack = null;
|
audioInstTrack = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Conductor.forceBPM(null); // Disable the forced BPM.
|
||||||
|
Conductor.mapTimeChanges(currentSongMetadata.timeChanges);
|
||||||
|
|
||||||
|
sortChartData();
|
||||||
|
|
||||||
|
clearVocals();
|
||||||
|
|
||||||
loadInstrumentalFromAsset(Paths.inst(songId));
|
loadInstrumentalFromAsset(Paths.inst(songId));
|
||||||
|
|
||||||
if (audioVocalTrackGroup != null)
|
var voiceList:Array<String> = song.getDifficulty(selectedDifficulty).buildVoiceList();
|
||||||
|
for (voicePath in voiceList)
|
||||||
{
|
{
|
||||||
audioVocalTrackGroup.stop();
|
loadVocalsFromAsset(voicePath);
|
||||||
audioVocalTrackGroup.clear();
|
|
||||||
}
|
}
|
||||||
// Add player vocals.
|
|
||||||
if (currentSongCharacterPlayer != null) audioVocalTrackGroup.addPlayerVoice(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
|
|
||||||
'-$currentSongCharacterPlayer'))));
|
|
||||||
// Add opponent vocals.
|
|
||||||
if (currentSongCharacterOpponent != null) audioVocalTrackGroup.addOpponentVoice(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
|
|
||||||
'-$currentSongCharacterOpponent'))));
|
|
||||||
|
|
||||||
postLoadInstrumental();
|
|
||||||
|
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
|
@ -3421,7 +3494,8 @@ class ChartEditorState extends HaxeUIState
|
||||||
function moveSongToScrollPosition():Void
|
function moveSongToScrollPosition():Void
|
||||||
{
|
{
|
||||||
// Update the songPosition in the Conductor.
|
// Update the songPosition in the Conductor.
|
||||||
Conductor.update(scrollPositionInMs);
|
var targetPos = scrollPositionInMs;
|
||||||
|
Conductor.update(targetPos);
|
||||||
|
|
||||||
// Update the songPosition in the audio tracks.
|
// Update the songPosition in the audio tracks.
|
||||||
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
||||||
|
|
|
@ -66,7 +66,7 @@ class StageBuilderState extends MusicBeatState
|
||||||
|
|
||||||
// snd.addEventListener(SampleDataEvent.SAMPLE_DATA, sineShit);
|
// snd.addEventListener(SampleDataEvent.SAMPLE_DATA, sineShit);
|
||||||
// snd.__buffer.
|
// snd.__buffer.
|
||||||
// snd = Assets.getSound(Paths.music('freakyMenu'));
|
// snd = Assets.getSound(Paths.music('freakyMenu/freakyMenu'));
|
||||||
// for (thing in snd.load)
|
// for (thing in snd.load)
|
||||||
// thing = Std.int(thing / 2);
|
// thing = Std.int(thing / 2);
|
||||||
// snd.play();
|
// snd.play();
|
||||||
|
|
|
@ -122,6 +122,93 @@ class Constants
|
||||||
*/
|
*/
|
||||||
public static final DEFAULT_VARIATION:String = 'default';
|
public static final DEFAULT_VARIATION:String = 'default';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default intensity for camera zooms.
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_ZOOM_INTENSITY:Float = 0.015;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default rate for camera zooms (in beats per zoom).
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_ZOOM_RATE:Int = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default BPM for charts, so things don't break if none is specified.
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_BPM:Int = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default numerator for the time signature.
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_TIME_SIGNATURE_NUM:Int = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default denominator for the time signature.
|
||||||
|
*/
|
||||||
|
public static final DEFAULT_TIME_SIGNATURE_DEN:Int = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TIMING
|
||||||
|
*/
|
||||||
|
// ==============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A magic number used when calculating scroll speed and note distances.
|
||||||
|
*/
|
||||||
|
public static final PIXELS_PER_MS:Float = 0.45;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum interval within which a note can be hit, in milliseconds.
|
||||||
|
*/
|
||||||
|
public static final HIT_WINDOW_MS:Float = 160.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant for the number of seconds in a minute.
|
||||||
|
*/
|
||||||
|
public static final SECS_PER_MIN:Int = 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant for the number of milliseconds in a second.
|
||||||
|
*/
|
||||||
|
public static final MS_PER_SEC:Int = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of microseconds in a millisecond.
|
||||||
|
*/
|
||||||
|
public static final US_PER_MS:Int = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of microseconds in a second.
|
||||||
|
*/
|
||||||
|
public static final US_PER_SEC:Int = US_PER_MS * MS_PER_SEC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of nanoseconds in a microsecond.
|
||||||
|
*/
|
||||||
|
public static final NS_PER_US:Int = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of nanoseconds in a millisecond.
|
||||||
|
*/
|
||||||
|
public static final NS_PER_MS:Int = NS_PER_US * US_PER_MS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of nanoseconds in a second.
|
||||||
|
*/
|
||||||
|
public static final NS_PER_SEC:Int = NS_PER_US * US_PER_MS * MS_PER_SEC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of steps in a beat.
|
||||||
|
* One step is one 16th note and one beat is one quarter note.
|
||||||
|
*/
|
||||||
|
public static final STEPS_PER_BEAT:Int = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All MP3 decoders introduce a playback delay of `528` samples,
|
||||||
|
* which at 44,100 Hz (samples per second) is ~12 ms.
|
||||||
|
*/
|
||||||
|
public static final MP3_DELAY_MS:Float = 528 / 44100 * Constants.MS_PER_SEC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HEALTH VALUES
|
* HEALTH VALUES
|
||||||
*/
|
*/
|
||||||
|
@ -201,80 +288,14 @@ class Constants
|
||||||
*/
|
*/
|
||||||
public static final GHOST_TAPPING:Bool = false;
|
public static final GHOST_TAPPING:Bool = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* TIMING
|
|
||||||
*/
|
|
||||||
// ==============================
|
|
||||||
public static final HIT_WINDOW_MS:Int = 160;
|
|
||||||
|
|
||||||
public static final PIXELS_PER_MS:Float = 0.45;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of seconds in a minute.
|
|
||||||
*/
|
|
||||||
public static final SECS_PER_MIN:Int = 60;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of milliseconds in a second.
|
|
||||||
*/
|
|
||||||
public static final MS_PER_SEC:Int = 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of microseconds in a millisecond.
|
|
||||||
*/
|
|
||||||
public static final US_PER_MS:Int = 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of microseconds in a second.
|
|
||||||
*/
|
|
||||||
public static final US_PER_SEC:Int = US_PER_MS * MS_PER_SEC;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of nanoseconds in a microsecond.
|
|
||||||
*/
|
|
||||||
public static final NS_PER_US:Int = 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of nanoseconds in a millisecond.
|
|
||||||
*/
|
|
||||||
public static final NS_PER_MS:Int = NS_PER_US * US_PER_MS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of nanoseconds in a second.
|
|
||||||
*/
|
|
||||||
public static final NS_PER_SEC:Int = NS_PER_US * US_PER_MS * MS_PER_SEC;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All MP3 decoders introduce a playback delay of `528` samples,
|
|
||||||
* which at 44,100 Hz (samples per second) is ~12 ms.
|
|
||||||
*/
|
|
||||||
public static final MP3_DELAY_MS:Float = 528 / 44100 * MS_PER_SEC;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default BPM of the conductor.
|
|
||||||
*/
|
|
||||||
public static final DEFAULT_BPM:Float = 100.0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default numerator for the time signature.
|
|
||||||
*/
|
|
||||||
public static final DEFAULT_TIME_SIGNATURE_NUM:Int = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default denominator for the time signature.
|
|
||||||
*/
|
|
||||||
public static final DEFAULT_TIME_SIGNATURE_DEN:Int = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of steps in a beat.
|
|
||||||
* One step is one 16th note and one beat is one quarter note.
|
|
||||||
*/
|
|
||||||
public static final STEPS_PER_BEAT:Int = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OTHER
|
* OTHER
|
||||||
*/
|
*/
|
||||||
// ==============================
|
// ==============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The separator between an asset library and the asset path.
|
||||||
|
*/
|
||||||
public static final LIBRARY_SEPARATOR:String = ':';
|
public static final LIBRARY_SEPARATOR:String = ':';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -287,16 +308,13 @@ class Constants
|
||||||
*/
|
*/
|
||||||
public static final COUNTDOWN_VOLUME:Float = 0.6;
|
public static final COUNTDOWN_VOLUME:Float = 0.6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The horizontal offset of the strumline from the left edge of the screen.
|
||||||
|
*/
|
||||||
public static final STRUMLINE_X_OFFSET:Float = 48;
|
public static final STRUMLINE_X_OFFSET:Float = 48;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The vertical offset of the strumline from the top edge of the screen.
|
||||||
|
*/
|
||||||
public static final STRUMLINE_Y_OFFSET:Float = 24;
|
public static final STRUMLINE_Y_OFFSET:Float = 24;
|
||||||
|
|
||||||
/**
|
|
||||||
* The default intensity for camera zooms.
|
|
||||||
*/
|
|
||||||
public static final DEFAULT_ZOOM_INTENSITY:Float = 0.015;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default rate for camera zooms (in beats per zoom).
|
|
||||||
*/
|
|
||||||
public static final DEFAULT_ZOOM_RATE:Int = 4;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,22 +28,15 @@ class SortUtil
|
||||||
return FlxSort.byValues(order, a.noteData.time, b.noteData.time);
|
return FlxSort.byValues(order, a.noteData.time, b.noteData.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static inline function alphabetically(a:String, b:String)
|
/**
|
||||||
|
* Sort predicate for sorting strings alphabetically.
|
||||||
|
*/
|
||||||
|
public static function alphabetically(a:String, b:String)
|
||||||
{
|
{
|
||||||
a = a.toUpperCase();
|
a = a.toUpperCase();
|
||||||
b = b.toUpperCase();
|
b = b.toUpperCase();
|
||||||
|
|
||||||
if (a < b)
|
// Sort alphabetically. Yes that's how this works.
|
||||||
{
|
return a == b ? 0 : a > b ? 1 : -1;
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (a > b)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue