Merge branch 'rewrite/master' into input-offsets
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
package funkin.ui.debug;
|
||||
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.waveform.WaveformData;
|
||||
import funkin.audio.waveform.WaveformSprite;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import funkin.graphics.rendering.MeshRender;
|
||||
|
||||
class WaveformTestState extends MusicBeatState
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
var waveformData:WaveformData;
|
||||
var waveformData2:WaveformData;
|
||||
|
||||
var waveformAudio:FunkinSound;
|
||||
|
||||
// var waveformSprite:WaveformSprite;
|
||||
// var waveformSprite2:WaveformSprite;
|
||||
var timeMarker:FlxSprite;
|
||||
|
||||
var polygonSprite:MeshRender;
|
||||
var vertexCount:Int = 3;
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
var testSprite = new FlxSprite(0, 0);
|
||||
testSprite.loadGraphic(Paths.image('funkay'));
|
||||
testSprite.updateHitbox();
|
||||
testSprite.clipRect = new FlxRect(0, 0, FlxG.width, FlxG.height);
|
||||
// add(testSprite);
|
||||
|
||||
waveformAudio = FunkinSound.load(Paths.inst('bopeebo', '-erect'));
|
||||
|
||||
waveformData = WaveformDataParser.interpretFlxSound(waveformAudio);
|
||||
|
||||
polygonSprite = new MeshRender(FlxG.width / 2, FlxG.height / 2, FlxColor.WHITE);
|
||||
|
||||
setPolygonVertices(vertexCount);
|
||||
add(polygonSprite);
|
||||
|
||||
// waveformSprite = WaveformSprite.buildFromWaveformData(waveformData, HORIZONTAL, FlxColor.fromString("#ADD8E6"));
|
||||
// waveformSprite.duration = 5.0 * 160;
|
||||
// waveformSprite.width = FlxG.width * 160;
|
||||
// waveformSprite.height = FlxG.height; // / 2;
|
||||
// waveformSprite.amplitude = 2.0;
|
||||
// waveformSprite.minWaveformSize = 25;
|
||||
// waveformSprite.clipRect = new FlxRect(0, 0, FlxG.width, FlxG.height);
|
||||
// add(waveformSprite);
|
||||
//
|
||||
// waveformSprite2 = WaveformSprite.buildFromWaveformData(waveformData2, HORIZONTAL, FlxColor.fromString("#FF0000"), 5.0);
|
||||
// waveformSprite2.width = FlxG.width;
|
||||
// waveformSprite2.height = FlxG.height / 2;
|
||||
// waveformSprite2.y = FlxG.height / 2;
|
||||
// add(waveformSprite2);
|
||||
|
||||
timeMarker = new FlxSprite(0, FlxG.height * 1 / 6);
|
||||
timeMarker.makeGraphic(1, Std.int(FlxG.height * 2 / 3), FlxColor.RED);
|
||||
add(timeMarker);
|
||||
|
||||
// drawWaveform(time, duration);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.keys.justPressed.SPACE)
|
||||
{
|
||||
if (waveformAudio.isPlaying)
|
||||
{
|
||||
waveformAudio.stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
waveformAudio.play();
|
||||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.ENTER)
|
||||
{
|
||||
// if (waveformSprite.orientation == HORIZONTAL)
|
||||
// {
|
||||
// // waveformSprite.orientation = VERTICAL;
|
||||
// // waveformSprite2.orientation = VERTICAL;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // waveformSprite.orientation = HORIZONTAL;
|
||||
// // waveformSprite2.orientation = HORIZONTAL;
|
||||
// }
|
||||
}
|
||||
|
||||
if (waveformAudio.isPlaying)
|
||||
{
|
||||
// waveformSprite takes a time in fractional seconds, not milliseconds.
|
||||
var timeSeconds = waveformAudio.time / 1000;
|
||||
// waveformSprite.time = timeSeconds;
|
||||
// waveformSprite2.time = timeSeconds;
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.UP)
|
||||
{
|
||||
vertexCount += 1;
|
||||
setPolygonVertices(vertexCount);
|
||||
// waveformSprite.duration += 1.0;
|
||||
// waveformSprite2.duration += 1.0;
|
||||
}
|
||||
if (FlxG.keys.justPressed.DOWN)
|
||||
{
|
||||
vertexCount -= 1;
|
||||
setPolygonVertices(vertexCount);
|
||||
// waveformSprite.duration -= 1.0;
|
||||
// waveformSprite2.duration -= 1.0;
|
||||
}
|
||||
if (FlxG.keys.justPressed.LEFT)
|
||||
{
|
||||
// waveformSprite.time -= 1.0;
|
||||
// waveformSprite2.time -= 1.0;
|
||||
}
|
||||
if (FlxG.keys.justPressed.RIGHT)
|
||||
{
|
||||
// waveformSprite.time += 1.0;
|
||||
// waveformSprite2.time += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
function setPolygonVertices(count:Int)
|
||||
{
|
||||
polygonSprite.clear();
|
||||
|
||||
var size = 100.0;
|
||||
|
||||
// Build a polygon with count vertices.
|
||||
|
||||
var vertices:Array<Array<Float>> = [];
|
||||
|
||||
var angle = 0.0;
|
||||
|
||||
for (i in 0...count)
|
||||
{
|
||||
var x = Math.cos(angle) * size;
|
||||
var y = Math.sin(angle) * size;
|
||||
|
||||
vertices.push([x, y]);
|
||||
|
||||
angle += 2 * Math.PI / count;
|
||||
}
|
||||
|
||||
trace('vertices: ${vertices}');
|
||||
|
||||
var centerVertex = polygonSprite.build_vertex(0, 0);
|
||||
var firstVertex = -1;
|
||||
var lastVertex = -1;
|
||||
|
||||
for (vertex in vertices)
|
||||
{
|
||||
var x = vertex[0];
|
||||
var y = vertex[1];
|
||||
|
||||
var newVertex = polygonSprite.build_vertex(x, y);
|
||||
|
||||
if (firstVertex == -1)
|
||||
{
|
||||
firstVertex = newVertex;
|
||||
}
|
||||
|
||||
if (lastVertex != -1)
|
||||
{
|
||||
polygonSprite.add_tri(centerVertex, lastVertex, newVertex);
|
||||
}
|
||||
|
||||
lastVertex = newVertex;
|
||||
}
|
||||
|
||||
polygonSprite.add_tri(centerVertex, lastVertex, firstVertex);
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,52 @@
|
||||
package funkin.ui.debug.charting;
|
||||
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import haxe.ui.containers.HBox;
|
||||
import haxe.ui.containers.Grid;
|
||||
import haxe.ui.containers.ScrollView;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
import flixel.addons.display.FlxSliceSprite;
|
||||
import flixel.addons.display.FlxTiledSprite;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxSubState;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.input.mouse.FlxMouseEvent;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.misc.VarTween;
|
||||
import haxe.ui.Toolkit;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSort;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.visualize.PolygonSpectogram;
|
||||
import funkin.audio.visualize.PolygonSpectogram;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.waveform.WaveformSprite;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorDifficultyToolbox;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongOffsets;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.input.TurboKeyHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
@@ -50,22 +57,14 @@ import funkin.play.components.HealthIcon;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.save.Save;
|
||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.CopyItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.DeselectItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand;
|
||||
@@ -83,17 +82,22 @@ import funkin.ui.debug.charting.commands.SelectItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.SetItemSelectionCommand;
|
||||
import funkin.ui.debug.charting.components.ChartEditorEventSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
|
||||
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
|
||||
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorMeasureTicks;
|
||||
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
|
||||
import funkin.ui.debug.charting.components.ChartEditorSelectionSquareSprite;
|
||||
import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorDifficultyToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorFreeplayToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorOffsetsToolbox;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
import funkin.ui.haxeui.HaxeUIState;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.logging.CrashHandler;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import haxe.DynamicAccess;
|
||||
@@ -101,23 +105,26 @@ import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.backend.flixel.UIRuntimeState;
|
||||
import haxe.ui.backend.flixel.UIState;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Image;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.Grid;
|
||||
import haxe.ui.containers.HBox;
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
import haxe.ui.containers.menus.MenuItem;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
import haxe.ui.containers.menus.MenuCheckBox;
|
||||
import haxe.ui.containers.menus.MenuItem;
|
||||
import haxe.ui.containers.ScrollView;
|
||||
import haxe.ui.containers.TreeView;
|
||||
import haxe.ui.containers.TreeViewNode;
|
||||
import haxe.ui.components.Image;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.DragEvent;
|
||||
@@ -125,13 +132,8 @@ import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.focus.FocusManager;
|
||||
import haxe.ui.Toolkit;
|
||||
import openfl.display.BitmapData;
|
||||
import funkin.audio.visualize.PolygonSpectogram;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.audio.visualize.PolygonVisGroup;
|
||||
import flixel.input.mouse.FlxMouseEvent;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.system.debug.log.LogStyle;
|
||||
|
||||
using Lambda;
|
||||
|
||||
@@ -153,17 +155,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
*/
|
||||
// ==============================
|
||||
// Layouts
|
||||
public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata');
|
||||
|
||||
public static final CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata');
|
||||
public static final CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:String = Paths.ui('chart-editor/toolbox/playtest-properties');
|
||||
public static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata');
|
||||
public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty');
|
||||
|
||||
public static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview');
|
||||
public static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/opponent-preview');
|
||||
public static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata');
|
||||
public static final CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT:String = Paths.ui('chart-editor/toolbox/offsets');
|
||||
public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata');
|
||||
public static final CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata');
|
||||
public static final CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT:String = Paths.ui('chart-editor/toolbox/freeplay');
|
||||
public static final CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:String = Paths.ui('chart-editor/toolbox/playtest-properties');
|
||||
|
||||
// Validation
|
||||
public static final SUPPORTED_MUSIC_FORMATS:Array<String> = ['ogg'];
|
||||
public static final SUPPORTED_MUSIC_FORMATS:Array<String> = #if sys ['ogg'] #else ['mp3'] #end;
|
||||
|
||||
// Layout
|
||||
|
||||
@@ -393,13 +397,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS);
|
||||
measureTicks.y = gridTiledSprite.y;
|
||||
|
||||
if (audioVisGroup != null && audioVisGroup.playerVis != null)
|
||||
for (member in audioWaveforms.members)
|
||||
{
|
||||
audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS - GRID_TOP_PAD);
|
||||
}
|
||||
if (audioVisGroup != null && audioVisGroup.opponentVis != null)
|
||||
{
|
||||
audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS - GRID_TOP_PAD);
|
||||
member.time = scrollPositionInMs / Constants.MS_PER_SEC;
|
||||
|
||||
// Doing this desyncs the waveforms from the grid.
|
||||
// member.y = Math.max(this.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS - ChartEditorState.GRID_TOP_PAD);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,8 +504,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
function get_playheadPositionInMs():Float
|
||||
{
|
||||
if (audioVisGroup != null && audioVisGroup.playerVis != null)
|
||||
audioVisGroup.playerVis.realtimeStartOffset = -Conductor.instance.getStepTimeInMs(playheadPositionInSteps);
|
||||
return Conductor.instance.getStepTimeInMs(playheadPositionInSteps);
|
||||
}
|
||||
|
||||
@@ -510,7 +511,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
playheadPositionInSteps = Conductor.instance.getTimeInSteps(value);
|
||||
|
||||
if (audioVisGroup != null && audioVisGroup.playerVis != null) audioVisGroup.playerVis.realtimeStartOffset = -value;
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -690,6 +690,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
*/
|
||||
var activeToolboxes:Map<String, CollapsibleDialog> = new Map<String, CollapsibleDialog>();
|
||||
|
||||
/**
|
||||
* The camera component we're using for this state.
|
||||
*/
|
||||
var uiCamera:FlxCamera;
|
||||
|
||||
// Audio
|
||||
|
||||
/**
|
||||
@@ -1101,14 +1106,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
* `null` until vocal track(s) are loaded.
|
||||
* When switching characters, the elements of the VoicesGroup will be swapped to match the new character.
|
||||
*/
|
||||
var audioVocalTrackGroup:Null<VoicesGroup> = null;
|
||||
var audioVocalTrackGroup:VoicesGroup = new VoicesGroup();
|
||||
|
||||
/**
|
||||
* The audio vis for the inst/vocals.
|
||||
* The audio waveform visualization for the inst/vocals.
|
||||
* `null` until vocal track(s) are loaded.
|
||||
* When switching characters, the elements of the PolygonVisGroup will be swapped to match the new character.
|
||||
* When switching characters, the elements will be swapped to match the new character.
|
||||
*/
|
||||
var audioVisGroup:Null<PolygonVisGroup> = null;
|
||||
var audioWaveforms:FlxTypedSpriteGroup<WaveformSprite> = new FlxTypedSpriteGroup<WaveformSprite>();
|
||||
|
||||
/**
|
||||
* A map of the audio tracks for each character's vocals.
|
||||
@@ -1291,6 +1296,29 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
return currentSongChartData.events = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience property to get the rating for this difficulty in the Freeplay menu.
|
||||
*/
|
||||
var currentSongChartDifficultyRating(get, set):Int;
|
||||
|
||||
function get_currentSongChartDifficultyRating():Int
|
||||
{
|
||||
var result:Null<Int> = currentSongMetadata.playData.ratings.get(selectedDifficulty);
|
||||
if (result == null)
|
||||
{
|
||||
// Initialize to the default value if not set.
|
||||
currentSongMetadata.playData.ratings.set(selectedDifficulty, 0);
|
||||
return 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function set_currentSongChartDifficultyRating(value:Int):Int
|
||||
{
|
||||
currentSongMetadata.playData.ratings.set(selectedDifficulty, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
var currentSongNoteStyle(get, set):String;
|
||||
|
||||
function get_currentSongNoteStyle():String
|
||||
@@ -1308,6 +1336,30 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
return currentSongMetadata.playData.noteStyle = value;
|
||||
}
|
||||
|
||||
var currentSongFreeplayPreviewStart(get, set):Int;
|
||||
|
||||
function get_currentSongFreeplayPreviewStart():Int
|
||||
{
|
||||
return currentSongMetadata.playData.previewStart;
|
||||
}
|
||||
|
||||
function set_currentSongFreeplayPreviewStart(value:Int):Int
|
||||
{
|
||||
return currentSongMetadata.playData.previewStart = value;
|
||||
}
|
||||
|
||||
var currentSongFreeplayPreviewEnd(get, set):Int;
|
||||
|
||||
function get_currentSongFreeplayPreviewEnd():Int
|
||||
{
|
||||
return currentSongMetadata.playData.previewEnd;
|
||||
}
|
||||
|
||||
function set_currentSongFreeplayPreviewEnd(value:Int):Int
|
||||
{
|
||||
return currentSongMetadata.playData.previewEnd = value;
|
||||
}
|
||||
|
||||
var currentSongStage(get, set):String;
|
||||
|
||||
function get_currentSongStage():String
|
||||
@@ -1441,19 +1493,28 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
return value;
|
||||
}
|
||||
|
||||
var currentVocalOffset(get, set):Float;
|
||||
var currentVocalOffsetPlayer(get, set):Float;
|
||||
|
||||
function get_currentVocalOffset():Float
|
||||
function get_currentVocalOffsetPlayer():Float
|
||||
{
|
||||
// Currently there's only one vocal offset, so we just grab the player's offset since both should be the same.
|
||||
// Should probably make it so we can set offsets for player + opponent individually, though.
|
||||
return currentSongOffsets.getVocalOffset(currentPlayerChar);
|
||||
}
|
||||
|
||||
function set_currentVocalOffset(value:Float):Float
|
||||
function set_currentVocalOffsetPlayer(value:Float):Float
|
||||
{
|
||||
// Currently there's only one vocal offset, so we just apply it to both characters.
|
||||
currentSongOffsets.setVocalOffset(currentPlayerChar, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
var currentVocalOffsetOpponent(get, set):Float;
|
||||
|
||||
function get_currentVocalOffsetOpponent():Float
|
||||
{
|
||||
return currentSongOffsets.getVocalOffset(currentOpponentChar);
|
||||
}
|
||||
|
||||
function set_currentVocalOffsetOpponent(value:Float):Float
|
||||
{
|
||||
currentSongOffsets.setVocalOffset(currentOpponentChar, value);
|
||||
return value;
|
||||
}
|
||||
@@ -1858,11 +1919,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
*/
|
||||
var notePreviewViewportBitmap:Null<BitmapData> = null;
|
||||
|
||||
/**r
|
||||
/**
|
||||
* The IMAGE used for the measure ticks. Updated by ChartEditorThemeHandler.
|
||||
*/
|
||||
var measureTickBitmap:Null<BitmapData> = null;
|
||||
|
||||
/**
|
||||
* The IMAGE used for the offset ticks. Updated by ChartEditorThemeHandler.
|
||||
*/
|
||||
var offsetTickBitmap:Null<BitmapData> = null;
|
||||
|
||||
/**
|
||||
* The tiled sprite used to display the grid.
|
||||
* The height is the length of the song, and scrolling is done by simply the sprite.
|
||||
@@ -2028,7 +2094,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
loadPreferences();
|
||||
|
||||
fixCamera();
|
||||
uiCamera = new FlxCamera();
|
||||
FlxG.cameras.reset(uiCamera);
|
||||
|
||||
buildDefaultSongData();
|
||||
|
||||
@@ -2246,8 +2313,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
// Initialize the song chart data.
|
||||
songChartData = new Map<String, SongChartData>();
|
||||
|
||||
audioVocalTrackGroup = new VoicesGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2334,8 +2399,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
add(healthIconBF);
|
||||
healthIconBF.zIndex = 30;
|
||||
|
||||
audioVisGroup = new PolygonVisGroup();
|
||||
add(audioVisGroup);
|
||||
add(audioWaveforms);
|
||||
}
|
||||
|
||||
function buildMeasureTicks():Void
|
||||
@@ -2879,13 +2943,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
menubarItemVolumeVocalsPlayer.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.playerVolume = volume;
|
||||
audioVocalTrackGroup.playerVolume = volume;
|
||||
menubarLabelVolumeVocalsPlayer.text = 'Player - ${Std.int(event.value)}%';
|
||||
};
|
||||
|
||||
menubarItemVolumeVocalsOpponent.onChange = event -> {
|
||||
var volume:Float = event.value.toFloat() / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.opponentVolume = volume;
|
||||
audioVocalTrackGroup.opponentVolume = volume;
|
||||
menubarLabelVolumeVocalsOpponent.text = 'Enemy - ${Std.int(event.value)}%';
|
||||
};
|
||||
|
||||
@@ -2894,7 +2958,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
||||
#if FLX_PITCH
|
||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pitch = pitch;
|
||||
audioVocalTrackGroup.pitch = pitch;
|
||||
#end
|
||||
var pitchDisplay:Float = Std.int(pitch * 100) / 100; // Round to 2 decimal places.
|
||||
menubarLabelPlaybackSpeed.text = 'Playback Speed - ${pitchDisplay}x';
|
||||
@@ -2902,8 +2966,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
|
||||
menubarItemToggleToolboxDifficulty.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxMetadata.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxOffsets.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxNotes.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxEventData.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxFreeplay.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxPlaytestProperties.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxPlayerPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value);
|
||||
menubarItemToggleToolboxOpponentPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value);
|
||||
@@ -3204,7 +3270,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
Conductor.instance.update(audioInstTrack.time);
|
||||
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
||||
// Resync vocals.
|
||||
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||
if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||
{
|
||||
audioVocalTrackGroup.time = audioInstTrack.time;
|
||||
}
|
||||
@@ -3222,7 +3288,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
Conductor.instance.update(audioInstTrack.time);
|
||||
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
||||
// Resync vocals.
|
||||
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||
if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||
{
|
||||
audioVocalTrackGroup.time = audioInstTrack.time;
|
||||
}
|
||||
@@ -5287,7 +5353,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
Paths.setCurrentLevel('weekend1');
|
||||
}
|
||||
|
||||
subStateClosed.add(fixCamera);
|
||||
subStateClosed.add(reviveUICamera);
|
||||
subStateClosed.add(resetConductorAfterTest);
|
||||
|
||||
FlxTransitionableState.skipNextTransIn = false;
|
||||
@@ -5310,7 +5376,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
FlxG.sound.music = audioInstTrack;
|
||||
}
|
||||
if (audioVocalTrackGroup != null) targetState.vocals = audioVocalTrackGroup;
|
||||
targetState.vocals = audioVocalTrackGroup;
|
||||
|
||||
// Kill and replace the UI camera so it doesn't get destroyed during the state transition.
|
||||
uiCamera.kill();
|
||||
FlxG.cameras.remove(uiCamera, false);
|
||||
FlxG.cameras.reset(new FlxCamera());
|
||||
|
||||
this.persistentUpdate = false;
|
||||
this.persistentDraw = false;
|
||||
@@ -5401,13 +5472,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix a camera issue caused when closing the PlayState used when testing.
|
||||
* Revive the UI camera and re-establish it as the main camera so UI elements depending on it don't explode.
|
||||
*/
|
||||
function fixCamera(_:FlxSubState = null):Void
|
||||
function reviveUICamera(_:FlxSubState = null):Void
|
||||
{
|
||||
FlxG.cameras.reset(new FlxCamera());
|
||||
FlxG.camera.focusOn(new FlxPoint(FlxG.width / 2, FlxG.height / 2));
|
||||
FlxG.camera.zoom = 1.0;
|
||||
uiCamera.revive();
|
||||
FlxG.cameras.reset(uiCamera);
|
||||
|
||||
add(this.root);
|
||||
}
|
||||
@@ -5422,7 +5492,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
if (audioInstTrack != null)
|
||||
{
|
||||
audioInstTrack.play(false, audioInstTrack.time);
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(false, audioInstTrack.time);
|
||||
audioVocalTrackGroup.play(false, audioInstTrack.time);
|
||||
}
|
||||
|
||||
playbarPlay.text = '||'; // Pause
|
||||
@@ -5660,7 +5730,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instance.instrumentalOffset;
|
||||
// Update the songPosition in the Conductor.
|
||||
Conductor.instance.update(audioInstTrack.time);
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.time = audioInstTrack.time;
|
||||
audioVocalTrackGroup.time = audioInstTrack.time;
|
||||
}
|
||||
|
||||
// We need to update the note sprites because we changed the scroll position.
|
||||
@@ -5881,7 +5951,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
function stopAudioPlayback():Void
|
||||
{
|
||||
if (audioInstTrack != null) audioInstTrack.pause();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||
audioVocalTrackGroup.pause();
|
||||
|
||||
playbarPlay.text = '>';
|
||||
}
|
||||
@@ -5916,7 +5986,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Keep the track at the end.
|
||||
audioInstTrack.time = audioInstTrack.length;
|
||||
}
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||
audioVocalTrackGroup.pause();
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -5930,12 +6000,32 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
healthIconsDirty = true;
|
||||
}
|
||||
|
||||
function hardRefreshOffsetsToolbox():Void
|
||||
{
|
||||
var offsetsToolbox:ChartEditorOffsetsToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT);
|
||||
if (offsetsToolbox != null)
|
||||
{
|
||||
offsetsToolbox.refreshAudioPreview();
|
||||
offsetsToolbox.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function hardRefreshFreeplayToolbox():Void
|
||||
{
|
||||
var freeplayToolbox:ChartEditorFreeplayToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT);
|
||||
if (freeplayToolbox != null)
|
||||
{
|
||||
freeplayToolbox.refreshAudioPreview();
|
||||
freeplayToolbox.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the voices group.
|
||||
*/
|
||||
public function clearVocals():Void
|
||||
{
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.clear();
|
||||
audioVocalTrackGroup.clear();
|
||||
}
|
||||
|
||||
function isNoteSelected(note:Null<SongNoteData>):Bool
|
||||
@@ -5960,7 +6050,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
// Stop the music.
|
||||
if (welcomeMusic != null) welcomeMusic.destroy();
|
||||
if (audioInstTrack != null) audioInstTrack.destroy();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.destroy();
|
||||
audioVocalTrackGroup.destroy();
|
||||
}
|
||||
|
||||
function applyCanQuickSave():Void
|
||||
|
||||
@@ -46,26 +46,14 @@ class CopyItemsCommand implements ChartEditorCommand
|
||||
|
||||
function performVisuals(state:ChartEditorState):Void
|
||||
{
|
||||
var hasNotes:Bool = false;
|
||||
var hasEvents:Bool = false;
|
||||
|
||||
// Wiggle copied notes.
|
||||
if (state.currentNoteSelection.length > 0)
|
||||
{
|
||||
// Display the "Copied Notes" text.
|
||||
if (state.txtCopyNotif != null)
|
||||
{
|
||||
state.txtCopyNotif.visible = true;
|
||||
state.txtCopyNotif.text = "Copied " + state.currentNoteSelection.length + " notes to clipboard";
|
||||
state.txtCopyNotif.x = FlxG.mouse.x - (state.txtCopyNotif.width / 2);
|
||||
state.txtCopyNotif.y = FlxG.mouse.y - 16;
|
||||
FlxTween.tween(state.txtCopyNotif, {y: state.txtCopyNotif.y - 32}, 0.5,
|
||||
{
|
||||
type: FlxTween.ONESHOT,
|
||||
ease: FlxEase.quadOut,
|
||||
onComplete: function(_) {
|
||||
state.txtCopyNotif.visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
hasNotes = true;
|
||||
|
||||
// Wiggle the notes.
|
||||
for (note in state.renderedNotes.members)
|
||||
{
|
||||
if (state.isNoteSelected(note.noteData))
|
||||
@@ -91,8 +79,13 @@ class CopyItemsCommand implements ChartEditorCommand
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wiggle copied events.
|
||||
if (state.currentEventSelection.length > 0)
|
||||
{
|
||||
hasEvents = true;
|
||||
|
||||
// Wiggle the events.
|
||||
for (event in state.renderedEvents.members)
|
||||
{
|
||||
if (state.isEventSelected(event.eventData))
|
||||
@@ -119,6 +112,39 @@ class CopyItemsCommand implements ChartEditorCommand
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display the "Copied Notes" text.
|
||||
if ((hasNotes || hasEvents) && state.txtCopyNotif != null)
|
||||
{
|
||||
var copiedString:String = '';
|
||||
if (hasNotes)
|
||||
{
|
||||
var copiedNotes:Int = state.currentNoteSelection.length;
|
||||
copiedString += '${copiedNotes} note';
|
||||
if (copiedNotes > 1) copiedString += 's';
|
||||
|
||||
if (hasEvents) copiedString += ' and ';
|
||||
}
|
||||
if (hasEvents)
|
||||
{
|
||||
var copiedEvents:Int = state.currentEventSelection.length;
|
||||
copiedString += '${state.currentEventSelection.length} event';
|
||||
if (copiedEvents > 1) copiedString += 's';
|
||||
}
|
||||
|
||||
state.txtCopyNotif.visible = true;
|
||||
state.txtCopyNotif.text = 'Copied ${copiedString} to clipboard';
|
||||
state.txtCopyNotif.x = FlxG.mouse.x - (state.txtCopyNotif.width / 2);
|
||||
state.txtCopyNotif.y = FlxG.mouse.y - 16;
|
||||
FlxTween.tween(state.txtCopyNotif, {y: state.txtCopyNotif.y - 32}, 0.5,
|
||||
{
|
||||
type: FlxTween.ONESHOT,
|
||||
ease: FlxEase.quadOut,
|
||||
onComplete: function(_) {
|
||||
state.txtCopyNotif.visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
||||
/**
|
||||
* Command that copies a given set of notes and song events to the clipboard,
|
||||
* without deleting them from the chart editor.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class SetAudioOffsetCommand implements ChartEditorCommand
|
||||
{
|
||||
var type:AudioOffsetType;
|
||||
var oldOffset:Float = 0;
|
||||
var newOffset:Float;
|
||||
var refreshOffsetsToolbox:Bool;
|
||||
|
||||
public function new(type:AudioOffsetType, newOffset:Float, refreshOffsetsToolbox:Bool = true)
|
||||
{
|
||||
this.type = type;
|
||||
this.newOffset = newOffset;
|
||||
this.refreshOffsetsToolbox = refreshOffsetsToolbox;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case INSTRUMENTAL:
|
||||
oldOffset = state.currentInstrumentalOffset;
|
||||
state.currentInstrumentalOffset = newOffset;
|
||||
|
||||
// Update rendering.
|
||||
Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset;
|
||||
state.songLengthInMs = (state.audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset;
|
||||
case PLAYER:
|
||||
oldOffset = state.currentVocalOffsetPlayer;
|
||||
state.currentVocalOffsetPlayer = newOffset;
|
||||
|
||||
// Update rendering.
|
||||
state.audioVocalTrackGroup.playerVoicesOffset = state.currentVocalOffsetPlayer;
|
||||
case OPPONENT:
|
||||
oldOffset = state.currentVocalOffsetOpponent;
|
||||
state.currentVocalOffsetOpponent = newOffset;
|
||||
|
||||
// Update rendering.
|
||||
state.audioVocalTrackGroup.opponentVoicesOffset = state.currentVocalOffsetOpponent;
|
||||
}
|
||||
|
||||
// Update the offsets toolbox.
|
||||
if (refreshOffsetsToolbox)
|
||||
{
|
||||
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT);
|
||||
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case INSTRUMENTAL:
|
||||
state.currentInstrumentalOffset = oldOffset;
|
||||
|
||||
// Update rendering.
|
||||
Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset;
|
||||
state.songLengthInMs = (state.audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset;
|
||||
case PLAYER:
|
||||
state.currentVocalOffsetPlayer = oldOffset;
|
||||
|
||||
// Update rendering.
|
||||
state.audioVocalTrackGroup.playerVoicesOffset = state.currentVocalOffsetPlayer;
|
||||
case OPPONENT:
|
||||
state.currentVocalOffsetOpponent = oldOffset;
|
||||
|
||||
// Update rendering.
|
||||
state.audioVocalTrackGroup.opponentVoicesOffset = state.currentVocalOffsetOpponent;
|
||||
}
|
||||
|
||||
// Update the offsets toolbox.
|
||||
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT);
|
||||
}
|
||||
|
||||
public function shouldAddToHistory(state:ChartEditorState):Bool
|
||||
{
|
||||
// This command is undoable. Add to the history if we actually performed an action.
|
||||
return (newOffset != oldOffset);
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case INSTRUMENTAL:
|
||||
return 'Set Inst. Audio Offset to $newOffset';
|
||||
case PLAYER:
|
||||
return 'Set Player Audio Offset to $newOffset';
|
||||
case OPPONENT:
|
||||
return 'Set Opponent Audio Offset to $newOffset';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AudioOffsetType
|
||||
{
|
||||
INSTRUMENTAL;
|
||||
PLAYER;
|
||||
OPPONENT;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package funkin.ui.debug.charting.commands;
|
||||
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
|
||||
/**
|
||||
* Command that sets the start time or end time of the Freeplay preview.
|
||||
*/
|
||||
@:nullSafety
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class SetFreeplayPreviewCommand implements ChartEditorCommand
|
||||
{
|
||||
var previousStartTime:Int = 0;
|
||||
var previousEndTime:Int = 0;
|
||||
var newStartTime:Null<Int> = null;
|
||||
var newEndTime:Null<Int> = null;
|
||||
|
||||
public function new(newStartTime:Null<Int>, newEndTime:Null<Int>)
|
||||
{
|
||||
this.newStartTime = newStartTime;
|
||||
this.newEndTime = newEndTime;
|
||||
}
|
||||
|
||||
public function execute(state:ChartEditorState):Void
|
||||
{
|
||||
this.previousStartTime = state.currentSongFreeplayPreviewStart;
|
||||
this.previousEndTime = state.currentSongFreeplayPreviewEnd;
|
||||
|
||||
if (newStartTime != null) state.currentSongFreeplayPreviewStart = newStartTime;
|
||||
if (newEndTime != null) state.currentSongFreeplayPreviewEnd = newEndTime;
|
||||
}
|
||||
|
||||
public function undo(state:ChartEditorState):Void
|
||||
{
|
||||
state.currentSongFreeplayPreviewStart = previousStartTime;
|
||||
state.currentSongFreeplayPreviewEnd = previousEndTime;
|
||||
}
|
||||
|
||||
public function shouldAddToHistory(state:ChartEditorState):Bool
|
||||
{
|
||||
return (newStartTime != null && newStartTime != previousStartTime) || (newEndTime != null && newEndTime != previousEndTime);
|
||||
}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
var setStart = newStartTime != null && newStartTime != previousStartTime;
|
||||
var setEnd = newEndTime != null && newEndTime != previousEndTime;
|
||||
|
||||
if (setStart && !setEnd)
|
||||
{
|
||||
return "Set Freeplay Preview Start Time";
|
||||
}
|
||||
else if (setEnd && !setStart)
|
||||
{
|
||||
return "Set Freeplay Preview End Time";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Set Freeplay Preview Start and End Times";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,14 @@ package funkin.ui.debug.charting.handlers;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.system.FlxSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.audio.visualize.PolygonVisGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.assets.SoundUtil;
|
||||
import funkin.audio.waveform.WaveformData;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import funkin.audio.waveform.WaveformSprite;
|
||||
import flixel.util.FlxColor;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import openfl.utils.Assets;
|
||||
@@ -125,15 +128,42 @@ class ChartEditorAudioHandler
|
||||
|
||||
public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool
|
||||
{
|
||||
var perfA = haxe.Timer.stamp();
|
||||
|
||||
var result:Bool = playInstrumental(state, instId);
|
||||
if (!result) return false;
|
||||
|
||||
var perfB = haxe.Timer.stamp();
|
||||
|
||||
stopExistingVocals(state);
|
||||
|
||||
var perfC = haxe.Timer.stamp();
|
||||
|
||||
result = playVocals(state, BF, playerId, instId);
|
||||
|
||||
var perfD = haxe.Timer.stamp();
|
||||
|
||||
// if (!result) return false;
|
||||
result = playVocals(state, DAD, opponentId, instId);
|
||||
// if (!result) return false;
|
||||
|
||||
var perfE = haxe.Timer.stamp();
|
||||
|
||||
state.hardRefreshOffsetsToolbox();
|
||||
|
||||
var perfF = haxe.Timer.stamp();
|
||||
|
||||
state.hardRefreshFreeplayToolbox();
|
||||
|
||||
var perfG = haxe.Timer.stamp();
|
||||
|
||||
trace('Switched to instrumental in ${perfB - perfA} seconds.');
|
||||
trace('Stopped existing vocals in ${perfC - perfB} seconds.');
|
||||
trace('Played BF vocals in ${perfD - perfC} seconds.');
|
||||
trace('Played DAD vocals in ${perfE - perfD} seconds.');
|
||||
trace('Hard refreshed offsets toolbox in ${perfF - perfE} seconds.');
|
||||
trace('Hard refreshed freeplay toolbox in ${perfG - perfF} seconds.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -144,7 +174,10 @@ class ChartEditorAudioHandler
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
||||
var perfA = haxe.Timer.stamp();
|
||||
var instTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(instTrackData);
|
||||
var perfB = haxe.Timer.stamp();
|
||||
trace('Built instrumental track in ${perfB - perfA} seconds.');
|
||||
if (instTrack == null) return false;
|
||||
|
||||
stopExistingInstrumental(state);
|
||||
@@ -172,10 +205,12 @@ class ChartEditorAudioHandler
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var vocalTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(vocalTrackData);
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
trace('Built vocal track in ${perfEnd - perfStart} seconds.');
|
||||
|
||||
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
||||
if (state.audioVisGroup == null) state.audioVisGroup = new PolygonVisGroup();
|
||||
|
||||
if (vocalTrack != null)
|
||||
{
|
||||
@@ -183,26 +218,57 @@ class ChartEditorAudioHandler
|
||||
{
|
||||
case BF:
|
||||
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
state.audioVisGroup.addPlayerVis(vocalTrack);
|
||||
state.audioVisGroup.playerVis.x = 885;
|
||||
state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195;
|
||||
state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
|
||||
state.audioVisGroup.playerVis.detail = 1;
|
||||
state.audioVisGroup.playerVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS - ChartEditorState.GRID_TOP_PAD);
|
||||
|
||||
state.audioVocalTrackGroup.playerVoicesOffset = state.currentVocalOffset;
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var waveformData:Null<WaveformData> = vocalTrack.waveformData;
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
trace('Interpreted waveform data in ${perfEnd - perfStart} seconds.');
|
||||
|
||||
if (waveformData != null)
|
||||
{
|
||||
var duration:Float = Conductor.instance.getStepTimeInMs(16) * 0.001;
|
||||
var waveformSprite:WaveformSprite = new WaveformSprite(waveformData, VERTICAL, FlxColor.WHITE);
|
||||
waveformSprite.x = 840;
|
||||
waveformSprite.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS - ChartEditorState.GRID_TOP_PAD);
|
||||
waveformSprite.height = (ChartEditorState.GRID_SIZE) * 16;
|
||||
waveformSprite.width = (ChartEditorState.GRID_SIZE) * 2;
|
||||
waveformSprite.time = 0;
|
||||
waveformSprite.duration = duration;
|
||||
state.audioWaveforms.add(waveformSprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] Failed to parse waveform data for vocal track.');
|
||||
}
|
||||
|
||||
state.audioVocalTrackGroup.playerVoicesOffset = state.currentVocalOffsetPlayer;
|
||||
return true;
|
||||
case DAD:
|
||||
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
state.audioVisGroup.addOpponentVis(vocalTrack);
|
||||
state.audioVisGroup.opponentVis.x = 405;
|
||||
|
||||
state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195;
|
||||
state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
|
||||
state.audioVisGroup.opponentVis.detail = 1;
|
||||
state.audioVisGroup.opponentVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS - ChartEditorState.GRID_TOP_PAD);
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var waveformData:Null<WaveformData> = vocalTrack.waveformData;
|
||||
var perfEnd = haxe.Timer.stamp();
|
||||
trace('Interpreted waveform data in ${perfEnd - perfStart} seconds.');
|
||||
|
||||
state.audioVocalTrackGroup.opponentVoicesOffset = state.currentVocalOffset;
|
||||
if (waveformData != null)
|
||||
{
|
||||
var duration:Float = Conductor.instance.getStepTimeInMs(16) * 0.001;
|
||||
var waveformSprite:WaveformSprite = new WaveformSprite(waveformData, VERTICAL, FlxColor.WHITE);
|
||||
waveformSprite.x = 360;
|
||||
waveformSprite.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS - ChartEditorState.GRID_TOP_PAD);
|
||||
waveformSprite.height = (ChartEditorState.GRID_SIZE) * 16;
|
||||
waveformSprite.width = (ChartEditorState.GRID_SIZE) * 2;
|
||||
waveformSprite.time = 0;
|
||||
waveformSprite.duration = duration;
|
||||
state.audioWaveforms.add(waveformSprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[WARN] Failed to parse waveform data for vocal track.');
|
||||
}
|
||||
|
||||
state.audioVocalTrackGroup.opponentVoicesOffset = state.currentVocalOffsetOpponent;
|
||||
|
||||
return true;
|
||||
case OTHER:
|
||||
@@ -219,13 +285,10 @@ class ChartEditorAudioHandler
|
||||
|
||||
public static function stopExistingVocals(state:ChartEditorState):Void
|
||||
{
|
||||
if (state.audioVocalTrackGroup != null)
|
||||
state.audioVocalTrackGroup.clear();
|
||||
if (state.audioWaveforms != null)
|
||||
{
|
||||
state.audioVocalTrackGroup.clear();
|
||||
}
|
||||
if (state.audioVisGroup != null)
|
||||
{
|
||||
state.audioVisGroup.clearAllVis();
|
||||
state.audioWaveforms.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ class ChartEditorImportExportHandler
|
||||
*/
|
||||
public static function loadSongAsTemplate(state:ChartEditorState, songId:String):Void
|
||||
{
|
||||
trace('===============START');
|
||||
|
||||
var song:Null<Song> = SongRegistry.instance.fetchEntry(songId);
|
||||
|
||||
if (song == null) return;
|
||||
@@ -98,11 +100,14 @@ class ChartEditorImportExportHandler
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
state.currentWorkingFilePath = null; // New file, so no path.
|
||||
state.switchToCurrentInstrumental();
|
||||
|
||||
state.postLoadInstrumental();
|
||||
|
||||
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
|
||||
state.success('Success', 'Loaded song (${rawSongMetadata[0].songName})');
|
||||
|
||||
trace('===============END');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,11 +137,8 @@ class ChartEditorImportExportHandler
|
||||
state.audioInstTrack.stop();
|
||||
state.audioInstTrack = null;
|
||||
}
|
||||
if (state.audioVocalTrackGroup != null)
|
||||
{
|
||||
state.audioVocalTrackGroup.stop();
|
||||
state.audioVocalTrackGroup.clear();
|
||||
}
|
||||
state.audioVocalTrackGroup.stop();
|
||||
state.audioVocalTrackGroup.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,6 +82,7 @@ class ChartEditorThemeHandler
|
||||
updateBackground(state);
|
||||
updateGridBitmap(state);
|
||||
updateMeasureTicks(state);
|
||||
updateOffsetTicks(state);
|
||||
updateSelectionSquare(state);
|
||||
updateNotePreview(state);
|
||||
}
|
||||
@@ -231,6 +232,9 @@ class ChartEditorThemeHandler
|
||||
// Else, gridTiledSprite will be built later.
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical measure ticks.
|
||||
*/
|
||||
static function updateMeasureTicks(state:ChartEditorState):Void
|
||||
{
|
||||
var measureTickWidth:Int = 6;
|
||||
@@ -286,6 +290,59 @@ class ChartEditorThemeHandler
|
||||
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick16Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal offset ticks.
|
||||
*/
|
||||
static function updateOffsetTicks(state:ChartEditorState):Void
|
||||
{
|
||||
var majorTickWidth:Int = 6;
|
||||
var minorTickWidth:Int = 3;
|
||||
|
||||
var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure); // 10 minor ticks wide.
|
||||
var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE); // 1 grid squares tall.
|
||||
state.offsetTickBitmap = new BitmapData(ticksWidth, ticksHeight, true);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), GRID_BEAT_DIVIDER_COLOR_DARK);
|
||||
|
||||
// Draw the major ticks.
|
||||
var leftTickX:Float = 0;
|
||||
var middleTickX:Float = state.offsetTickBitmap.width / 2 - (majorTickWidth / 2);
|
||||
var rightTickX:Float = state.offsetTickBitmap.width - (majorTickWidth / 2);
|
||||
var majorTickLength:Float = state.offsetTickBitmap.height;
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(leftTickX, 0, majorTickWidth / 2, majorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(middleTickX, 0, majorTickWidth, majorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(rightTickX, 0, majorTickWidth / 2, majorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
|
||||
// Draw the minor ticks.
|
||||
var minorTick2X:Float = state.offsetTickBitmap.width * 1 / 10 - (minorTickWidth / 2);
|
||||
var minorTick3X:Float = state.offsetTickBitmap.width * 2 / 10 - (minorTickWidth / 2);
|
||||
var minorTick4X:Float = state.offsetTickBitmap.width * 3 / 10 - (minorTickWidth / 2);
|
||||
var minorTick5X:Float = state.offsetTickBitmap.width * 4 / 10 - (minorTickWidth / 2);
|
||||
var minorTick7X:Float = state.offsetTickBitmap.width * 6 / 10 - (minorTickWidth / 2);
|
||||
var minorTick8X:Float = state.offsetTickBitmap.width * 7 / 10 - (minorTickWidth / 2);
|
||||
var minorTick9X:Float = state.offsetTickBitmap.width * 8 / 10 - (minorTickWidth / 2);
|
||||
var minorTick10X:Float = state.offsetTickBitmap.width * 9 / 10 - (minorTickWidth / 2);
|
||||
var minorTickLength:Float = state.offsetTickBitmap.height * 1 / 3;
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick2X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick3X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick4X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick5X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick7X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick8X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick9X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick10X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
|
||||
// Draw the offset ticks.
|
||||
// var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT); // 1 grid squares wide.
|
||||
// var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE); // 1 measure tall.
|
||||
// state.offsetTickBitmap = new BitmapData(ticksWidth, ticksHeight, true);
|
||||
// state.offsetTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), GRID_BEAT_DIVIDER_COLOR_DARK);
|
||||
//
|
||||
//// Draw the offset ticks.
|
||||
// state.offsetTickBitmap.fillRect(new Rectangle(0, 0, offsetTickWidth / 2, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
// var rightTickX:Float = state.offsetTickBitmap.width - (offsetTickWidth / 2);
|
||||
// state.offsetTickBitmap.fillRect(new Rectangle(rightTickX, 0, offsetTickWidth / 2, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||
}
|
||||
|
||||
static function updateSelectionSquare(state:ChartEditorState):Void
|
||||
{
|
||||
var selectionSquareBorderColor:FlxColor = switch (state.currentTheme)
|
||||
|
||||
@@ -35,6 +35,8 @@ import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorOffsetsToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorFreeplayToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorEventDataToolbox;
|
||||
import funkin.ui.debug.charting.toolboxes.ChartEditorDifficultyToolbox;
|
||||
import haxe.ui.containers.Frame;
|
||||
@@ -89,6 +91,10 @@ class ChartEditorToolboxHandler
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
|
||||
// TODO: Fix this.
|
||||
cast(toolbox, ChartEditorBaseToolbox).refresh();
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT:
|
||||
cast(toolbox, ChartEditorBaseToolbox).refresh();
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT:
|
||||
cast(toolbox, ChartEditorBaseToolbox).refresh();
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
||||
onShowToolboxPlayerPreview(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
|
||||
@@ -124,8 +130,6 @@ class ChartEditorToolboxHandler
|
||||
onHideToolboxEventData(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:
|
||||
onHideToolboxPlaytestProperties(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
|
||||
onHideToolboxMetadata(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
||||
onHideToolboxPlayerPreview(state, toolbox);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
|
||||
@@ -202,6 +206,10 @@ class ChartEditorToolboxHandler
|
||||
toolbox = buildToolboxDifficultyLayout(state);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
|
||||
toolbox = buildToolboxMetadataLayout(state);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT:
|
||||
toolbox = buildToolboxOffsetsLayout(state);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT:
|
||||
toolbox = buildToolboxFreeplayLayout(state);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
|
||||
toolbox = buildToolboxPlayerPreviewLayout(state);
|
||||
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
|
||||
@@ -304,8 +312,6 @@ class ChartEditorToolboxHandler
|
||||
|
||||
static function onHideToolboxNoteData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function onHideToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
|
||||
static function onShowToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
|
||||
@@ -373,6 +379,24 @@ class ChartEditorToolboxHandler
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
static function buildToolboxOffsetsLayout(state:ChartEditorState):Null<ChartEditorBaseToolbox>
|
||||
{
|
||||
var toolbox:ChartEditorBaseToolbox = ChartEditorOffsetsToolbox.build(state);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
static function buildToolboxFreeplayLayout(state:ChartEditorState):Null<ChartEditorBaseToolbox>
|
||||
{
|
||||
var toolbox:ChartEditorBaseToolbox = ChartEditorFreeplayToolbox.build(state);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
static function buildToolboxEventDataLayout(state:ChartEditorState):Null<ChartEditorBaseToolbox>
|
||||
{
|
||||
var toolbox:ChartEditorBaseToolbox = ChartEditorEventDataToolbox.build(state);
|
||||
|
||||
@@ -18,6 +18,7 @@ import haxe.ui.core.Component;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import haxe.ui.components.TextField;
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.HBox;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.data.ArrayDataSource;
|
||||
@@ -214,7 +215,20 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox
|
||||
input.text = field.type;
|
||||
}
|
||||
|
||||
target.addComponent(input);
|
||||
// Putting in a box so we can add a unit label easily if there is one.
|
||||
var inputBox:HBox = new HBox();
|
||||
inputBox.addComponent(input);
|
||||
|
||||
// Add a unit label if applicable.
|
||||
if (field.units != null && field.units != "")
|
||||
{
|
||||
var units:Label = new Label();
|
||||
units.text = field.units;
|
||||
units.verticalAlign = "center";
|
||||
inputBox.addComponent(units);
|
||||
}
|
||||
|
||||
target.addComponent(inputBox);
|
||||
|
||||
// Update the value of the event data.
|
||||
input.onChange = function(event:UIEvent) {
|
||||
|
||||
@@ -0,0 +1,693 @@
|
||||
package funkin.ui.debug.charting.toolboxes;
|
||||
|
||||
import funkin.audio.SoundGroup;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
import haxe.ui.components.Label;
|
||||
import flixel.addons.display.FlxTiledSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.backend.flixel.components.SpriteWrapper;
|
||||
import funkin.ui.debug.charting.commands.SetFreeplayPreviewCommand;
|
||||
import funkin.ui.haxeui.components.WaveformPlayer;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import haxe.ui.containers.VBox;
|
||||
import haxe.ui.containers.Absolute;
|
||||
import haxe.ui.containers.ScrollView;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.DragEvent;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
|
||||
/**
|
||||
* The toolbox which allows modifying information like Song Title, Scroll Speed, Characters/Stages, and starting BPM.
|
||||
*/
|
||||
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/freeplay.xml"))
|
||||
class ChartEditorFreeplayToolbox extends ChartEditorBaseToolbox
|
||||
{
|
||||
var waveformContainer:Absolute;
|
||||
var waveformScrollview:ScrollView;
|
||||
var waveformMusic:WaveformPlayer;
|
||||
var freeplayButtonZoomIn:Button;
|
||||
var freeplayButtonZoomOut:Button;
|
||||
var freeplayButtonPause:Button;
|
||||
var freeplayButtonPlay:Button;
|
||||
var freeplayButtonStop:Button;
|
||||
var freeplayPreviewStart:NumberStepper;
|
||||
var freeplayPreviewEnd:NumberStepper;
|
||||
var freeplayTicksContainer:Absolute;
|
||||
var playheadSprite:SpriteWrapper;
|
||||
var previewSelectionSprite:SpriteWrapper;
|
||||
|
||||
static final TICK_LABEL_X_OFFSET:Float = 4.0;
|
||||
|
||||
static final PLAYHEAD_RIGHT_PAD:Float = 10.0;
|
||||
|
||||
static final BASE_SCALE:Float = 64.0;
|
||||
static final STARTING_SCALE:Float = 1024.0;
|
||||
static final MIN_SCALE:Float = 4.0;
|
||||
static final WAVEFORM_ZOOM_MULT:Float = 1.5;
|
||||
|
||||
static final MAGIC_SCALE_BASE_TIME:Float = 5.0;
|
||||
|
||||
var waveformScale:Float = STARTING_SCALE;
|
||||
|
||||
var playheadAbsolutePos(get, set):Float;
|
||||
|
||||
function get_playheadAbsolutePos():Float
|
||||
{
|
||||
return playheadSprite.left;
|
||||
}
|
||||
|
||||
function set_playheadAbsolutePos(value:Float):Float
|
||||
{
|
||||
return playheadSprite.left = value;
|
||||
}
|
||||
|
||||
var playheadRelativePos(get, set):Float;
|
||||
|
||||
function get_playheadRelativePos():Float
|
||||
{
|
||||
return playheadSprite.left - waveformScrollview.hscrollPos;
|
||||
}
|
||||
|
||||
function set_playheadRelativePos(value:Float):Float
|
||||
{
|
||||
return playheadSprite.left = waveformScrollview.hscrollPos + value;
|
||||
}
|
||||
|
||||
var previewBoxStartPosAbsolute(get, set):Float;
|
||||
|
||||
function get_previewBoxStartPosAbsolute():Float
|
||||
{
|
||||
return previewSelectionSprite.left;
|
||||
}
|
||||
|
||||
function set_previewBoxStartPosAbsolute(value:Float):Float
|
||||
{
|
||||
return previewSelectionSprite.left = value;
|
||||
}
|
||||
|
||||
var previewBoxEndPosAbsolute(get, set):Float;
|
||||
|
||||
function get_previewBoxEndPosAbsolute():Float
|
||||
{
|
||||
return previewSelectionSprite.left + previewSelectionSprite.width;
|
||||
}
|
||||
|
||||
function set_previewBoxEndPosAbsolute(value:Float):Float
|
||||
{
|
||||
if (value < previewBoxStartPosAbsolute) return previewSelectionSprite.left = previewBoxStartPosAbsolute;
|
||||
return previewSelectionSprite.width = value - previewBoxStartPosAbsolute;
|
||||
}
|
||||
|
||||
var previewBoxStartPosRelative(get, set):Float;
|
||||
|
||||
function get_previewBoxStartPosRelative():Float
|
||||
{
|
||||
return previewSelectionSprite.left - waveformScrollview.hscrollPos;
|
||||
}
|
||||
|
||||
function set_previewBoxStartPosRelative(value:Float):Float
|
||||
{
|
||||
return previewSelectionSprite.left = waveformScrollview.hscrollPos + value;
|
||||
}
|
||||
|
||||
var previewBoxEndPosRelative(get, set):Float;
|
||||
|
||||
function get_previewBoxEndPosRelative():Float
|
||||
{
|
||||
return previewSelectionSprite.left + previewSelectionSprite.width - waveformScrollview.hscrollPos;
|
||||
}
|
||||
|
||||
function set_previewBoxEndPosRelative(value:Float):Float
|
||||
{
|
||||
if (value < previewBoxStartPosRelative) return previewSelectionSprite.left = previewBoxStartPosRelative;
|
||||
return previewSelectionSprite.width = value - previewBoxStartPosRelative;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount you need to multiply the zoom by such that, at the base zoom level, one tick is equal to `MAGIC_SCALE_BASE_TIME` seconds.
|
||||
*/
|
||||
var waveformMagicFactor:Float = 1.0;
|
||||
|
||||
var audioPreviewTracks:SoundGroup;
|
||||
|
||||
var tickTiledSprite:FlxTiledSprite;
|
||||
|
||||
var freeplayPreviewVolume(get, null):Float;
|
||||
|
||||
function get_freeplayPreviewVolume():Float
|
||||
{
|
||||
return freeplayMusicVolume.value * 2 / 100;
|
||||
}
|
||||
|
||||
var tickLabels:Array<Label> = [];
|
||||
|
||||
public function new(chartEditorState2:ChartEditorState)
|
||||
{
|
||||
super(chartEditorState2);
|
||||
|
||||
initialize();
|
||||
|
||||
this.onDialogClosed = onClose;
|
||||
}
|
||||
|
||||
function onClose(event:UIEvent)
|
||||
{
|
||||
chartEditorState.menubarItemToggleToolboxFreeplay.selected = false;
|
||||
}
|
||||
|
||||
function initialize():Void
|
||||
{
|
||||
// Starting position.
|
||||
// TODO: Save and load this.
|
||||
this.x = 150;
|
||||
this.y = 250;
|
||||
|
||||
freeplayMusicVolume.onChange = (_) -> {
|
||||
setTrackVolume(freeplayPreviewVolume);
|
||||
};
|
||||
freeplayMusicMute.onClick = (_) -> {
|
||||
toggleMuteTrack();
|
||||
};
|
||||
freeplayButtonZoomIn.onClick = (_) -> {
|
||||
zoomWaveformIn();
|
||||
};
|
||||
freeplayButtonZoomOut.onClick = (_) -> {
|
||||
zoomWaveformOut();
|
||||
};
|
||||
freeplayButtonPause.onClick = (_) -> {
|
||||
pauseAudioPreview();
|
||||
};
|
||||
freeplayButtonPlay.onClick = (_) -> {
|
||||
playAudioPreview();
|
||||
};
|
||||
freeplayButtonStop.onClick = (_) -> {
|
||||
stopAudioPreview();
|
||||
};
|
||||
testPreview.onClick = (_) -> {
|
||||
performPreview();
|
||||
};
|
||||
freeplayPreviewStart.onChange = (event:UIEvent) -> {
|
||||
if (event.value == chartEditorState.currentSongFreeplayPreviewStart) return;
|
||||
if (waveformDragStartPos != null) return; // The values are changing because we are dragging the preview.
|
||||
|
||||
chartEditorState.performCommand(new SetFreeplayPreviewCommand(event.value, null));
|
||||
refresh();
|
||||
}
|
||||
freeplayPreviewEnd.onChange = (event:UIEvent) -> {
|
||||
if (event.value == chartEditorState.currentSongFreeplayPreviewEnd) return;
|
||||
if (waveformDragStartPos != null) return; // The values are changing because we are dragging the preview.
|
||||
|
||||
chartEditorState.performCommand(new SetFreeplayPreviewCommand(null, event.value));
|
||||
refresh();
|
||||
}
|
||||
waveformScrollview.onScroll = (_) -> {
|
||||
if (!audioPreviewTracks.playing)
|
||||
{
|
||||
// Move the playhead if it would go out of view.
|
||||
var prevPlayheadRelativePos = playheadRelativePos;
|
||||
playheadRelativePos = FlxMath.bound(playheadRelativePos, 0, waveformScrollview.width - PLAYHEAD_RIGHT_PAD);
|
||||
trace('newPos: ${playheadRelativePos}');
|
||||
var diff = playheadRelativePos - prevPlayheadRelativePos;
|
||||
|
||||
if (diff != 0)
|
||||
{
|
||||
// We have to change the song time to match the playhead position when we move it.
|
||||
var currentWaveformIndex:Int = Std.int(playheadAbsolutePos * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var targetSongTimeSeconds:Float = waveformMusic.waveform.waveformData.indexToSeconds(currentWaveformIndex);
|
||||
audioPreviewTracks.time = targetSongTimeSeconds * Constants.MS_PER_SEC;
|
||||
}
|
||||
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The scrollview probably changed because the song position changed.
|
||||
// If we try to move the song now it will glitch.
|
||||
}
|
||||
|
||||
// Either way, clipRect has changed, so we need to refresh the waveforms.
|
||||
refresh();
|
||||
};
|
||||
|
||||
initializeTicks();
|
||||
|
||||
refreshAudioPreview();
|
||||
refresh();
|
||||
refreshTicks();
|
||||
|
||||
waveformMusic.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||
onStartDragWaveform();
|
||||
});
|
||||
|
||||
freeplayTicksContainer.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||
onStartDragPlayhead();
|
||||
});
|
||||
}
|
||||
|
||||
function initializeTicks():Void
|
||||
{
|
||||
tickTiledSprite = new FlxTiledSprite(chartEditorState.offsetTickBitmap, 100, chartEditorState.offsetTickBitmap.height, true, false);
|
||||
freeplayTicksSprite.sprite = tickTiledSprite;
|
||||
tickTiledSprite.width = 5000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the audio tracks from the chart editor state and create copies of them to play in the Offsets Toolbox.
|
||||
* These must be DEEP CLONES or else the editor will affect the audio preview!
|
||||
*/
|
||||
public function refreshAudioPreview():Void
|
||||
{
|
||||
if (audioPreviewTracks == null)
|
||||
{
|
||||
audioPreviewTracks = new SoundGroup();
|
||||
// Make sure audioPreviewTracks (and all its children) receives update() calls.
|
||||
chartEditorState.add(audioPreviewTracks);
|
||||
}
|
||||
else
|
||||
{
|
||||
audioPreviewTracks.stop();
|
||||
audioPreviewTracks.clear();
|
||||
}
|
||||
|
||||
var instTrack = chartEditorState.audioInstTrack.clone();
|
||||
audioPreviewTracks.add(instTrack);
|
||||
|
||||
var playerVoice = chartEditorState.audioVocalTrackGroup.getPlayerVoice();
|
||||
if (playerVoice != null) audioPreviewTracks.add(playerVoice.clone());
|
||||
|
||||
var opponentVoice = chartEditorState.audioVocalTrackGroup.getOpponentVoice();
|
||||
if (opponentVoice != null) audioPreviewTracks.add(opponentVoice.clone());
|
||||
|
||||
// Build player waveform.
|
||||
// waveformMusic.waveform.forceUpdate = true;
|
||||
var perfStart = haxe.Timer.stamp();
|
||||
var waveformData1 = playerVoice.waveformData;
|
||||
var waveformData2 = opponentVoice.waveformData;
|
||||
var waveformData3 = chartEditorState.audioInstTrack.waveformData;
|
||||
var waveformData = waveformData1.merge(waveformData2).merge(waveformData3);
|
||||
trace('Waveform data merging took: ${haxe.Timer.stamp() - perfStart} seconds');
|
||||
|
||||
waveformMusic.waveform.waveformData = waveformData;
|
||||
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
||||
waveformMusic.waveform.duration = instTrack.length / Constants.MS_PER_SEC;
|
||||
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
|
||||
public function refreshTicks():Void
|
||||
{
|
||||
while (tickLabels.length > 0)
|
||||
{
|
||||
var label = tickLabels.pop();
|
||||
freeplayTicksContainer.removeComponent(label);
|
||||
}
|
||||
|
||||
var labelYPos:Float = chartEditorState.offsetTickBitmap.height / 2;
|
||||
var labelHeight:Float = chartEditorState.offsetTickBitmap.height / 2;
|
||||
|
||||
var numberOfTicks:Int = Math.floor(waveformMusic.waveform.width / chartEditorState.offsetTickBitmap.width * 2) + 1;
|
||||
|
||||
for (index in 0...numberOfTicks)
|
||||
{
|
||||
var tickPos = chartEditorState.offsetTickBitmap.width / 2 * index;
|
||||
var tickTime = tickPos * (waveformScale / BASE_SCALE * waveformMagicFactor) / waveformMusic.waveform.waveformData.pointsPerSecond();
|
||||
|
||||
var tickLabel:Label = new Label();
|
||||
tickLabel.text = formatTime(tickTime);
|
||||
tickLabel.styleNames = "offset-ticks-label";
|
||||
tickLabel.height = labelHeight;
|
||||
// Positioning within offsetTicksContainer is absolute (relative to the container itself).
|
||||
tickLabel.top = labelYPos;
|
||||
tickLabel.left = tickPos + TICK_LABEL_X_OFFSET;
|
||||
|
||||
freeplayTicksContainer.addComponent(tickLabel);
|
||||
tickLabels.push(tickLabel);
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(seconds:Float):String
|
||||
{
|
||||
if (seconds <= 0) return "0.0";
|
||||
|
||||
var integerSeconds = Math.floor(seconds);
|
||||
var decimalSeconds = Math.floor((seconds - integerSeconds) * 10);
|
||||
|
||||
if (integerSeconds < 60)
|
||||
{
|
||||
return '${integerSeconds}.${decimalSeconds}';
|
||||
}
|
||||
else
|
||||
{
|
||||
var integerMinutes = Math.floor(integerSeconds / 60);
|
||||
var remainingSeconds = integerSeconds % 60;
|
||||
var remainingSecondsPad:String = remainingSeconds < 10 ? '0$remainingSeconds' : '$remainingSeconds';
|
||||
|
||||
return '${integerMinutes}:${remainingSecondsPad}${decimalSeconds > 0 ? '.$decimalSeconds' : ''}';
|
||||
}
|
||||
}
|
||||
|
||||
function buildTickLabel():Void {}
|
||||
|
||||
public function onStartDragPlayhead():Void
|
||||
{
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_MOVE, onDragPlayhead);
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_UP, onStopDragPlayhead);
|
||||
|
||||
movePlayheadToMouse();
|
||||
}
|
||||
|
||||
public function onDragPlayhead(event:MouseEvent):Void
|
||||
{
|
||||
movePlayheadToMouse();
|
||||
}
|
||||
|
||||
public function onStopDragPlayhead(event:MouseEvent):Void
|
||||
{
|
||||
// Stop dragging.
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_MOVE, onDragPlayhead);
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_UP, onStopDragPlayhead);
|
||||
}
|
||||
|
||||
function movePlayheadToMouse():Void
|
||||
{
|
||||
// Determine the position of the mouse relative to the
|
||||
var mouseXPos = FlxG.mouse.x;
|
||||
|
||||
var relativeMouseXPos = mouseXPos - waveformScrollview.screenX;
|
||||
var targetPlayheadPos = relativeMouseXPos + waveformScrollview.hscrollPos;
|
||||
|
||||
// Move the playhead to the mouse position.
|
||||
playheadAbsolutePos = targetPlayheadPos;
|
||||
|
||||
// Move the audio preview to the playhead position.
|
||||
var currentWaveformIndex:Int = Std.int(playheadAbsolutePos * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var targetSongTimeSeconds:Float = waveformMusic.waveform.waveformData.indexToSeconds(currentWaveformIndex);
|
||||
audioPreviewTracks.time = targetSongTimeSeconds * Constants.MS_PER_SEC;
|
||||
}
|
||||
|
||||
var waveformDragStartPos:Null<Float> = null;
|
||||
|
||||
var waveformDragPreviewStartPos:Float;
|
||||
var waveformDragPreviewEndPos:Float;
|
||||
|
||||
public function onStartDragWaveform():Void
|
||||
{
|
||||
waveformDragStartPos = FlxG.mouse.x;
|
||||
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_MOVE, onDragWaveform);
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_UP, onStopDragWaveform);
|
||||
}
|
||||
|
||||
public function onDragWaveform(event:MouseEvent):Void
|
||||
{
|
||||
// Set waveformDragPreviewStartPos and waveformDragPreviewEndPos to the position the drag started and the current mouse position.
|
||||
// This only affects the visuals.
|
||||
|
||||
var currentAbsMousePos = FlxG.mouse.x;
|
||||
var dragDiff = currentAbsMousePos - waveformDragStartPos;
|
||||
|
||||
var currentRelativeMousePos = currentAbsMousePos - waveformScrollview.screenX;
|
||||
var relativeStartPos = waveformDragStartPos - waveformScrollview.screenX;
|
||||
|
||||
var isDraggingRight = dragDiff > 0;
|
||||
var hasDraggedEnough = Math.abs(dragDiff) > 10;
|
||||
|
||||
if (hasDraggedEnough)
|
||||
{
|
||||
if (isDraggingRight)
|
||||
{
|
||||
waveformDragPreviewStartPos = relativeStartPos;
|
||||
waveformDragPreviewEndPos = currentRelativeMousePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
waveformDragPreviewStartPos = currentRelativeMousePos;
|
||||
waveformDragPreviewEndPos = relativeStartPos;
|
||||
}
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public function onStopDragWaveform(event:MouseEvent):Void
|
||||
{
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_MOVE, onDragWaveform);
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_UP, onStopDragWaveform);
|
||||
|
||||
var previewStartPosAbsolute = waveformDragPreviewStartPos + waveformScrollview.hscrollPos;
|
||||
var previewStartPosIndex:Int = Std.int(previewStartPosAbsolute * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var previewStartPosMs:Int = Std.int(waveformMusic.waveform.waveformData.indexToSeconds(previewStartPosIndex) * Constants.MS_PER_SEC);
|
||||
|
||||
var previewEndPosAbsolute = waveformDragPreviewEndPos + waveformScrollview.hscrollPos;
|
||||
var previewEndPosIndex:Int = Std.int(previewEndPosAbsolute * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var previewEndPosMs:Int = Std.int(waveformMusic.waveform.waveformData.indexToSeconds(previewEndPosIndex) * Constants.MS_PER_SEC);
|
||||
|
||||
chartEditorState.performCommand(new SetFreeplayPreviewCommand(previewStartPosMs, previewEndPosMs));
|
||||
|
||||
waveformDragStartPos = null;
|
||||
waveformDragPreviewStartPos = 0;
|
||||
waveformDragPreviewEndPos = 0;
|
||||
|
||||
refresh();
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
|
||||
public function playAudioPreview():Void
|
||||
{
|
||||
if (isPerformingPreview) stopPerformingPreview();
|
||||
|
||||
audioPreviewTracks.volume = freeplayPreviewVolume;
|
||||
audioPreviewTracks.play(false, audioPreviewTracks.time);
|
||||
}
|
||||
|
||||
public function addOffsetsToAudioPreview():Void
|
||||
{
|
||||
var trackInst = audioPreviewTracks.members[0];
|
||||
if (trackInst != null)
|
||||
{
|
||||
trackInst.time -= chartEditorState.currentInstrumentalOffset;
|
||||
}
|
||||
|
||||
var trackPlayer = audioPreviewTracks.members[1];
|
||||
if (trackPlayer != null)
|
||||
{
|
||||
trackPlayer.time -= chartEditorState.currentVocalOffsetPlayer;
|
||||
}
|
||||
|
||||
var trackOpponent = audioPreviewTracks.members[2];
|
||||
if (trackOpponent != null)
|
||||
{
|
||||
trackOpponent.time -= chartEditorState.currentVocalOffsetOpponent;
|
||||
}
|
||||
}
|
||||
|
||||
public function pauseAudioPreview():Void
|
||||
{
|
||||
if (isPerformingPreview) stopPerformingPreview();
|
||||
|
||||
audioPreviewTracks.pause();
|
||||
}
|
||||
|
||||
public function stopAudioPreview():Void
|
||||
{
|
||||
if (isPerformingPreview) stopPerformingPreview();
|
||||
|
||||
audioPreviewTracks.stop();
|
||||
|
||||
audioPreviewTracks.time = 0;
|
||||
|
||||
waveformScrollview.hscrollPos = 0;
|
||||
playheadAbsolutePos = 0 + playheadSprite.width;
|
||||
refresh();
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
|
||||
public function zoomWaveformIn():Void
|
||||
{
|
||||
if (isPerformingPreview) stopPerformingPreview();
|
||||
|
||||
if (waveformScale > MIN_SCALE)
|
||||
{
|
||||
waveformScale = waveformScale / WAVEFORM_ZOOM_MULT;
|
||||
if (waveformScale < MIN_SCALE) waveformScale = MIN_SCALE;
|
||||
|
||||
trace('Zooming in, scale: ${waveformScale}');
|
||||
|
||||
// Update the playhead too!
|
||||
playheadAbsolutePos = playheadAbsolutePos * WAVEFORM_ZOOM_MULT;
|
||||
|
||||
// Recenter the scroll view on the playhead.
|
||||
var vaguelyCenterPlayheadOffset = waveformScrollview.width / 8;
|
||||
waveformScrollview.hscrollPos = playheadAbsolutePos - vaguelyCenterPlayheadOffset;
|
||||
|
||||
refresh();
|
||||
refreshTicks();
|
||||
}
|
||||
else
|
||||
{
|
||||
waveformScale = MIN_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
public function zoomWaveformOut():Void
|
||||
{
|
||||
waveformScale = waveformScale * WAVEFORM_ZOOM_MULT;
|
||||
if (waveformScale < MIN_SCALE) waveformScale = MIN_SCALE;
|
||||
|
||||
trace('Zooming out, scale: ${waveformScale}');
|
||||
|
||||
// Update the playhead too!
|
||||
playheadAbsolutePos = playheadAbsolutePos / WAVEFORM_ZOOM_MULT;
|
||||
|
||||
// Recenter the scroll view on the playhead.
|
||||
var vaguelyCenterPlayheadOffset = waveformScrollview.width / 8;
|
||||
waveformScrollview.hscrollPos = playheadAbsolutePos - vaguelyCenterPlayheadOffset;
|
||||
|
||||
refresh();
|
||||
refreshTicks();
|
||||
}
|
||||
|
||||
public function setTrackVolume(volume:Float):Void
|
||||
{
|
||||
audioPreviewTracks.volume = volume;
|
||||
}
|
||||
|
||||
public function muteTrack():Void
|
||||
{
|
||||
audioPreviewTracks.muted = true;
|
||||
}
|
||||
|
||||
public function unmuteTrack():Void
|
||||
{
|
||||
audioPreviewTracks.muted = false;
|
||||
}
|
||||
|
||||
public function toggleMuteTrack():Void
|
||||
{
|
||||
audioPreviewTracks.muted = !audioPreviewTracks.muted;
|
||||
}
|
||||
|
||||
var isPerformingPreview:Bool = false;
|
||||
var isFadingOutPreview:Bool = false;
|
||||
|
||||
public function performPreview():Void
|
||||
{
|
||||
isPerformingPreview = true;
|
||||
isFadingOutPreview = false;
|
||||
audioPreviewTracks.play(true, chartEditorState.currentSongFreeplayPreviewStart);
|
||||
audioPreviewTracks.fadeIn(FreeplayState.FADE_IN_DURATION, FreeplayState.FADE_IN_START_VOLUME * freeplayPreviewVolume,
|
||||
FreeplayState.FADE_IN_END_VOLUME * freeplayPreviewVolume, null);
|
||||
}
|
||||
|
||||
public function stopPerformingPreview():Void
|
||||
{
|
||||
isPerformingPreview = false;
|
||||
isFadingOutPreview = false;
|
||||
audioPreviewTracks.volume = freeplayPreviewVolume;
|
||||
audioPreviewTracks.pause();
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (isPerformingPreview && !audioPreviewTracks.playing)
|
||||
{
|
||||
stopPerformingPreview();
|
||||
}
|
||||
|
||||
if (isPerformingPreview && audioPreviewTracks.playing)
|
||||
{
|
||||
var startFadeOutTime = chartEditorState.currentSongFreeplayPreviewEnd - (FreeplayState.FADE_OUT_DURATION * Constants.MS_PER_SEC);
|
||||
trace('startFadeOutTime: ${audioPreviewTracks.time} >= ${startFadeOutTime}');
|
||||
if (!isFadingOutPreview && audioPreviewTracks.time >= startFadeOutTime)
|
||||
{
|
||||
isFadingOutPreview = true;
|
||||
audioPreviewTracks.fadeOut(FreeplayState.FADE_OUT_DURATION, FreeplayState.FADE_OUT_END_VOLUME * freeplayPreviewVolume, (_) -> {
|
||||
trace('Stop performing preview! ${audioPreviewTracks.time}');
|
||||
stopPerformingPreview();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (audioPreviewTracks.playing)
|
||||
{
|
||||
var targetScrollPos:Float = waveformMusic.waveform.waveformData.secondsToIndex(audioPreviewTracks.time / Constants.MS_PER_SEC) / (waveformScale / BASE_SCALE * waveformMagicFactor);
|
||||
// waveformScrollview.hscrollPos = targetScrollPos;
|
||||
var prevPlayheadAbsolutePos = playheadAbsolutePos;
|
||||
playheadAbsolutePos = targetScrollPos;
|
||||
var playheadDiff = playheadAbsolutePos - prevPlayheadAbsolutePos;
|
||||
|
||||
// BEHAVIOR C.
|
||||
// Copy Audacity!
|
||||
// If the playhead is out of view, jump forward or backward by one screen width until it's in view.
|
||||
if (playheadAbsolutePos < waveformScrollview.hscrollPos)
|
||||
{
|
||||
waveformScrollview.hscrollPos -= waveformScrollview.width;
|
||||
}
|
||||
if (playheadAbsolutePos > waveformScrollview.hscrollPos + waveformScrollview.width)
|
||||
{
|
||||
waveformScrollview.hscrollPos += waveformScrollview.width;
|
||||
}
|
||||
}
|
||||
freeplayLabelTime.text = formatTime(audioPreviewTracks.time / Constants.MS_PER_SEC);
|
||||
if (waveformDragStartPos != null && (waveformDragPreviewStartPos > 0 && waveformDragPreviewEndPos > 0))
|
||||
{
|
||||
var previewStartPosAbsolute = waveformDragPreviewStartPos + waveformScrollview.hscrollPos;
|
||||
var previewStartPosIndex:Int = Std.int(previewStartPosAbsolute * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var previewStartPosMs:Int = Std.int(waveformMusic.waveform.waveformData.indexToSeconds(previewStartPosIndex) * Constants.MS_PER_SEC);
|
||||
|
||||
var previewEndPosAbsolute = waveformDragPreviewEndPos + waveformScrollview.hscrollPos;
|
||||
var previewEndPosIndex:Int = Std.int(previewEndPosAbsolute * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var previewEndPosMs:Int = Std.int(waveformMusic.waveform.waveformData.indexToSeconds(previewEndPosIndex) * Constants.MS_PER_SEC);
|
||||
|
||||
// Set the values in milliseconds.
|
||||
freeplayPreviewStart.value = previewStartPosMs;
|
||||
freeplayPreviewEnd.value = previewEndPosMs;
|
||||
|
||||
previewBoxStartPosAbsolute = previewStartPosAbsolute;
|
||||
previewBoxEndPosAbsolute = previewEndPosAbsolute;
|
||||
}
|
||||
else
|
||||
{
|
||||
previewBoxStartPosAbsolute = waveformMusic.waveform.waveformData.secondsToIndex(chartEditorState.currentSongFreeplayPreviewStart / Constants.MS_PER_SEC) / (waveformScale / BASE_SCALE * waveformMagicFactor);
|
||||
previewBoxEndPosAbsolute = waveformMusic.waveform.waveformData.secondsToIndex(chartEditorState.currentSongFreeplayPreviewEnd / Constants.MS_PER_SEC) / (waveformScale / BASE_SCALE * waveformMagicFactor);
|
||||
|
||||
freeplayPreviewStart.value = chartEditorState.currentSongFreeplayPreviewStart;
|
||||
freeplayPreviewEnd.value = chartEditorState.currentSongFreeplayPreviewEnd;
|
||||
}
|
||||
}
|
||||
|
||||
public override function refresh():Void
|
||||
{
|
||||
super.refresh();
|
||||
|
||||
waveformMagicFactor = MAGIC_SCALE_BASE_TIME / (chartEditorState.offsetTickBitmap.width / waveformMusic.waveform.waveformData.pointsPerSecond());
|
||||
|
||||
var currentZoomFactor = waveformScale / BASE_SCALE * waveformMagicFactor;
|
||||
|
||||
var maxWidth:Int = -1;
|
||||
|
||||
waveformMusic.waveform.time = -chartEditorState.currentInstrumentalOffset / Constants.MS_PER_SEC;
|
||||
waveformMusic.waveform.width = (waveformMusic.waveform.waveformData?.length ?? 1000) / currentZoomFactor;
|
||||
if (waveformMusic.waveform.width > maxWidth) maxWidth = Std.int(waveformMusic.waveform.width);
|
||||
waveformMusic.waveform.height = 65;
|
||||
waveformMusic.waveform.markDirty();
|
||||
|
||||
waveformContainer.width = maxWidth;
|
||||
tickTiledSprite.width = maxWidth;
|
||||
}
|
||||
|
||||
public static function build(chartEditorState:ChartEditorState):ChartEditorFreeplayToolbox
|
||||
{
|
||||
return new ChartEditorFreeplayToolbox(chartEditorState);
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,6 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
var buttonCharacterGirlfriend:Button;
|
||||
var buttonCharacterOpponent:Button;
|
||||
var inputBPM:NumberStepper;
|
||||
var inputOffsetInst:NumberStepper;
|
||||
var inputOffsetVocal:NumberStepper;
|
||||
var labelScrollSpeed:Label;
|
||||
var inputScrollSpeed:Slider;
|
||||
var frameVariation:Frame;
|
||||
@@ -138,25 +136,6 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
chartEditorState.updateTimeSignature();
|
||||
};
|
||||
|
||||
inputOffsetInst.onChange = function(event:UIEvent) {
|
||||
if (event.value == null) return;
|
||||
|
||||
chartEditorState.currentInstrumentalOffset = event.value;
|
||||
Conductor.instance.instrumentalOffset = event.value;
|
||||
// Update song length.
|
||||
chartEditorState.songLengthInMs = (chartEditorState.audioInstTrack?.length ?? 1000.0) + Conductor.instance.instrumentalOffset;
|
||||
};
|
||||
|
||||
inputOffsetVocal.onChange = function(event:UIEvent) {
|
||||
if (event.value == null) return;
|
||||
|
||||
chartEditorState.currentVocalOffset = event.value;
|
||||
if (chartEditorState.audioVocalTrackGroup != null)
|
||||
{
|
||||
chartEditorState.audioVocalTrackGroup.playerVoicesOffset = event.value;
|
||||
chartEditorState.audioVocalTrackGroup.opponentVoicesOffset = event.value;
|
||||
}
|
||||
};
|
||||
inputScrollSpeed.onChange = function(event:UIEvent) {
|
||||
var valid:Bool = event.target.value != null && event.target.value > 0;
|
||||
|
||||
@@ -172,6 +151,10 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
labelScrollSpeed.text = 'Scroll Speed: ${chartEditorState.currentSongChartScrollSpeed}x';
|
||||
};
|
||||
|
||||
inputDifficultyRating.onChange = function(event:UIEvent) {
|
||||
chartEditorState.currentSongChartDifficultyRating = event.target.value;
|
||||
};
|
||||
|
||||
buttonCharacterOpponent.onClick = function(_) {
|
||||
chartEditorState.openCharacterDropdown(CharacterType.DAD, false);
|
||||
};
|
||||
@@ -196,8 +179,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
|
||||
inputStage.value = chartEditorState.currentSongMetadata.playData.stage;
|
||||
inputNoteStyle.value = chartEditorState.currentSongMetadata.playData.noteStyle;
|
||||
inputBPM.value = chartEditorState.currentSongMetadata.timeChanges[0].bpm;
|
||||
inputOffsetInst.value = chartEditorState.currentSongMetadata.offsets.getInstrumentalOffset();
|
||||
inputOffsetVocal.value = chartEditorState.currentSongMetadata.offsets.getVocalOffset(chartEditorState.currentSongMetadata.playData.characters.player);
|
||||
inputDifficultyRating.value = chartEditorState.currentSongChartDifficultyRating;
|
||||
inputScrollSpeed.value = chartEditorState.currentSongChartScrollSpeed;
|
||||
labelScrollSpeed.text = 'Scroll Speed: ${chartEditorState.currentSongChartScrollSpeed}x';
|
||||
frameVariation.text = 'Variation: ${chartEditorState.selectedVariation.toTitleCase()}';
|
||||
|
||||
@@ -0,0 +1,845 @@
|
||||
package funkin.ui.debug.charting.toolboxes;
|
||||
|
||||
import funkin.audio.SoundGroup;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
import haxe.ui.components.Label;
|
||||
import flixel.addons.display.FlxTiledSprite;
|
||||
import flixel.math.FlxMath;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.backend.flixel.components.SpriteWrapper;
|
||||
import funkin.ui.debug.charting.commands.SetAudioOffsetCommand;
|
||||
import funkin.ui.haxeui.components.WaveformPlayer;
|
||||
import funkin.audio.waveform.WaveformDataParser;
|
||||
import haxe.ui.containers.VBox;
|
||||
import haxe.ui.containers.Absolute;
|
||||
import haxe.ui.containers.ScrollView;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.DragEvent;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
|
||||
/**
|
||||
* The toolbox which allows modifying information like Song Title, Scroll Speed, Characters/Stages, and starting BPM.
|
||||
*/
|
||||
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
|
||||
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/offsets.xml"))
|
||||
class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||
{
|
||||
var waveformContainer:Absolute;
|
||||
var waveformScrollview:ScrollView;
|
||||
var waveformPlayer:WaveformPlayer;
|
||||
var waveformOpponent:WaveformPlayer;
|
||||
var waveformInstrumental:WaveformPlayer;
|
||||
var offsetButtonZoomIn:Button;
|
||||
var offsetButtonZoomOut:Button;
|
||||
var offsetButtonPause:Button;
|
||||
var offsetButtonPlay:Button;
|
||||
var offsetButtonStop:Button;
|
||||
var offsetStepperPlayer:NumberStepper;
|
||||
var offsetStepperOpponent:NumberStepper;
|
||||
var offsetStepperInstrumental:NumberStepper;
|
||||
var offsetTicksContainer:Absolute;
|
||||
var playheadSprite:SpriteWrapper;
|
||||
|
||||
static final TICK_LABEL_X_OFFSET:Float = 4.0;
|
||||
|
||||
static final PLAYHEAD_RIGHT_PAD:Float = 10.0;
|
||||
|
||||
static final BASE_SCALE:Float = 64.0;
|
||||
static final MIN_SCALE:Float = 4.0;
|
||||
static final WAVEFORM_ZOOM_MULT:Float = 1.5;
|
||||
|
||||
static final MAGIC_SCALE_BASE_TIME:Float = 5.0;
|
||||
|
||||
var waveformScale:Float = BASE_SCALE;
|
||||
|
||||
var playheadAbsolutePos(get, set):Float;
|
||||
|
||||
function get_playheadAbsolutePos():Float
|
||||
{
|
||||
return playheadSprite.left;
|
||||
}
|
||||
|
||||
function set_playheadAbsolutePos(value:Float):Float
|
||||
{
|
||||
return playheadSprite.left = value;
|
||||
}
|
||||
|
||||
var playheadRelativePos(get, set):Float;
|
||||
|
||||
function get_playheadRelativePos():Float
|
||||
{
|
||||
return playheadSprite.left - waveformScrollview.hscrollPos;
|
||||
}
|
||||
|
||||
function set_playheadRelativePos(value:Float):Float
|
||||
{
|
||||
return playheadSprite.left = waveformScrollview.hscrollPos + value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount you need to multiply the zoom by such that, at the base zoom level, one tick is equal to `MAGIC_SCALE_BASE_TIME` seconds.
|
||||
*/
|
||||
var waveformMagicFactor:Float = 1.0;
|
||||
|
||||
var audioPreviewTracks:SoundGroup;
|
||||
|
||||
var tickTiledSprite:FlxTiledSprite;
|
||||
|
||||
var tickLabels:Array<Label> = [];
|
||||
|
||||
// Local store of the audio offsets, so we can detect when they change.
|
||||
var audioPreviewPlayerOffset:Float = 0;
|
||||
var audioPreviewOpponentOffset:Float = 0;
|
||||
var audioPreviewInstrumentalOffset:Float = 0;
|
||||
|
||||
public function new(chartEditorState2:ChartEditorState)
|
||||
{
|
||||
super(chartEditorState2);
|
||||
|
||||
initialize();
|
||||
|
||||
this.onDialogClosed = onClose;
|
||||
}
|
||||
|
||||
function onClose(event:UIEvent)
|
||||
{
|
||||
chartEditorState.menubarItemToggleToolboxOffsets.selected = false;
|
||||
}
|
||||
|
||||
function initialize():Void
|
||||
{
|
||||
// Starting position.
|
||||
// TODO: Save and load this.
|
||||
this.x = 150;
|
||||
this.y = 250;
|
||||
|
||||
offsetPlayerVolume.onChange = (_) -> {
|
||||
var targetVolume = offsetPlayerVolume.value * 2 / 100;
|
||||
setTrackVolume(PLAYER, targetVolume);
|
||||
};
|
||||
offsetPlayerMute.onClick = (_) -> {
|
||||
toggleMuteTrack(PLAYER);
|
||||
};
|
||||
offsetPlayerSolo.onClick = (_) -> {
|
||||
soloTrack(PLAYER);
|
||||
};
|
||||
offsetOpponentVolume.onChange = (_) -> {
|
||||
var targetVolume = offsetOpponentVolume.value * 2 / 100;
|
||||
setTrackVolume(OPPONENT, targetVolume);
|
||||
};
|
||||
offsetOpponentMute.onClick = (_) -> {
|
||||
toggleMuteTrack(OPPONENT);
|
||||
};
|
||||
offsetOpponentSolo.onClick = (_) -> {
|
||||
soloTrack(OPPONENT);
|
||||
};
|
||||
offsetInstrumentalVolume.onChange = (_) -> {
|
||||
var targetVolume = offsetInstrumentalVolume.value * 2 / 100;
|
||||
setTrackVolume(INSTRUMENTAL, targetVolume);
|
||||
};
|
||||
offsetInstrumentalMute.onClick = (_) -> {
|
||||
toggleMuteTrack(INSTRUMENTAL);
|
||||
};
|
||||
offsetInstrumentalSolo.onClick = (_) -> {
|
||||
soloTrack(INSTRUMENTAL);
|
||||
};
|
||||
offsetButtonZoomIn.onClick = (_) -> {
|
||||
zoomWaveformIn();
|
||||
};
|
||||
offsetButtonZoomOut.onClick = (_) -> {
|
||||
zoomWaveformOut();
|
||||
};
|
||||
offsetButtonPause.onClick = (_) -> {
|
||||
pauseAudioPreview();
|
||||
};
|
||||
offsetButtonPlay.onClick = (_) -> {
|
||||
playAudioPreview();
|
||||
};
|
||||
offsetButtonStop.onClick = (_) -> {
|
||||
stopAudioPreview();
|
||||
};
|
||||
offsetStepperPlayer.onChange = (event:UIEvent) -> {
|
||||
if (event.value == chartEditorState.currentVocalOffsetPlayer) return;
|
||||
if (dragWaveform != null) return;
|
||||
|
||||
chartEditorState.performCommand(new SetAudioOffsetCommand(PLAYER, event.value));
|
||||
refresh();
|
||||
}
|
||||
offsetStepperOpponent.onChange = (event:UIEvent) -> {
|
||||
if (event.value == chartEditorState.currentVocalOffsetOpponent) return;
|
||||
if (dragWaveform != null) return;
|
||||
|
||||
chartEditorState.performCommand(new SetAudioOffsetCommand(OPPONENT, event.value));
|
||||
refresh();
|
||||
}
|
||||
offsetStepperInstrumental.onChange = (event:UIEvent) -> {
|
||||
if (event.value == chartEditorState.currentInstrumentalOffset) return;
|
||||
if (dragWaveform != null) return;
|
||||
|
||||
chartEditorState.performCommand(new SetAudioOffsetCommand(INSTRUMENTAL, event.value));
|
||||
refresh();
|
||||
}
|
||||
waveformScrollview.onScroll = (_) -> {
|
||||
if (!audioPreviewTracks.playing)
|
||||
{
|
||||
// Move the playhead if it would go out of view.
|
||||
var prevPlayheadRelativePos = playheadRelativePos;
|
||||
playheadRelativePos = FlxMath.bound(playheadRelativePos, 0, waveformScrollview.width - PLAYHEAD_RIGHT_PAD);
|
||||
var diff = playheadRelativePos - prevPlayheadRelativePos;
|
||||
|
||||
if (diff != 0)
|
||||
{
|
||||
// We have to change the song time to match the playhead position when we move it.
|
||||
var currentWaveformIndex:Int = Std.int(playheadAbsolutePos * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var targetSongTimeSeconds:Float = waveformPlayer.waveform.waveformData.indexToSeconds(currentWaveformIndex);
|
||||
audioPreviewTracks.time = targetSongTimeSeconds * Constants.MS_PER_SEC;
|
||||
}
|
||||
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The scrollview probably changed because the song position changed.
|
||||
// If we try to move the song now it will glitch.
|
||||
}
|
||||
|
||||
// Either way, clipRect has changed, so we need to refresh the waveforms.
|
||||
refresh();
|
||||
};
|
||||
|
||||
initializeTicks();
|
||||
|
||||
refreshAudioPreview();
|
||||
refresh();
|
||||
refreshTicks();
|
||||
|
||||
waveformPlayer.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||
onStartDragWaveform(PLAYER);
|
||||
});
|
||||
waveformOpponent.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||
onStartDragWaveform(OPPONENT);
|
||||
});
|
||||
waveformInstrumental.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||
onStartDragWaveform(INSTRUMENTAL);
|
||||
});
|
||||
|
||||
offsetTicksContainer.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||
onStartDragPlayhead();
|
||||
});
|
||||
}
|
||||
|
||||
function initializeTicks():Void
|
||||
{
|
||||
tickTiledSprite = new FlxTiledSprite(chartEditorState.offsetTickBitmap, 100, chartEditorState.offsetTickBitmap.height, true, false);
|
||||
offsetTicksSprite.sprite = tickTiledSprite;
|
||||
tickTiledSprite.width = 5000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the audio tracks from the chart editor state and create copies of them to play in the Offsets Toolbox.
|
||||
* These must be DEEP CLONES or else the editor will affect the audio preview!
|
||||
*/
|
||||
public function refreshAudioPreview():Void
|
||||
{
|
||||
if (audioPreviewTracks == null)
|
||||
{
|
||||
audioPreviewTracks = new SoundGroup();
|
||||
// Make sure audioPreviewTracks (and all its children) receives update() calls.
|
||||
chartEditorState.add(audioPreviewTracks);
|
||||
}
|
||||
else
|
||||
{
|
||||
audioPreviewTracks.stop();
|
||||
audioPreviewTracks.clear();
|
||||
}
|
||||
|
||||
var instTrack = chartEditorState.audioInstTrack.clone();
|
||||
audioPreviewTracks.add(instTrack);
|
||||
|
||||
var playerVoice = chartEditorState.audioVocalTrackGroup.getPlayerVoice();
|
||||
if (playerVoice != null) audioPreviewTracks.add(playerVoice.clone());
|
||||
|
||||
var opponentVoice = chartEditorState.audioVocalTrackGroup.getOpponentVoice();
|
||||
if (opponentVoice != null) audioPreviewTracks.add(opponentVoice.clone());
|
||||
|
||||
// Build player waveform.
|
||||
// waveformPlayer.waveform.forceUpdate = true;
|
||||
waveformPlayer.waveform.waveformData = playerVoice.waveformData;
|
||||
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
||||
waveformPlayer.waveform.duration = playerVoice.length / Constants.MS_PER_SEC;
|
||||
|
||||
// Build opponent waveform.
|
||||
// waveformOpponent.waveform.forceUpdate = true;
|
||||
waveformOpponent.waveform.waveformData = opponentVoice.waveformData;
|
||||
waveformOpponent.waveform.duration = opponentVoice.length / Constants.MS_PER_SEC;
|
||||
|
||||
// Build instrumental waveform.
|
||||
// waveformInstrumental.waveform.forceUpdate = true;
|
||||
waveformInstrumental.waveform.waveformData = chartEditorState.audioInstTrack.waveformData;
|
||||
waveformInstrumental.waveform.duration = instTrack.length / Constants.MS_PER_SEC;
|
||||
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
|
||||
public function refreshTicks():Void
|
||||
{
|
||||
while (tickLabels.length > 0)
|
||||
{
|
||||
var label = tickLabels.pop();
|
||||
offsetTicksContainer.removeComponent(label);
|
||||
}
|
||||
|
||||
var labelYPos:Float = chartEditorState.offsetTickBitmap.height / 2;
|
||||
var labelHeight:Float = chartEditorState.offsetTickBitmap.height / 2;
|
||||
|
||||
var numberOfTicks:Int = Math.floor(waveformInstrumental.waveform.width / chartEditorState.offsetTickBitmap.width * 2) + 1;
|
||||
|
||||
for (index in 0...numberOfTicks)
|
||||
{
|
||||
var tickPos = chartEditorState.offsetTickBitmap.width / 2 * index;
|
||||
var tickTime = tickPos * (waveformScale / BASE_SCALE * waveformMagicFactor) / waveformInstrumental.waveform.waveformData.pointsPerSecond();
|
||||
|
||||
var tickLabel:Label = new Label();
|
||||
tickLabel.text = formatTime(tickTime);
|
||||
tickLabel.styleNames = "offset-ticks-label";
|
||||
tickLabel.height = labelHeight;
|
||||
// Positioning within offsetTicksContainer is absolute (relative to the container itself).
|
||||
tickLabel.top = labelYPos;
|
||||
tickLabel.left = tickPos + TICK_LABEL_X_OFFSET;
|
||||
|
||||
offsetTicksContainer.addComponent(tickLabel);
|
||||
tickLabels.push(tickLabel);
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(seconds:Float):String
|
||||
{
|
||||
if (seconds <= 0) return "0.0";
|
||||
|
||||
var integerSeconds = Math.floor(seconds);
|
||||
var decimalSeconds = Math.floor((seconds - integerSeconds) * 10);
|
||||
|
||||
if (integerSeconds < 60)
|
||||
{
|
||||
return '${integerSeconds}.${decimalSeconds}';
|
||||
}
|
||||
else
|
||||
{
|
||||
var integerMinutes = Math.floor(integerSeconds / 60);
|
||||
var remainingSeconds = integerSeconds % 60;
|
||||
var remainingSecondsPad:String = remainingSeconds < 10 ? '0$remainingSeconds' : '$remainingSeconds';
|
||||
|
||||
return '${integerMinutes}:${remainingSecondsPad}${decimalSeconds > 0 ? '.$decimalSeconds' : ''}';
|
||||
}
|
||||
}
|
||||
|
||||
function buildTickLabel():Void {}
|
||||
|
||||
public function onStartDragPlayhead():Void
|
||||
{
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_MOVE, onDragPlayhead);
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_UP, onStopDragPlayhead);
|
||||
|
||||
movePlayheadToMouse();
|
||||
}
|
||||
|
||||
public function onDragPlayhead(event:MouseEvent):Void
|
||||
{
|
||||
movePlayheadToMouse();
|
||||
}
|
||||
|
||||
public function onStopDragPlayhead(event:MouseEvent):Void
|
||||
{
|
||||
// Stop dragging.
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_MOVE, onDragPlayhead);
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_UP, onStopDragPlayhead);
|
||||
}
|
||||
|
||||
function movePlayheadToMouse():Void
|
||||
{
|
||||
// Determine the position of the mouse relative to the
|
||||
var mouseXPos = FlxG.mouse.x;
|
||||
|
||||
var relativeMouseXPos = mouseXPos - waveformScrollview.screenX;
|
||||
var targetPlayheadPos = relativeMouseXPos + waveformScrollview.hscrollPos;
|
||||
|
||||
// Move the playhead to the mouse position.
|
||||
playheadAbsolutePos = targetPlayheadPos;
|
||||
|
||||
// Move the audio preview to the playhead position.
|
||||
var currentWaveformIndex:Int = Std.int(playheadAbsolutePos * (waveformScale / BASE_SCALE * waveformMagicFactor));
|
||||
var targetSongTimeSeconds:Float = waveformPlayer.waveform.waveformData.indexToSeconds(currentWaveformIndex);
|
||||
audioPreviewTracks.time = targetSongTimeSeconds * Constants.MS_PER_SEC;
|
||||
}
|
||||
|
||||
public function onStartDragWaveform(waveform:Waveform):Void
|
||||
{
|
||||
dragMousePosition = FlxG.mouse.x;
|
||||
dragWaveform = waveform;
|
||||
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_MOVE, onDragWaveform);
|
||||
Screen.instance.registerEvent(MouseEvent.MOUSE_UP, onStopDragWaveform);
|
||||
}
|
||||
|
||||
var dragMousePosition:Float = 0;
|
||||
var dragWaveform:Waveform = null;
|
||||
var dragOffsetMs:Float = 0;
|
||||
|
||||
public function onDragWaveform(event:MouseEvent):Void
|
||||
{
|
||||
var newDragMousePosition = FlxG.mouse.x;
|
||||
var deltaMousePosition = newDragMousePosition - dragMousePosition;
|
||||
|
||||
if (deltaMousePosition == 0) return;
|
||||
|
||||
var deltaPixels:Float = deltaMousePosition * (waveformScale / BASE_SCALE * waveformMagicFactor);
|
||||
var deltaMilliseconds:Float = switch (dragWaveform)
|
||||
{
|
||||
case PLAYER:
|
||||
deltaPixels / waveformPlayer.waveform.waveformData.pointsPerSecond() * Constants.MS_PER_SEC;
|
||||
case OPPONENT:
|
||||
deltaPixels / waveformOpponent.waveform.waveformData.pointsPerSecond() * Constants.MS_PER_SEC;
|
||||
case INSTRUMENTAL:
|
||||
deltaPixels / waveformInstrumental.waveform.waveformData.pointsPerSecond() * Constants.MS_PER_SEC;
|
||||
};
|
||||
|
||||
switch (dragWaveform)
|
||||
{
|
||||
case PLAYER:
|
||||
// chartEditorState.currentVocalOffsetPlayer += deltaMilliseconds;
|
||||
dragOffsetMs += deltaMilliseconds;
|
||||
offsetStepperPlayer.value += deltaMilliseconds;
|
||||
case OPPONENT:
|
||||
// chartEditorState.currentVocalOffsetOpponent += deltaMilliseconds;
|
||||
dragOffsetMs += deltaMilliseconds;
|
||||
offsetStepperOpponent.value += deltaMilliseconds;
|
||||
case INSTRUMENTAL:
|
||||
// chartEditorState.currentInstrumentalOffset += deltaMilliseconds;
|
||||
dragOffsetMs += deltaMilliseconds;
|
||||
offsetStepperInstrumental.value += deltaMilliseconds;
|
||||
}
|
||||
|
||||
dragMousePosition = newDragMousePosition;
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public function onStopDragWaveform(event:MouseEvent):Void
|
||||
{
|
||||
// Stop dragging.
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_MOVE, onDragWaveform);
|
||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_UP, onStopDragWaveform);
|
||||
|
||||
// Apply the offset change after dragging happens.
|
||||
// We only do this once per drag so we don't get 20 commands a second in the history.
|
||||
if (dragOffsetMs != 0)
|
||||
{
|
||||
// false to not refresh this toolbox, we will manually do that later.
|
||||
switch (dragWaveform)
|
||||
{
|
||||
case PLAYER:
|
||||
chartEditorState.performCommand(new SetAudioOffsetCommand(PLAYER, chartEditorState.currentVocalOffsetPlayer + dragOffsetMs, false));
|
||||
case OPPONENT:
|
||||
chartEditorState.performCommand(new SetAudioOffsetCommand(OPPONENT, chartEditorState.currentVocalOffsetOpponent + dragOffsetMs, false));
|
||||
case INSTRUMENTAL:
|
||||
chartEditorState.performCommand(new SetAudioOffsetCommand(INSTRUMENTAL, chartEditorState.currentInstrumentalOffset + dragOffsetMs, false));
|
||||
}
|
||||
}
|
||||
|
||||
dragOffsetMs = 0;
|
||||
dragMousePosition = 0;
|
||||
dragWaveform = null;
|
||||
|
||||
refresh();
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
|
||||
public function playAudioPreview():Void
|
||||
{
|
||||
audioPreviewTracks.play(false, audioPreviewTracks.time);
|
||||
}
|
||||
|
||||
public function addOffsetsToAudioPreview():Void
|
||||
{
|
||||
var trackInst = audioPreviewTracks.members[0];
|
||||
if (trackInst != null)
|
||||
{
|
||||
audioPreviewInstrumentalOffset = chartEditorState.currentInstrumentalOffset;
|
||||
trackInst.time -= audioPreviewInstrumentalOffset;
|
||||
}
|
||||
|
||||
var trackPlayer = audioPreviewTracks.members[1];
|
||||
if (trackPlayer != null)
|
||||
{
|
||||
audioPreviewPlayerOffset = chartEditorState.currentVocalOffsetPlayer;
|
||||
trackPlayer.time -= audioPreviewPlayerOffset;
|
||||
}
|
||||
|
||||
var trackOpponent = audioPreviewTracks.members[2];
|
||||
if (trackOpponent != null)
|
||||
{
|
||||
audioPreviewOpponentOffset = chartEditorState.currentVocalOffsetOpponent;
|
||||
trackOpponent.time -= audioPreviewOpponentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public function pauseAudioPreview():Void
|
||||
{
|
||||
audioPreviewTracks.pause();
|
||||
}
|
||||
|
||||
public function stopAudioPreview():Void
|
||||
{
|
||||
audioPreviewTracks.stop();
|
||||
|
||||
audioPreviewTracks.time = 0;
|
||||
|
||||
var trackInst = audioPreviewTracks.members[0];
|
||||
if (trackInst != null)
|
||||
{
|
||||
audioPreviewInstrumentalOffset = chartEditorState.currentInstrumentalOffset;
|
||||
trackInst.time = -audioPreviewInstrumentalOffset;
|
||||
}
|
||||
|
||||
var trackPlayer = audioPreviewTracks.members[1];
|
||||
if (trackPlayer != null)
|
||||
{
|
||||
audioPreviewPlayerOffset = chartEditorState.currentVocalOffsetPlayer;
|
||||
trackPlayer.time = -audioPreviewPlayerOffset;
|
||||
}
|
||||
|
||||
var trackOpponent = audioPreviewTracks.members[2];
|
||||
if (trackOpponent != null)
|
||||
{
|
||||
audioPreviewOpponentOffset = chartEditorState.currentVocalOffsetOpponent;
|
||||
trackOpponent.time = -audioPreviewOpponentOffset;
|
||||
}
|
||||
|
||||
waveformScrollview.hscrollPos = 0;
|
||||
playheadAbsolutePos = 0 + playheadSprite.width;
|
||||
refresh();
|
||||
addOffsetsToAudioPreview();
|
||||
}
|
||||
|
||||
public function zoomWaveformIn():Void
|
||||
{
|
||||
if (waveformScale > MIN_SCALE)
|
||||
{
|
||||
waveformScale = waveformScale / WAVEFORM_ZOOM_MULT;
|
||||
if (waveformScale < MIN_SCALE) waveformScale = MIN_SCALE;
|
||||
|
||||
// Update the playhead too!
|
||||
playheadAbsolutePos = playheadAbsolutePos * WAVEFORM_ZOOM_MULT;
|
||||
|
||||
// Recenter the scroll view on the playhead.
|
||||
var vaguelyCenterPlayheadOffset = waveformScrollview.width / 8;
|
||||
waveformScrollview.hscrollPos = playheadAbsolutePos - vaguelyCenterPlayheadOffset;
|
||||
|
||||
refresh();
|
||||
refreshTicks();
|
||||
}
|
||||
else
|
||||
{
|
||||
waveformScale = MIN_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
public function zoomWaveformOut():Void
|
||||
{
|
||||
waveformScale = waveformScale * WAVEFORM_ZOOM_MULT;
|
||||
if (waveformScale < MIN_SCALE) waveformScale = MIN_SCALE;
|
||||
|
||||
// Update the playhead too!
|
||||
playheadAbsolutePos = playheadAbsolutePos / WAVEFORM_ZOOM_MULT;
|
||||
|
||||
// Recenter the scroll view on the playhead.
|
||||
var vaguelyCenterPlayheadOffset = waveformScrollview.width / 8;
|
||||
waveformScrollview.hscrollPos = playheadAbsolutePos - vaguelyCenterPlayheadOffset;
|
||||
|
||||
refresh();
|
||||
refreshTicks();
|
||||
}
|
||||
|
||||
public function setTrackVolume(target:Waveform, volume:Float):Void
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case Waveform.INSTRUMENTAL:
|
||||
var trackInst = audioPreviewTracks.members[0];
|
||||
if (trackInst != null)
|
||||
{
|
||||
trackInst.volume = volume;
|
||||
}
|
||||
case Waveform.PLAYER:
|
||||
var trackPlayer = audioPreviewTracks.members[1];
|
||||
if (trackPlayer != null)
|
||||
{
|
||||
trackPlayer.volume = volume;
|
||||
}
|
||||
case Waveform.OPPONENT:
|
||||
var trackOpponent = audioPreviewTracks.members[2];
|
||||
if (trackOpponent != null)
|
||||
{
|
||||
trackOpponent.volume = volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function muteTrack(target:Waveform):Void
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case Waveform.INSTRUMENTAL:
|
||||
var trackInst = audioPreviewTracks.members[0];
|
||||
if (trackInst != null)
|
||||
{
|
||||
trackInst.muted = true;
|
||||
offsetInstrumentalMute.text = trackInst.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
case Waveform.PLAYER:
|
||||
var trackPlayer = audioPreviewTracks.members[1];
|
||||
if (trackPlayer != null)
|
||||
{
|
||||
trackPlayer.muted = true;
|
||||
offsetPlayerMute.text = trackPlayer.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
case Waveform.OPPONENT:
|
||||
var trackOpponent = audioPreviewTracks.members[2];
|
||||
if (trackOpponent != null)
|
||||
{
|
||||
trackOpponent.muted = true;
|
||||
offsetOpponentMute.text = trackOpponent.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function unmuteTrack(target:Waveform):Void
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case Waveform.INSTRUMENTAL:
|
||||
var trackInst = audioPreviewTracks.members[0];
|
||||
if (trackInst != null)
|
||||
{
|
||||
trackInst.muted = false;
|
||||
offsetInstrumentalMute.text = trackInst.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
case Waveform.PLAYER:
|
||||
var trackPlayer = audioPreviewTracks.members[1];
|
||||
if (trackPlayer != null)
|
||||
{
|
||||
trackPlayer.muted = false;
|
||||
offsetPlayerMute.text = trackPlayer.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
case Waveform.OPPONENT:
|
||||
var trackOpponent = audioPreviewTracks.members[2];
|
||||
if (trackOpponent != null)
|
||||
{
|
||||
trackOpponent.muted = false;
|
||||
offsetOpponentMute.text = trackOpponent.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleMuteTrack(target:Waveform):Void
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case Waveform.INSTRUMENTAL:
|
||||
var trackInst = audioPreviewTracks.members[0];
|
||||
if (trackInst != null)
|
||||
{
|
||||
trackInst.muted = !trackInst.muted;
|
||||
offsetInstrumentalMute.text = trackInst.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
case Waveform.PLAYER:
|
||||
var trackPlayer = audioPreviewTracks.members[1];
|
||||
if (trackPlayer != null)
|
||||
{
|
||||
trackPlayer.muted = !trackPlayer.muted;
|
||||
offsetPlayerMute.text = trackPlayer.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
case Waveform.OPPONENT:
|
||||
var trackOpponent = audioPreviewTracks.members[2];
|
||||
if (trackOpponent != null)
|
||||
{
|
||||
trackOpponent.muted = !trackOpponent.muted;
|
||||
offsetOpponentMute.text = trackOpponent.muted ? "Unmute" : "Mute";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicking the solo button will unmute the track and mute all other tracks.
|
||||
* @param target
|
||||
*/
|
||||
public function soloTrack(target:Waveform):Void
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case Waveform.PLAYER:
|
||||
muteTrack(Waveform.OPPONENT);
|
||||
muteTrack(Waveform.INSTRUMENTAL);
|
||||
unmuteTrack(Waveform.PLAYER);
|
||||
case Waveform.OPPONENT:
|
||||
muteTrack(Waveform.PLAYER);
|
||||
muteTrack(Waveform.INSTRUMENTAL);
|
||||
unmuteTrack(Waveform.OPPONENT);
|
||||
case Waveform.INSTRUMENTAL:
|
||||
muteTrack(Waveform.PLAYER);
|
||||
muteTrack(Waveform.OPPONENT);
|
||||
unmuteTrack(Waveform.INSTRUMENTAL);
|
||||
}
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (audioPreviewTracks.playing)
|
||||
{
|
||||
trace('Playback time: ${audioPreviewTracks.time}');
|
||||
|
||||
var targetScrollPos:Float = waveformInstrumental.waveform.waveformData.secondsToIndex(audioPreviewTracks.time / Constants.MS_PER_SEC) / (waveformScale / BASE_SCALE * waveformMagicFactor);
|
||||
// waveformScrollview.hscrollPos = targetScrollPos;
|
||||
var prevPlayheadAbsolutePos = playheadAbsolutePos;
|
||||
playheadAbsolutePos = targetScrollPos;
|
||||
var playheadDiff = playheadAbsolutePos - prevPlayheadAbsolutePos;
|
||||
|
||||
// BEHAVIOR A.
|
||||
// Just move the scroll view with the playhead, constraining it so that the playhead is always visible.
|
||||
// waveformScrollview.hscrollPos += playheadDiff;
|
||||
// waveformScrollview.hscrollPos = FlxMath.bound(waveformScrollview.hscrollPos, playheadAbsolutePos - playheadSprite.width, playheadAbsolutePos);
|
||||
|
||||
// BEHAVIOR B.
|
||||
// Keep `playheadAbsolutePos` within the bounds of the screen.
|
||||
// The scroll view will eventually move to where the playhead is 1/8th of the way from the left. This looks kinda nice!
|
||||
// TODO: This causes a hard snap to scroll when the playhead is to the right of the playheadCenterPoint.
|
||||
// var playheadCenterPoint = waveformScrollview.width / 8;
|
||||
// waveformScrollview.hscrollPos = FlxMath.bound(waveformScrollview.hscrollPos, playheadAbsolutePos - playheadCenterPoint, playheadAbsolutePos);
|
||||
|
||||
// playheadRelativePos = 0;
|
||||
|
||||
// BEHAVIOR C.
|
||||
// Copy Audacity!
|
||||
// If the playhead is out of view, jump forward or backward by one screen width until it's in view.
|
||||
if (playheadAbsolutePos < waveformScrollview.hscrollPos)
|
||||
{
|
||||
waveformScrollview.hscrollPos -= waveformScrollview.width;
|
||||
}
|
||||
if (playheadAbsolutePos > waveformScrollview.hscrollPos + waveformScrollview.width)
|
||||
{
|
||||
waveformScrollview.hscrollPos += waveformScrollview.width;
|
||||
}
|
||||
}
|
||||
|
||||
if (chartEditorState.currentInstrumentalOffset != audioPreviewInstrumentalOffset)
|
||||
{
|
||||
var track = audioPreviewTracks.members[0];
|
||||
if (track != null)
|
||||
{
|
||||
track.time += audioPreviewInstrumentalOffset;
|
||||
track.time -= chartEditorState.currentInstrumentalOffset;
|
||||
audioPreviewInstrumentalOffset = chartEditorState.currentInstrumentalOffset;
|
||||
}
|
||||
}
|
||||
if (chartEditorState.currentVocalOffsetPlayer != audioPreviewPlayerOffset)
|
||||
{
|
||||
var track = audioPreviewTracks.members[1];
|
||||
if (track != null)
|
||||
{
|
||||
track.time += audioPreviewPlayerOffset;
|
||||
track.time -= chartEditorState.currentVocalOffsetPlayer;
|
||||
audioPreviewPlayerOffset = chartEditorState.currentVocalOffsetPlayer;
|
||||
}
|
||||
}
|
||||
if (chartEditorState.currentVocalOffsetOpponent != audioPreviewOpponentOffset)
|
||||
{
|
||||
var track = audioPreviewTracks.members[2];
|
||||
if (track != null)
|
||||
{
|
||||
track.time += audioPreviewOpponentOffset;
|
||||
track.time -= chartEditorState.currentVocalOffsetOpponent;
|
||||
audioPreviewOpponentOffset = chartEditorState.currentVocalOffsetOpponent;
|
||||
}
|
||||
}
|
||||
offsetLabelTime.text = formatTime(audioPreviewTracks.time / Constants.MS_PER_SEC);
|
||||
// Keep the playhead in view.
|
||||
// playheadRelativePos = FlxMath.bound(playheadRelativePos, waveformScrollview.hscrollPos + 1,
|
||||
// Math.min(waveformScrollview.hscrollPos + waveformScrollview.width, waveformContainer.width));
|
||||
}
|
||||
|
||||
public override function refresh():Void
|
||||
{
|
||||
super.refresh();
|
||||
|
||||
waveformMagicFactor = MAGIC_SCALE_BASE_TIME / (chartEditorState.offsetTickBitmap.width / waveformInstrumental.waveform.waveformData.pointsPerSecond());
|
||||
|
||||
var currentZoomFactor = waveformScale / BASE_SCALE * waveformMagicFactor;
|
||||
|
||||
var maxWidth:Int = -1;
|
||||
|
||||
offsetStepperPlayer.value = chartEditorState.currentVocalOffsetPlayer;
|
||||
offsetStepperOpponent.value = chartEditorState.currentVocalOffsetOpponent;
|
||||
offsetStepperInstrumental.value = chartEditorState.currentInstrumentalOffset;
|
||||
|
||||
waveformPlayer.waveform.time = -chartEditorState.currentVocalOffsetPlayer / Constants.MS_PER_SEC; // Negative offsets make the song start early.
|
||||
waveformPlayer.waveform.width = (waveformPlayer.waveform.waveformData?.length ?? 1000) / currentZoomFactor;
|
||||
if (waveformPlayer.waveform.width > maxWidth) maxWidth = Std.int(waveformPlayer.waveform.width);
|
||||
waveformPlayer.waveform.height = 65;
|
||||
|
||||
waveformOpponent.waveform.time = -chartEditorState.currentVocalOffsetOpponent / Constants.MS_PER_SEC;
|
||||
waveformOpponent.waveform.width = (waveformOpponent.waveform.waveformData?.length ?? 1000) / currentZoomFactor;
|
||||
if (waveformOpponent.waveform.width > maxWidth) maxWidth = Std.int(waveformOpponent.waveform.width);
|
||||
waveformOpponent.waveform.height = 65;
|
||||
|
||||
waveformInstrumental.waveform.time = -chartEditorState.currentInstrumentalOffset / Constants.MS_PER_SEC;
|
||||
waveformInstrumental.waveform.width = (waveformInstrumental.waveform.waveformData?.length ?? 1000) / currentZoomFactor;
|
||||
if (waveformInstrumental.waveform.width > maxWidth) maxWidth = Std.int(waveformInstrumental.waveform.width);
|
||||
waveformInstrumental.waveform.height = 65;
|
||||
|
||||
// Live update the drag, but don't actually change the underlying offset until we release the mouse to finish dragging.
|
||||
if (dragWaveform != null) switch (dragWaveform)
|
||||
{
|
||||
case PLAYER:
|
||||
// chartEditorState.currentVocalOffsetPlayer += deltaMilliseconds;
|
||||
waveformPlayer.waveform.time -= dragOffsetMs / Constants.MS_PER_SEC;
|
||||
offsetStepperPlayer.value += dragOffsetMs;
|
||||
case OPPONENT:
|
||||
// chartEditorState.currentVocalOffsetOpponent += deltaMilliseconds;
|
||||
waveformOpponent.waveform.time -= dragOffsetMs / Constants.MS_PER_SEC;
|
||||
offsetStepperOpponent.value += dragOffsetMs;
|
||||
case INSTRUMENTAL:
|
||||
// chartEditorState.currentInstrumentalOffset += deltaMilliseconds;
|
||||
waveformInstrumental.waveform.time -= dragOffsetMs / Constants.MS_PER_SEC;
|
||||
offsetStepperInstrumental.value += dragOffsetMs;
|
||||
default:
|
||||
// No drag, no
|
||||
}
|
||||
|
||||
waveformPlayer.waveform.markDirty();
|
||||
waveformOpponent.waveform.markDirty();
|
||||
waveformInstrumental.waveform.markDirty();
|
||||
|
||||
waveformContainer.width = maxWidth;
|
||||
tickTiledSprite.width = maxWidth;
|
||||
}
|
||||
|
||||
public static function build(chartEditorState:ChartEditorState):ChartEditorOffsetsToolbox
|
||||
{
|
||||
return new ChartEditorOffsetsToolbox(chartEditorState);
|
||||
}
|
||||
}
|
||||
|
||||
enum Waveform
|
||||
{
|
||||
PLAYER;
|
||||
OPPONENT;
|
||||
INSTRUMENTAL;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package funkin.ui.debug.dialogue;
|
||||
|
||||
import flixel.FlxState;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import funkin.data.dialogue.ConversationData;
|
||||
import funkin.data.dialogue.ConversationRegistry;
|
||||
import funkin.data.dialogue.DialogueBoxData;
|
||||
import funkin.data.dialogue.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.SpeakerData;
|
||||
import funkin.data.dialogue.SpeakerRegistry;
|
||||
import funkin.play.cutscene.dialogue.Conversation;
|
||||
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||
import funkin.play.cutscene.dialogue.Speaker;
|
||||
|
||||
/**
|
||||
* A state with displays a conversation with no background.
|
||||
* Used for testing.
|
||||
* @param conversationId The conversation to display.
|
||||
*/
|
||||
class ConversationDebugState extends MusicBeatState
|
||||
{
|
||||
final conversationId:String = 'senpai';
|
||||
|
||||
var conversation:Conversation;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
// TODO: Fix this BS
|
||||
Paths.setCurrentLevel('week6');
|
||||
}
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
conversation = ConversationRegistry.instance.fetchEntry(conversationId);
|
||||
conversation.completeCallback = onConversationComplete;
|
||||
add(conversation);
|
||||
|
||||
ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false));
|
||||
}
|
||||
|
||||
function onConversationComplete():Void
|
||||
{
|
||||
remove(conversation);
|
||||
conversation = null;
|
||||
}
|
||||
|
||||
public override function dispatchEvent(event:ScriptEvent):Void
|
||||
{
|
||||
// Dispatch event to conversation script.
|
||||
ScriptEventDispatcher.callEvent(conversation, event);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (conversation != null)
|
||||
{
|
||||
if (controls.CUTSCENE_ADVANCE) conversation.advanceConversation();
|
||||
|
||||
if (controls.CUTSCENE_SKIP)
|
||||
{
|
||||
conversation.trySkipConversation(elapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
conversation.trySkipConversation(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user