fix: taud note with SDx not firing due to unbound inst

This commit is contained in:
minjaesong
2026-05-30 09:05:28 +09:00
parent 1d3b5ce8aa
commit 038db60b59
5 changed files with 52 additions and 2 deletions

View File

@@ -315,6 +315,13 @@ class AudioJSR223Delegate(private val vm: VM) {
getPlayhead(playhead)?.resetParams()
}
/** Clear funk-repeat (S$Fx) state (per-voice run-state + per-instrument loop-inversion masks)
* without disturbing tempo / volume / position. Call on a fresh play-from-start so stale funk
* state from a prior playback doesn't bleed into the replay. */
fun resetFunkState(playhead: Int) {
getPlayhead(playhead)?.resetFunkState()
}
fun purgeQueue(playhead: Int) {
getPlayhead(playhead)?.purgeQueue()
}

View File

@@ -2905,7 +2905,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
for (vi in 0 until ts.voices.size) {
val voice = ts.voices[vi]
if (!voice.active && voice.noteDelayTick < 0) continue
val inst = instruments[voice.instrumentId]
var inst = instruments[voice.instrumentId]
// Note cut. Zero noteVolume / rowVolume (silence this note) but leave channelVolume
// alone — IT's note cut stops the sample, it doesn't reset chan->global_volume.
@@ -2921,6 +2921,13 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
maybeSpawnBackgroundForNNA(ts, voice, vi)
triggerNote(voice, voice.delayedNote, voice.delayedInst, voice.delayedVol)
voice.noteDelayTick = -1
// triggerNote may have swapped in a new instrument; re-bind so the rest of this
// tick's per-voice work (playbackRate at L3090, envelope/fadeout/auto-vibrato)
// uses the instrument that just fired, not the one the voice held on entry. On a
// never-triggered voice the stale binding is instruments[0] (samplingRate 0),
// which would zero playbackRate and freeze the sample — the "first note on a
// fresh channel via S$Dx is silent" bug.
inst = instruments[voice.instrumentId]
}
if (!voice.active) {
@@ -4094,6 +4101,20 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
}
}
/** Clear funk-repeat (S$Fx) state only — per-voice run-state plus the per-instrument
* loop-inversion masks — without touching tempo / volume / position. taut calls this on
* every fresh play-from-start so accumulated inversions and a stale funkSpeed don't bleed
* from a prior session into the replay; full resetParams would also clobber bpm / tickRate /
* volume, which a replay must preserve. Masks still persist across a natural song loop. */
fun resetFunkState() {
trackerState?.voices?.forEach {
it.funkSpeed = 0
it.funkAccumulator = 0
it.funkWritePos = 0
}
parent.instruments.forEach { it.funkMask = null }
}
fun purgeQueue() {
pcmQueue.clear()
if (isPcmMode) {