mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-12-26 23:16:46 +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",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "1e1151094f2ca0987025f1a9d33401e44dce3f28",
|
||||
"ref": "3590c94858fc6dbcf9b4d522cd644ad571269677",
|
||||
"url": "https://github.com/haxeui/haxeui-core/"
|
||||
},
|
||||
{
|
||||
|
@ -116,4 +116,4 @@
|
|||
"version": "0.11.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,5 +98,6 @@ class Main extends Sprite
|
|||
// - It scans the class path and registers any HaxeUI components.
|
||||
Toolkit.init();
|
||||
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;
|
||||
|
||||
static function get_beatLengthMs():Float
|
||||
{
|
||||
// Tied directly to BPM.
|
||||
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;
|
||||
|
||||
|
@ -272,7 +273,8 @@ class Conductor
|
|||
{
|
||||
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
beatHit.removeAll();
|
||||
|
|
|
@ -127,7 +127,7 @@ class FreeplayState extends MusicBeatSubState
|
|||
|
||||
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)
|
||||
|
|
|
@ -261,7 +261,7 @@ class InitState extends FlxTransitionableState
|
|||
*/
|
||||
function startGameNormally():Void
|
||||
{
|
||||
FlxG.sound.cache(Paths.music('freakyMenu'));
|
||||
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||
FlxG.switchState(new TitleState());
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ class MainMenuState extends MusicBeatState
|
|||
|
||||
if (!FlxG.sound.music.playing)
|
||||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu'));
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
}
|
||||
|
||||
persistentUpdate = persistentDraw = true;
|
||||
|
|
|
@ -49,7 +49,7 @@ class TitleState extends MusicBeatState
|
|||
swagShader = new ColorSwap();
|
||||
|
||||
curWacky = FlxG.random.getObject(getIntroTextShit());
|
||||
FlxG.sound.cache(Paths.music('freakyMenu'));
|
||||
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
|
||||
|
||||
// DEBUG BULLSHIT
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.modding.module;
|
||||
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.modding.events.ScriptEvent.UpdateScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
|
@ -76,8 +77,7 @@ class ModuleHandler
|
|||
}
|
||||
else
|
||||
{
|
||||
// Sort alphabetically. Yes that's how this works.
|
||||
return a > b ? 1 : -1;
|
||||
return SortUtil.alphabetically(a, b);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2374,7 +2374,7 @@ class PlayState extends MusicBeatState
|
|||
|
||||
if (targetSongId == null)
|
||||
{
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu'));
|
||||
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
|
||||
|
||||
transIn = FlxTransitionableState.defaultTransIn;
|
||||
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
|
||||
{
|
||||
FlxG.watch.addQuick('playAnim(${characterName})', name);
|
||||
trace('playAnim(${characterName}): ${name}');
|
||||
// trace('playAnim(${characterName}): ${name}');
|
||||
super.playAnimation(name, restart, ignoreOther, reversed);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,15 @@ class SongEvent
|
|||
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.
|
||||
* 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.
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -88,9 +88,9 @@ class SustainTrail extends FlxSprite
|
|||
* @param SustainLength Length in milliseconds.
|
||||
* @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;
|
||||
|
||||
|
@ -111,7 +111,7 @@ class SustainTrail extends FlxSprite
|
|||
|
||||
// CALCULATE SIZE
|
||||
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
|
||||
|
||||
flipY = PreferencesMenu.getPref('downscroll');
|
||||
|
@ -123,6 +123,13 @@ class SustainTrail extends FlxSprite
|
|||
|
||||
updateClipping();
|
||||
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;
|
||||
|
||||
height = sustainHeight(s, PlayState.instance.currentChart.scrollSpeed);
|
||||
height = sustainHeight(s, getScrollSpeed());
|
||||
updateColorTransform();
|
||||
updateClipping();
|
||||
return sustainLength = s;
|
||||
|
@ -152,7 +159,7 @@ class SustainTrail extends FlxSprite
|
|||
*/
|
||||
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)
|
||||
{
|
||||
visible = false;
|
||||
|
|
|
@ -393,6 +393,9 @@ abstract SongNoteData(RawSongNoteData)
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp of the note, in milliseconds.
|
||||
*/
|
||||
public var time(get, set):Float;
|
||||
|
||||
function get_time():Float
|
||||
|
@ -405,11 +408,14 @@ abstract SongNoteData(RawSongNoteData)
|
|||
return this.t = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp of the note, in steps.
|
||||
*/
|
||||
public var stepTime(get, never):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
|
||||
{
|
||||
return this.d % strumlineSize;
|
||||
return abstract.data % strumlineSize;
|
||||
}
|
||||
|
||||
public function getDirectionName(strumlineSize:Int = 4):String
|
||||
{
|
||||
switch (this.d % strumlineSize)
|
||||
switch (abstract.data % strumlineSize)
|
||||
{
|
||||
case 0:
|
||||
return 'Left';
|
||||
|
@ -463,7 +469,7 @@ abstract SongNoteData(RawSongNoteData)
|
|||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
function get_length():Float
|
||||
|
@ -489,6 +499,22 @@ abstract SongNoteData(RawSongNoteData)
|
|||
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 function get_isHoldNote():Bool
|
||||
|
@ -514,21 +540,37 @@ abstract SongNoteData(RawSongNoteData)
|
|||
@:op(A == B)
|
||||
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)
|
||||
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)
|
||||
public function op_greaterThan(other:SongNoteData):Bool
|
||||
{
|
||||
return this.t > other.time;
|
||||
return abstract.time > other.time;
|
||||
}
|
||||
|
||||
@:op(A < B)
|
||||
|
@ -607,7 +649,7 @@ abstract SongEventData(RawSongEventData)
|
|||
|
||||
function get_stepTime():Float
|
||||
{
|
||||
return Conductor.getTimeInSteps(this.t);
|
||||
return Conductor.getTimeInSteps(abstract.time);
|
||||
}
|
||||
|
||||
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.SongMetadata;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
|
@ -106,8 +107,7 @@ class ChartEditorDialogHandler
|
|||
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
||||
|
||||
var songList:Array<String> = SongDataParser.listSongIds();
|
||||
// Sort alphabetically
|
||||
songList.sort((a, b) -> a > b ? 1 : -1);
|
||||
songList.sort(SortUtil.alphabetically);
|
||||
|
||||
for (targetSongId in songList)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import funkin.play.event.SongEventData.SongEventParser;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
import flixel.FlxObject;
|
||||
|
@ -16,6 +18,8 @@ import funkin.play.song.SongData.SongEventData;
|
|||
*/
|
||||
class ChartEditorEventSprite extends FlxSprite
|
||||
{
|
||||
public static final DEFAULT_EVENT = 'Default';
|
||||
|
||||
public var parentState:ChartEditorState;
|
||||
|
||||
/**
|
||||
|
@ -35,17 +39,75 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
|
||||
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);
|
||||
this.updateHitbox();
|
||||
}
|
||||
|
@ -56,13 +118,13 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
|
||||
if (this.eventData == null)
|
||||
{
|
||||
// Disown parent.
|
||||
// Disown parent. MAKE SURE TO REVIVE BEFORE REUSING
|
||||
this.kill();
|
||||
return this.eventData;
|
||||
}
|
||||
|
||||
this.visible = true;
|
||||
|
||||
playAnimation(this.eventData.event);
|
||||
// Update the position to match the note data.
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// True if the note is below the view area.
|
||||
var belowViewArea = (this.y > viewAreaBottom);
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
super();
|
||||
|
@ -124,14 +114,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
|
||||
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();
|
||||
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;
|
||||
|
||||
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
|
||||
// TODO: stepTime doesn't account for fluctuating BPMs.
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
return this.parentNoteSprite;
|
||||
}
|
||||
|
||||
function set_childNoteSprite(value:ChartEditorNoteSprite):ChartEditorNoteSprite
|
||||
{
|
||||
this.childNoteSprite = value;
|
||||
|
||||
if (this.parentNoteSprite != null)
|
||||
if (origin != null)
|
||||
{
|
||||
this.noteData = this.parentNoteSprite.noteData;
|
||||
this.x += origin.x;
|
||||
this.y += origin.y;
|
||||
}
|
||||
|
||||
return this.childNoteSprite;
|
||||
}
|
||||
|
||||
function get_noteStyle():String
|
||||
|
@ -244,7 +180,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
{
|
||||
// Decide whether to display a note or a sustain.
|
||||
var baseAnimationName:String = 'tap';
|
||||
if (this.parentNoteSprite != null) baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd';
|
||||
|
||||
// Play the appropriate animation for the type, direction, and skin.
|
||||
var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle}';
|
||||
|
@ -257,17 +192,6 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
{
|
||||
case 'tap':
|
||||
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();
|
||||
|
||||
|
@ -280,22 +204,30 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
*/
|
||||
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)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// True if the note is below the view area.
|
||||
var belowViewArea = (this.y > viewAreaBottom);
|
||||
|
||||
// TODO: Check if this note's parent or child is visible.
|
||||
|
||||
return false;
|
||||
return !aboveViewArea && !belowViewArea;
|
||||
}
|
||||
|
||||
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;
|
||||
else
|
||||
return this.parentNoteSprite;
|
||||
var noteHeight:Float = 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
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.FlxTiledSprite;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -22,32 +16,41 @@ import flixel.util.FlxSort;
|
|||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.visualize.PolygonSpectogram;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.input.TurboKeyHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.HealthIcon;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.Strumline;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
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.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.ChartEditorToolboxHandler.ChartEditorToolMode;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
import funkin.ui.haxeui.HaxeUIState;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import haxe.DynamicAccess;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.containers.menus.MenuItem;
|
||||
import haxe.ui.containers.TreeView;
|
||||
import haxe.ui.containers.TreeViewNode;
|
||||
|
@ -57,6 +60,7 @@ import haxe.ui.events.DragEvent;
|
|||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import openfl.Assets;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
|
||||
|
@ -121,6 +125,11 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -182,29 +191,35 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return scrollPositionInPixels / GRID_SIZE;
|
||||
}
|
||||
|
||||
function set_scrollPositionInSteps(value:Float):Float
|
||||
{
|
||||
scrollPositionInPixels = value * GRID_SIZE;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
function get_scrollPositionInMs():Float
|
||||
{
|
||||
return scrollPositionInSteps * Conductor.stepLengthMs;
|
||||
return Conductor.getStepTimeInMs(scrollPositionInSteps);
|
||||
}
|
||||
|
||||
function set_scrollPositionInMs(value:Float):Float
|
||||
{
|
||||
scrollPositionInPixels = value / Conductor.stepLengthMs;
|
||||
scrollPositionInSteps = Conductor.getTimeInSteps(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -216,11 +231,26 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
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.
|
||||
* 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
|
||||
{
|
||||
return playheadPositionInPixels / GRID_SIZE;
|
||||
|
@ -228,60 +258,76 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
/**
|
||||
* playheadPosition, converted to milliseconds.
|
||||
* DEPENDANT on BPM, because the duration of a grid square changes with BPM.
|
||||
*/
|
||||
var playheadPositionInMs(get, null):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.
|
||||
* 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;
|
||||
|
||||
function get_songLengthInSteps():Float
|
||||
{
|
||||
return songLengthInPixels / GRID_SIZE;
|
||||
return Conductor.getTimeInSteps(songLengthInMs);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* songLength, converted to milliseconds.
|
||||
* TODO: Handle BPM changes.
|
||||
* This is the song's length in PIXELS, same format as scrollPosition.
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
function set_currentTheme(value:ChartEditorTheme):ChartEditorTheme
|
||||
|
@ -1002,6 +1048,13 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
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.
|
||||
* Only displays a subset of the data from `currentSongChartEventData`,
|
||||
|
@ -1088,7 +1141,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
gridGhostNote = new ChartEditorNoteSprite(this);
|
||||
gridGhostNote.alpha = 0.6;
|
||||
gridGhostNote.noteData = new SongNoteData(-1, -1, 0, '');
|
||||
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "");
|
||||
gridGhostNote.visible = false;
|
||||
add(gridGhostNote);
|
||||
|
||||
|
@ -1187,6 +1240,10 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
function buildNoteGroup():Void
|
||||
{
|
||||
renderedHoldNotes = new FlxTypedSpriteGroup<ChartEditorHoldNoteSprite>();
|
||||
renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedHoldNotes);
|
||||
|
||||
renderedNotes = new FlxTypedSpriteGroup<ChartEditorNoteSprite>();
|
||||
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
add(renderedNotes);
|
||||
|
@ -1812,12 +1869,9 @@ class ChartEditorState extends HaxeUIState
|
|||
moveSongToScrollPosition();
|
||||
}
|
||||
|
||||
// Cursor position snapped to the grid.
|
||||
|
||||
// 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 = Conductor.getStepTimeInMs(cursorFractionalStep);
|
||||
// The direction value for the column at the cursor.
|
||||
var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE);
|
||||
if (cursorColumn < 0) cursorColumn = 0;
|
||||
|
@ -1855,7 +1909,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 = Conductor.getStepTimeInMs(cursorStepStart);
|
||||
var cursorColumnBase:Int = Math.floor(cursorX / GRID_SIZE);
|
||||
var cursorColumnBaseStart:Int = Math.floor(cursorXStart / GRID_SIZE);
|
||||
|
||||
|
@ -1994,8 +2048,7 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
if (highlightedNote != null)
|
||||
{
|
||||
// Handle the case of clicking on a sustain piece.
|
||||
highlightedNote = highlightedNote.getBaseNoteSprite();
|
||||
// TODO: Handle the case of clicking on a sustain piece.
|
||||
// Control click to select/deselect an individual note.
|
||||
if (isNoteSelected(highlightedNote.noteData))
|
||||
{
|
||||
|
@ -2059,12 +2112,13 @@ 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);
|
||||
// TODO: This should be beat snapped?
|
||||
var dragLengthSteps:Float = Conductor.getTimeInSteps(cursorMs) - currentPlaceNoteData.stepTime;
|
||||
|
||||
// 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;
|
||||
// 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?
|
||||
|
||||
|
@ -2197,8 +2251,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
if (highlightedNote != null)
|
||||
{
|
||||
// Handle the case of clicking on a sustain piece.
|
||||
highlightedNote = highlightedNote.getBaseNoteSprite();
|
||||
// TODO: Handle the case of clicking on a sustain piece.
|
||||
// Remove the note.
|
||||
performCommand(new RemoveNotesCommand([highlightedNote.noteData]));
|
||||
}
|
||||
|
@ -2286,10 +2339,10 @@ class ChartEditorState extends HaxeUIState
|
|||
// Update for whether downscroll is enabled.
|
||||
renderedNotes.flipX = (isViewDownscroll);
|
||||
|
||||
// Calculate the view bounds.
|
||||
var viewAreaTop:Float = this.scrollPositionInPixels - GRID_TOP_PAD;
|
||||
var viewHeight:Float = (FlxG.height - MENU_BAR_HEIGHT);
|
||||
var viewAreaBottom:Float = this.scrollPositionInPixels + viewHeight;
|
||||
// Calculate the top and bottom of the view area.
|
||||
var viewAreaTopPixels:Float = 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 viewAreaBottomPixels:Float = viewAreaTopPixels + visibleGridHeightPixels;
|
||||
|
||||
// Remove notes that are no longer visible and list the ones that are.
|
||||
var displayedNoteData:Array<SongNoteData> = [];
|
||||
|
@ -2297,7 +2350,7 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
if (noteSprite == null || !noteSprite.exists || !noteSprite.visible) continue;
|
||||
|
||||
if (!noteSprite.isNoteVisible(viewAreaBottom, viewAreaTop))
|
||||
if (!noteSprite.isNoteVisible(viewAreaBottomPixels, viewAreaTopPixels))
|
||||
{
|
||||
// This sprite is off-screen.
|
||||
// Kill the note sprite and recycle it.
|
||||
|
@ -2309,18 +2362,6 @@ class ChartEditorState extends HaxeUIState
|
|||
// Kill the note sprite and recycle it.
|
||||
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
|
||||
{
|
||||
// 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.
|
||||
var displayedEventData:Array<SongEventData> = [];
|
||||
for (eventSprite in renderedEvents.members)
|
||||
{
|
||||
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.
|
||||
// Kill the event sprite and recycle it.
|
||||
|
@ -2368,59 +2438,36 @@ class ChartEditorState extends HaxeUIState
|
|||
continue;
|
||||
}
|
||||
|
||||
// Get the position the note should be at.
|
||||
var noteTimePixels:Float = noteData.time / Conductor.stepLengthMs * GRID_SIZE;
|
||||
|
||||
// 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!
|
||||
if (!ChartEditorNoteSprite.wouldNoteBeVisible(viewAreaBottomPixels, viewAreaTopPixels, noteData,
|
||||
renderedNotes)) continue; // Else, this note is visible and we need to render it!
|
||||
|
||||
// Get a note sprite from the pool.
|
||||
// If we can reuse a deleted note, do so.
|
||||
// If a new note is needed, call buildNoteSprite.
|
||||
var noteSprite:ChartEditorNoteSprite = renderedNotes.recycle(() -> new ChartEditorNoteSprite(this));
|
||||
trace('Creating new Note... (${renderedNotes.members.length})');
|
||||
noteSprite.parentState = this;
|
||||
|
||||
// The note sprite handles animation playback and positioning.
|
||||
noteSprite.noteData = noteData;
|
||||
|
||||
// Setting note data resets position relative to the grid so we fix that.
|
||||
noteSprite.x += renderedNotes.x;
|
||||
noteSprite.y += renderedNotes.y;
|
||||
noteSprite.updateNotePosition(renderedNotes);
|
||||
|
||||
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 noteLengthMs:Float = noteSprite.noteData.length;
|
||||
var noteLengthSteps:Float = (noteLengthMs / Conductor.stepLengthMs);
|
||||
var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
|
||||
var holdNoteSprite:ChartEditorHoldNoteSprite = renderedHoldNotes.recycle(() -> new ChartEditorHoldNoteSprite(this));
|
||||
trace('Creating new HoldNote... (${renderedHoldNotes.members.length})');
|
||||
|
||||
while (noteLengthSteps > 0)
|
||||
{
|
||||
if (noteLengthSteps <= 1.0)
|
||||
{
|
||||
// Last note in the hold.
|
||||
// TODO: We may need to make it shorter and clip it visually.
|
||||
}
|
||||
var noteLengthPixels:Float = noteSprite.noteData.stepLength * GRID_SIZE;
|
||||
|
||||
var nextNoteSprite:ChartEditorNoteSprite = renderedNotes.recycle(ChartEditorNoteSprite);
|
||||
nextNoteSprite.parentState = this;
|
||||
nextNoteSprite.parentNoteSprite = lastNoteSprite;
|
||||
lastNoteSprite.childNoteSprite = nextNoteSprite;
|
||||
holdNoteSprite.noteData = noteSprite.noteData;
|
||||
holdNoteSprite.noteDirection = noteSprite.noteData.getDirection();
|
||||
|
||||
lastNoteSprite = nextNoteSprite;
|
||||
holdNoteSprite.setHeightDirectly(noteLengthPixels);
|
||||
|
||||
noteLengthSteps -= 1;
|
||||
}
|
||||
|
||||
// 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));
|
||||
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2433,21 +2480,16 @@ class ChartEditorState extends HaxeUIState
|
|||
continue;
|
||||
}
|
||||
|
||||
// Get the position the event should be at.
|
||||
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;
|
||||
if (!ChartEditorEventSprite.wouldEventBeVisible(viewAreaBottomPixels, viewAreaTopPixels, eventData, renderedNotes)) continue;
|
||||
|
||||
// Else, this event is visible and we need to render it!
|
||||
|
||||
// Get an event sprite from the pool.
|
||||
// If we can reuse a deleted event, do so.
|
||||
// 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;
|
||||
trace('Creating new Event... (${renderedEvents.members.length})');
|
||||
|
||||
// The event sprite handles animation playback and positioning.
|
||||
eventSprite.eventData = eventData;
|
||||
|
@ -2457,6 +2499,37 @@ class ChartEditorState extends HaxeUIState
|
|||
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.
|
||||
for (member in renderedSelectionSquares.members)
|
||||
{
|
||||
|
@ -2468,7 +2541,8 @@ class ChartEditorState extends HaxeUIState
|
|||
// Recycle selection squares if possible.
|
||||
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);
|
||||
|
||||
|
@ -2500,6 +2574,12 @@ class ChartEditorState extends HaxeUIState
|
|||
// Sort the events DESCENDING. This keeps the sustain behind the associated note.
|
||||
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
|
||||
|
@ -2882,7 +2962,7 @@ class ChartEditorState extends HaxeUIState
|
|||
*/
|
||||
function handleNotePreview():Void
|
||||
{
|
||||
//
|
||||
// TODO: Finish this.
|
||||
if (notePreviewDirty)
|
||||
{
|
||||
notePreviewDirty = false;
|
||||
|
@ -3023,11 +3103,13 @@ class ChartEditorState extends HaxeUIState
|
|||
// Assume notes are sorted by time.
|
||||
for (noteData in currentSongChartNoteData)
|
||||
{
|
||||
// Check for notes between the old and new song positions.
|
||||
|
||||
if (noteData.time < oldSongPosition) // Note is in the past.
|
||||
continue;
|
||||
|
||||
if (noteData.time >= newSongPosition) // Note is in the future.
|
||||
return;
|
||||
if (noteData.time > newSongPosition) // Note is in the future.
|
||||
return; // Assume all notes are also in the future.
|
||||
|
||||
// Note was just hit.
|
||||
|
||||
|
@ -3160,6 +3242,7 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
// Move the rendered notes to the correct position.
|
||||
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
|
||||
|
@ -3169,29 +3252,11 @@ class ChartEditorState extends HaxeUIState
|
|||
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.
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
*
|
||||
* @return Success or failure.
|
||||
*/
|
||||
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 charKey Character to load the vocal track for.
|
||||
* @return Success or failure.
|
||||
|
@ -3346,9 +3420,8 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
for (metadata in rawSongMetadata)
|
||||
{
|
||||
var variation:String = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation;
|
||||
|
||||
songMetadata.set(variation, metadata);
|
||||
var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation;
|
||||
songMetadata.set(variation, Reflect.copy(metadata));
|
||||
songChartData.set(variation, SongDataParser.parseSongChartData(songId, metadata.variation));
|
||||
}
|
||||
|
||||
|
@ -3359,21 +3432,21 @@ class ChartEditorState extends HaxeUIState
|
|||
audioInstTrack.stop();
|
||||
audioInstTrack = null;
|
||||
}
|
||||
|
||||
Conductor.forceBPM(null); // Disable the forced BPM.
|
||||
Conductor.mapTimeChanges(currentSongMetadata.timeChanges);
|
||||
|
||||
sortChartData();
|
||||
|
||||
clearVocals();
|
||||
|
||||
loadInstrumentalFromAsset(Paths.inst(songId));
|
||||
|
||||
if (audioVocalTrackGroup != null)
|
||||
var voiceList:Array<String> = song.getDifficulty(selectedDifficulty).buildVoiceList();
|
||||
for (voicePath in voiceList)
|
||||
{
|
||||
audioVocalTrackGroup.stop();
|
||||
audioVocalTrackGroup.clear();
|
||||
loadVocalsFromAsset(voicePath);
|
||||
}
|
||||
// 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(
|
||||
{
|
||||
|
@ -3421,7 +3494,8 @@ class ChartEditorState extends HaxeUIState
|
|||
function moveSongToScrollPosition():Void
|
||||
{
|
||||
// Update the songPosition in the Conductor.
|
||||
Conductor.update(scrollPositionInMs);
|
||||
var targetPos = scrollPositionInMs;
|
||||
Conductor.update(targetPos);
|
||||
|
||||
// Update the songPosition in the audio tracks.
|
||||
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;
|
||||
|
|
|
@ -66,7 +66,7 @@ class StageBuilderState extends MusicBeatState
|
|||
|
||||
// snd.addEventListener(SampleDataEvent.SAMPLE_DATA, sineShit);
|
||||
// snd.__buffer.
|
||||
// snd = Assets.getSound(Paths.music('freakyMenu'));
|
||||
// snd = Assets.getSound(Paths.music('freakyMenu/freakyMenu'));
|
||||
// for (thing in snd.load)
|
||||
// thing = Std.int(thing / 2);
|
||||
// snd.play();
|
||||
|
|
|
@ -122,6 +122,93 @@ class Constants
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -201,80 +288,14 @@ class Constants
|
|||
*/
|
||||
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
|
||||
*/
|
||||
// ==============================
|
||||
|
||||
/**
|
||||
* The separator between an asset library and the asset path.
|
||||
*/
|
||||
public static final LIBRARY_SEPARATOR:String = ':';
|
||||
|
||||
/**
|
||||
|
@ -287,16 +308,13 @@ class Constants
|
|||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* The vertical offset of the strumline from the top edge of the screen.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
b = b.toUpperCase();
|
||||
|
||||
if (a < b)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (a > b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// Sort alphabetically. Yes that's how this works.
|
||||
return a == b ? 0 : a > b ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue