fix: Bug 1 keyboard preview cam lan 2+ — token race: stale check != token -> > token (claim-then-compare) trong _sfWasmFallbackNote + API path (T1)

This commit is contained in:
2026-08-11 15:31:56 +07:00
parent cadb5402a3
commit aa06d40b98
4 changed files with 246 additions and 4 deletions
+2 -2
View File
@@ -218,7 +218,7 @@ const _sfWasmFallbackNote = (track, pitch, velocity, durationMs, startTime, key,
const bank = (eng && eng.soundfont_bank) || 0;
const program = (eng && eng.soundfont_program) || (track && track.instrumentProgram !== undefined ? track.instrumentProgram : 0);
const k = key || (track ? track.id : 'global');
if (_nativeSfPreviews[k] && token != null && _nativeSfPreviews[k].token !== token) return; // stale
if (_nativeSfPreviews[k] && token != null && _nativeSfPreviews[k].token > token) return; // stale (claim-then-compare: chi bo neu co note MOI HON)
const S = window.SonicSF;
if (!S) return;
const ch = (track && track.midiChannel != null) ? track.midiChannel : 0;
@@ -264,7 +264,7 @@ const playNativeSfNote = (track, pitch, velocity, durationMs, startTime, key) =>
_sfWasmFallbackNote(track, pitch, velocity, durationMs, startTime, k, token);
return;
}
if (_nativeSfPreviews[k] && _nativeSfPreviews[k].token !== token) return; // stale
if (_nativeSfPreviews[k] && _nativeSfPreviews[k].token > token) return; // stale (claim-then-compare: chi bo neu co note MOI HON)
window.__nativeSfOk = true;
const audio = new Audio(API_BASE_URL + res.url);
_nativeSfPreviews[k] = { audio: audio, token: token };
+2 -2
View File
@@ -49,9 +49,9 @@ const _nativeSfPreviews={};// WASM fallback: backend pyfluidsynth render HONG tr
// native path cam lang. Fallback choi note TRUC TIEP qua SonicSF (Web Audio
// + libfluidsynth WASM da bundle) - am ra _gainNode -> masterBus.input.
// window.__nativeSfOk: undefined (chua biet) | true (native OK) | false (dung WASM).
const _sfWasmFallbackNote=(track,pitch,velocity,durationMs,startTime,key,token)=>{try{const eng=track&&track.synth_engine;const sfId=eng&&eng.soundfont_id||(track&&track.instrumentId&&String(track.instrumentId).startsWith('sf_')?track.instrumentId:null);if(!sfId)return;const bank=eng&&eng.soundfont_bank||0;const program=eng&&eng.soundfont_program||(track&&track.instrumentProgram!==undefined?track.instrumentProgram:0);const k=key||(track?track.id:'global');if(_nativeSfPreviews[k]&&token!=null&&_nativeSfPreviews[k].token!==token)return;// stale
const _sfWasmFallbackNote=(track,pitch,velocity,durationMs,startTime,key,token)=>{try{const eng=track&&track.synth_engine;const sfId=eng&&eng.soundfont_id||(track&&track.instrumentId&&String(track.instrumentId).startsWith('sf_')?track.instrumentId:null);if(!sfId)return;const bank=eng&&eng.soundfont_bank||0;const program=eng&&eng.soundfont_program||(track&&track.instrumentProgram!==undefined?track.instrumentProgram:0);const k=key||(track?track.id:'global');if(_nativeSfPreviews[k]&&token!=null&&_nativeSfPreviews[k].token>token)return;// stale (claim-then-compare: chi bo neu co note MOI HON)
const S=window.SonicSF;if(!S)return;const ch=track&&track.midiChannel!=null?track.midiChannel:0;const durMs=Math.max(200,durationMs||500);const ctx=getAudioContext();const delaySec=startTime?Math.max(0,startTime-ctx.currentTime):0;_nativeSfPreviews[k]={wasm:{ch:ch,pitch:pitch},token:token};const fire=function(){Promise.resolve(S.selectInstrument(ch,bank,program,sfId)).then(function(){S.playNote(pitch,velocity!=null?velocity:0.8,durMs,undefined,eng?undefined:program,null,ch,eng||undefined);}).catch(function(){try{S.playNote(pitch,velocity!=null?velocity:0.8,durMs,undefined,eng?undefined:program,null,ch,eng||undefined);}catch(e2){}});};if(delaySec>0){setTimeout(fire,delaySec*1000);}else{fire();}}catch(e){console.warn('[NativeSF] wasm fallback note error:',e);}};const playNativeSfNote=(track,pitch,velocity,durationMs,startTime,key)=>{try{const eng=track&&track.synth_engine;const sfId=eng&&eng.soundfont_id||(track&&track.instrumentId&&String(track.instrumentId).startsWith('sf_')?track.instrumentId:null);if(!sfId)return;const bank=eng&&eng.soundfont_bank||0;const program=eng&&eng.soundfont_program||(track&&track.instrumentProgram!==undefined?track.instrumentProgram:0);const k=key||(track?track.id:'global');const prev=_nativeSfPreviews[k];const token=(prev?prev.token:0)+1;// Native hong da biet (501) -> di thang WASM fallback, khong goi API.
if(window.__nativeSfOk===false){_sfWasmFallbackNote(track,pitch,velocity,durationMs,startTime,k,token);return;}const durationSec=Math.max(0.2,(durationMs||500)/1000);window.SonicAPI.soundfontRender({soundfont_id:sfId,bank:bank,program:program,bpm:120,notes:[{pitch:pitch,start_beat:0,duration_beats:durationSec*2,velocity:velocity!=null?velocity:0.8}]}).then(function(res){if(!res||!res.success||!res.url){window.__nativeSfOk=false;_sfWasmFallbackNote(track,pitch,velocity,durationMs,startTime,k,token);return;}if(_nativeSfPreviews[k]&&_nativeSfPreviews[k].token!==token)return;// stale
if(window.__nativeSfOk===false){_sfWasmFallbackNote(track,pitch,velocity,durationMs,startTime,k,token);return;}const durationSec=Math.max(0.2,(durationMs||500)/1000);window.SonicAPI.soundfontRender({soundfont_id:sfId,bank:bank,program:program,bpm:120,notes:[{pitch:pitch,start_beat:0,duration_beats:durationSec*2,velocity:velocity!=null?velocity:0.8}]}).then(function(res){if(!res||!res.success||!res.url){window.__nativeSfOk=false;_sfWasmFallbackNote(track,pitch,velocity,durationMs,startTime,k,token);return;}if(_nativeSfPreviews[k]&&_nativeSfPreviews[k].token>token)return;// stale (claim-then-compare: chi bo neu co note MOI HON)
window.__nativeSfOk=true;const audio=new Audio(API_BASE_URL+res.url);_nativeSfPreviews[k]={audio:audio,token:token};const ctx=getAudioContext();const delay=startTime?Math.max(0,(startTime-ctx.currentTime)*1000):0;setTimeout(function(){if(!_nativeSfPreviews[k]||_nativeSfPreviews[k].audio!==audio)return;audio.play().catch(function(){});audio._sfStopTimer&&clearTimeout(audio._sfStopTimer);audio._sfStopTimer=setTimeout(function(){try{audio.pause();}catch(e){}},durationSec*1000+400);},delay);}).catch(function(){window.__nativeSfOk=false;_sfWasmFallbackNote(track,pitch,velocity,durationMs,startTime,k,token);});}catch(e){console.warn('[NativeSF] playNativeSfNote error:',e);}};const stopNativeSfNote=key=>{try{const k=key||'global';const prev=_nativeSfPreviews[k];if(!prev)return;prev.token++;try{if(prev.audio){prev.audio.pause();prev.audio.currentTime=0;}}catch(e){}try{if(prev.wasm&&window.SonicSF)window.SonicSF.stopNote(prev.wasm.ch,prev.wasm.pitch);}catch(e){}}catch(e){}};const stopAllNativeSfNotes=()=>{try{Object.keys(_nativeSfPreviews).forEach(function(k){stopNativeSfNote(k);});}catch(e){}};// ── Native soundfont item render (standalone, transport) ──────────────────
// Render TOÀN BỘ MIDI item bằng native FluidSynth → decode AudioBuffer →
// schedule nguồn audio đúng vị trí item (giống audio clip). opts: