Merge pull request #269 from FunkinCrew/feature/chart-editor-measure-numbers

Measure numbers next to ticks and support for initial time signature.
This commit is contained in:
Cameron Taylor 2024-01-05 20:41:07 -05:00 committed by GitHub
commit e507cbd327
9 changed files with 225 additions and 41 deletions

2
assets

@ -1 +1 @@
Subproject commit 4246be3aa353e43772760d02ae9ff262718dee06
Subproject commit b282f3431c15b719222196813da98ab70839d3e5

View file

@ -112,5 +112,6 @@ class Main extends Sprite
Toolkit.theme = 'dark'; // don't be cringe
Toolkit.autoScale = false;
funkin.input.Cursor.registerHaxeUICursors();
haxe.ui.tooltips.ToolTipManager.defaultDelay = 200;
}
}

View file

@ -15,6 +15,7 @@ import flixel.group.FlxSpriteGroup;
import flixel.input.keyboard.FlxKey;
import flixel.math.FlxMath;
import flixel.math.FlxPoint;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxRect;
import flixel.sound.FlxSound;
import flixel.system.FlxAssets.FlxSoundAsset;
@ -81,6 +82,7 @@ import funkin.ui.debug.charting.components.ChartEditorEventSprite;
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
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;
@ -169,7 +171,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
/**
* The width of the scroll area.
*/
public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = 12;
public static final PLAYHEAD_SCROLL_AREA_WIDTH:Int = Std.int(GRID_SIZE);
/**
* The height of the playhead, in pixels.
@ -335,17 +337,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.scrollPositionInPixels = value;
// Move the grid sprite to the correct position.
if (gridTiledSprite != null && gridPlayheadScrollArea != null)
if (gridTiledSprite != null && measureTicks != null)
{
if (isViewDownscroll)
{
gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
gridPlayheadScrollArea.y = gridTiledSprite.y;
measureTicks.y = gridTiledSprite.y;
}
else
{
gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
gridPlayheadScrollArea.y = gridTiledSprite.y;
measureTicks.y = gridTiledSprite.y;
if (audioVisGroup != null && audioVisGroup.playerVis != null)
{
@ -367,6 +369,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (selectionBoxStartPos != null) selectionBoxStartPos.y -= diff;
// Update the note preview viewport box.
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
// Update the measure tick display.
if (measureTicks != null) measureTicks.y = gridTiledSprite?.y ?? 0.0;
return this.scrollPositionInPixels;
}
@ -1701,23 +1705,28 @@ 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 tiled sprite used to display the grid.
* The height is the length of the song, and scrolling is done by simply the sprite.
*/
var gridTiledSprite:Null<FlxSprite> = null;
/**
* The measure ticks area. Includes the numbers and the background sprite.
*/
var measureTicks:Null<ChartEditorMeasureTicks> = null;
/**
* The playhead representing the current position in the song.
* Can move around on the grid independently of the view.
*/
var gridPlayhead:FlxSpriteGroup = new FlxSpriteGroup();
/**
* The sprite for the scroll area under
*/
var gridPlayheadScrollArea:Null<FlxSprite> = null;
/**
* A sprite used to indicate the note that will be placed on click.
*/
@ -1875,6 +1884,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.updateTheme();
buildGrid();
buildMeasureTicks();
buildNotePreview();
buildSelectionBox();
@ -2130,20 +2140,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
buildNoteGroup();
gridPlayheadScrollArea = new FlxSprite(0, 0);
gridPlayheadScrollArea.makeGraphic(10, 10, PLAYHEAD_SCROLL_AREA_COLOR); // Make it 10x10px and then scale it as needed.
add(gridPlayheadScrollArea);
gridPlayheadScrollArea.setGraphicSize(PLAYHEAD_SCROLL_AREA_WIDTH, 3000);
gridPlayheadScrollArea.updateHitbox();
gridPlayheadScrollArea.x = gridTiledSprite.x - PLAYHEAD_SCROLL_AREA_WIDTH;
gridPlayheadScrollArea.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
gridPlayheadScrollArea.zIndex = 25;
// The playhead that show the current position in the song.
add(gridPlayhead);
gridPlayhead.zIndex = 30;
var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2);
var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH);
var playheadBaseYPos:Float = MENU_BAR_HEIGHT + GRID_TOP_PAD;
gridPlayhead.setPosition(gridTiledSprite.x, playheadBaseYPos);
var playheadSprite:FlxSprite = new FlxSprite().makeGraphic(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR);
@ -2174,11 +2175,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(audioVisGroup);
}
function buildMeasureTicks():Void
{
measureTicks = new ChartEditorMeasureTicks(this);
var measureTicksWidth = (GRID_SIZE);
measureTicks.x = gridTiledSprite.x - measureTicksWidth;
measureTicks.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
measureTicks.zIndex = 20;
add(measureTicks);
}
function buildNotePreview():Void
{
var height:Int = FlxG.height - MENU_BAR_HEIGHT - GRID_TOP_PAD - PLAYBAR_HEIGHT - GRID_TOP_PAD - GRID_TOP_PAD;
notePreview = new ChartEditorNotePreview(height);
notePreview.x = 350;
notePreview.x = 320;
notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
add(notePreview);
@ -2258,6 +2270,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
bounds.height = MIN_HEIGHT;
}
trace('Note preview viewport bounds: ' + bounds.toString());
return bounds;
}
@ -2868,7 +2882,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (metronomeVolume > 0.0 && this.subState == null && (audioInstTrack != null && audioInstTrack.isPlaying))
{
playMetronomeTick(Conductor.instance.currentBeat % 4 == 0);
playMetronomeTick(Conductor.instance.currentBeat % Conductor.instance.beatsPerMeasure == 0);
}
// Show the mouse cursor.
@ -3581,7 +3595,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
scrollAnchorScreenPos = null;
}
else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea) && !isCursorOverHaxeUI)
else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks) && !isCursorOverHaxeUI)
{
gridPlayheadScrollAreaPressed = true;
}
@ -4298,7 +4312,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
targetCursorMode = Pointer;
}
else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea))
else if (measureTicks != null && FlxG.mouse.overlaps(measureTicks))
{
targetCursorMode = Pointer;
}
@ -4592,7 +4606,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Visibly center the Dad health icon.
if (healthIconDad != null)
{
healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 45 - (healthIconDad.width / 2));
healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 75 - (healthIconDad.width / 2));
healthIconDad.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconDad.height / 2));
}
}
@ -5078,11 +5092,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function onSongLengthChanged():Void
{
if (gridTiledSprite != null) gridTiledSprite.height = songLengthInPixels;
if (gridPlayheadScrollArea != null)
if (gridTiledSprite != null)
{
gridPlayheadScrollArea.setGraphicSize(Std.int(gridPlayheadScrollArea.width), songLengthInPixels);
gridPlayheadScrollArea.updateHitbox();
gridTiledSprite.height = songLengthInPixels;
}
if (measureTicks != null)
{
measureTicks.setHeight(songLengthInPixels);
}
// Remove any notes past the end of the song.
@ -5190,6 +5206,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectedDifficulty = prevDifficulty;
Conductor.instance.mapTimeChanges(this.currentSongMetadata.timeChanges);
updateTimeSignature();
refreshDifficultyTreeSelection();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
@ -5343,6 +5360,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = vocalTargetVolume;
}
function updateTimeSignature():Void
{
// Redo the grid bitmap to be 4/4.
this.updateTheme();
gridTiledSprite.loadGraphic(gridBitmap);
measureTicks.reloadTickBitmap();
}
/**
* HAXEUI FUNCTIONS
*/
@ -5455,6 +5480,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (notePreviewViewportBoundsDirty)
{
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
notePreviewViewportBoundsDirty = false;
}
}

