Redo note positioning

This commit is contained in:
EliteMasterEric 2023-07-19 01:30:36 -04:00
parent ae76b24a65
commit 140afd3c31
2 changed files with 84 additions and 55 deletions

View file

@ -175,8 +175,11 @@ class ChartEditorNoteSprite extends FlxSprite
this.x = cursorColumn * ChartEditorState.GRID_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. // 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)
if (this.noteData.stepTime >= 0) this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE; {
// noteData.stepTime is a calculated value which accounts for BPM changes
this.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE;
}
if (origin != null) if (origin != null)
{ {

View file

@ -1,5 +1,6 @@
package funkin.ui.debug.charting; package funkin.ui.debug.charting;
import funkin.util.SortUtil;
import funkin.ui.debug.charting.ChartEditorCommand; import funkin.ui.debug.charting.ChartEditorCommand;
import flixel.input.keyboard.FlxKey; import flixel.input.keyboard.FlxKey;
import funkin.input.TurboKeyHandler; import funkin.input.TurboKeyHandler;
@ -172,29 +173,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;
} }
@ -206,11 +213,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;
@ -218,60 +240,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
@ -1667,7 +1705,8 @@ class ChartEditorState extends HaxeUIState
// 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 cursorStep:Int = Std.int(Math.floor(cursorFractionalStep));
var cursorMs:Float = cursorStep * Conductor.stepLengthMs * (16 / noteSnapQuant); var cursorMs:Float = Conductor.getStepTimeInMs(cursorStep);
trace('${cursorFractionalStep} ${cursorStep} ${cursorMs}');
// 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;
@ -1705,7 +1744,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);
@ -1877,12 +1916,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?
@ -2187,7 +2227,7 @@ class ChartEditorState extends HaxeUIState
} }
// Get the position the note should be at. // Get the position the note should be at.
var noteTimePixels:Float = noteData.time / Conductor.stepLengthMs * GRID_SIZE; var noteTimePixels:Float = noteData.stepTime * GRID_SIZE;
// Make sure the note appears when scrolling up. // Make sure the note appears when scrolling up.
var modifiedViewAreaTop = viewAreaTop - GRID_SIZE; var modifiedViewAreaTop = viewAreaTop - GRID_SIZE;
@ -2209,11 +2249,11 @@ class ChartEditorState extends HaxeUIState
noteSprite.x += renderedNotes.x; noteSprite.x += renderedNotes.x;
noteSprite.y += renderedNotes.y; noteSprite.y += renderedNotes.y;
// TODO: Replace this with SustainTrail.
if (noteSprite.noteData.length > 0) if (noteSprite.noteData.length > 0)
{ {
// If the note is a hold, we need to make sure it's long enough. // If the note is a hold, we need to make sure it's long enough.
var noteLengthMs:Float = noteSprite.noteData.length; var noteLengthSteps:Float = noteSprite.noteData.stepLength;
var noteLengthSteps:Float = (noteLengthMs / Conductor.stepLengthMs);
var lastNoteSprite:ChartEditorNoteSprite = noteSprite; var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
while (noteLengthSteps > 0) while (noteLengthSteps > 0)
@ -2252,7 +2292,7 @@ class ChartEditorState extends HaxeUIState
} }
// Get the position the event should be at. // Get the position the event should be at.
var eventTimePixels:Float = eventData.time / Conductor.stepLengthMs * GRID_SIZE; var eventTimePixels:Float = eventData.stepTime * GRID_SIZE;
// Make sure the event appears when scrolling up. // Make sure the event appears when scrolling up.
var modifiedViewAreaTop = viewAreaTop - GRID_SIZE; var modifiedViewAreaTop = viewAreaTop - GRID_SIZE;
@ -2264,7 +2304,7 @@ class ChartEditorState extends HaxeUIState
// 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;
// The event sprite handles animation playback and positioning. // The event sprite handles animation playback and positioning.
@ -2655,7 +2695,7 @@ class ChartEditorState extends HaxeUIState
*/ */
function handleNotePreview():Void function handleNotePreview():Void
{ {
// // TODO: Finish this.
if (notePreviewDirty) if (notePreviewDirty)
{ {
notePreviewDirty = false; notePreviewDirty = false;
@ -2924,25 +2964,6 @@ 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.
* *
@ -3102,7 +3123,12 @@ class ChartEditorState extends HaxeUIState
Conductor.mapTimeChanges(currentSongMetadata.timeChanges); Conductor.mapTimeChanges(currentSongMetadata.timeChanges);
loadInstrumentalFromAsset(Paths.inst(songId)); loadInstrumentalFromAsset(Paths.inst(songId));
loadVocalsFromAsset(Paths.voices(songId));
var voiceList:Array<String> = song.getDifficulty(selectedDifficulty).buildVoiceList();
for (voicePath in voiceList)
{
loadVocalsFromAsset(voicePath);
}
NotificationManager.instance.addNotification( NotificationManager.instance.addNotification(
{ {