Done with BPM change fixes, currently working on rendering efficiency

This commit is contained in:
EliteMasterEric 2023-07-22 20:16:43 -04:00
parent aaad06c97d
commit 3c218ec01c
15 changed files with 425 additions and 203 deletions

View file

@ -3,7 +3,6 @@ package funkin;
import funkin.util.Constants;
import flixel.util.FlxSignal;
import flixel.math.FlxMath;
import funkin.SongLoad.SwagSong;
import funkin.play.song.Song.SongDifficulty;
import funkin.play.song.SongData.SongTimeChange;
@ -13,12 +12,6 @@ import funkin.play.song.SongData.SongTimeChange;
*/
class Conductor
{
public static final PIXELS_PER_MS:Float = 0.45;
public static final HIT_WINDOW_MS:Float = 160;
public static final SECONDS_PER_MINUTE:Float = 60;
public static final MILLIS_PER_SECOND:Float = 1000;
public static final STEPS_PER_BEAT:Int = 4;
// onBeatHit is called every quarter note
// onStepHit is called every sixteenth note
// 4/4 = 4 beats per measure = 16 steps per measure
@ -82,18 +75,18 @@ class Conductor
}
/**
* Duration of a beat in milliseconds. Calculated based on bpm.
* Duration of a beat (quarter note) in milliseconds. Calculated based on bpm.
*/
public static var beatLengthMs(get, null):Float;
static function get_beatLengthMs():Float
{
// Tied directly to BPM.
return ((SECONDS_PER_MINUTE / bpm) * MILLIS_PER_SECOND);
return ((Constants.SECS_PER_MIN / bpm) * Constants.MS_PER_SEC);
}
/**
* Duration of a step (quarter) in milliseconds. Calculated based on bpm.
* Duration of a step (sixtennth note) in milliseconds. Calculated based on bpm.
*/
public static var stepLengthMs(get, null):Float;
@ -280,7 +273,8 @@ class Conductor
{
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
currentTimeChange.beatTime = prevTimeChange.beatTime
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC);
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC)
+ 0.01;
}
}
}
@ -323,7 +317,8 @@ class Conductor
}
}
var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / stepLengthMs;
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs;
resultStep += resultFractionalStep; // Math.floor();
return resultStep;
@ -359,7 +354,8 @@ class Conductor
}
}
resultMs += (stepTime - lastTimeChange.beatTime * 4) * stepLengthMs;
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
resultMs += (stepTime - lastTimeChange.beatTime * 4) * lastStepLengthMs;
return resultMs;
}
@ -394,7 +390,8 @@ class Conductor
}
}
resultMs += (beatTime - lastTimeChange.beatTime) * stepLengthMs * Constants.STEPS_PER_BEAT;
var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator;
resultMs += (beatTime - lastTimeChange.beatTime) * lastStepLengthMs * Constants.STEPS_PER_BEAT;
return resultMs;
}

View file

@ -127,7 +127,7 @@ class FreeplayState extends MusicBeatSubState
if (FlxG.sound.music != null)
{
if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu'));
if (!FlxG.sound.music.playing) FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
}
// if (StoryMenuState.weekUnlocked[2] || isDebug)

View file

@ -261,7 +261,7 @@ class InitState extends FlxTransitionableState
*/
function startGameNormally():Void
{
FlxG.sound.cache(Paths.music('freakyMenu'));
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
FlxG.switchState(new TitleState());
}

View file

@ -56,7 +56,7 @@ class MainMenuState extends MusicBeatState
if (!FlxG.sound.music.playing)
{
FlxG.sound.playMusic(Paths.music('freakyMenu'));
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
}
persistentUpdate = persistentDraw = true;

View file

@ -49,7 +49,7 @@ class TitleState extends MusicBeatState
swagShader = new ColorSwap();
curWacky = FlxG.random.getObject(getIntroTextShit());
FlxG.sound.cache(Paths.music('freakyMenu'));
FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu'));
// DEBUG BULLSHIT

View file

@ -1637,9 +1637,9 @@ class PlayState extends MusicBeatState
{
if (note == null) continue;
var hitWindowStart = note.strumTime - Conductor.HIT_WINDOW_MS;
var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS;
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Conductor.HIT_WINDOW_MS;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
if (Conductor.songPosition > hitWindowEnd)
{
@ -1714,9 +1714,9 @@ class PlayState extends MusicBeatState
{
if (note == null || note.hasBeenHit) continue;
var hitWindowStart = note.strumTime - Conductor.HIT_WINDOW_MS;
var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS;
var hitWindowCenter = note.strumTime;
var hitWindowEnd = note.strumTime + Conductor.HIT_WINDOW_MS;
var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS;
if (Conductor.songPosition > hitWindowEnd)
{
@ -2367,7 +2367,7 @@ class PlayState extends MusicBeatState
if (targetSongId == null)
{
FlxG.sound.playMusic(Paths.music('freakyMenu'));
FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
transIn = FlxTransitionableState.defaultTransIn;
transOut = FlxTransitionableState.defaultTransOut;

View file

@ -213,7 +213,7 @@ class Strumline extends FlxSpriteGroup
var vwoosh:Float = (strumTime < Conductor.songPosition) && vwoosh ? 2.0 : 1.0;
var scrollSpeed:Float = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0;
return Conductor.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (PreferencesMenu.getPref('downscroll') ? 1 : -1);
return Constants.PIXELS_PER_MS * (Conductor.songPosition - strumTime) * scrollSpeed * vwoosh * (PreferencesMenu.getPref('downscroll') ? 1 : -1);
}
function updateNotes():Void
@ -273,7 +273,7 @@ class Strumline extends FlxSpriteGroup
}
}
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Conductor.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
var renderWindowEnd = holdNote.strumTime + holdNote.fullSustainLength + Constants.HIT_WINDOW_MS + RENDER_DISTANCE_MS / 8;
if (holdNote.missedNote && Conductor.songPosition >= renderWindowEnd)
{
@ -308,7 +308,7 @@ class Strumline extends FlxSpriteGroup
// Hold note was dropped before completing, keep it in its clipped state.
holdNote.visible = true;
var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Conductor.PIXELS_PER_MS;
var yOffset:Float = (holdNote.fullSustainLength - holdNote.sustainLength) * Constants.PIXELS_PER_MS;
trace('yOffset: ' + yOffset);
trace('holdNote.fullSustainLength: ' + holdNote.fullSustainLength);
@ -678,7 +678,7 @@ class Strumline extends FlxSpriteGroup
{
// The note sprite pool is full and all note splashes are active.
// We have to create a new note.
result = new SustainTrail(0, 100, noteStyle.getHoldNoteAssetPath(), noteStyle);
result = new SustainTrail(0, 100, noteStyle);
this.holdNotes.add(result);
}

View file

@ -88,9 +88,9 @@ class SustainTrail extends FlxSprite
* @param SustainLength Length in milliseconds.
* @param fileName
*/
public function new(noteDirection:NoteDirection, sustainLength:Float, fileName:String, noteStyle:NoteStyle)
public function new(noteDirection:NoteDirection, sustainLength:Float, noteStyle:NoteStyle)
{
super(0, 0, fileName);
super(0, 0, noteStyle.getHoldNoteAssetPath());
antialiasing = true;
@ -111,7 +111,7 @@ class SustainTrail extends FlxSprite
// CALCULATE SIZE
width = graphic.width / 8 * zoom; // amount of notes * 2
height = sustainHeight(sustainLength, PlayState.instance.currentChart.scrollSpeed);
height = sustainHeight(sustainLength, getScrollSpeed());
// instead of scrollSpeed, PlayState.SONG.speed
flipY = PreferencesMenu.getPref('downscroll');
@ -123,6 +123,13 @@ class SustainTrail extends FlxSprite
updateClipping();
indices = new DrawData<Int>(12, true, TRIANGLE_VERTEX_INDICES);
this.active = true; // This NEEDS to be true for the note to be drawn!
}
function getScrollSpeed():Float
{
return PlayState?.instance?.currentChart?.scrollSpeed ?? 1.0;
}
/**
@ -139,7 +146,7 @@ class SustainTrail extends FlxSprite
{
if (s < 0) s = 0;
height = sustainHeight(s, PlayState.instance.currentChart.scrollSpeed);
height = sustainHeight(s, getScrollSpeed());
updateColorTransform();
updateClipping();
return sustainLength = s;
@ -152,7 +159,7 @@ class SustainTrail extends FlxSprite
*/
public function updateClipping(songTime:Float = 0):Void
{
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), PlayState.instance.currentChart.scrollSpeed), 0, height);
var clipHeight:Float = FlxMath.bound(sustainHeight(sustainLength - (songTime - strumTime), getScrollSpeed()), 0, height);
if (clipHeight == 0)
{
visible = false;

View file

@ -386,6 +386,9 @@ abstract SongNoteData(RawSongNoteData)
};
}
/**
* The timestamp of the note, in milliseconds.
*/
public var time(get, set):Float;
public function get_time():Float
@ -398,6 +401,9 @@ abstract SongNoteData(RawSongNoteData)
return this.t = value;
}
/**
* The timestamp of the note, in steps.
*/
public var stepTime(get, never):Float;
public function get_stepTime():Float
@ -470,6 +476,10 @@ abstract SongNoteData(RawSongNoteData)
return getStrumlineIndex(strumlineSize) == 0;
}
/**
* If this is a hold note, this is the length of the hold note in milliseconds.
* @default 0 (not a hold note)
*/
public var length(get, set):Float;
function get_length():Float
@ -482,6 +492,10 @@ abstract SongNoteData(RawSongNoteData)
return this.l = value;
}
/**
* If this is a hold note, this is the length of the hold note in steps.
* @default 0 (not a hold note)
*/
public var stepLength(get, set):Float;
function get_stepLength():Float

View file

@ -67,8 +67,12 @@ class ChartEditorEventSprite extends FlxSprite
// Push all the other events as frames.
for (eventName in SongEventParser.listEventIds())
{
var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName'));
if (!exists) continue; // No graphic for this event.
var frames:FlxAtlasFrames = Paths.getSparrowAtlas('ui/chart-editor/events/$eventName');
if (frames == null) continue; // No graphic for this event.
if (frames == null) continue; // Could not load graphic for this event.
frames.parent.persist = true;
for (frame in frames.frames)
{
@ -140,19 +144,34 @@ class ChartEditorEventSprite extends FlxSprite
}
/**
* Return whether this note (or its parent) is currently visible.
* Return whether this event is currently visible.
*/
public function isEventVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
public function isNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
{
var outsideViewArea = (this.y + this.height < viewAreaTop || this.y > viewAreaBottom);
// True if the note is above the view area.
var aboveViewArea = (this.y + this.height < viewAreaTop);
if (!outsideViewArea)
{
return true;
}
// True if the note is below the view area.
var belowViewArea = (this.y > viewAreaBottom);
// TODO: Check if this note's parent or child is visible.
return !aboveViewArea && !belowViewArea;
}
return false;
/**
* Return whether an event, if placed in the scene, would be visible.
*/
public static function wouldNoteBeVisible(viewAreaBottom:Float, viewAreaTop:Float, eventData:SongEventData, ?origin:FlxObject):Bool
{
var noteHeight:Float = ChartEditorState.GRID_SIZE;
var notePosY:Float = eventData.stepTime * ChartEditorState.GRID_SIZE;
if (origin != null) notePosY += origin.y;
// True if the note is above the view area.
var aboveViewArea = (notePosY + noteHeight < viewAreaTop);
// True if the note is below the view area.
var belowViewArea = (notePosY > viewAreaBottom);
return !aboveViewArea && !belowViewArea;
}
}

View file

@ -0,0 +1,143 @@
package funkin.ui.debug.charting;
import funkin.play.notes.Strumline;
import funkin.data.notestyle.NoteStyleRegistry;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxFramesCollection;
import flixel.graphics.frames.FlxTileFrames;
import flixel.math.FlxPoint;
import funkin.play.notes.SustainTrail;
import funkin.play.song.SongData.SongNoteData;
/**
* A hold note sprite that can be used to display a note in a chart.
* Designed to be used and reused efficiently. Has no gameplay functionality.
*/
class ChartEditorHoldNoteSprite extends SustainTrail
{
/**
* The ChartEditorState this note belongs to.
*/
public var parentState:ChartEditorState;
public function new(parent:ChartEditorState)
{
var noteStyle = NoteStyleRegistry.instance.fetchDefault();
super(0, 100, noteStyle);
this.parentState = parent;
zoom = 1.0;
zoom *= noteStyle.fetchHoldNoteScale();
zoom *= 0.7;
zoom *= ChartEditorState.GRID_SIZE / Strumline.STRUMLINE_SIZE;
setup();
}
/**
* Set the height directly, to a value in pixels.
* @param h The desired height in pixels.
*/
public function setHeightDirectly(h:Float)
{
sustainLength = h / (getScrollSpeed() * Constants.PIXELS_PER_MS);
fullSustainLength = sustainLength;
}
function setup():Void
{
strumTime = 999999999;
missedNote = false;
hitNote = false;
visible = true;
alpha = 1.0;
width = graphic.width / 8 * zoom; // amount of notes * 2
}
public override function revive():Void
{
super.revive();
setup();
}
/**
* Return whether this note is currently visible.
*/
public function isHoldNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
{
// True if the note is above the view area.
var aboveViewArea = (this.y + this.height < viewAreaTop);
// True if the note is below the view area.
var belowViewArea = (this.y > viewAreaBottom);
return !aboveViewArea && !belowViewArea;
}
/**
* Return whether a hold note, if placed in the scene, would be visible.
*/
public static function wouldHoldNoteBeVisible(viewAreaBottom:Float, viewAreaTop:Float, noteData:SongNoteData, ?origin:FlxObject):Bool
{
var noteHeight:Float = noteData.stepLength * ChartEditorState.GRID_SIZE;
var notePosY:Float = noteData.stepTime * ChartEditorState.GRID_SIZE;
if (origin != null) notePosY += origin.y;
// True if the note is above the view area.
var aboveViewArea = (notePosY + noteHeight < viewAreaTop);
// True if the note is below the view area.
var belowViewArea = (notePosY > viewAreaBottom);
return !aboveViewArea && !belowViewArea;
}
public function updateHoldNotePosition(?origin:FlxObject)
{
var cursorColumn:Int = this.noteData.data;
if (cursorColumn < 0) cursorColumn = 0;
if (cursorColumn >= (ChartEditorState.STRUMLINE_SIZE * 2 + 1))
{
cursorColumn = (ChartEditorState.STRUMLINE_SIZE * 2 + 1);
}
else
{
// Invert player and opponent columns.
if (cursorColumn >= ChartEditorState.STRUMLINE_SIZE)
{
cursorColumn -= ChartEditorState.STRUMLINE_SIZE;
}
else
{
cursorColumn += ChartEditorState.STRUMLINE_SIZE;
}
}
this.x = cursorColumn * ChartEditorState.GRID_SIZE;
// Notes far in the song will start far down, but the group they belong to will have a high negative offset.
if (this.noteData.stepTime >= 0)
{
// noteData.stepTime is a calculated value which accounts for BPM changes
var stepTime:Float = this.noteData.stepTime;
var roundedStepTime:Float = Math.floor(stepTime + 0.01); // Add epsilon to fix rounding issues
this.y = roundedStepTime * ChartEditorState.GRID_SIZE;
}
this.x += ChartEditorState.GRID_SIZE / 2;
this.x -= this.width / 2;
this.y += ChartEditorState.GRID_SIZE / 2;
if (origin != null)
{
this.x += origin.x;
this.y += origin.y;
}
}
}