View file

@ -0,0 +1,71 @@
package funkin.ui.debug.charting.components;
import flixel.FlxSprite;
import flixel.addons.display.FlxTiledSprite;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.text.FlxText;
import flixel.util.FlxColor;
@:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorMeasureTicks extends FlxTypedSpriteGroup<FlxSprite>
{
var chartEditorState:ChartEditorState;
var tickTiledSprite:FlxTiledSprite;
var measureNumber:FlxText;
override function set_y(value:Float):Float
{
var result = super.set_y(value);
updateMeasureNumber();
return result;
}
public function new(chartEditorState:ChartEditorState)
{
super();
this.chartEditorState = chartEditorState;
tickTiledSprite = new FlxTiledSprite(chartEditorState.measureTickBitmap, chartEditorState.measureTickBitmap.width, 1000, false, true);
add(tickTiledSprite);
measureNumber = new FlxText(0, 0, ChartEditorState.GRID_SIZE, "1");
measureNumber.setFormat(Paths.font('vcr.ttf'), 20, FlxColor.WHITE);
measureNumber.borderStyle = FlxTextBorderStyle.OUTLINE;
measureNumber.borderColor = FlxColor.BLACK;
add(measureNumber);
}
public function reloadTickBitmap():Void
{
tickTiledSprite.loadGraphic(chartEditorState.measureTickBitmap);
}
/**
* At time of writing, we only have to manipulate one measure number because we can only see one measure at a time.
*/
function updateMeasureNumber()
{
if (measureNumber == null) return;
var viewTopPosition = 0 - this.y;
var viewHeight = FlxG.height - ChartEditorState.MENU_BAR_HEIGHT - ChartEditorState.PLAYBAR_HEIGHT;
var viewBottomPosition = viewTopPosition + viewHeight;
var measureNumberInViewport = Math.floor(viewTopPosition / ChartEditorState.GRID_SIZE / Conductor.instance.stepsPerMeasure) + 1;
var measureNumberPosition = measureNumberInViewport * ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure;
measureNumber.text = '${measureNumberInViewport + 1}';
measureNumber.y = measureNumberPosition + this.y;
// trace(measureNumber.text + ' at ' + measureNumber.y);
}
public function setHeight(songLengthInPixels:Float):Void
{
tickTiledSprite.height = songLengthInPixels;
}
}

