mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Completed functionality for offset window.
This commit is contained in:
parent
f74604c48e
commit
f6f9ad3985
7 changed files with 488 additions and 99 deletions
|
@ -113,7 +113,7 @@ class WaveformData
|
||||||
*/
|
*/
|
||||||
public function lenSeconds():Float
|
public function lenSeconds():Float
|
||||||
{
|
{
|
||||||
return lenSamples() / sampleRate;
|
return inline lenSamples() / sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,7 +121,7 @@ class WaveformData
|
||||||
*/
|
*/
|
||||||
public function secondsToIndex(seconds:Float):Int
|
public function secondsToIndex(seconds:Float):Int
|
||||||
{
|
{
|
||||||
return Std.int(seconds * pointsPerSecond());
|
return Std.int(seconds * inline pointsPerSecond());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -129,7 +129,7 @@ class WaveformData
|
||||||
*/
|
*/
|
||||||
public function indexToSeconds(index:Int):Float
|
public function indexToSeconds(index:Int):Float
|
||||||
{
|
{
|
||||||
return index / pointsPerSecond();
|
return index / inline pointsPerSecond();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,7 +216,7 @@ class WaveformDataChannel
|
||||||
public function minSample(i:Int)
|
public function minSample(i:Int)
|
||||||
{
|
{
|
||||||
var offset = (i * parent.channels + this.channelId) * 2;
|
var offset = (i * parent.channels + this.channelId) * 2;
|
||||||
return parent.get(offset);
|
return inline parent.get(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -224,7 +224,7 @@ class WaveformDataChannel
|
||||||
*/
|
*/
|
||||||
public function minSampleMapped(i:Int)
|
public function minSampleMapped(i:Int)
|
||||||
{
|
{
|
||||||
return minSample(i) / parent.maxSampleValue();
|
return inline minSample(i) / inline parent.maxSampleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,10 +233,10 @@ class WaveformDataChannel
|
||||||
*/
|
*/
|
||||||
public function minSampleRange(start:Int, end:Int)
|
public function minSampleRange(start:Int, end:Int)
|
||||||
{
|
{
|
||||||
var min = parent.maxSampleValue();
|
var min = inline parent.maxSampleValue();
|
||||||
for (i in start...end)
|
for (i in start...end)
|
||||||
{
|
{
|
||||||
var sample = minSample(i);
|
var sample = inline minSample(i);
|
||||||
if (sample < min) min = sample;
|
if (sample < min) min = sample;
|
||||||
}
|
}
|
||||||
return min;
|
return min;
|
||||||
|
@ -247,7 +247,7 @@ class WaveformDataChannel
|
||||||
*/
|
*/
|
||||||
public function minSampleRangeMapped(start:Int, end:Int)
|
public function minSampleRangeMapped(start:Int, end:Int)
|
||||||
{
|
{
|
||||||
return minSampleRange(start, end) / parent.maxSampleValue();
|
return inline minSampleRange(start, end) / inline parent.maxSampleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -256,7 +256,7 @@ class WaveformDataChannel
|
||||||
public function maxSample(i:Int)
|
public function maxSample(i:Int)
|
||||||
{
|
{
|
||||||
var offset = (i * parent.channels + this.channelId) * 2 + 1;
|
var offset = (i * parent.channels + this.channelId) * 2 + 1;
|
||||||
return parent.get(offset);
|
return inline parent.get(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -264,7 +264,7 @@ class WaveformDataChannel
|
||||||
*/
|
*/
|
||||||
public function maxSampleMapped(i:Int)
|
public function maxSampleMapped(i:Int)
|
||||||
{
|
{
|
||||||
return maxSample(i) / parent.maxSampleValue();
|
return inline maxSample(i) / inline parent.maxSampleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -273,10 +273,10 @@ class WaveformDataChannel
|
||||||
*/
|
*/
|
||||||
public function maxSampleRange(start:Int, end:Int)
|
public function maxSampleRange(start:Int, end:Int)
|
||||||
{
|
{
|
||||||
var max = -parent.maxSampleValue();
|
var max = -(inline parent.maxSampleValue());
|
||||||
for (i in start...end)
|
for (i in start...end)
|
||||||
{
|
{
|
||||||
var sample = maxSample(i);
|
var sample = inline maxSample(i);
|
||||||
if (sample > max) max = sample;
|
if (sample > max) max = sample;
|
||||||
}
|
}
|
||||||
return max;
|
return max;
|
||||||
|
@ -287,18 +287,18 @@ class WaveformDataChannel
|
||||||
*/
|
*/
|
||||||
public function maxSampleRangeMapped(start:Int, end:Int)
|
public function maxSampleRangeMapped(start:Int, end:Int)
|
||||||
{
|
{
|
||||||
return maxSampleRange(start, end) / parent.maxSampleValue();
|
return inline maxSampleRange(start, end) / inline parent.maxSampleValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMinSample(i:Int, value:Int)
|
public function setMinSample(i:Int, value:Int)
|
||||||
{
|
{
|
||||||
var offset = (i * parent.channels + this.channelId) * 2;
|
var offset = (i * parent.channels + this.channelId) * 2;
|
||||||
parent.set(offset, value);
|
inline parent.set(offset, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMaxSample(i:Int, value:Int)
|
public function setMaxSample(i:Int, value:Int)
|
||||||
{
|
{
|
||||||
var offset = (i * parent.channels + this.channelId) * 2 + 1;
|
var offset = (i * parent.channels + this.channelId) * 2 + 1;
|
||||||
parent.set(offset, value);
|
inline parent.set(offset, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import funkin.ui.debug.charting.toolboxes.ChartEditorOffsetsToolbox;
|
||||||
import funkin.util.logging.CrashHandler;
|
import funkin.util.logging.CrashHandler;
|
||||||
import haxe.ui.containers.HBox;
|
import haxe.ui.containers.HBox;
|
||||||
import haxe.ui.containers.Grid;
|
import haxe.ui.containers.Grid;
|
||||||
|
@ -1098,7 +1099,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
* `null` until vocal track(s) are loaded.
|
* `null` until vocal track(s) are loaded.
|
||||||
* When switching characters, the elements of the VoicesGroup will be swapped to match the new character.
|
* 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 waveform visualization for the inst/vocals.
|
* The audio waveform visualization for the inst/vocals.
|
||||||
|
@ -2257,8 +2258,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
// Initialize the song chart data.
|
// Initialize the song chart data.
|
||||||
songChartData = new Map<String, SongChartData>();
|
songChartData = new Map<String, SongChartData>();
|
||||||
|
|
||||||
audioVocalTrackGroup = new VoicesGroup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2889,13 +2888,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
menubarItemVolumeVocalsPlayer.onChange = event -> {
|
menubarItemVolumeVocalsPlayer.onChange = event -> {
|
||||||
var volume:Float = event.value.toFloat() / 100.0;
|
var volume:Float = event.value.toFloat() / 100.0;
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.playerVolume = volume;
|
audioVocalTrackGroup.playerVolume = volume;
|
||||||
menubarLabelVolumeVocalsPlayer.text = 'Player - ${Std.int(event.value)}%';
|
menubarLabelVolumeVocalsPlayer.text = 'Player - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
|
|
||||||
menubarItemVolumeVocalsOpponent.onChange = event -> {
|
menubarItemVolumeVocalsOpponent.onChange = event -> {
|
||||||
var volume:Float = event.value.toFloat() / 100.0;
|
var volume:Float = event.value.toFloat() / 100.0;
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.opponentVolume = volume;
|
audioVocalTrackGroup.opponentVolume = volume;
|
||||||
menubarLabelVolumeVocalsOpponent.text = 'Enemy - ${Std.int(event.value)}%';
|
menubarLabelVolumeVocalsOpponent.text = 'Enemy - ${Std.int(event.value)}%';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2904,7 +2903,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
||||||
#if FLX_PITCH
|
#if FLX_PITCH
|
||||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pitch = pitch;
|
audioVocalTrackGroup.pitch = pitch;
|
||||||
#end
|
#end
|
||||||
var pitchDisplay:Float = Std.int(pitch * 100) / 100; // Round to 2 decimal places.
|
var pitchDisplay:Float = Std.int(pitch * 100) / 100; // Round to 2 decimal places.
|
||||||
menubarLabelPlaybackSpeed.text = 'Playback Speed - ${pitchDisplay}x';
|
menubarLabelPlaybackSpeed.text = 'Playback Speed - ${pitchDisplay}x';
|
||||||
|
@ -3215,7 +3214,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
Conductor.instance.update(audioInstTrack.time);
|
Conductor.instance.update(audioInstTrack.time);
|
||||||
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
||||||
// Resync vocals.
|
// Resync vocals.
|
||||||
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||||
{
|
{
|
||||||
audioVocalTrackGroup.time = audioInstTrack.time;
|
audioVocalTrackGroup.time = audioInstTrack.time;
|
||||||
}
|
}
|
||||||
|
@ -3233,7 +3232,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
Conductor.instance.update(audioInstTrack.time);
|
Conductor.instance.update(audioInstTrack.time);
|
||||||
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
||||||
// Resync vocals.
|
// Resync vocals.
|
||||||
if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100)
|
||||||
{
|
{
|
||||||
audioVocalTrackGroup.time = audioInstTrack.time;
|
audioVocalTrackGroup.time = audioInstTrack.time;
|
||||||
}
|
}
|
||||||
|
@ -5321,7 +5320,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
FlxG.sound.music = audioInstTrack;
|
FlxG.sound.music = audioInstTrack;
|
||||||
}
|
}
|
||||||
if (audioVocalTrackGroup != null) targetState.vocals = audioVocalTrackGroup;
|
targetState.vocals = audioVocalTrackGroup;
|
||||||
|
|
||||||
this.persistentUpdate = false;
|
this.persistentUpdate = false;
|
||||||
this.persistentDraw = false;
|
this.persistentDraw = false;
|
||||||
|
@ -5433,7 +5432,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
if (audioInstTrack != null)
|
if (audioInstTrack != null)
|
||||||
{
|
{
|
||||||
audioInstTrack.play(false, audioInstTrack.time);
|
audioInstTrack.play(false, audioInstTrack.time);
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(false, audioInstTrack.time);
|
audioVocalTrackGroup.play(false, audioInstTrack.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
playbarPlay.text = '||'; // Pause
|
playbarPlay.text = '||'; // Pause
|
||||||
|
@ -5671,7 +5670,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instance.instrumentalOffset;
|
audioInstTrack.time = scrollPositionInMs + playheadPositionInMs - Conductor.instance.instrumentalOffset;
|
||||||
// Update the songPosition in the Conductor.
|
// Update the songPosition in the Conductor.
|
||||||
Conductor.instance.update(audioInstTrack.time);
|
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.
|
// We need to update the note sprites because we changed the scroll position.
|
||||||
|
@ -5892,7 +5891,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
function stopAudioPlayback():Void
|
function stopAudioPlayback():Void
|
||||||
{
|
{
|
||||||
if (audioInstTrack != null) audioInstTrack.pause();
|
if (audioInstTrack != null) audioInstTrack.pause();
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
audioVocalTrackGroup.pause();
|
||||||
|
|
||||||
playbarPlay.text = '>';
|
playbarPlay.text = '>';
|
||||||
}
|
}
|
||||||
|
@ -5927,7 +5926,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Keep the track at the end.
|
// Keep the track at the end.
|
||||||
audioInstTrack.time = audioInstTrack.length;
|
audioInstTrack.time = audioInstTrack.length;
|
||||||
}
|
}
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
audioVocalTrackGroup.pause();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -5941,12 +5940,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
healthIconsDirty = true;
|
healthIconsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hardRefreshOffsetsToolbox():Void
|
||||||
|
{
|
||||||
|
var offsetsToolbox:ChartEditorOffsetsToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT);
|
||||||
|
if (offsetsToolbox != null)
|
||||||
|
{
|
||||||
|
offsetsToolbox.refreshAudioPreview();
|
||||||
|
offsetsToolbox.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the voices group.
|
* Clear the voices group.
|
||||||
*/
|
*/
|
||||||
public function clearVocals():Void
|
public function clearVocals():Void
|
||||||
{
|
{
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.clear();
|
audioVocalTrackGroup.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNoteSelected(note:Null<SongNoteData>):Bool
|
function isNoteSelected(note:Null<SongNoteData>):Bool
|
||||||
|
@ -5971,7 +5980,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Stop the music.
|
// Stop the music.
|
||||||
if (welcomeMusic != null) welcomeMusic.destroy();
|
if (welcomeMusic != null) welcomeMusic.destroy();
|
||||||
if (audioInstTrack != null) audioInstTrack.destroy();
|
if (audioInstTrack != null) audioInstTrack.destroy();
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.destroy();
|
audioVocalTrackGroup.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCanQuickSave():Void
|
function applyCanQuickSave():Void
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -137,6 +137,8 @@ class ChartEditorAudioHandler
|
||||||
result = playVocals(state, DAD, opponentId, instId);
|
result = playVocals(state, DAD, opponentId, instId);
|
||||||
// if (!result) return false;
|
// if (!result) return false;
|
||||||
|
|
||||||
|
state.hardRefreshOffsetsToolbox();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,10 +246,7 @@ class ChartEditorAudioHandler
|
||||||
|
|
||||||
public static function stopExistingVocals(state:ChartEditorState):Void
|
public static function stopExistingVocals(state:ChartEditorState):Void
|
||||||
{
|
{
|
||||||
if (state.audioVocalTrackGroup != null)
|
state.audioVocalTrackGroup.clear();
|
||||||
{
|
|
||||||
state.audioVocalTrackGroup.clear();
|
|
||||||
}
|
|
||||||
if (state.audioWaveforms != null)
|
if (state.audioWaveforms != null)
|
||||||
{
|
{
|
||||||
state.audioWaveforms.clear();
|
state.audioWaveforms.clear();
|
||||||
|
|
|
@ -132,11 +132,8 @@ class ChartEditorImportExportHandler
|
||||||
state.audioInstTrack.stop();
|
state.audioInstTrack.stop();
|
||||||
state.audioInstTrack = null;
|
state.audioInstTrack = null;
|
||||||
}
|
}
|
||||||
if (state.audioVocalTrackGroup != null)
|
state.audioVocalTrackGroup.stop();
|
||||||
{
|
state.audioVocalTrackGroup.clear();
|
||||||
state.audioVocalTrackGroup.stop();
|
|
||||||
state.audioVocalTrackGroup.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -306,10 +306,11 @@ class ChartEditorThemeHandler
|
||||||
// Draw the major ticks.
|
// Draw the major ticks.
|
||||||
var leftTickX:Float = 0;
|
var leftTickX:Float = 0;
|
||||||
var middleTickX:Float = state.offsetTickBitmap.width / 2 - (majorTickWidth / 2);
|
var middleTickX:Float = state.offsetTickBitmap.width / 2 - (majorTickWidth / 2);
|
||||||
var rightTickX:Float = state.offsetTickBitmap.width - majorTickWidth;
|
var rightTickX:Float = state.offsetTickBitmap.width - (majorTickWidth / 2);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(leftTickX, 0, majorTickWidth / 2, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
var majorTickLength:Float = state.offsetTickBitmap.height;
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(middleTickX, 0, majorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
state.offsetTickBitmap.fillRect(new Rectangle(leftTickX, 0, majorTickWidth / 2, majorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(rightTickX, 0, majorTickWidth / 2, state.offsetTickBitmap.height), 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.
|
// Draw the minor ticks.
|
||||||
var minorTick2X:Float = state.offsetTickBitmap.width * 1 / 10 - (minorTickWidth / 2);
|
var minorTick2X:Float = state.offsetTickBitmap.width * 1 / 10 - (minorTickWidth / 2);
|
||||||
|
@ -320,14 +321,15 @@ class ChartEditorThemeHandler
|
||||||
var minorTick8X:Float = state.offsetTickBitmap.width * 7 / 10 - (minorTickWidth / 2);
|
var minorTick8X:Float = state.offsetTickBitmap.width * 7 / 10 - (minorTickWidth / 2);
|
||||||
var minorTick9X:Float = state.offsetTickBitmap.width * 8 / 10 - (minorTickWidth / 2);
|
var minorTick9X:Float = state.offsetTickBitmap.width * 8 / 10 - (minorTickWidth / 2);
|
||||||
var minorTick10X:Float = state.offsetTickBitmap.width * 9 / 10 - (minorTickWidth / 2);
|
var minorTick10X:Float = state.offsetTickBitmap.width * 9 / 10 - (minorTickWidth / 2);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick2X, 0, minorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
var minorTickLength:Float = state.offsetTickBitmap.height * 1 / 3;
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick3X, 0, minorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
state.offsetTickBitmap.fillRect(new Rectangle(minorTick2X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick4X, 0, minorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
state.offsetTickBitmap.fillRect(new Rectangle(minorTick3X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick5X, 0, minorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
state.offsetTickBitmap.fillRect(new Rectangle(minorTick4X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick7X, 0, minorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
state.offsetTickBitmap.fillRect(new Rectangle(minorTick5X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick8X, 0, minorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
state.offsetTickBitmap.fillRect(new Rectangle(minorTick7X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick9X, 0, minorTickWidth, state.offsetTickBitmap.height), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
state.offsetTickBitmap.fillRect(new Rectangle(minorTick8X, 0, minorTickWidth, minorTickLength), GRID_MEASURE_DIVIDER_COLOR_LIGHT);
|
||||||
state.offsetTickBitmap.fillRect(new Rectangle(minorTick10X, 0, minorTickWidth, state.offsetTickBitmap.height), 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.
|
// Draw the offset ticks.
|
||||||
// var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT); // 1 grid squares wide.
|
// var ticksWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT); // 1 grid squares wide.
|
||||||
|
|
|
@ -4,11 +4,16 @@ import funkin.audio.SoundGroup;
|
||||||
import haxe.ui.components.Button;
|
import haxe.ui.components.Button;
|
||||||
import haxe.ui.components.HorizontalSlider;
|
import haxe.ui.components.HorizontalSlider;
|
||||||
import haxe.ui.components.Label;
|
import haxe.ui.components.Label;
|
||||||
|
import flixel.addons.display.FlxTiledSprite;
|
||||||
|
import flixel.math.FlxMath;
|
||||||
import haxe.ui.components.NumberStepper;
|
import haxe.ui.components.NumberStepper;
|
||||||
import haxe.ui.components.Slider;
|
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.ui.haxeui.components.WaveformPlayer;
|
||||||
import funkin.audio.waveform.WaveformDataParser;
|
import funkin.audio.waveform.WaveformDataParser;
|
||||||
import haxe.ui.containers.VBox;
|
import haxe.ui.containers.VBox;
|
||||||
|
import haxe.ui.containers.Absolute;
|
||||||
import haxe.ui.containers.ScrollView;
|
import haxe.ui.containers.ScrollView;
|
||||||
import haxe.ui.containers.Frame;
|
import haxe.ui.containers.Frame;
|
||||||
import haxe.ui.core.Screen;
|
import haxe.ui.core.Screen;
|
||||||
|
@ -25,7 +30,7 @@ import haxe.ui.events.UIEvent;
|
||||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/offsets.xml"))
|
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/offsets.xml"))
|
||||||
class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
{
|
{
|
||||||
var waveformContainer:VBox;
|
var waveformContainer:Absolute;
|
||||||
var waveformScrollview:ScrollView;
|
var waveformScrollview:ScrollView;
|
||||||
var waveformPlayer:WaveformPlayer;
|
var waveformPlayer:WaveformPlayer;
|
||||||
var waveformOpponent:WaveformPlayer;
|
var waveformOpponent:WaveformPlayer;
|
||||||
|
@ -38,15 +43,56 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
var offsetStepperPlayer:NumberStepper;
|
var offsetStepperPlayer:NumberStepper;
|
||||||
var offsetStepperOpponent:NumberStepper;
|
var offsetStepperOpponent:NumberStepper;
|
||||||
var offsetStepperInstrumental: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 BASE_SCALE:Float = 64.0;
|
||||||
static final MIN_SCALE:Float = 4.0;
|
static final MIN_SCALE:Float = 4.0;
|
||||||
static final WAVEFORM_ZOOM_MULT:Float = 1.5;
|
static final WAVEFORM_ZOOM_MULT:Float = 1.5;
|
||||||
|
|
||||||
|
static final MAGIC_SCALE_BASE_TIME:Float = 5.0;
|
||||||
|
|
||||||
var waveformScale:Float = BASE_SCALE;
|
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 audioPreviewTracks:SoundGroup;
|
||||||
|
|
||||||
|
var tickTiledSprite:FlxTiledSprite;
|
||||||
|
|
||||||
|
var tickLabels:Array<Label> = [];
|
||||||
|
|
||||||
// Local store of the audio offsets, so we can detect when they change.
|
// Local store of the audio offsets, so we can detect when they change.
|
||||||
var audioPreviewPlayerOffset:Float = 0;
|
var audioPreviewPlayerOffset:Float = 0;
|
||||||
var audioPreviewOpponentOffset:Float = 0;
|
var audioPreviewOpponentOffset:Float = 0;
|
||||||
|
@ -119,24 +165,43 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
stopAudioPreview();
|
stopAudioPreview();
|
||||||
};
|
};
|
||||||
offsetStepperPlayer.onChange = (event:UIEvent) -> {
|
offsetStepperPlayer.onChange = (event:UIEvent) -> {
|
||||||
chartEditorState.currentVocalOffsetPlayer = event.value;
|
if (event.value == chartEditorState.currentVocalOffsetPlayer) return;
|
||||||
|
if (dragWaveform != null) return;
|
||||||
|
|
||||||
|
chartEditorState.performCommand(new SetAudioOffsetCommand(PLAYER, event.value));
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
offsetStepperOpponent.onChange = (event:UIEvent) -> {
|
offsetStepperOpponent.onChange = (event:UIEvent) -> {
|
||||||
chartEditorState.currentVocalOffsetOpponent = event.value;
|
if (event.value == chartEditorState.currentVocalOffsetOpponent) return;
|
||||||
|
if (dragWaveform != null) return;
|
||||||
|
|
||||||
|
chartEditorState.performCommand(new SetAudioOffsetCommand(OPPONENT, event.value));
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
offsetStepperInstrumental.onChange = (event:UIEvent) -> {
|
offsetStepperInstrumental.onChange = (event:UIEvent) -> {
|
||||||
chartEditorState.currentInstrumentalOffset = event.value;
|
if (event.value == chartEditorState.currentInstrumentalOffset) return;
|
||||||
|
if (dragWaveform != null) return;
|
||||||
|
|
||||||
|
chartEditorState.performCommand(new SetAudioOffsetCommand(INSTRUMENTAL, event.value));
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
waveformScrollview.onScroll = (_) -> {
|
waveformScrollview.onScroll = (_) -> {
|
||||||
if (!audioPreviewTracks.playing)
|
if (!audioPreviewTracks.playing)
|
||||||
{
|
{
|
||||||
// We have to change the song position to match.
|
// Move the playhead if it would go out of view.
|
||||||
var currentWaveformIndex:Int = Std.int(waveformScrollview.hscrollPos / BASE_SCALE * waveformScale);
|
var prevPlayheadRelativePos = playheadRelativePos;
|
||||||
var targetSongTimeSeconds:Float = waveformPlayer.waveform.waveformData.indexToSeconds(currentWaveformIndex);
|
playheadRelativePos = FlxMath.bound(playheadRelativePos, 0, waveformScrollview.width - PLAYHEAD_RIGHT_PAD);
|
||||||
audioPreviewTracks.time = targetSongTimeSeconds * Constants.MS_PER_SEC;
|
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 = waveformPlayer.waveform.waveformData.indexToSeconds(currentWaveformIndex);
|
||||||
|
audioPreviewTracks.time = targetSongTimeSeconds * Constants.MS_PER_SEC;
|
||||||
|
}
|
||||||
|
|
||||||
addOffsetsToAudioPreview();
|
addOffsetsToAudioPreview();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -149,24 +214,11 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
refresh();
|
refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build player waveform.
|
initializeTicks();
|
||||||
// waveformPlayer.waveform.forceUpdate = true;
|
|
||||||
waveformPlayer.waveform.waveformData = chartEditorState.audioVocalTrackGroup.buildPlayerVoiceWaveform();
|
|
||||||
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
|
|
||||||
waveformPlayer.waveform.duration = chartEditorState.audioVocalTrackGroup.getPlayerVoiceLength() / Constants.MS_PER_SEC;
|
|
||||||
|
|
||||||
// Build opponent waveform.
|
|
||||||
// waveformOpponent.waveform.forceUpdate = true;
|
|
||||||
waveformOpponent.waveform.waveformData = chartEditorState.audioVocalTrackGroup.buildOpponentVoiceWaveform();
|
|
||||||
waveformOpponent.waveform.duration = chartEditorState.audioVocalTrackGroup.getOpponentVoiceLength() / Constants.MS_PER_SEC;
|
|
||||||
|
|
||||||
// Build instrumental waveform.
|
|
||||||
// waveformInstrumental.waveform.forceUpdate = true;
|
|
||||||
waveformInstrumental.waveform.waveformData = WaveformDataParser.interpretFlxSound(chartEditorState.audioInstTrack);
|
|
||||||
waveformInstrumental.waveform.duration = chartEditorState.audioInstTrack.length / Constants.MS_PER_SEC;
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
refreshAudioPreview();
|
refreshAudioPreview();
|
||||||
|
refresh();
|
||||||
|
refreshTicks();
|
||||||
|
|
||||||
waveformPlayer.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
waveformPlayer.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||||
onStartDragWaveform(PLAYER);
|
onStartDragWaveform(PLAYER);
|
||||||
|
@ -177,6 +229,17 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
waveformInstrumental.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
waveformInstrumental.registerEvent(MouseEvent.MOUSE_DOWN, (_) -> {
|
||||||
onStartDragWaveform(INSTRUMENTAL);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,15 +260,124 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
audioPreviewTracks.clear();
|
audioPreviewTracks.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
audioPreviewTracks.add(chartEditorState.audioInstTrack.clone());
|
var instTrack = chartEditorState.audioInstTrack.clone();
|
||||||
audioPreviewTracks.add(chartEditorState.audioVocalTrackGroup.getPlayerVoice().clone());
|
audioPreviewTracks.add(instTrack);
|
||||||
audioPreviewTracks.add(chartEditorState.audioVocalTrackGroup.getOpponentVoice().clone());
|
|
||||||
|
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 = WaveformDataParser.interpretFlxSound(playerVoice);
|
||||||
|
// 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 = WaveformDataParser.interpretFlxSound(opponentVoice);
|
||||||
|
waveformOpponent.waveform.duration = opponentVoice.length / Constants.MS_PER_SEC;
|
||||||
|
|
||||||
|
// Build instrumental waveform.
|
||||||
|
// waveformInstrumental.waveform.forceUpdate = true;
|
||||||
|
waveformInstrumental.waveform.waveformData = WaveformDataParser.interpretFlxSound(instTrack);
|
||||||
|
waveformInstrumental.waveform.duration = instTrack.length / Constants.MS_PER_SEC;
|
||||||
|
|
||||||
addOffsetsToAudioPreview();
|
addOffsetsToAudioPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dragMousePosition:Float = 0;
|
public function refreshTicks():Void
|
||||||
var dragWaveform:Waveform = null;
|
{
|
||||||
|
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
|
public function onStartDragWaveform(waveform:Waveform):Void
|
||||||
{
|
{
|
||||||
|
@ -216,6 +388,10 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
Screen.instance.registerEvent(MouseEvent.MOUSE_UP, onStopDragWaveform);
|
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
|
public function onDragWaveform(event:MouseEvent):Void
|
||||||
{
|
{
|
||||||
var newDragMousePosition = FlxG.mouse.x;
|
var newDragMousePosition = FlxG.mouse.x;
|
||||||
|
@ -223,7 +399,7 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
|
|
||||||
if (deltaMousePosition == 0) return;
|
if (deltaMousePosition == 0) return;
|
||||||
|
|
||||||
var deltaPixels:Float = deltaMousePosition / BASE_SCALE * waveformScale;
|
var deltaPixels:Float = deltaMousePosition * (waveformScale / BASE_SCALE * waveformMagicFactor);
|
||||||
var deltaMilliseconds:Float = switch (dragWaveform)
|
var deltaMilliseconds:Float = switch (dragWaveform)
|
||||||
{
|
{
|
||||||
case PLAYER:
|
case PLAYER:
|
||||||
|
@ -239,11 +415,17 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
switch (dragWaveform)
|
switch (dragWaveform)
|
||||||
{
|
{
|
||||||
case PLAYER:
|
case PLAYER:
|
||||||
chartEditorState.currentVocalOffsetPlayer += deltaMilliseconds;
|
// chartEditorState.currentVocalOffsetPlayer += deltaMilliseconds;
|
||||||
|
dragOffsetMs += deltaMilliseconds;
|
||||||
|
offsetStepperPlayer.value += deltaMilliseconds;
|
||||||
case OPPONENT:
|
case OPPONENT:
|
||||||
chartEditorState.currentVocalOffsetOpponent += deltaMilliseconds;
|
// chartEditorState.currentVocalOffsetOpponent += deltaMilliseconds;
|
||||||
|
dragOffsetMs += deltaMilliseconds;
|
||||||
|
offsetStepperOpponent.value += deltaMilliseconds;
|
||||||
case INSTRUMENTAL:
|
case INSTRUMENTAL:
|
||||||
chartEditorState.currentInstrumentalOffset += deltaMilliseconds;
|
// chartEditorState.currentInstrumentalOffset += deltaMilliseconds;
|
||||||
|
dragOffsetMs += deltaMilliseconds;
|
||||||
|
offsetStepperInstrumental.value += deltaMilliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
dragMousePosition = newDragMousePosition;
|
dragMousePosition = newDragMousePosition;
|
||||||
|
@ -257,15 +439,33 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_MOVE, onDragWaveform);
|
Screen.instance.unregisterEvent(MouseEvent.MOUSE_MOVE, onDragWaveform);
|
||||||
Screen.instance.unregisterEvent(MouseEvent.MOUSE_UP, onStopDragWaveform);
|
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;
|
dragMousePosition = 0;
|
||||||
dragWaveform = null;
|
dragWaveform = null;
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
addOffsetsToAudioPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playAudioPreview():Void
|
public function playAudioPreview():Void
|
||||||
{
|
{
|
||||||
// chartEditorState.stopAudioPlayback();
|
audioPreviewTracks.play(false, audioPreviewTracks.time);
|
||||||
|
|
||||||
audioPreviewTracks.resume();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addOffsetsToAudioPreview():Void
|
public function addOffsetsToAudioPreview():Void
|
||||||
|
@ -325,24 +525,34 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
}
|
}
|
||||||
|
|
||||||
waveformScrollview.hscrollPos = 0;
|
waveformScrollview.hscrollPos = 0;
|
||||||
|
playheadAbsolutePos = 0 + playheadSprite.width;
|
||||||
refresh();
|
refresh();
|
||||||
|
addOffsetsToAudioPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function zoomWaveformIn():Void
|
public function zoomWaveformIn():Void
|
||||||
{
|
{
|
||||||
if (waveformScale > 1)
|
if (waveformScale > MIN_SCALE)
|
||||||
{
|
{
|
||||||
waveformScale = waveformScale / WAVEFORM_ZOOM_MULT;
|
waveformScale = waveformScale / WAVEFORM_ZOOM_MULT;
|
||||||
if (waveformScale < MIN_SCALE) waveformScale = MIN_SCALE;
|
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
|
else
|
||||||
{
|
{
|
||||||
waveformScale = 1;
|
waveformScale = MIN_SCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace('Zooming in, scale: ${waveformScale}');
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function zoomWaveformOut():Void
|
public function zoomWaveformOut():Void
|
||||||
|
@ -352,7 +562,15 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
|
|
||||||
trace('Zooming out, scale: ${waveformScale}');
|
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();
|
refresh();
|
||||||
|
refreshTicks();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setTrackVolume(target:Waveform, volume:Float):Void
|
public function setTrackVolume(target:Waveform, volume:Float):Void
|
||||||
|
@ -495,8 +713,37 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
{
|
{
|
||||||
trace('Playback time: ${audioPreviewTracks.time}');
|
trace('Playback time: ${audioPreviewTracks.time}');
|
||||||
|
|
||||||
var targetScrollPos:Float = waveformPlayer.waveform.waveformData.secondsToIndex(audioPreviewTracks.time / Constants.MS_PER_SEC) / waveformScale * BASE_SCALE;
|
var targetScrollPos:Float = waveformInstrumental.waveform.waveformData.secondsToIndex(audioPreviewTracks.time / Constants.MS_PER_SEC) / (waveformScale / BASE_SCALE * waveformMagicFactor);
|
||||||
waveformScrollview.hscrollPos = targetScrollPos;
|
// 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)
|
if (chartEditorState.currentInstrumentalOffset != audioPreviewInstrumentalOffset)
|
||||||
|
@ -529,13 +776,19 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
audioPreviewOpponentOffset = chartEditorState.currentVocalOffsetOpponent;
|
audioPreviewOpponentOffset = chartEditorState.currentVocalOffsetOpponent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
public override function refresh():Void
|
||||||
{
|
{
|
||||||
super.refresh();
|
super.refresh();
|
||||||
|
|
||||||
// Set the width based on the waveformScale value.
|
waveformMagicFactor = MAGIC_SCALE_BASE_TIME / (chartEditorState.offsetTickBitmap.width / waveformInstrumental.waveform.waveformData.pointsPerSecond());
|
||||||
|
|
||||||
|
var currentZoomFactor = waveformScale / BASE_SCALE * waveformMagicFactor;
|
||||||
|
|
||||||
var maxWidth:Int = -1;
|
var maxWidth:Int = -1;
|
||||||
|
|
||||||
|
@ -544,25 +797,45 @@ class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
|
||||||
offsetStepperInstrumental.value = chartEditorState.currentInstrumentalOffset;
|
offsetStepperInstrumental.value = chartEditorState.currentInstrumentalOffset;
|
||||||
|
|
||||||
waveformPlayer.waveform.time = -chartEditorState.currentVocalOffsetPlayer / Constants.MS_PER_SEC; // Negative offsets make the song start early.
|
waveformPlayer.waveform.time = -chartEditorState.currentVocalOffsetPlayer / Constants.MS_PER_SEC; // Negative offsets make the song start early.
|
||||||
waveformPlayer.waveform.width = waveformPlayer.waveform.waveformData.length / waveformScale * BASE_SCALE;
|
waveformPlayer.waveform.width = (waveformPlayer.waveform.waveformData?.length ?? 1000) / currentZoomFactor;
|
||||||
if (waveformPlayer.waveform.width > maxWidth) maxWidth = Std.int(waveformPlayer.waveform.width);
|
if (waveformPlayer.waveform.width > maxWidth) maxWidth = Std.int(waveformPlayer.waveform.width);
|
||||||
waveformPlayer.waveform.height = 65;
|
waveformPlayer.waveform.height = 65;
|
||||||
|
|
||||||
waveformOpponent.waveform.time = -chartEditorState.currentVocalOffsetOpponent / Constants.MS_PER_SEC;
|
waveformOpponent.waveform.time = -chartEditorState.currentVocalOffsetOpponent / Constants.MS_PER_SEC;
|
||||||
waveformOpponent.waveform.width = waveformOpponent.waveform.waveformData.length / waveformScale * BASE_SCALE;
|
waveformOpponent.waveform.width = (waveformOpponent.waveform.waveformData?.length ?? 1000) / currentZoomFactor;
|
||||||
if (waveformOpponent.waveform.width > maxWidth) maxWidth = Std.int(waveformOpponent.waveform.width);
|
if (waveformOpponent.waveform.width > maxWidth) maxWidth = Std.int(waveformOpponent.waveform.width);
|
||||||
waveformOpponent.waveform.height = 65;
|
waveformOpponent.waveform.height = 65;
|
||||||
|
|
||||||
waveformInstrumental.waveform.time = -chartEditorState.currentInstrumentalOffset / Constants.MS_PER_SEC;
|
waveformInstrumental.waveform.time = -chartEditorState.currentInstrumentalOffset / Constants.MS_PER_SEC;
|
||||||
waveformInstrumental.waveform.width = waveformInstrumental.waveform.waveformData.length / waveformScale * BASE_SCALE;
|
waveformInstrumental.waveform.width = (waveformInstrumental.waveform.waveformData?.length ?? 1000) / currentZoomFactor;
|
||||||
if (waveformInstrumental.waveform.width > maxWidth) maxWidth = Std.int(waveformInstrumental.waveform.width);
|
if (waveformInstrumental.waveform.width > maxWidth) maxWidth = Std.int(waveformInstrumental.waveform.width);
|
||||||
waveformInstrumental.waveform.height = 65;
|
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();
|
waveformPlayer.waveform.markDirty();
|
||||||
waveformOpponent.waveform.markDirty();
|
waveformOpponent.waveform.markDirty();
|
||||||
waveformInstrumental.waveform.markDirty();
|
waveformInstrumental.waveform.markDirty();
|
||||||
|
|
||||||
waveformContainer.width = maxWidth;
|
waveformContainer.width = maxWidth;
|
||||||
|
tickTiledSprite.width = maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function build(chartEditorState:ChartEditorState):ChartEditorOffsetsToolbox
|
public static function build(chartEditorState:ChartEditorState):ChartEditorOffsetsToolbox
|
||||||
|
|
Loading…
Reference in a new issue