View file

@ -204,15 +204,30 @@ class ChartEditorNoteSprite extends FlxSprite
*/
public function isNoteVisible(viewAreaBottom:Float, viewAreaTop:Float):Bool
{
var outsideViewArea = (this.y + this.height < viewAreaTop || this.y > viewAreaBottom);
// True if the note is above the view area.
var aboveViewArea = (this.y + this.height < viewAreaTop);
if (!outsideViewArea)
{
return true;
}
// True if the note is below the view area.
var belowViewArea = (this.y > viewAreaBottom);
// TODO: Check if this note's parent or child is visible.
return !aboveViewArea && !belowViewArea;
}
return false;
/**
* Return whether a note, if placed in the scene, would be visible.
*/
public static function wouldNoteBeVisible(viewAreaBottom:Float, viewAreaTop:Float, noteData:SongNoteData, ?origin:FlxObject):Bool
{
var noteHeight:Float = ChartEditorState.GRID_SIZE;
var notePosY:Float = noteData.stepTime * ChartEditorState.GRID_SIZE;
if (origin != null) notePosY += origin.y;
// True if the note is above the view area.
var aboveViewArea = (notePosY + noteHeight < viewAreaTop);
// True if the note is below the view area.
var belowViewArea = (notePosY > viewAreaBottom);
return !aboveViewArea && !belowViewArea;
}
}

View file