View file

@ -194,7 +194,7 @@ class ChartEditorAudioHandler
case DAD:
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
state.audioVisGroup.addOpponentVis(vocalTrack);
state.audioVisGroup.opponentVis.x = 435;
state.audioVisGroup.opponentVis.x = 405;
state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.instance.getStepTimeInMs(16) * 0.00195;
state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;

View file

@ -686,6 +686,7 @@ class ChartEditorDialogHandler
Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
state.updateTimeSignature();
state.selectedVariation = Constants.DEFAULT_VARIATION;
state.selectedDifficulty = state.availableDifficulties[0];

View file

@ -118,6 +118,7 @@ class ChartEditorImportExportHandler
Conductor.instance.forceBPM(null); // Disable the forced BPM.
Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
state.updateTimeSignature();
state.notePreviewDirty = true;
state.notePreviewViewportBoundsDirty = true;

View file

@ -81,6 +81,7 @@ class ChartEditorThemeHandler
{
updateBackground(state);
updateGridBitmap(state);
updateMeasureTicks(state);
updateSelectionSquare(state);
updateNotePreview(state);
}
@ -207,9 +208,6 @@ class ChartEditorThemeHandler
}
}
// Divider at top
state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor);
// Draw vertical dividers between the strumlines.
var gridStrumlineDividerColor:FlxColor = switch (state.currentTheme)
@ -233,6 +231,61 @@ class ChartEditorThemeHandler
// Else, gridTiledSprite will be built later.
}
static function updateMeasureTicks(state:ChartEditorState):Void
{
var measureTickWidth:Int = 6;
var beatTickWidth:Int = 4;
var stepTickWidth:Int = 2;
// Draw the measure ticks.
var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE); // 1 grid squares wide.
var ticksHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.instance.stepsPerMeasure); // 1 measure tall.
state.measureTickBitmap = new BitmapData(ticksWidth, ticksHeight, true);
state.measureTickBitmap.fillRect(new Rectangle(0, 0, ticksWidth, ticksHeight), GRID_BEAT_DIVIDER_COLOR_DARK);
// Draw the measure ticks.
state.measureTickBitmap.fillRect(new Rectangle(0, 0, state.measureTickBitmap.width, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
var bottomTickY:Float = state.measureTickBitmap.height - (measureTickWidth / 2);
state.measureTickBitmap.fillRect(new Rectangle(0, bottomTickY, state.measureTickBitmap.width, measureTickWidth / 2), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
// Draw the beat ticks.
var beatTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.instance.beatsPerMeasure - (beatTickWidth / 2);
var beatTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.instance.beatsPerMeasure - (beatTickWidth / 2);
var beatTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.instance.beatsPerMeasure - (beatTickWidth / 2);
var beatTickLength:Float = state.measureTickBitmap.width * 2 / 3;
state.measureTickBitmap.fillRect(new Rectangle(0, beatTick2Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, beatTick3Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, beatTick4Y, beatTickLength, beatTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
// Draw the step ticks.
// TODO: Make this a loop or something.
var stepTick2Y:Float = state.measureTickBitmap.height * 1 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick3Y:Float = state.measureTickBitmap.height * 2 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick4Y:Float = state.measureTickBitmap.height * 3 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick6Y:Float = state.measureTickBitmap.height * 5 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick7Y:Float = state.measureTickBitmap.height * 6 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick8Y:Float = state.measureTickBitmap.height * 7 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick10Y:Float = state.measureTickBitmap.height * 9 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick11Y:Float = state.measureTickBitmap.height * 10 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick12Y:Float = state.measureTickBitmap.height * 11 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick14Y:Float = state.measureTickBitmap.height * 13 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick15Y:Float = state.measureTickBitmap.height * 14 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTick16Y:Float = state.measureTickBitmap.height * 15 / Conductor.instance.stepsPerMeasure - (stepTickWidth / 2);
var stepTickLength:Float = state.measureTickBitmap.width * 1 / 3;
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick2Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick3Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick4Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick6Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick7Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick8Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick10Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick11Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick12Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick14Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick15Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
state.measureTickBitmap.fillRect(new Rectangle(0, stepTick16Y, stepTickLength, stepTickWidth), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
}
static function updateSelectionSquare(state:ChartEditorState):Void
{
var selectionSquareBorderColor:FlxColor = switch (state.currentTheme)
@ -289,14 +342,21 @@ class ChartEditorThemeHandler
ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2), ChartEditorState.GRID_SIZE - (SELECTION_SQUARE_BORDER_WIDTH * 2)),
viewportFillColor);
state.notePreviewViewport = new FlxSliceSprite(state.notePreviewViewportBitmap,
new FlxRect(SELECTION_SQUARE_BORDER_WIDTH
+ 1, SELECTION_SQUARE_BORDER_WIDTH
+ 1, ChartEditorState.GRID_SIZE
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 2),
ChartEditorState.GRID_SIZE
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 2)),
32, 32);
if (state.notePreviewViewport != null)
{
state.notePreviewViewport.loadGraphic(state.notePreviewViewportBitmap);
}
else
{
state.notePreviewViewport = new FlxSliceSprite(state.notePreviewViewportBitmap,
new FlxRect(SELECTION_SQUARE_BORDER_WIDTH
+ 1, SELECTION_SQUARE_BORDER_WIDTH
+ 1,
ChartEditorState.GRID_SIZE
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 2), ChartEditorState.GRID_SIZE
- (2 * SELECTION_SQUARE_BORDER_WIDTH + 2)),
32, 32);
}
}
public static function buildPlayheadBlock():FlxSprite

