Fix a bug where hitsounds could play in the chart editor several times.

This commit is contained in:
EliteMasterEric 2024-05-22 15:12:09 -04:00
parent 25016f45f9
commit 1bc3b4bef5
6 changed files with 60 additions and 22 deletions

View File

@ -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);

View File

@ -223,12 +223,12 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
// 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<FunkinSound>
*/
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<FunkinSound>
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.
*

View File

@ -178,9 +178,10 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
function get_time():Float
{
if (getFirstAlive() != null)
var firstAlive:Null<FunkinSound> = getFirstAlive();
if (firstAlive != null)
{
return getFirstAlive().time;
return firstAlive.time;
}
else
{
@ -260,7 +261,7 @@ class SoundGroup extends FlxTypedGroup<FunkinSound>
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;
});

View File

@ -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;
}
}

View File

@ -175,7 +175,7 @@ class ChartEditorAudioHandler
if (instId == '') instId = 'default';
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
var perfStart:Float = TimerUtil.start();
var instTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(instTrackData);
var instTrack:Null<FunkinSound> = 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<Bytes> = state.audioVocalTrackData.get(trackId);
var perfStart:Float = TimerUtil.start();
var vocalTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(vocalTrackData);
var vocalTrack:Null<FunkinSound> = SoundUtil.buildSoundFromBytes(vocalTrackData, 'chartEditor-vocals-${charId}${instId == '' ? '' : '-${instId}'}');
trace('Built vocal track in ${TimerUtil.seconds(perfStart)}.');
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();

View File

@ -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<Bytes>):Null<FunkinSound>
public static function buildSoundFromBytes(input:Null<Bytes>, label:String = 'unknown'):Null<FunkinSound>
{
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;
}
}