From 5a4d200fdcad95455d7d1b99c303d2699827010e Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 6 May 2026 12:15:48 +0900 Subject: [PATCH] IT voice retire rule for fadeout=0 --- .../net/torvald/tsvm/peripheral/AudioAdapter.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt index cec3c99..9d0544f 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt @@ -1265,11 +1265,21 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { voice.envIndex = wStart voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0) } else if (voice.envIndex >= maxIdx) { - voice.envVolume = (inst.volEnvelopes[maxIdx].value / 63.0).coerceIn(0.0, 1.0) + val vEnd = inst.volEnvelopes[maxIdx].value + voice.envVolume = (vEnd / 63.0).coerceIn(0.0, 1.0) + // Schism's "envelope-end + last-value-0 ⇒ cut" rule (player/sndmix.c:493-498): + // applies only in fall-through (no active sustain or loop wrap) since Schism + // suppresses fade_flag inside both wrap branches. Without this rule, instruments + // with fadeout=0 + envelope ending at 0 would silently hold their voices forever. + if (vEnd == 0 && !wrapping) voice.active = false } else { val vOffset = inst.volEnvelopes[voice.envIndex].offset.toDouble() + val vCurValue = inst.volEnvelopes[voice.envIndex].value if (vOffset == 0.0) { - voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0) + // Reached a terminator point — envelope holds here. + voice.envVolume = (vCurValue / 63.0).coerceIn(0.0, 1.0) + // Same Schism cut rule as above: only when in fall-through. + if (vCurValue == 0 && !wrapping) voice.active = false } else { voice.envTimeSec += tickSec if (voice.envTimeSec >= vOffset) { @@ -1279,7 +1289,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { voice.envIndex = nextIdx voice.envVolume = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0) } else { - val cur = (inst.volEnvelopes[voice.envIndex].value / 63.0).coerceIn(0.0, 1.0) + val cur = (vCurValue / 63.0).coerceIn(0.0, 1.0) val nxt = (inst.volEnvelopes[(voice.envIndex + 1).coerceAtMost(maxIdx)].value / 63.0).coerceIn(0.0, 1.0) voice.envVolume = cur + (nxt - cur) * (voice.envTimeSec / vOffset) }