@ -1,19 +1,10 @@
package funkin.ui.debug.charting;
import funkin.graphics.rendering.SustainTrail;
import funkin.util.SortUtil;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.ui.debug.charting.ChartEditorCommand;
import flixel.input.keyboard.FlxKey;
import funkin.input.TurboKeyHandler;
import haxe.ui.notifications.NotificationType;
import haxe.ui.notifications.NotificationManager;
import haxe.DynamicAccess;
import haxe.io.Path;
import flixel.addons.display.FlxSliceSprite;
import flixel.addons.display.FlxTiledSprite;
import flixel.FlxSprite;
import flixel.group.FlxSpriteGroup;
import flixel.input.keyboard.FlxKey;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.sound.FlxSound;
@ -22,10 +13,13 @@ import flixel.util.FlxSort;
import flixel.util.FlxTimer;
import funkin.audio.visualize.PolygonSpectogram;
import funkin.audio.VoicesGroup;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.input.Cursor;
import funkin.input.TurboKeyHandler;
import funkin.modding.events.ScriptEvent;
import funkin.play.HealthIcon;
import funkin.play.notes.NoteSprite;
import funkin.play.notes.Strumline;
import funkin.play.song.Song;
import funkin.play.song.SongData.SongChartData;
import funkin.play.song.SongData.SongDataParser;
@ -33,13 +27,18 @@ import funkin.play.song.SongData.SongEventData;
import funkin.play.song.SongData.SongMetadata;
import funkin.play.song.SongData.SongNoteData;
import funkin.play.song.SongDataUtils;
import funkin.ui.debug.charting.ChartEditorCommand;
import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme;
import funkin.ui.debug.charting.ChartEditorToolboxHandler.ChartEditorToolMode;
import funkin.ui.haxeui.components.CharacterPlayer;
import funkin.ui.haxeui.HaxeUIState;
import funkin.util.FileUtil;
import funkin.util.DateUtil;
import funkin.util.FileUtil;
import funkin.util.SerializerUtil;
import funkin.util.SortUtil;
import funkin.util.WindowUtil;
import haxe.DynamicAccess;
import haxe.io.Path;
import haxe.ui.components.Label;
import haxe.ui.components.Slider;
import haxe.ui.containers.dialogs.Dialog;
@ -50,7 +49,8 @@ import haxe.ui.core.Component;
import haxe.ui.core.Screen;
import haxe.ui.events.DragEvent;
import haxe.ui.events.UIEvent;
import funkin.util.WindowUtil;
import haxe.ui.notifications.NotificationManager;
import haxe.ui.notifications.NotificationType;
import openfl.display.BitmapData;
import openfl.geom.Rectangle;
@ -112,7 +112,12 @@ class ChartEditorState extends HaxeUIState
/**
* The height of the menu bar in the layout.
*/
static final MENU_BAR_HEIGHT = 32;
static final MENU_BAR_HEIGHT:Int = 32;
/**
* The height of the playbar in the layout.
*/
static final PLAYBAR_HEIGHT:Int = 48;
/**
* Duration to wait before autosaving the chart.
@ -946,7 +951,12 @@ class ChartEditorState extends HaxeUIState
*/
var renderedNotes:FlxTypedSpriteGroup<ChartEditorNoteSprite>;
var renderedHoldNotes:FlxTypedSpriteGroup<SustainTrail>;
/**
* The sprite group containing the hold note graphics.
* Only displays a subset of the data from `currentSongChartNoteData`,
* and kills notes that are off-screen to be recycled later.
*/
var renderedHoldNotes:FlxTypedSpriteGroup<ChartEditorHoldNoteSprite>;
/**
* The sprite group containing the song events.
@ -1032,7 +1042,7 @@ class ChartEditorState extends HaxeUIState
gridGhostNote = new ChartEditorNoteSprite(this);
gridGhostNote.alpha = 0.6;
gridGhostNote.noteData = new SongNoteData(-1, -1, 0, "");
gridGhostNote.noteData = new SongNoteData(0, 0, 0, "");
gridGhostNote.visible = false;
add(gridGhostNote);
@ -1127,6 +1137,10 @@ class ChartEditorState extends HaxeUIState
*/
function buildNoteGroup():Void
{
renderedHoldNotes = new FlxTypedSpriteGroup<ChartEditorHoldNoteSprite>();
renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
add(renderedHoldNotes);
renderedNotes = new FlxTypedSpriteGroup<ChartEditorNoteSprite>();
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
add(renderedNotes);
@ -1704,12 +1718,9 @@ class ChartEditorState extends HaxeUIState
moveSongToScrollPosition();
}
// Cursor position snapped to the grid.
// The song position of the cursor, in steps.
var cursorFractionalStep:Float = cursorY / GRID_SIZE / (16 / noteSnapQuant);
var cursorStep:Int = Std.int(Math.floor(cursorFractionalStep));
var cursorMs:Float = Conductor.getStepTimeInMs(cursorStep);
var cursorMs:Float = Conductor.getStepTimeInMs(cursorFractionalStep);
// The direction value for the column at the cursor.
var cursorColumn:Int = Math.floor(cursorX / GRID_SIZE);
if (cursorColumn < 0) cursorColumn = 0;
@ -2145,10 +2156,10 @@ class ChartEditorState extends HaxeUIState
// Update for whether downscroll is enabled.
renderedNotes.flipX = (isViewDownscroll);
// Calculate the view bounds.
var viewAreaTop:Float = this.scrollPositionInPixels - GRID_TOP_PAD;
var viewHeight:Float = (FlxG.height - MENU_BAR_HEIGHT);
var viewAreaBottom:Float = this.scrollPositionInPixels + viewHeight;
// Calculate the top and bottom of the view area.
var viewAreaTopPixels:Float = MENU_BAR_HEIGHT;
var visibleGridHeightPixels:Float = FlxG.height - MENU_BAR_HEIGHT - PLAYBAR_HEIGHT; // The area underneath the menu bar and playbar is not visible.
var viewAreaBottomPixels:Float = viewAreaTopPixels + visibleGridHeightPixels;
// Remove notes that are no longer visible and list the ones that are.
var displayedNoteData:Array<SongNoteData> = [];
@ -2156,7 +2167,7 @@ class ChartEditorState extends HaxeUIState
{
if (noteSprite == null || !noteSprite.exists || !noteSprite.visible) continue;
if (!noteSprite.isNoteVisible(viewAreaBottom, viewAreaTop))
if (!noteSprite.isNoteVisible(viewAreaBottomPixels, viewAreaTopPixels))
{
// This sprite is off-screen.
// Kill the note sprite and recycle it.
@ -2168,18 +2179,6 @@ class ChartEditorState extends HaxeUIState
// Kill the note sprite and recycle it.
noteSprite.noteData = null;
}
// else if (noteSprite.noteData.length > 0 && (noteSprite.parentNoteSprite == null && noteSprite.childNoteSprite == null))
// {
// // Note was extended.
// // Kill the note sprite and recycle it.
// noteSprite.noteData = null;
// }
// else if (noteSprite.noteData.length == 0 && (noteSprite.parentNoteSprite != null || noteSprite.childNoteSprite != null))
// {
// // Note was shortened.
// // Kill the note sprite and recycle it.
// noteSprite.noteData = null;
// }
else
{
// Note is already displayed and should remain displayed.
@ -2190,13 +2189,42 @@ class ChartEditorState extends HaxeUIState
}
}
var displayedHoldNoteData:Array<SongNoteData> = [];
for (holdNoteSprite in renderedHoldNotes.members)
{
if (holdNoteSprite == null || !holdNoteSprite.exists || !holdNoteSprite.visible) continue;
if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD))
{
holdNoteSprite.kill();
}
else if (currentSongChartNoteData.indexOf(holdNoteSprite.noteData) == -1 || holdNoteSprite.noteData.length == 0)
{
// This hold note was deleted.
// Kill the hold note sprite and recycle it.
holdNoteSprite.kill();
}
else if (displayedHoldNoteData.indexOf(holdNoteSprite.noteData) != -1)
{
// This hold note is a duplicate.
// Kill the hold note sprite and recycle it.
holdNoteSprite.kill();
}
else
{
displayedHoldNoteData.push(holdNoteSprite.noteData);
// Update the event sprite's position.
holdNoteSprite.updateHoldNotePosition(renderedNotes);
}
}
// Remove events that are no longer visible and list the ones that are.
var displayedEventData:Array<SongEventData> = [];
for (eventSprite in renderedEvents.members)
{
if (eventSprite == null || !eventSprite.exists || !eventSprite.visible) continue;
if (!eventSprite.isEventVisible(viewAreaBottom, viewAreaTop))
if (!eventSprite.isEventVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD))
{
// This sprite is off-screen.
// Kill the event sprite and recycle it.
@ -2227,63 +2255,36 @@ class ChartEditorState extends HaxeUIState
continue;
}
// Get the position the note should be at.
var noteTimePixels:Float = noteData.stepTime * GRID_SIZE;
// Make sure the note appears when scrolling up.
var modifiedViewAreaTop = viewAreaTop - GRID_SIZE;
if (noteTimePixels < modifiedViewAreaTop || noteTimePixels > viewAreaBottom) continue;
// Else, this note is visible and we need to render it!
if (!ChartEditorNoteSprite.wouldNoteBeVisible(viewAreaBottomPixels, viewAreaTopPixels, noteData,
renderedNotes)) continue; // Else, this note is visible and we need to render it!
// Get a note sprite from the pool.
// If we can reuse a deleted note, do so.
// If a new note is needed, call buildNoteSprite.
var noteSprite:ChartEditorNoteSprite = renderedNotes.recycle(() -> new ChartEditorNoteSprite(this));
trace('Creating new Note... (${renderedNotes.members.length})');
noteSprite.parentState = this;
// The note sprite handles animation playback and positioning.
noteSprite.noteData = noteData;
// Setting note data resets position relative to the grid so we fix that.
noteSprite.x += renderedNotes.x;
noteSprite.y += renderedNotes.y;
noteSprite.updateNotePosition(renderedNotes);
// TODO: Replace this with SustainTrail.
if (noteSprite.noteData.length > 0)
// Add hold notes that are now visible (and not already displayed).
if (noteSprite.noteData.length > 0 && displayedHoldNoteData.indexOf(noteData) == -1)
{
var holdNoteSprite:SustainTrail = renderedHoldNotes.recycle(() -> new SustainTrail(this));
var holdNoteSprite:ChartEditorHoldNoteSprite = renderedHoldNotes.recycle(() -> new ChartEditorHoldNoteSprite(this));
trace('Creating new HoldNote... (${renderedHoldNotes.members.length})');
var noteLengthPixels:Float = noteSprite.noteData.stepLength * GRID_SIZE;
// If the note is a hold, we need to make sure it's long enough.
// var noteLengthSteps:Float = ;
// var lastNoteSprite:ChartEditorNoteSprite = noteSprite;
//
// while (noteLengthSteps > 0)
// {
// if (noteLengthSteps <= 1.0)
// {
// // Last note in the hold.
// // TODO: We may need to make it shorter and clip it visually.
// }
//
// var nextNoteSprite:ChartEditorNoteSprite = renderedNotes.recycle(ChartEditorNoteSprite);
// nextNoteSprite.parentState = this;
// nextNoteSprite.parentNoteSprite = lastNoteSprite;
// lastNoteSprite.childNoteSprite = nextNoteSprite;
//
// lastNoteSprite = nextNoteSprite;
//
// noteLengthSteps -= 1;
// }
//
// // Make sure the last note sprite shows the end cap properly.
// lastNoteSprite.childNoteSprite = null;
holdNoteSprite.noteData = noteSprite.noteData;
holdNoteSprite.noteDirection = noteSprite.noteData.getDirection();
// var noteLengthPixels:Float = (noteLengthMs / Conductor.stepLengthMs + 1) * GRID_SIZE;
// add(new FlxSprite(noteSprite.x, noteSprite.y - renderedNotes.y + noteLengthPixels).makeGraphic(40, 2, 0xFFFF0000));
holdNoteSprite.setHeightDirectly(noteLengthPixels);
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
}
}
@ -2296,13 +2297,7 @@ class ChartEditorState extends HaxeUIState
continue;
}
// Get the position the event should be at.
var eventTimePixels:Float = eventData.stepTime * GRID_SIZE;
// Make sure the event appears when scrolling up.
var modifiedViewAreaTop = viewAreaTop - GRID_SIZE;
if (eventTimePixels < modifiedViewAreaTop || eventTimePixels > viewAreaBottom) continue;
if (!ChartEditorEventSprite.wouldEventBeVisible(viewAreaBottomPixels, viewAreaTopPixels, eventData, renderedNotes)) continue;
// Else, this event is visible and we need to render it!
@ -2311,6 +2306,7 @@ class ChartEditorState extends HaxeUIState
// If a new event is needed, call buildEventSprite.
var eventSprite:ChartEditorEventSprite = renderedEvents.recycle(() -> new ChartEditorEventSprite(this), false, true);
eventSprite.parentState = this;
trace('Creating new Event... (${renderedEvents.members.length})');
// The event sprite handles animation playback and positioning.
eventSprite.eventData = eventData;
@ -2320,6 +2316,34 @@ class ChartEditorState extends HaxeUIState
eventSprite.y += renderedEvents.y;
}
// Add hold notes that have been made visible (but not their parents)
for (noteData in currentSongChartNoteData)
{
// Is the note a hold note?
if (noteData.length <= 0) continue;
// Is the hold note rendered already?
if (displayedHoldNoteData.indexOf(noteData) != -1) continue;
// Is the hold note offscreen?
if (!ChartEditorHoldNoteSprite.wouldHoldNoteBeVisible(viewAreaBottomPixels, viewAreaTopPixels, noteData, renderedHoldNotes)) continue;
// Hold note should be rendered.
var holdNoteSprite:ChartEditorHoldNoteSprite = renderedHoldNotes.recycle(() -> new ChartEditorHoldNoteSprite(this));
trace('Creating new HoldNote... (${renderedHoldNotes.members.length})');
var noteLengthPixels:Float = noteData.stepLength * GRID_SIZE;
holdNoteSprite.noteData = noteData;
holdNoteSprite.noteDirection = noteData.getDirection();
holdNoteSprite.setHeightDirectly(noteLengthPixels);
holdNoteSprite.updateHoldNotePosition(renderedHoldNotes);
displayedHoldNoteData.push(noteData);
}
// Destroy all existing selection squares.
for (member in renderedSelectionSquares.members)
{
@ -2958,6 +2982,7 @@ class ChartEditorState extends HaxeUIState
}
// Move the rendered notes to the correct position.
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y);
renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y);
if (gridSpectrogram != null)
@ -2974,6 +2999,7 @@ class ChartEditorState extends HaxeUIState
* Loads an instrumental from an absolute file path, replacing the current instrumental.
*
* @param path The absolute path to the audio file.
*
* @return Success or failure.
*/
public function loadInstrumentalFromPath(path:Path):Bool
@ -3114,7 +3140,7 @@ class ChartEditorState extends HaxeUIState
for (metadata in rawSongMetadata)
{
var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation;
this.songMetadata.set(variation, metadata);
this.songMetadata.set(variation, Reflect.copy(metadata));
}
this.songChartData = new Map<String, SongChartData>();
@ -3154,7 +3180,8 @@ class ChartEditorState extends HaxeUIState
function moveSongToScrollPosition():Void
{
// Update the songPosition in the Conductor.
Conductor.update(scrollPositionInMs);
var targetPos = scrollPositionInMs;
Conductor.update(targetPos);
// Update the songPosition in the audio tracks.
if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs;

View file

@ -66,7 +66,7 @@ class StageBuilderState extends MusicBeatState
// snd.addEventListener(SampleDataEvent.SAMPLE_DATA, sineShit);
// snd.__buffer.
// snd = Assets.getSound(Paths.music('freakyMenu'));
// snd = Assets.getSound(Paths.music('freakyMenu/freakyMenu'));
// for (thing in snd.load)
// thing = Std.int(thing / 2);
// snd.play();

View file

@ -122,6 +122,69 @@ class Constants
*/
public static final DEFAULT_VARIATION:String = 'default';
/**
* The default intensity for camera zooms.
*/
public static final DEFAULT_ZOOM_INTENSITY:Float = 0.015;
/**
* The default rate for camera zooms (in beats per zoom).
*/
public static final DEFAULT_ZOOM_RATE:Int = 4;
/**
* The default BPM for charts, so things don't break if none is specified.
*/
public static final DEFAULT_BPM:Int = 100;
/**
* Default numerator for the time signature.
*/
public static final DEFAULT_TIME_SIGNATURE_NUM:Int = 4;
/**
* Default denominator for the time signature.
*/
public static final DEFAULT_TIME_SIGNATURE_DEN:Int = 4;
/**
* TIMING
*/
// ==============================
/**
* A magic number used when calculating scroll speed and note distances.
*/
public static final PIXELS_PER_MS:Float = 0.45;
/**
* The maximum interval within which a note can be hit, in milliseconds.
*/
public static final HIT_WINDOW_MS:Float = 160;
/**
* Constant for the number of seconds in a minute.
*/
public static final SECS_PER_MIN:Float = 60;
/**
* Constant for the number of milliseconds in a second.
*/
public static final MS_PER_SEC:Float = 1000;
/**
* The number of steps in one beat.
*
* Each beat represents ONE quarter note, so one step is one sixteenth note!
*/
public static final STEPS_PER_BEAT:Int = 4;
/**
* All MP3 decoders introduce a playback delay of `528` samples,
* which at 44,100 Hz (samples per second) is ~12 ms.
*/
public static final MP3_DELAY_MS:Float = 528 / 44100 * Constants.MS_PER_SEC;
/**
* HEALTH VALUES
*/
@ -205,65 +268,12 @@ class Constants
* OTHER
*/
// ==============================
/**
* The separator between an asset library and the asset path.
*/
public static final LIBRARY_SEPARATOR:String = ':';
/**
* The number of seconds in a minute.
*/
public static final SECS_PER_MIN:Int = 60;
/**
* The number of milliseconds in a second.
*/
public static final MS_PER_SEC:Int = 1000;
/**
* The number of microseconds in a millisecond.
*/
public static final US_PER_MS:Int = 1000;
/**
* The number of microseconds in a second.
*/
public static final US_PER_SEC:Int = US_PER_MS * MS_PER_SEC;
/**
* The number of nanoseconds in a microsecond.
*/
public static final NS_PER_US:Int = 1000;
/**
* The number of nanoseconds in a millisecond.
*/
public static final NS_PER_MS:Int = NS_PER_US * US_PER_MS;
/**
* The number of nanoseconds in a second.
*/
public static final NS_PER_SEC:Int = NS_PER_US * US_PER_MS * MS_PER_SEC;
/**
* All MP3 decoders introduce a playback delay of `528` samples,
* which at 44,100 Hz (samples per second) is ~12 ms.
*/
public static final MP3_DELAY_MS:Float = 528 / 44100 * MS_PER_SEC;
/**
* The default BPM of the conductor.
*/
public static final DEFAULT_BPM:Float = 100.0;
public static final DEFAULT_TIME_SIGNATURE_NUM:Int = 4;
public static final DEFAULT_TIME_SIGNATURE_DEN:Int = 4;
public static final STEPS_PER_BEAT:Int = 4;
/**
* OTHER
*/
// ==============================
/**
* The scale factor to use when increasing the size of pixel art graphics.
*/
@ -276,14 +286,4 @@ class Constants
public static final STRUMLINE_X_OFFSET:Float = 48;
public static final STRUMLINE_Y_OFFSET:Float = 24;
/**
* The default intensity for camera zooms.
*/
public static final DEFAULT_ZOOM_INTENSITY:Float = 0.015;
/**
* The default rate for camera zooms (in beats per zoom).
*/
public static final DEFAULT_ZOOM_RATE:Int = 4;
}