View file

@ -116,6 +116,26 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
}
};
inputTimeSignature.onChange = function(event:UIEvent) {
var timeSignatureStr:String = event.data.text;
var timeSignature = timeSignatureStr.split('/');
if (timeSignature.length != 2) return;
var timeSignatureNum:Int = Std.parseInt(timeSignature[0]);
var timeSignatureDen:Int = Std.parseInt(timeSignature[1]);
var previousTimeSignatureNum:Int = chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum;
var previousTimeSignatureDen:Int = chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen;
if (timeSignatureNum == previousTimeSignatureNum && timeSignatureDen == previousTimeSignatureDen) return;
chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum = timeSignatureNum;
chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen = timeSignatureDen;
trace('Time signature changed to ${timeSignatureNum}/${timeSignatureDen}');
chartEditorState.updateTimeSignature();
};
inputOffsetInst.onChange = function(event:UIEvent) {
if (event.value == null) return;
@ -174,6 +194,10 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
frameVariation.text = 'Variation: ${chartEditorState.selectedVariation.toTitleCase()}';
frameDifficulty.text = 'Difficulty: ${chartEditorState.selectedDifficulty.toTitleCase()}';
var currentTimeSignature = '${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum}/${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen}';
trace('Setting time signature to ${currentTimeSignature}');
inputTimeSignature.value = {id: currentTimeSignature, text: currentTimeSignature};
var stageId:String = chartEditorState.currentSongMetadata.playData.stage;
var stageData:Null<StageData> = StageDataParser.parseStageData(stageId);
if (inputStage != null)