T3: Autosample UX - fail reason + toast + pre-warm feedback

vstiAutosample: fail map thanh object {ts, reason} (cooldown check
object-aware), recordFail o API-missing/response loi/catch, export
failReasonFor. soundfontPlayer: toast ly do khi ensure null (throttle
10s) roi fallback oscillator. app.jsx: pre-warm VSTi hien thi toast khi
autosample fail.
This commit is contained in:
2026-08-11 15:36:10 +07:00
parent 6da0c5b68d
commit fa2f96dbe3
4 changed files with 42 additions and 8 deletions
+7 -1
View File
@@ -15496,7 +15496,13 @@ const App = () => {
// VSTi live playback: pre-warm autosample (SF2 backend) note đu không b // VSTi live playback: pre-warm autosample (SF2 backend) note đu không b
// delay (SonicSF cũng ensure lazily trong _playNoteFluid nếu chưa có). // delay (SonicSF cũng ensure lazily trong _playNoteFluid nếu chưa có).
if (window.SonicVstiAutosample && instrumentId && !isSfInstrument) { if (window.SonicVstiAutosample && instrumentId && !isSfInstrument) {
window.SonicVstiAutosample.ensure({ plugin_id: instrumentId, type: 'vst3' }); const _warmEngine = { plugin_id: instrumentId, type: 'vst3' };
window.SonicVstiAutosample.ensure(_warmEngine).then(_sfId => {
if (!_sfId && window.SonicVstiAutosample.failReasonFor) {
const _reason = window.SonicVstiAutosample.failReasonFor(_warmEngine);
if (_reason && window.showToast) window.showToast('Autosample VSTi that bai: ' + _reason + ' - dung am default', 'warning');
}
});
} }
// Trigger FluidSynth load + program change when soundfont instrument selected // Trigger FluidSynth load + program change when soundfont instrument selected
if (window.SonicSF && window.SonicSF.selectInstrument && instrumentId && isSfInstrument) { if (window.SonicSF && window.SonicSF.selectInstrument && instrumentId && isSfInstrument) {
+1 -1
View File
@@ -786,7 +786,7 @@ const[instrumentDropdownTrackId,setInstrumentDropdownTrackId]=useState(null);con
// play qua Carla bridge (âm sai instrument + âm kẹt không dừng được). // play qua Carla bridge (âm sai instrument + âm kẹt không dừng được).
try{const _hasInst=!!instrumentId;const _wasVst=curTrk&&curTrk.synth_engine&&String(curTrk.synth_engine.type||'').indexOf('vst')!==-1;const _nowVst=_hasInst&&!isSfInstrument;if(_wasVst&&!_nowVst&&window.SonicCarlaMidi&&window.SonicCarlaMidi.stopBridge){window.SonicCarlaMidi.stopBridge();try{if(window.SonicSF&&window.SonicSF.stopAll)window.SonicSF.stopAll();}catch(e){}stopAllNativeSfNotes();console.log('[Instrument] Carla bridge unloaded — track',trackId,'switched from VSTi to non-VST');}}catch(e){console.warn('[Instrument] carla-stop on instrument switch error:',e);}var mch=curTrk?assignTrackMidiChannel(curTrk,mt):sfBank===128?9:0;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const hasInstrument=!!instrumentId;const instrType=isSfInstrument?'soundfont':hasInstrument?'vst3':'default';const synthEngine=hasInstrument?{type:instrType,plugin_id:instrumentId,soundfont_bank:sfBank!==undefined?sfBank:0,soundfont_program:sfProg!==undefined?sfProg:0,soundfont_id:isSfInstrument?instrumentId.replace('sf_',''):''}:undefined;return{...t,midiChannel:mch,instrumentId,instrumentProgram:sfProg,instrumentName:displayName,soundfont_bank:sfBank,soundfont_program:sfProg,synth_engine:synthEngine,type:hasInstrument?'MIDI':t.type==='MIDI'?'audio':t.type};}));setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setSynthCategory(null);// VSTi live playback: pre-warm autosample (SF2 backend) — note đầu không bị try{const _hasInst=!!instrumentId;const _wasVst=curTrk&&curTrk.synth_engine&&String(curTrk.synth_engine.type||'').indexOf('vst')!==-1;const _nowVst=_hasInst&&!isSfInstrument;if(_wasVst&&!_nowVst&&window.SonicCarlaMidi&&window.SonicCarlaMidi.stopBridge){window.SonicCarlaMidi.stopBridge();try{if(window.SonicSF&&window.SonicSF.stopAll)window.SonicSF.stopAll();}catch(e){}stopAllNativeSfNotes();console.log('[Instrument] Carla bridge unloaded — track',trackId,'switched from VSTi to non-VST');}}catch(e){console.warn('[Instrument] carla-stop on instrument switch error:',e);}var mch=curTrk?assignTrackMidiChannel(curTrk,mt):sfBank===128?9:0;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const hasInstrument=!!instrumentId;const instrType=isSfInstrument?'soundfont':hasInstrument?'vst3':'default';const synthEngine=hasInstrument?{type:instrType,plugin_id:instrumentId,soundfont_bank:sfBank!==undefined?sfBank:0,soundfont_program:sfProg!==undefined?sfProg:0,soundfont_id:isSfInstrument?instrumentId.replace('sf_',''):''}:undefined;return{...t,midiChannel:mch,instrumentId,instrumentProgram:sfProg,instrumentName:displayName,soundfont_bank:sfBank,soundfont_program:sfProg,synth_engine:synthEngine,type:hasInstrument?'MIDI':t.type==='MIDI'?'audio':t.type};}));setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setSynthCategory(null);// VSTi live playback: pre-warm autosample (SF2 backend) — note đầu không bị
// delay (SonicSF cũng ensure lazily trong _playNoteFluid nếu chưa có). // delay (SonicSF cũng ensure lazily trong _playNoteFluid nếu chưa có).
if(window.SonicVstiAutosample&&instrumentId&&!isSfInstrument){window.SonicVstiAutosample.ensure({plugin_id:instrumentId,type:'vst3'});}// Trigger FluidSynth load + program change when soundfont instrument selected if(window.SonicVstiAutosample&&instrumentId&&!isSfInstrument){const _warmEngine={plugin_id:instrumentId,type:'vst3'};window.SonicVstiAutosample.ensure(_warmEngine).then(_sfId=>{if(!_sfId&&window.SonicVstiAutosample.failReasonFor){const _reason=window.SonicVstiAutosample.failReasonFor(_warmEngine);if(_reason&&window.showToast)window.showToast('Autosample VSTi that bai: '+_reason+' - dung am default','warning');}});}// Trigger FluidSynth load + program change when soundfont instrument selected
if(window.SonicSF&&window.SonicSF.selectInstrument&&instrumentId&&isSfInstrument){const sfId=instrumentId.replace('sf_','');window.SonicSF.selectInstrument(mch,sfBank||0,sfProg||0,sfId);}setSubTabs(prev=>prev.map(s=>{if(s.trackId!==trackId)return s;return{...s,instrumentProgram:programNumber!==undefined?programNumber:undefined,instrumentName:displayName,instrumentId};}));const playingSub=subTabs.find(s=>s.trackId===trackId&&s.type==='PIANO_ROLL'&&s.isPlaying);if(playingSub){const pid=playingSub.id;const ctx=getAudioContext();const tNode=activeTrackNodesRef.current[trackId];if(tNode&&tNode.gainNode){tNode.gainNode.gain.setValueAtTime(tNode.gainNode.gain.value||1,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(0.001,ctx.currentTime+0.04);}setTimeout(()=>{stopAllPlayback();if(tNode&&tNode.gainNode){const trackData=activeTracksRef.current?activeTracksRef.current.find(t=>t.id===trackId):null;const volDb=trackData?trackData.volumeDb??0:0;const volLinear=volDb<=-50?0:Math.pow(10,volDb/20);tNode.gainNode.gain.setValueAtTime(0.001,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(volLinear||0.8,ctx.currentTime+0.015);}const context=getAudioContext();const offset=playingSub.currentTime||0;startOffsetTimeRef.current=offset;startAudioTimeRef.current=context.currentTime;startBufferOffsetRef.current=offset*(playingSub.speed||1.0);schedulePianoRollMidi(playingSub,offset);startSubTabPlayback(playingSub,offset);setSubTabs(prev=>prev.map(s=>s.id===pid?{...s,isPlaying:true}:s));if(subTabsRef.current){subTabsRef.current=subTabsRef.current.map(s=>s.id===pid?{...s,isPlaying:true}:s);}animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);},60);}setTimeout(()=>lucide.createIcons(),50);};const setTrackInstrument=(trackId,instrumentId,displayName)=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);if(instrumentId&&instrumentId.startsWith('sf_')){// Set instrument on track immediately so Synth button shows the name if(window.SonicSF&&window.SonicSF.selectInstrument&&instrumentId&&isSfInstrument){const sfId=instrumentId.replace('sf_','');window.SonicSF.selectInstrument(mch,sfBank||0,sfProg||0,sfId);}setSubTabs(prev=>prev.map(s=>{if(s.trackId!==trackId)return s;return{...s,instrumentProgram:programNumber!==undefined?programNumber:undefined,instrumentName:displayName,instrumentId};}));const playingSub=subTabs.find(s=>s.trackId===trackId&&s.type==='PIANO_ROLL'&&s.isPlaying);if(playingSub){const pid=playingSub.id;const ctx=getAudioContext();const tNode=activeTrackNodesRef.current[trackId];if(tNode&&tNode.gainNode){tNode.gainNode.gain.setValueAtTime(tNode.gainNode.gain.value||1,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(0.001,ctx.currentTime+0.04);}setTimeout(()=>{stopAllPlayback();if(tNode&&tNode.gainNode){const trackData=activeTracksRef.current?activeTracksRef.current.find(t=>t.id===trackId):null;const volDb=trackData?trackData.volumeDb??0:0;const volLinear=volDb<=-50?0:Math.pow(10,volDb/20);tNode.gainNode.gain.setValueAtTime(0.001,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(volLinear||0.8,ctx.currentTime+0.015);}const context=getAudioContext();const offset=playingSub.currentTime||0;startOffsetTimeRef.current=offset;startAudioTimeRef.current=context.currentTime;startBufferOffsetRef.current=offset*(playingSub.speed||1.0);schedulePianoRollMidi(playingSub,offset);startSubTabPlayback(playingSub,offset);setSubTabs(prev=>prev.map(s=>s.id===pid?{...s,isPlaying:true}:s));if(subTabsRef.current){subTabsRef.current=subTabsRef.current.map(s=>s.id===pid?{...s,isPlaying:true}:s);}animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);},60);}setTimeout(()=>lucide.createIcons(),50);};const setTrackInstrument=(trackId,instrumentId,displayName)=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);if(instrumentId&&instrumentId.startsWith('sf_')){// Set instrument on track immediately so Synth button shows the name
var mt2=activeTracksRef.current||tracks;var qTrk=null;for(var qi=0;qi<mt2.length;qi++){if(mt2[qi].id===trackId){qTrk=mt2[qi];break;}}var qch=qTrk?assignTrackMidiChannel(qTrk,mt2):0;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const sfClean=instrumentId.replace('sf_','');const synthEngine={type:'soundfont',plugin_id:instrumentId,soundfont_bank:0,soundfont_program:0,soundfont_id:sfClean};return{...t,midiChannel:qch,instrumentId,instrumentProgram:undefined,instrumentName:displayName,synth_engine:synthEngine};}));setSelectedSoundFontId(instrumentId);setSynthCategory('soundfont');setInstrumentSelectorTrackId(trackId);setSfPresets(null);setSfPresetSearchQuery('');const sfIdParam=instrumentId.replace('sf_','');// Use cached presets from instrumentSelectorData var mt2=activeTracksRef.current||tracks;var qTrk=null;for(var qi=0;qi<mt2.length;qi++){if(mt2[qi].id===trackId){qTrk=mt2[qi];break;}}var qch=qTrk?assignTrackMidiChannel(qTrk,mt2):0;updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const sfClean=instrumentId.replace('sf_','');const synthEngine={type:'soundfont',plugin_id:instrumentId,soundfont_bank:0,soundfont_program:0,soundfont_id:sfClean};return{...t,midiChannel:qch,instrumentId,instrumentProgram:undefined,instrumentName:displayName,synth_engine:synthEngine};}));setSelectedSoundFontId(instrumentId);setSynthCategory('soundfont');setInstrumentSelectorTrackId(trackId);setSfPresets(null);setSfPresetSearchQuery('');const sfIdParam=instrumentId.replace('sf_','');// Use cached presets from instrumentSelectorData
const cachedSf=(instrumentSelectorData?.soundfonts||[]).find(s=>s.id===instrumentId||s.id===sfIdParam);if(cachedSf&&cachedSf.presets){setSfPresets(cachedSf.presets);}else{window.SonicAPI.listSoundfontInstruments(sfIdParam).then(data=>setSfPresets(data.presets||[])).catch(e=>{console.error('listSoundfontInstruments failed:',e);setSfPresets([]);});}}else{setTrackInstrumentWithUndo(trackId,instrumentId,displayName);}};const[activeTool,setActiveTool]=useState('select');// 'select' | 'grab' | 'razor' const cachedSf=(instrumentSelectorData?.soundfonts||[]).find(s=>s.id===instrumentId||s.id===sfIdParam);if(cachedSf&&cachedSf.presets){setSfPresets(cachedSf.presets);}else{window.SonicAPI.listSoundfontInstruments(sfIdParam).then(data=>setSfPresets(data.presets||[])).catch(e=>{console.error('listSoundfontInstruments failed:',e);setSfPresets([]);});}}else{setTrackInstrumentWithUndo(trackId,instrumentId,displayName);}};const[activeTool,setActiveTool]=useState('select');// 'select' | 'grab' | 'razor'
+8 -1
View File
@@ -609,7 +609,14 @@
if (!sfId) { if (!sfId) {
// Không autosample được (404/501) → fallback oscillator để note // Không autosample được (404/501) → fallback oscillator để note
// KHÔNG câm; cooldown trong ensure chặn re-request. // KHÔNG câm; cooldown trong ensure chặn re-request.
try { self._playNoteFallback(note, velocity, durationMs, startTime, program, null, channel, synthEngine); } catch (e) {} try {
var _reason = window.SonicVstiAutosample.failReasonFor ? window.SonicVstiAutosample.failReasonFor(synthEngine) : null;
if (_reason && window.showToast && (!self._vstiToastAt || Date.now() - self._vstiToastAt > 10000)) {
self._vstiToastAt = Date.now();
window.showToast('Autosample VSTi that bai: ' + _reason + ' - dung am default', 'warning');
}
self._playNoteFallback(note, velocity, durationMs, startTime, program, null, channel, synthEngine);
} catch (e) {}
return; return;
} }
synthEngine.autosampled_sf_id = sfId; synthEngine.autosampled_sf_id = sfId;
+26 -5
View File
@@ -19,6 +19,13 @@ window.SonicVstiAutosample = window.SonicVstiAutosample || {};
function readFails() { function readFails() {
try { return JSON.parse(localStorage.getItem(LS_FAIL_KEY) || '{}') || {}; } catch (e) { return {}; } try { return JSON.parse(localStorage.getItem(LS_FAIL_KEY) || '{}') || {}; } catch (e) { return {}; }
} }
function recordFail(k, reason) {
try {
var fails2 = readFails();
fails2[k] = { ts: Date.now(), reason: reason || 'Autosample that bai' };
localStorage.setItem(LS_FAIL_KEY, JSON.stringify(fails2));
} catch (e) {}
}
// Key = plugin + preset → autosample lại khi đổi preset (âm preset mới). // Key = plugin + preset → autosample lại khi đổi preset (âm preset mới).
function keyFor(synthEngine) { function keyFor(synthEngine) {
@@ -51,10 +58,13 @@ window.SonicVstiAutosample = window.SonicVstiAutosample || {};
if (cached) return Promise.resolve(cached); if (cached) return Promise.resolve(cached);
if (_inFlight[k]) return _inFlight[k]; if (_inFlight[k]) return _inFlight[k];
var fails = readFails(); var fails = readFails();
if (fails[k] && (Date.now() - fails[k]) < FAIL_COOLDOWN_MS) { var _f = fails[k];
var _lastTs = (_f && typeof _f === 'object') ? _f.ts : _f;
if (_lastTs && (Date.now() - _lastTs) < FAIL_COOLDOWN_MS) {
return Promise.resolve(null); return Promise.resolve(null);
} }
if (!window.SonicAPI || !window.SonicAPI.autosampleVsti) { if (!window.SonicAPI || !window.SonicAPI.autosampleVsti) {
recordFail(k, 'API autosample khong kha dung');
return Promise.resolve(null); return Promise.resolve(null);
} }
_inFlight[k] = new Promise(function (resolve) { _inFlight[k] = new Promise(function (resolve) {
@@ -72,19 +82,29 @@ window.SonicVstiAutosample = window.SonicVstiAutosample || {};
writeMap(map); writeMap(map);
resolve(res.sf_id); resolve(res.sf_id);
} else { } else {
recordFail(k, (res && res.error) ? String(res.error) : 'Server khong tra sf_id');
resolve(null); resolve(null);
} }
}).catch(function () { }).catch(function (err) {
delete _inFlight[k]; delete _inFlight[k];
var fails2 = readFails(); recordFail(k, (err && err.message) ? String(err.message) : 'Loi mang / server autosample');
fails2[k] = Date.now();
try { localStorage.setItem(LS_FAIL_KEY, JSON.stringify(fails2)); } catch (e) {}
resolve(null); resolve(null);
}); });
}); });
return _inFlight[k]; return _inFlight[k];
} }
// Lý do autosample thất bại gần nhất (fail map) — null nếu chưa từng fail
// (hoặc entry cũ định dạng number). Dùng cho UI toast khi ensure() trả null.
function failReasonFor(synthEngine) {
var k = keyFor(synthEngine);
if (!k) return null;
var fails = readFails();
var f = fails[k];
if (!f || typeof f !== 'object') return null;
return f.reason || null;
}
// Xoá cache + cooldown — cho phép re-sample (đổi preset/plugin hoặc debug). // Xoá cache + cooldown — cho phép re-sample (đổi preset/plugin hoặc debug).
function clearCache() { function clearCache() {
try { localStorage.removeItem(LS_MAP_KEY); } catch (e) {} try { localStorage.removeItem(LS_MAP_KEY); } catch (e) {}
@@ -96,6 +116,7 @@ window.SonicVstiAutosample = window.SonicVstiAutosample || {};
sfIdFor: sfIdFor, sfIdFor: sfIdFor,
entryFor: entryFor, entryFor: entryFor,
ensure: ensure, ensure: ensure,
failReasonFor: failReasonFor,
clearCache: clearCache clearCache: clearCache
}; };
})(); })();