2024-01-20 14:07:31 -05:00
package funkin . ui . debug . charting . toolboxes ;
2024-01-27 03:24:49 -05:00
import funkin . audio . SoundGroup ;
2024-01-20 14:07:31 -05:00
import haxe . ui . components . Button ;
import haxe . ui . components . HorizontalSlider ;
import haxe . ui . components . Label ;
2024-02-02 21:53:45 -05:00
import flixel . addons . display . FlxTiledSprite ;
import flixel . math . FlxMath ;
2024-01-20 14:07:31 -05:00
import haxe . ui . components . NumberStepper ;
import haxe . ui . components . Slider ;
2024-02-02 21:53:45 -05:00
import haxe . ui . backend . flixel . components . SpriteWrapper ;
import funkin . ui . debug . charting . commands . SetAudioOffsetCommand ;
2024-01-25 20:23:18 -05:00
import funkin . ui . haxeui . components . WaveformPlayer ;
import funkin . audio . waveform . WaveformDataParser ;
2024-01-27 03:24:49 -05:00
import haxe . ui . containers . VBox ;
2024-02-02 21:53:45 -05:00
import haxe . ui . containers . Absolute ;
2024-01-27 03:24:49 -05:00
import haxe . ui . containers . ScrollView ;
2024-01-20 14:07:31 -05:00
import haxe . ui . containers . Frame ;
2024-01-27 03:24:49 -05:00
import haxe . ui . core . Screen ;
import haxe . ui . events . DragEvent ;
import haxe . ui . events . MouseEvent ;
2024-01-20 14:07:31 -05:00
import haxe . ui . events . UIEvent ;
/ * *
* The toolbox which allows modifying information like Song Title , Scroll Speed , Characters / Stages , and starting BPM .
* /
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
2024-01-27 03:24:49 -05:00
2024-01-20 14:07:31 -05:00
@ : access ( funkin . ui . debug . charting . ChartEditorState )
@ : build ( haxe . ui . ComponentBuilder . build ( " a s s e t s / e x c l u d e / d a t a / u i / c h a r t - e d i t o r / t o o l b o x e s / o f f s e t s . x m l " ) )
class ChartEditorOffsetsToolbox extends ChartEditorBaseToolbox
{
2024-02-02 21:53:45 -05:00
var waveformContainer: Absolute ;
2024-01-27 03:24:49 -05:00
var waveformScrollview: ScrollView ;
2024-01-25 20:23:18 -05:00
var waveformPlayer: WaveformPlayer ;
var waveformOpponent: WaveformPlayer ;
var waveformInstrumental: WaveformPlayer ;
var offsetButtonZoomIn: Button ;
var offsetButtonZoomOut: Button ;
2024-01-27 03:24:49 -05:00
var offsetButtonPause: Button ;
var offsetButtonPlay: Button ;
var offsetButtonStop: Button ;
var offsetStepperPlayer: NumberStepper ;
var offsetStepperOpponent: NumberStepper ;
var offsetStepperInstrumental: NumberStepper ;
2024-02-02 21:53:45 -05:00
var offsetTicksContainer: Absolute ;
var playheadSprite: SpriteWrapper ;
static final TICK_LABEL_X_OFFSET : Float = 4.0 ;
static final PLAYHEAD_RIGHT_PAD : Float = 10.0 ;
2024-01-27 03:24:49 -05:00
2024-01-30 21:50:25 -05:00
static final BASE_SCALE : Float = 64.0 ;
static final MIN_SCALE : Float = 4.0 ;
static final WAVEFORM_ZOOM_MULT : Float = 1.5 ;
2024-01-27 03:24:49 -05:00
2024-02-02 21:53:45 -05:00
static final MAGIC_SCALE_BASE_TIME : Float = 5.0 ;
2024-01-30 21:50:25 -05:00
var waveformScale: Float = BASE_SCALE ;
2024-01-27 03:24:49 -05:00
2024-02-02 21:53:45 -05:00
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 ;
2024-01-27 03:24:49 -05:00
var audioPreviewTracks: SoundGroup ;
2024-01-25 20:23:18 -05:00
2024-02-02 21:53:45 -05:00
var tickTiledSprite: FlxTiledSprite ;
var tickLabels: Array < Label > = [ ] ;
2024-01-27 03:24:49 -05:00
// Local store of the audio offsets, so we can detect when they change.
var audioPreviewPlayerOffset: Float = 0 ;
var audioPreviewOpponentOffset: Float = 0 ;
var audioPreviewInstrumentalOffset: Float = 0 ;
2024-01-25 20:23:18 -05:00
2024-01-20 14:07:31 -05:00
public function n e w ( chartEditorState2 : ChartEditorState )
{
super ( chartEditorState2 ) ;
initialize ( ) ;
this . onDialogClosed = onClose ;
}
function onClose ( event : UIEvent )
{
chartEditorState . menubarItemToggleToolboxOffsets . selected = false ;
}
function initialize ( ) : Void
{
// Starting position.
// TODO: Save and load this.
this . x = 150 ;
this . y = 250 ;
2024-01-30 21:50:25 -05:00
offsetPlayerVolume . onChange = ( _ ) - > {
var targetVolume = offsetPlayerVolume . value * 2 / 100 ;
setTrackVolume ( PLAYER , targetVolume ) ;
} ;
offsetPlayerMute . onClick = ( _ ) - > {
toggleMuteTrack ( PLAYER ) ;
} ;
offsetPlayerSolo . onClick = ( _ ) - > {
soloTrack ( PLAYER ) ;
} ;
offsetOpponentVolume . onChange = ( _ ) - > {
var targetVolume = offsetOpponentVolume . value * 2 / 100 ;
setTrackVolume ( OPPONENT , targetVolume ) ;
} ;
offsetOpponentMute . onClick = ( _ ) - > {
toggleMuteTrack ( OPPONENT ) ;
} ;
offsetOpponentSolo . onClick = ( _ ) - > {
soloTrack ( OPPONENT ) ;
} ;
offsetInstrumentalVolume . onChange = ( _ ) - > {
var targetVolume = offsetInstrumentalVolume . value * 2 / 100 ;
setTrackVolume ( INSTRUMENTAL , targetVolume ) ;
} ;
offsetInstrumentalMute . onClick = ( _ ) - > {
toggleMuteTrack ( INSTRUMENTAL ) ;
} ;
offsetInstrumentalSolo . onClick = ( _ ) - > {
soloTrack ( INSTRUMENTAL ) ;
} ;
2024-01-25 20:23:18 -05:00
offsetButtonZoomIn . onClick = ( _ ) - > {
zoomWaveformIn ( ) ;
} ;
offsetButtonZoomOut . onClick = ( _ ) - > {
zoomWaveformOut ( ) ;
} ;
2024-01-27 03:24:49 -05:00
offsetButtonPause . onClick = ( _ ) - > {
pauseAudioPreview ( ) ;
} ;
offsetButtonPlay . onClick = ( _ ) - > {
playAudioPreview ( ) ;
} ;
offsetButtonStop . onClick = ( _ ) - > {
stopAudioPreview ( ) ;
} ;
offsetStepperPlayer . onChange = ( event : UIEvent ) - > {
2024-02-02 21:53:45 -05:00
if ( event . value == chartEditorState . currentVocalOffsetPlayer ) return ;
if ( dragWaveform != null ) return ;
chartEditorState . performCommand ( new SetAudioOffsetCommand ( PLAYER , event . value ) ) ;
2024-01-27 03:24:49 -05:00
refresh ( ) ;
}
offsetStepperOpponent . onChange = ( event : UIEvent ) - > {
2024-02-02 21:53:45 -05:00
if ( event . value == chartEditorState . currentVocalOffsetOpponent ) return ;
if ( dragWaveform != null ) return ;
chartEditorState . performCommand ( new SetAudioOffsetCommand ( OPPONENT , event . value ) ) ;
2024-01-27 03:24:49 -05:00
refresh ( ) ;
}
offsetStepperInstrumental . onChange = ( event : UIEvent ) - > {
2024-02-02 21:53:45 -05:00
if ( event . value == chartEditorState . currentInstrumentalOffset ) return ;
if ( dragWaveform != null ) return ;
chartEditorState . performCommand ( new SetAudioOffsetCommand ( INSTRUMENTAL , event . value ) ) ;
2024-01-27 03:24:49 -05:00
refresh ( ) ;
}
waveformScrollview . onScroll = ( _ ) - > {
if ( ! audioPreviewTracks . playing )
{
2024-02-02 21:53:45 -05:00
// Move the playhead if it would go out of view.
var prevPlayheadRelativePos = playheadRelativePos ;
playheadRelativePos = FlxMath . bound ( playheadRelativePos , 0 , waveformScrollview . width - PLAYHEAD_RIGHT_PAD ) ;
var diff = playheadRelativePos - prevPlayheadRelativePos ;
if ( diff != 0 )
{
// We have to change the song time to match the playhead position when we move it.
var currentWaveformIndex: Int = Std . int ( playheadAbsolutePos * ( waveformScale / BASE_SCALE * waveformMagicFactor ) ) ;
var targetSongTimeSeconds: Float = waveformPlayer . waveform . waveformData . indexToSeconds ( currentWaveformIndex ) ;
audioPreviewTracks . time = targetSongTimeSeconds * Constants . MS_PER_SEC ;
}
2024-01-27 03:24:49 -05:00
addOffsetsToAudioPreview ( ) ;
}
e lse
{
// The scrollview probably changed because the song position changed.
// If we try to move the song now it will glitch.
}
// Either way, clipRect has changed, so we need to refresh the waveforms.
refresh ( ) ;
} ;
2024-01-25 20:23:18 -05:00
2024-02-02 21:53:45 -05:00
initializeTicks ( ) ;
2024-01-27 03:24:49 -05:00
refreshAudioPreview ( ) ;
2024-02-02 21:53:45 -05:00
refresh ( ) ;
refreshTicks ( ) ;
2024-01-27 03:24:49 -05:00
waveformPlayer . registerEvent ( MouseEvent . MOUSE_DOWN , ( _ ) - > {
onStartDragWaveform ( PLAYER ) ;
} ) ;
waveformOpponent . registerEvent ( MouseEvent . MOUSE_DOWN , ( _ ) - > {
onStartDragWaveform ( OPPONENT ) ;
} ) ;
waveformInstrumental . registerEvent ( MouseEvent . MOUSE_DOWN , ( _ ) - > {
onStartDragWaveform ( INSTRUMENTAL ) ;
} ) ;
2024-02-02 21:53:45 -05:00
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 ;
2024-01-27 03:24:49 -05:00
}
/ * *
* Pull the audio tracks from the chart editor state and create copies of them to play in the Offsets Toolbox .
* These must be DEEP CLONES or e lse the editor will affect the audio preview !
* /
public function refreshAudioPreview ( ) : Void
{
if ( audioPreviewTracks == null )
{
audioPreviewTracks = new SoundGroup ( ) ;
// Make sure audioPreviewTracks (and all its children) receives update() calls.
chartEditorState . add ( audioPreviewTracks ) ;
}
e lse
{
audioPreviewTracks . stop ( ) ;
audioPreviewTracks . clear ( ) ;
}
2024-02-02 21:53:45 -05:00
var instTrack = chartEditorState . audioInstTrack . clone ( ) ;
audioPreviewTracks . add ( instTrack ) ;
var playerVoice = chartEditorState . audioVocalTrackGroup . getPlayerVoice ( ) ;
if ( playerVoice != null ) audioPreviewTracks . add ( playerVoice . clone ( ) ) ;
var opponentVoice = chartEditorState . audioVocalTrackGroup . getOpponentVoice ( ) ;
if ( opponentVoice != null ) audioPreviewTracks . add ( opponentVoice . clone ( ) ) ;
// Build player waveform.
// waveformPlayer.waveform.forceUpdate = true;
2024-02-17 02:13:11 -05:00
waveformPlayer . waveform . waveformData = playerVoice ? . waveformData ;
2024-02-02 21:53:45 -05:00
// Set the width and duration to render the full waveform, with the clipRect applied we only render a segment of it.
2024-02-17 02:13:11 -05:00
waveformPlayer . waveform . duration = ( playerVoice ? . length ? ? 1000 ) / C o n s t a n t s . M S _ P E R _ S E C ;
2024-02-02 21:53:45 -05:00
// Build opponent waveform.
// waveformOpponent.waveform.forceUpdate = true;
2024-02-13 01:16:09 -05:00
// note: if song only has one set of vocals (Vocals.ogg/mp3) then this is null and crashes charting editor
// so we null check
2024-02-17 02:13:11 -05:00
waveformOpponent . waveform . waveformData = opponentVoice ? . waveformData ;
waveformOpponent . waveform . duration = ( opponentVoice ? . length ? ? 1000 ) / C o n s t a n t s . M S _ P E R _ S E C ;
2024-02-02 21:53:45 -05:00
// Build instrumental waveform.
// waveformInstrumental.waveform.forceUpdate = true;
2024-02-09 14:58:57 -05:00
waveformInstrumental . waveform . waveformData = chartEditorState . audioInstTrack . waveformData ;
2024-02-17 02:13:11 -05:00
waveformInstrumental . waveform . duration = ( instTrack ? . length ? ? 1000 ) / C o n s t a n t s . M S _ P E R _ S E C ;
2024-01-27 03:24:49 -05:00
addOffsetsToAudioPreview ( ) ;
}
2024-02-02 21:53:45 -05:00
public function refreshTicks ( ) : Void
{
while ( tickLabels . length > 0 )
{
var label = tickLabels . pop ( ) ;
offsetTicksContainer . removeComponent ( label ) ;
}
var labelYPos: Float = chartEditorState . offsetTickBitmap . height / 2 ;
var labelHeight: Float = chartEditorState . offsetTickBitmap . height / 2 ;
var numberOfTicks: Int = Math . floor ( waveformInstrumental . waveform . width / chartEditorState . offsetTickBitmap . width * 2 ) + 1 ;
for ( index in 0 ... numberOfTicks )
{
var tickPos = chartEditorState . offsetTickBitmap . width / 2 * index ;
var tickTime = tickPos * ( waveformScale / BASE_SCALE * waveformMagicFactor ) / waveformInstrumental . waveform . waveformData . pointsPerSecond ( ) ;
var tickLabel: Label = new Label ( ) ;
tickLabel . text = formatTime ( tickTime ) ;
tickLabel . styleNames = " o f f s e t - t i c k s - l a b e l " ;
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 } ' ;
}
e lse
{
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 ;
}
2024-01-27 03:24:49 -05:00
public function onStartDragWaveform ( waveform : Waveform ) : Void
{
dragMousePosition = FlxG . mouse . x ;
dragWaveform = waveform ;
Screen . instance . registerEvent ( MouseEvent . MOUSE_MOVE , onDragWaveform ) ;
Screen . instance . registerEvent ( MouseEvent . MOUSE_UP , onStopDragWaveform ) ;
}
2024-02-02 21:53:45 -05:00
var dragMousePosition: Float = 0 ;
var dragWaveform: Waveform = null ;
var dragOffsetMs: Float = 0 ;
2024-01-27 03:24:49 -05:00
public function onDragWaveform ( event : MouseEvent ) : Void
{
var newDragMousePosition = FlxG . mouse . x ;
var deltaMousePosition = newDragMousePosition - dragMousePosition ;
if ( deltaMousePosition == 0 ) return ;
2024-02-02 21:53:45 -05:00
var deltaPixels: Float = deltaMousePosition * ( waveformScale / BASE_SCALE * waveformMagicFactor ) ;
2024-01-27 03:24:49 -05:00
var deltaMilliseconds: Float = switch ( dragWaveform )
{
c ase PLAYER :
deltaPixels / waveformPlayer . waveform . waveformData . pointsPerSecond ( ) * Constants . MS_PER_SEC ;
c ase OPPONENT :
deltaPixels / waveformOpponent . waveform . waveformData . pointsPerSecond ( ) * Constants . MS_PER_SEC ;
c ase INSTRUMENTAL :
deltaPixels / waveformInstrumental . waveform . waveformData . pointsPerSecond ( ) * Constants . MS_PER_SEC ;
} ;
switch ( dragWaveform )
{
c ase PLAYER :
2024-02-02 21:53:45 -05:00
// chartEditorState.currentVocalOffsetPlayer += deltaMilliseconds;
dragOffsetMs += deltaMilliseconds ;
offsetStepperPlayer . value += deltaMilliseconds ;
2024-01-27 03:24:49 -05:00
c ase OPPONENT :
2024-02-02 21:53:45 -05:00
// chartEditorState.currentVocalOffsetOpponent += deltaMilliseconds;
dragOffsetMs += deltaMilliseconds ;
offsetStepperOpponent . value += deltaMilliseconds ;
2024-01-27 03:24:49 -05:00
c ase INSTRUMENTAL :
2024-02-02 21:53:45 -05:00
// chartEditorState.currentInstrumentalOffset += deltaMilliseconds;
dragOffsetMs += deltaMilliseconds ;
offsetStepperInstrumental . value += deltaMilliseconds ;
2024-01-27 03:24:49 -05:00
}
dragMousePosition = newDragMousePosition ;
refresh ( ) ;
}
public function onStopDragWaveform ( event : MouseEvent ) : Void
{
// Stop dragging.
Screen . instance . unregisterEvent ( MouseEvent . MOUSE_MOVE , onDragWaveform ) ;
Screen . instance . unregisterEvent ( MouseEvent . MOUSE_UP , onStopDragWaveform ) ;
2024-02-02 21:53:45 -05:00
// 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 )
{
c ase PLAYER :
chartEditorState . performCommand ( new SetAudioOffsetCommand ( PLAYER , chartEditorState . currentVocalOffsetPlayer + dragOffsetMs , false ) ) ;
c ase OPPONENT :
chartEditorState . performCommand ( new SetAudioOffsetCommand ( OPPONENT , chartEditorState . currentVocalOffsetOpponent + dragOffsetMs , false ) ) ;
c ase INSTRUMENTAL :
chartEditorState . performCommand ( new SetAudioOffsetCommand ( INSTRUMENTAL , chartEditorState . currentInstrumentalOffset + dragOffsetMs , false ) ) ;
}
}
dragOffsetMs = 0 ;
2024-01-27 03:24:49 -05:00
dragMousePosition = 0 ;
dragWaveform = null ;
2024-02-02 21:53:45 -05:00
refresh ( ) ;
addOffsetsToAudioPreview ( ) ;
2024-01-27 03:24:49 -05:00
}
public function playAudioPreview ( ) : Void
{
2024-02-02 21:53:45 -05:00
audioPreviewTracks . play ( false , audioPreviewTracks . time ) ;
2024-01-27 03:24:49 -05:00
}
public function addOffsetsToAudioPreview ( ) : Void
{
var trackInst = audioPreviewTracks . members [ 0 ] ;
if ( trackInst != null )
{
audioPreviewInstrumentalOffset = chartEditorState . currentInstrumentalOffset ;
trackInst . time -= audioPreviewInstrumentalOffset ;
}
var trackPlayer = audioPreviewTracks . members [ 1 ] ;
if ( trackPlayer != null )
{
audioPreviewPlayerOffset = chartEditorState . currentVocalOffsetPlayer ;
trackPlayer . time -= audioPreviewPlayerOffset ;
}
var trackOpponent = audioPreviewTracks . members [ 2 ] ;
if ( trackOpponent != null )
{
audioPreviewOpponentOffset = chartEditorState . currentVocalOffsetOpponent ;
trackOpponent . time -= audioPreviewOpponentOffset ;
}
}
public function pauseAudioPreview ( ) : Void
{
audioPreviewTracks . pause ( ) ;
}
public function stopAudioPreview ( ) : Void
{
audioPreviewTracks . stop ( ) ;
audioPreviewTracks . time = 0 ;
var trackInst = audioPreviewTracks . members [ 0 ] ;
if ( trackInst != null )
{
audioPreviewInstrumentalOffset = chartEditorState . currentInstrumentalOffset ;
trackInst . time = - audioPreviewInstrumentalOffset ;
}
var trackPlayer = audioPreviewTracks . members [ 1 ] ;
if ( trackPlayer != null )
{
audioPreviewPlayerOffset = chartEditorState . currentVocalOffsetPlayer ;
trackPlayer . time = - audioPreviewPlayerOffset ;
}
var trackOpponent = audioPreviewTracks . members [ 2 ] ;
if ( trackOpponent != null )
{
audioPreviewOpponentOffset = chartEditorState . currentVocalOffsetOpponent ;
trackOpponent . time = - audioPreviewOpponentOffset ;
}
waveformScrollview . hscrollPos = 0 ;
2024-02-02 21:53:45 -05:00
playheadAbsolutePos = 0 + playheadSprite . width ;
2024-01-25 20:23:18 -05:00
refresh ( ) ;
2024-02-02 21:53:45 -05:00
addOffsetsToAudioPreview ( ) ;
2024-01-25 20:23:18 -05:00
}
public function zoomWaveformIn ( ) : Void
{
2024-02-02 21:53:45 -05:00
if ( waveformScale > MIN_SCALE )
2024-01-25 20:23:18 -05:00
{
2024-01-30 21:50:25 -05:00
waveformScale = waveformScale / WAVEFORM_ZOOM_MULT ;
if ( waveformScale < MIN_SCALE ) waveformScale = MIN_SCALE ;
2024-02-02 21:53:45 -05:00
// 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 ( ) ;
2024-01-25 20:23:18 -05:00
}
e lse
{
2024-02-02 21:53:45 -05:00
waveformScale = MIN_SCALE ;
2024-01-25 20:23:18 -05:00
}
}
public function zoomWaveformOut ( ) : Void
{
2024-01-30 21:50:25 -05:00
waveformScale = waveformScale * WAVEFORM_ZOOM_MULT ;
if ( waveformScale < MIN_SCALE ) waveformScale = MIN_SCALE ;
2024-01-25 20:23:18 -05:00
2024-02-02 21:53:45 -05:00
// 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 ;
2024-01-20 14:07:31 -05:00
refresh ( ) ;
2024-02-02 21:53:45 -05:00
refreshTicks ( ) ;
2024-01-20 14:07:31 -05:00
}
2024-01-30 21:50:25 -05:00
public function setTrackVolume ( target : Waveform , volume : Float ) : Void
{
switch ( target )
{
c ase Waveform . INSTRUMENTAL :
var trackInst = audioPreviewTracks . members [ 0 ] ;
if ( trackInst != null )
{
trackInst . volume = volume ;
}
c ase Waveform . PLAYER :
var trackPlayer = audioPreviewTracks . members [ 1 ] ;
if ( trackPlayer != null )
{
trackPlayer . volume = volume ;
}
c ase Waveform . OPPONENT :
var trackOpponent = audioPreviewTracks . members [ 2 ] ;
if ( trackOpponent != null )
{
trackOpponent . volume = volume ;
}
}
}
public function muteTrack ( target : Waveform ) : Void
{
switch ( target )
{
c ase Waveform . INSTRUMENTAL :
var trackInst = audioPreviewTracks . members [ 0 ] ;
if ( trackInst != null )
{
trackInst . muted = true ;
offsetInstrumentalMute . text = trackInst . muted ? " U n m u t e " : " M u t e " ;
}
c ase Waveform . PLAYER :
var trackPlayer = audioPreviewTracks . members [ 1 ] ;
if ( trackPlayer != null )
{
trackPlayer . muted = true ;
offsetPlayerMute . text = trackPlayer . muted ? " U n m u t e " : " M u t e " ;
}
c ase Waveform . OPPONENT :
var trackOpponent = audioPreviewTracks . members [ 2 ] ;
if ( trackOpponent != null )
{
trackOpponent . muted = true ;
offsetOpponentMute . text = trackOpponent . muted ? " U n m u t e " : " M u t e " ;
}
}
}
public function unmuteTrack ( target : Waveform ) : Void
{
switch ( target )
{
c ase Waveform . INSTRUMENTAL :
var trackInst = audioPreviewTracks . members [ 0 ] ;
if ( trackInst != null )
{
trackInst . muted = false ;
offsetInstrumentalMute . text = trackInst . muted ? " U n m u t e " : " M u t e " ;
}
c ase Waveform . PLAYER :
var trackPlayer = audioPreviewTracks . members [ 1 ] ;
if ( trackPlayer != null )
{
trackPlayer . muted = false ;
offsetPlayerMute . text = trackPlayer . muted ? " U n m u t e " : " M u t e " ;
}
c ase Waveform . OPPONENT :
var trackOpponent = audioPreviewTracks . members [ 2 ] ;
if ( trackOpponent != null )
{
trackOpponent . muted = false ;
offsetOpponentMute . text = trackOpponent . muted ? " U n m u t e " : " M u t e " ;
}
}
}
public function toggleMuteTrack ( target : Waveform ) : Void
{
switch ( target )
{
c ase Waveform . INSTRUMENTAL :
var trackInst = audioPreviewTracks . members [ 0 ] ;
if ( trackInst != null )
{
trackInst . muted = ! trackInst . muted ;
offsetInstrumentalMute . text = trackInst . muted ? " U n m u t e " : " M u t e " ;
}
c ase Waveform . PLAYER :
var trackPlayer = audioPreviewTracks . members [ 1 ] ;
if ( trackPlayer != null )
{
trackPlayer . muted = ! trackPlayer . muted ;
offsetPlayerMute . text = trackPlayer . muted ? " U n m u t e " : " M u t e " ;
}
c ase Waveform . OPPONENT :
var trackOpponent = audioPreviewTracks . members [ 2 ] ;
if ( trackOpponent != null )
{
trackOpponent . muted = ! trackOpponent . muted ;
offsetOpponentMute . text = trackOpponent . muted ? " U n m u t e " : " M u t e " ;
}
}
}
/ * *
* Clicking the solo button will unmute the track and mute all other tracks .
* @ param target
* /
public function soloTrack ( target : Waveform ) : Void
{
switch ( target )
{
c ase Waveform . PLAYER :
muteTrack ( Waveform . OPPONENT ) ;
muteTrack ( Waveform . INSTRUMENTAL ) ;
unmuteTrack ( Waveform . PLAYER ) ;
c ase Waveform . OPPONENT :
muteTrack ( Waveform . PLAYER ) ;
muteTrack ( Waveform . INSTRUMENTAL ) ;
unmuteTrack ( Waveform . OPPONENT ) ;
c ase Waveform . INSTRUMENTAL :
muteTrack ( Waveform . PLAYER ) ;
muteTrack ( Waveform . OPPONENT ) ;
unmuteTrack ( Waveform . INSTRUMENTAL ) ;
}
}
2024-01-27 03:24:49 -05:00
public override function update ( elapsed : Float )
{
super . update ( elapsed ) ;
if ( audioPreviewTracks . playing )
{
trace ( ' P l a y b a c k t i m e : ${ audioPreviewTracks . time } ' ) ;
2024-02-02 21:53:45 -05:00
var targetScrollPos: Float = waveformInstrumental . waveform . waveformData . secondsToIndex ( audioPreviewTracks . time / Constants . MS_PER_SEC ) / ( waveformScale / BASE_SCALE * waveformMagicFactor ) ;
// waveformScrollview.hscrollPos = targetScrollPos;
var prevPlayheadAbsolutePos = playheadAbsolutePos ;
playheadAbsolutePos = targetScrollPos ;
var playheadDiff = playheadAbsolutePos - prevPlayheadAbsolutePos ;
// BEHAVIOR A.
// Just move the scroll view with the playhead, constraining it so that the playhead is always visible.
// waveformScrollview.hscrollPos += playheadDiff;
// waveformScrollview.hscrollPos = FlxMath.bound(waveformScrollview.hscrollPos, playheadAbsolutePos - playheadSprite.width, playheadAbsolutePos);
// BEHAVIOR B.
// Keep `playheadAbsolutePos` within the bounds of the screen.
// The scroll view will eventually move to where the playhead is 1/8th of the way from the left. This looks kinda nice!
// TODO: This causes a hard snap to scroll when the playhead is to the right of the playheadCenterPoint.
// var playheadCenterPoint = waveformScrollview.width / 8;
// waveformScrollview.hscrollPos = FlxMath.bound(waveformScrollview.hscrollPos, playheadAbsolutePos - playheadCenterPoint, playheadAbsolutePos);
// playheadRelativePos = 0;
// BEHAVIOR C.
// Copy Audacity!
// If the playhead is out of view, jump forward or backward by one screen width until it's in view.
if ( playheadAbsolutePos < waveformScrollview . hscrollPos )
{
waveformScrollview . hscrollPos -= waveformScrollview . width ;
}
if ( playheadAbsolutePos > waveformScrollview . hscrollPos + waveformScrollview . width )
{
waveformScrollview . hscrollPos += waveformScrollview . width ;
}
2024-01-27 03:24:49 -05:00
}
if ( chartEditorState . currentInstrumentalOffset != audioPreviewInstrumentalOffset )
{
var track = audioPreviewTracks . members [ 0 ] ;
if ( track != null )
{
track . time += audioPreviewInstrumentalOffset ;
track . time -= chartEditorState . currentInstrumentalOffset ;
audioPreviewInstrumentalOffset = chartEditorState . currentInstrumentalOffset ;
}
}
if ( chartEditorState . currentVocalOffsetPlayer != audioPreviewPlayerOffset )
{
var track = audioPreviewTracks . members [ 1 ] ;
if ( track != null )
{
track . time += audioPreviewPlayerOffset ;
track . time -= chartEditorState . currentVocalOffsetPlayer ;
audioPreviewPlayerOffset = chartEditorState . currentVocalOffsetPlayer ;
}
}
if ( chartEditorState . currentVocalOffsetOpponent != audioPreviewOpponentOffset )
{
var track = audioPreviewTracks . members [ 2 ] ;
if ( track != null )
{
track . time += audioPreviewOpponentOffset ;
track . time -= chartEditorState . currentVocalOffsetOpponent ;
audioPreviewOpponentOffset = chartEditorState . currentVocalOffsetOpponent ;
}
}
2024-02-09 14:58:57 -05:00
offsetLabelTime . text = formatTime ( audioPreviewTracks . time / Constants . MS_PER_SEC ) ;
2024-02-02 21:53:45 -05:00
// Keep the playhead in view.
// playheadRelativePos = FlxMath.bound(playheadRelativePos, waveformScrollview.hscrollPos + 1,
// Math.min(waveformScrollview.hscrollPos + waveformScrollview.width, waveformContainer.width));
2024-01-27 03:24:49 -05:00
}
2024-01-20 14:07:31 -05:00
public override function refresh ( ) : Void
{
super . refresh ( ) ;
2024-01-25 20:23:18 -05:00
2024-02-02 21:53:45 -05:00
waveformMagicFactor = MAGIC_SCALE_BASE_TIME / ( chartEditorState . offsetTickBitmap . width / waveformInstrumental . waveform . waveformData . pointsPerSecond ( ) ) ;
var currentZoomFactor = waveformScale / BASE_SCALE * waveformMagicFactor ;
2024-01-25 20:23:18 -05:00
2024-01-27 03:24:49 -05:00
var maxWidth: Int = - 1 ;
offsetStepperPlayer . value = chartEditorState . currentVocalOffsetPlayer ;
offsetStepperOpponent . value = chartEditorState . currentVocalOffsetOpponent ;
offsetStepperInstrumental . value = chartEditorState . currentInstrumentalOffset ;
2024-01-25 20:23:18 -05:00
2024-01-27 03:24:49 -05:00
waveformPlayer . waveform . time = - chartEditorState . currentVocalOffsetPlayer / Constants . MS_PER_SEC ; // Negative offsets make the song start early.
2024-02-02 21:53:45 -05:00
waveformPlayer . waveform . width = ( waveformPlayer . waveform . waveformData ? . length ? ? 1000 ) / c u r r e n t Z o o m F a c t o r ;
2024-01-27 03:24:49 -05:00
if ( waveformPlayer . waveform . width > maxWidth ) maxWidth = Std . int ( waveformPlayer . waveform . width ) ;
2024-01-30 21:50:25 -05:00
waveformPlayer . waveform . height = 65 ;
2024-01-25 20:23:18 -05:00
2024-01-27 03:24:49 -05:00
waveformOpponent . waveform . time = - chartEditorState . currentVocalOffsetOpponent / Constants . MS_PER_SEC ;
2024-02-02 21:53:45 -05:00
waveformOpponent . waveform . width = ( waveformOpponent . waveform . waveformData ? . length ? ? 1000 ) / c u r r e n t Z o o m F a c t o r ;
2024-01-27 03:24:49 -05:00
if ( waveformOpponent . waveform . width > maxWidth ) maxWidth = Std . int ( waveformOpponent . waveform . width ) ;
2024-01-30 21:50:25 -05:00
waveformOpponent . waveform . height = 65 ;
2024-01-27 03:24:49 -05:00
waveformInstrumental . waveform . time = - chartEditorState . currentInstrumentalOffset / Constants . MS_PER_SEC ;
2024-02-02 21:53:45 -05:00
waveformInstrumental . waveform . width = ( waveformInstrumental . waveform . waveformData ? . length ? ? 1000 ) / c u r r e n t Z o o m F a c t o r ;
2024-01-27 03:24:49 -05:00
if ( waveformInstrumental . waveform . width > maxWidth ) maxWidth = Std . int ( waveformInstrumental . waveform . width ) ;
2024-01-30 21:50:25 -05:00
waveformInstrumental . waveform . height = 65 ;
2024-01-27 03:24:49 -05:00
2024-02-02 21:53:45 -05:00
// 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 )
{
c ase PLAYER :
// chartEditorState.currentVocalOffsetPlayer += deltaMilliseconds;
waveformPlayer . waveform . time -= dragOffsetMs / Constants . MS_PER_SEC ;
offsetStepperPlayer . value += dragOffsetMs ;
c ase OPPONENT :
// chartEditorState.currentVocalOffsetOpponent += deltaMilliseconds;
waveformOpponent . waveform . time -= dragOffsetMs / Constants . MS_PER_SEC ;
offsetStepperOpponent . value += dragOffsetMs ;
c ase INSTRUMENTAL :
// chartEditorState.currentInstrumentalOffset += deltaMilliseconds;
waveformInstrumental . waveform . time -= dragOffsetMs / Constants . MS_PER_SEC ;
offsetStepperInstrumental . value += dragOffsetMs ;
d efault :
// No drag, no
}
2024-01-27 03:24:49 -05:00
waveformPlayer . waveform . markDirty ( ) ;
waveformOpponent . waveform . markDirty ( ) ;
waveformInstrumental . waveform . markDirty ( ) ;
waveformContainer . width = maxWidth ;
2024-02-02 21:53:45 -05:00
tickTiledSprite . width = maxWidth ;
2024-01-20 14:07:31 -05:00
}
public static function build ( chartEditorState : ChartEditorState ) : ChartEditorOffsetsToolbox
{
return new ChartEditorOffsetsToolbox ( chartEditorState ) ;
}
}
2024-01-27 03:24:49 -05:00
enum Waveform
{
PLAYER ;
OPPONENT ;
INSTRUMENTAL ;
}