diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index e73b2860c..e4ff1a3b4 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -405,6 +405,7 @@ class Conductor // Take into account instrumental and file format song offsets. songPos += applyOffsets ? (instrumentalOffset + formatOffset + audioVisualOffset) : 0; + var oldSongPos = this.songPosition; var oldMeasure:Float = this.currentMeasure; var oldBeat:Float = this.currentBeat; var oldStep:Float = this.currentStep; @@ -430,7 +431,8 @@ class Conductor else if (currentTimeChange != null && this.songPosition > 0.0) { // roundDecimal prevents representing 8 as 7.9999999 - this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT) + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6); + this.currentStepTime = FlxMath.roundDecimal((currentTimeChange.beatTime * Constants.STEPS_PER_BEAT) + + (this.songPosition - currentTimeChange.timeStamp) / stepLengthMs, 6); this.currentBeatTime = currentStepTime / Constants.STEPS_PER_BEAT; this.currentMeasureTime = currentStepTime / stepsPerMeasure; this.currentStep = Math.floor(currentStepTime); diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index df05cc3ef..0aef1c38b 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -223,12 +223,12 @@ class FunkinSound extends FlxSound implements ICloneable // already paused before we lost focus. if (_lostFocus && !_alreadyPaused) { - trace('Resuming audio (${this._label}) on focus!'); + // trace('Resuming audio (${this._label}) on focus!'); resume(); } else { - trace('Not resuming audio (${this._label}) on focus!'); + // trace('Not resuming audio (${this._label}) on focus!'); } _lostFocus = false; } @@ -238,7 +238,7 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocusLost():Void { - trace('Focus lost, pausing audio!'); + // trace('Focus lost, pausing audio!'); _lostFocus = true; _alreadyPaused = _paused; pause(); @@ -297,6 +297,11 @@ class FunkinSound extends FlxSound implements ICloneable return sound; } + public override function toString():String + { + return 'FunkinSound(${this._label}, ${this.time})'; + } + /** * Creates a new `FunkinSound` object and loads it as the current music track. * diff --git a/source/funkin/audio/SoundGroup.hx b/source/funkin/audio/SoundGroup.hx index 5fc2abe0e..8caf56a17 100644 --- a/source/funkin/audio/SoundGroup.hx +++ b/source/funkin/audio/SoundGroup.hx @@ -178,9 +178,10 @@ class SoundGroup extends FlxTypedGroup function get_time():Float { - if (getFirstAlive() != null) + var firstAlive:Null = getFirstAlive(); + if (firstAlive != null) { - return getFirstAlive().time; + return firstAlive.time; } else { @@ -260,7 +261,7 @@ class SoundGroup extends FlxTypedGroup function set_pitch(val:Float):Float { #if FLX_PITCH - trace('Setting audio pitch to ' + val); + // trace('Setting audio pitch to ' + val); forEachAlive(function(snd:FunkinSound) { snd.pitch = val; }); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index b75cd8bf1..0ce6f6ee1 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2254,9 +2254,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function setupWelcomeMusic() { - this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop')); - FlxG.sound.list.add(this.welcomeMusic); - this.welcomeMusic.looped = true; + this.welcomeMusic = FunkinSound.load(Paths.music('chartEditorLoop/chartEditorLoop'), 1.0, true); } public function loadPreferences():Void @@ -3385,17 +3383,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // If middle mouse panning during song playback, we move ONLY the playhead, without scrolling. Neat! - var oldStepTime:Float = Conductor.instance.currentStepTime; - var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset; - Conductor.instance.update(audioInstTrack.time); - handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); // Resync vocals. if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100) { audioVocalTrackGroup.time = audioInstTrack.time; } + + var oldStepTime:Float = Conductor.instance.currentStepTime; + var oldSongPosition:Float = Conductor.instance.songPosition; + trace(audioInstTrack); + Conductor.instance.update(audioInstTrack.time); + var diffStepTime:Float = Conductor.instance.currentStepTime - oldStepTime; + var newSongPosition:Float = Conductor.instance.songPosition; + handleHitsounds(oldSongPosition, newSongPosition); + // Move the playhead. playheadPositionInPixels += diffStepTime * GRID_SIZE; @@ -3404,19 +3407,23 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // Else, move the entire view. - var oldSongPosition:Float = Conductor.instance.songPosition + Conductor.instance.instrumentalOffset; - Conductor.instance.update(audioInstTrack.time); - handleHitsounds(oldSongPosition, Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); + // Resync vocals. if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100) { audioVocalTrackGroup.time = audioInstTrack.time; } + var oldSongPosition:Float = Conductor.instance.songPosition; + Conductor.instance.update(audioInstTrack.time); + // We need time in fractional steps here to allow the song to actually play. // Also account for a potentially offset playhead. scrollPositionInPixels = (Conductor.instance.currentStepTime + Conductor.instance.instrumentalOffsetSteps) * GRID_SIZE - playheadPositionInPixels; + var newSongPosition:Float = Conductor.instance.songPosition; + handleHitsounds(oldSongPosition, newSongPosition); + // DO NOT move song to scroll position here specifically. // We need to update the note sprites. @@ -3631,7 +3638,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // 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})'); + // trace('Creating new Event... (${renderedEvents.members.length})'); // The event sprite handles animation playback and positioning. eventSprite.eventData = eventData; @@ -6278,12 +6285,31 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } + /** + * Set to true when a hitsound is played, then set to false the frame after. + * Used to prevent doubling up on hitsounds. + */ + var playedHitsoundLastFrame:Bool = false; + /** * Handle the playback of hitsounds. */ function handleHitsounds(oldSongPosition:Float, newSongPosition:Float):Void { if (!hitsoundsEnabled) return; + if (oldSongPosition == newSongPosition) return; + + // Don't play hitsounds when moving backwards in time. + // This prevents issues caused by FlxSound occasionally jumping backwards in time at the start of playback. + if (newSongPosition < oldSongPosition) return; + + // Skip hitsounds this frame if they were already played one frame ago. + if (playedHitsoundLastFrame) + { + // trace('IGNORING hitsound (${oldSongPosition} -> ${newSongPosition}), already played last check!'); + playedHitsoundLastFrame = false; + return; + } // Assume notes are sorted by time. for (noteData in currentSongChartNoteData) @@ -6293,7 +6319,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (noteData.time < oldSongPosition) // Note is in the past. continue; - if (noteData.time > newSongPosition) // Note is in the future. + if (noteData.time >= newSongPosition) // Note is in the future. return; // Assume all notes are also in the future. // Note was just hit. @@ -6318,6 +6344,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState case 1: // Opponent if (hitsoundVolumeOpponent > 0) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'), hitsoundVolumeOpponent); } + + playedHitsoundLastFrame = true; } } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 26e246371..4b6edf42c 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -175,7 +175,7 @@ class ChartEditorAudioHandler if (instId == '') instId = 'default'; var instTrackData:Null = state.audioInstTrackData.get(instId); var perfStart:Float = TimerUtil.start(); - var instTrack:Null = SoundUtil.buildSoundFromBytes(instTrackData); + var instTrack:Null = SoundUtil.buildSoundFromBytes(instTrackData, 'chartEditor-inst${instId == '' ? '' : '-${instId}'}'); trace('Built instrumental track in ${TimerUtil.seconds(perfStart)} seconds.'); if (instTrack == null) return false; @@ -205,7 +205,7 @@ class ChartEditorAudioHandler var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}'; var vocalTrackData:Null = state.audioVocalTrackData.get(trackId); var perfStart:Float = TimerUtil.start(); - var vocalTrack:Null = SoundUtil.buildSoundFromBytes(vocalTrackData); + var vocalTrack:Null = SoundUtil.buildSoundFromBytes(vocalTrackData, 'chartEditor-vocals-${charId}${instId == '' ? '' : '-${instId}'}'); trace('Built vocal track in ${TimerUtil.seconds(perfStart)}.'); if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup(); diff --git a/source/funkin/util/assets/SoundUtil.hx b/source/funkin/util/assets/SoundUtil.hx index 43602b999..f4140cbc1 100644 --- a/source/funkin/util/assets/SoundUtil.hx +++ b/source/funkin/util/assets/SoundUtil.hx @@ -12,13 +12,15 @@ class SoundUtil * @param input The byte data. * @return The playable sound, or `null` if loading failed. */ - public static function buildSoundFromBytes(input:Null):Null + public static function buildSoundFromBytes(input:Null, label:String = 'unknown'):Null { if (input == null) return null; var openflSound:OpenFLSound = new OpenFLSound(); openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(input), input.length); var output:FunkinSound = FunkinSound.load(openflSound, 1.0, false); + @:privateAccess + output._label = label; return output; } }