From 2f26a97dcb5070295370ffcea8f5adab401e5116 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 31 Aug 2023 21:56:49 -0400 Subject: [PATCH 1/4] WIP on chart editor snapping --- .../ui/debug/charting/ChartEditorState.hx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 83c052050..8cc90aead 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -179,6 +179,8 @@ class ChartEditorState extends HaxeUIState */ static final SNAP_QUANTS:Array = [4, 8, 12, 16, 20, 24, 32, 48, 64, 96, 192]; + static final BASE_QUANT:Int = 16; + /** * INSTANCE DATA */ @@ -1770,7 +1772,7 @@ class ChartEditorState extends HaxeUIState // These ones only happen if the modal dialog is not open. handleScrollKeybinds(); // handleZoom(); - // handleSnap(); + handleSnap(); handleCursor(); handleMenubar(); @@ -2028,11 +2030,25 @@ class ChartEditorState extends HaxeUIState if (FlxG.keys.justPressed.LEFT) { noteSnapQuantIndex--; + NotificationManager.instance.addNotification( + { + title: 'Note Snapping', + body: 'Updated note snapping to 1/${noteSnapQuant}', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); } if (FlxG.keys.justPressed.RIGHT) { noteSnapQuantIndex++; + NotificationManager.instance.addNotification( + { + title: 'Note Snapping', + body: 'Updated note snapping to 1/${noteSnapQuant}', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); } } From 97b640f30b4912b5ab31e0367108197c37c7973c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 31 Aug 2023 23:48:15 -0400 Subject: [PATCH 2/4] Note snapping yay! --- .../charting/ChartEditorDialogHandler.hx | 26 +++++++++++++ .../debug/charting/ChartEditorNoteSprite.hx | 8 ++-- .../ui/debug/charting/ChartEditorState.hx | 37 +++++++++++++++++-- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 63dc8bd92..fa47e17b2 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -290,6 +290,7 @@ class ChartEditorDialogHandler if (state.loadInstrumentalFromBytes(selectedFile.bytes)) { trace('Selected file: ' + selectedFile.fullPath); + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -297,6 +298,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end dialog.hideDialog(DialogButton.APPLY); removeDropHandler(onDropFile); @@ -305,6 +307,7 @@ class ChartEditorDialogHandler { trace('Failed to load instrumental (${selectedFile.fullPath})'); + #if !mac NotificationManager.instance.addNotification( { title: 'Failure', @@ -312,6 +315,7 @@ class ChartEditorDialogHandler type: NotificationType.Error, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end } } }); @@ -323,6 +327,7 @@ class ChartEditorDialogHandler if (state.loadInstrumentalFromPath(path)) { // Tell the user the load was successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -330,6 +335,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end dialog.hideDialog(DialogButton.APPLY); removeDropHandler(onDropFile); @@ -346,6 +352,7 @@ class ChartEditorDialogHandler } // Tell the user the load was successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Failure', @@ -353,6 +360,7 @@ class ChartEditorDialogHandler type: NotificationType.Error, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end } }; @@ -622,6 +630,7 @@ class ChartEditorDialogHandler if (state.loadVocalsFromPath(path, charKey)) { // Tell the user the load was successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -629,6 +638,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end vocalsEntryLabel.text = 'Vocals for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; dialogNoVocals.hidden = true; removeDropHandler(onDropFile); @@ -645,6 +655,7 @@ class ChartEditorDialogHandler } // Vocals failed to load. + #if !mac NotificationManager.instance.addNotification( { title: 'Failure', @@ -652,6 +663,7 @@ class ChartEditorDialogHandler type: NotificationType.Error, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.'; } @@ -768,6 +780,7 @@ class ChartEditorDialogHandler if (songMetadataVariation == null) { // Tell the user the load was not successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Failure', @@ -775,12 +788,14 @@ class ChartEditorDialogHandler type: NotificationType.Error, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end return; } songMetadata.set(variation, songMetadataVariation); // Tell the user the load was successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -788,6 +803,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; @@ -809,6 +825,7 @@ class ChartEditorDialogHandler songMetadata.set(variation, songMetadataVariation); // Tell the user the load was successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -816,6 +833,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; @@ -835,6 +853,7 @@ class ChartEditorDialogHandler songChartData.set(variation, songChartDataVariation); // Tell the user the load was successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -842,6 +861,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; }; @@ -860,6 +880,7 @@ class ChartEditorDialogHandler songChartData.set(variation, songChartDataVariation); // Tell the user the load was successful. + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -867,6 +888,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; } @@ -942,6 +964,7 @@ class ChartEditorDialogHandler state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); dialog.hideDialog(DialogButton.APPLY); + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -949,6 +972,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end } }); } @@ -962,6 +986,7 @@ class ChartEditorDialogHandler state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); dialog.hideDialog(DialogButton.APPLY); + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -969,6 +994,7 @@ class ChartEditorDialogHandler type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end }; addDropHandler(importBox, onDropFile); diff --git a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx index 0adbf1a20..ca480291a 100644 --- a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx @@ -130,8 +130,10 @@ class ChartEditorNoteSprite extends FlxSprite return this.noteData; } - public function updateNotePosition(?origin:FlxObject) + public function updateNotePosition(?origin:FlxObject):Void { + if (this.noteData == null) return; + var cursorColumn:Int = this.noteData.data; if (cursorColumn < 0) cursorColumn = 0; @@ -158,9 +160,7 @@ class ChartEditorNoteSprite extends FlxSprite 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.y = this.noteData.stepTime * ChartEditorState.GRID_SIZE; } if (origin != null) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 8cc90aead..058703733 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -187,8 +187,16 @@ class ChartEditorState extends HaxeUIState // ============================== public var currentZoomLevel:Float = 1.0; - var noteSnapQuantIndex:Int = 3; + /** + * The internal index of what note snapping value is in use. + * Increment to make placement more preceise and decrement to make placement less precise. + */ + var noteSnapQuantIndex:Int = 3; // default is 16 + /** + * The current note snapping value. + * For example, `32` when snapping to 32nd notes. + */ public var noteSnapQuant(get, never):Int; function get_noteSnapQuant():Int @@ -196,6 +204,17 @@ class ChartEditorState extends HaxeUIState return SNAP_QUANTS[noteSnapQuantIndex]; } + /** + * The ratio of the current note snapping value to the default. + * For example, `32` becomes `0.5` when snapping to 16th notes. + */ + public var noteSnapRatio(get, never):Float; + + function get_noteSnapRatio():Float + { + return BASE_QUANT / noteSnapQuant; + } + /** * scrollPosition is the current position in the song, in pixels. * One pixel is 1/40 of 1 step, and 1/160 of 1 beat. @@ -2030,6 +2049,7 @@ class ChartEditorState extends HaxeUIState if (FlxG.keys.justPressed.LEFT) { noteSnapQuantIndex--; + #if !mac NotificationManager.instance.addNotification( { title: 'Note Snapping', @@ -2037,11 +2057,13 @@ class ChartEditorState extends HaxeUIState type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end } if (FlxG.keys.justPressed.RIGHT) { noteSnapQuantIndex++; + #if !mac NotificationManager.instance.addNotification( { title: 'Note Snapping', @@ -2049,6 +2071,7 @@ class ChartEditorState extends HaxeUIState type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end } } @@ -2136,8 +2159,12 @@ class ChartEditorState extends HaxeUIState } // The song position of the cursor, in steps. - var cursorFractionalStep:Float = cursorY / GRID_SIZE / (16 / noteSnapQuant); + var cursorFractionalStep:Float = cursorY / GRID_SIZE; var cursorMs:Float = Conductor.getStepTimeInMs(cursorFractionalStep); + // Round the cursor step to the nearest snap quant. + var cursorSnappedStep:Float = Math.floor(cursorFractionalStep / noteSnapRatio) * noteSnapRatio; + var cursorSnappedMs:Float = Conductor.getStepTimeInMs(cursorSnappedStep); + // The direction value for the column at the cursor. var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE); if (cursorColumn < 0) cursorColumn = 0; @@ -2560,7 +2587,7 @@ class ChartEditorState extends HaxeUIState { eventData.event = selectedEventKind; } - eventData.time = cursorMs; + eventData.time = cursorSnappedMs; gridGhostEvent.visible = true; gridGhostEvent.eventData = eventData; @@ -2581,7 +2608,7 @@ class ChartEditorState extends HaxeUIState noteData.data = cursorColumn; gridGhostNote.playNoteAnimation(); } - noteData.time = cursorMs; + noteData.time = cursorSnappedMs; gridGhostNote.visible = true; gridGhostNote.noteData = noteData; @@ -4017,6 +4044,7 @@ class ChartEditorState extends HaxeUIState } } + #if !mac NotificationManager.instance.addNotification( { title: 'Success', @@ -4024,6 +4052,7 @@ class ChartEditorState extends HaxeUIState type: NotificationType.Success, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); + #end } /** From d3f5a81b1301cd9426f697ad27e39fde15d4bf4b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 11 Sep 2023 18:29:08 -0400 Subject: [PATCH 3/4] Fix a bug where ghost note would be properly snapped but placed note would not. --- source/funkin/ui/debug/charting/ChartEditorState.hx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index fe2ab6dab..52468070d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2414,7 +2414,7 @@ class ChartEditorState extends HaxeUIState // Handle extending the note as you drag. // TODO: This should be beat snapped? - var dragLengthSteps:Float = Conductor.getTimeInSteps(cursorMs) - currentPlaceNoteData.stepTime; + var dragLengthSteps:Float = Conductor.getTimeInSteps(cursorSnappedMs) - currentPlaceNoteData.stepTime; // Without this, the newly placed note feels too short compared to the user's input. var INCREMENT:Float = 1.0; @@ -2508,14 +2508,14 @@ class ChartEditorState extends HaxeUIState { // Create an event and place it in the chart. // TODO: Figure out configuring event data. - var newEventData:SongEventData = new SongEventData(cursorMs, selectedEventKind, selectedEventData); + var newEventData:SongEventData = new SongEventData(cursorSnappedMs, selectedEventKind, selectedEventData); performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL)); } else { // Create a note and place it in the chart. - var newNoteData:SongNoteData = new SongNoteData(cursorMs, cursorColumn, 0, selectedNoteKind); + var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, selectedNoteKind); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); @@ -2598,8 +2598,7 @@ class ChartEditorState extends HaxeUIState if (gridGhostNote == null) throw "ERROR: Tried to handle cursor, but gridGhostNote is null! Check ChartEditorState.buildGrid()"; - var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, - selectedNoteKind); + var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, selectedNoteKind); if (cursorColumn != noteData.data || selectedNoteKind != noteData.kind) { From a63246ff517377d9c8f861e3d4af046e5963c9ad Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 12 Sep 2023 00:25:05 -0400 Subject: [PATCH 4/4] Remove notification on snapping change. --- .../ui/debug/charting/ChartEditorState.hx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 52468070d..ab9cc6808 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2048,29 +2048,11 @@ class ChartEditorState extends HaxeUIState if (FlxG.keys.justPressed.LEFT) { noteSnapQuantIndex--; - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Note Snapping', - body: 'Updated note snapping to 1/${noteSnapQuant}', - type: NotificationType.Success, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end } if (FlxG.keys.justPressed.RIGHT) { noteSnapQuantIndex++; - #if !mac - NotificationManager.instance.addNotification( - { - title: 'Note Snapping', - body: 'Updated note snapping to 1/${noteSnapQuant}', - type: NotificationType.Success, - expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME - }); - #end } }