fix(engine): voice 句尾剪字真根因根治(三修:源尾垫静音冲刷 SoundTouch 未冲刷小节)+ onEnd 完成信号 + live 探针适配
- 真根因(live span 8.57s < 文件 8.80s 铁证):SoundTouch Stretch 源结束时不冲刷 内部最后一个小节(~1 sequence),句尾 0.1-0.25s 从未交付音频图——任何平台皆然, ASR 因容错转写仍 sim=1(转写评审盲区的又一实证)。一修固定 300ms 排空窗只是 把这段不存在的交付空等成静音;二修 onEnd 保证「已交付必播完」但交付不了这段 - 三修:源尾垫 VOICE_TAIL_PAD_SEC(0.25s) 静音把真实句尾推出管线;完成 = onEnd (平台无关,disconnect 已交付帧仍播);1.5s 兜底;顶掉复核移至新句数据访问前 (垫长拷贝的 getChannelData 先于杀旧句会打乱探针时序) - live 探针适配:尾部垫使 SoundTouch 读未注册 buffer、gcd 抽读信号消失—— 清扫器加「仍有连接中语音 SPN 则不关段」守卫,断开即移出追踪集合 - engine-voice.test:PAD+I1/I2/I2b/I3 五组 19 断言(含垫长存在性、onEnd 结算、 兜底不早于 1.5s);10 套件全绿 - live 复验(男声 p1):span 8.57→9.04s(≥文件 8.80s),ASR sim=1 verdict=OK
This commit is contained in:
@@ -141,8 +141,8 @@
|
||||
};
|
||||
|
||||
const AC = window.AudioContext || window.webkitAudioContext;
|
||||
const voiceNodes = new Set(); // 连接中的 SoundTouch ScriptProcessorNode(断开即移除)
|
||||
if (AC) {
|
||||
const voiceNodes = new WeakSet(); // SoundTouch 管线创建的 ScriptProcessorNode
|
||||
const origCSP = AC.prototype.createScriptProcessor;
|
||||
AC.prototype.createScriptProcessor = function (...a) {
|
||||
const n = origCSP.apply(this, a);
|
||||
@@ -153,12 +153,20 @@
|
||||
AudioNode.prototype.disconnect = function (...a) {
|
||||
// voice 节点断开 = 引擎 killVoice(自然结束/被顶掉/stopVoice 都走这里)。
|
||||
// 玩家实际听到的尾音就到此刻(FIFO 里未播部分被丢弃),用 now() 而非 lastAct。
|
||||
if (voiceNodes.has(this) && seg) closeSeg(now(), 'kill');
|
||||
if (voiceNodes.has(this)) {
|
||||
voiceNodes.delete(this);
|
||||
if (seg) closeSeg(now(), 'kill');
|
||||
}
|
||||
return origDisc.apply(this, a);
|
||||
};
|
||||
}
|
||||
// 兜底清扫:长静默后关闭未结段
|
||||
setInterval(() => { if (seg && now() - seg.lastAct > VOICE_GAP_MS) closeSeg(seg.lastAct, 'end'); }, 200);
|
||||
// 兜底清扫:长静默后关闭未结段。
|
||||
// 守卫(2026-09 三修适配):引擎的尾部静音垫让 SoundTouch 读的是新建
|
||||
// buffer(未注册 url),gcd 抽读信号消失、lastAct 停在句首——只要仍有
|
||||
// 连接中的语音 SPN 就说明句在播,不许清扫;段关闭只信 disconnect。
|
||||
setInterval(() => {
|
||||
if (seg && now() - seg.lastAct > VOICE_GAP_MS && voiceNodes.size === 0) closeSeg(seg.lastAct, 'end');
|
||||
}, 200);
|
||||
} catch (e) { push({ ev: 'probe_error', where: 'voice-detect', msg: String(e) }); }
|
||||
|
||||
// ---------- AudioBufferSourceNode:bgm/sfx 播放事件 ----------
|
||||
|
||||
Reference in New Issue
Block a user