fix(latency): threadpool renders + RENDER_LOCK + client pre-render cache

This commit is contained in:
2026-08-16 20:21:48 +07:00
parent 90b5a63719
commit ed26e1706d
6 changed files with 139 additions and 42 deletions
+85 -11
View File
@@ -344,6 +344,79 @@ const stopNativeVstNote = (key) => {
const stopAllNativeVstNotes = () => {
try { Object.keys(_nativeVstPreviews).forEach(function (k) { stopNativeVstNote(k); }); } catch (e) {}
};
// URL cache cho midi-render: key = fingerprint (item+instrument+preset+notes)
// → re-play cùng item dùng lại WAV đã render, không chờ round-trip.
const _nativeVstItemUrls = {};
const _nativeVstItemKey = (track, item, bpm) => {
try {
const eng = track && track.synth_engine;
const instrumentId = (eng && eng.plugin_id) || (track && track.instrumentId) || '';
const notes = (item && item.notes) || [];
const nk = notes.map(function (n) {
return (n.pitch || 60) + ':' + (n.start_beat || 0) + ':' + (n.duration_beats || 1) + ':' + (n.velocity != null ? n.velocity : 0.8);
}).join(',');
return [item && item.id ? item.id : (instrumentId + ':' + nk), instrumentId, (eng && eng.preset_id) || '', (eng && eng.preset_path) || '', bpm].join('|') + '|' + nk;
} catch (e) { return ''; }
};
const _renderNativeVstItem = (track, item, bpm) => {
try {
const eng = track && track.synth_engine;
const instrumentId = (eng && eng.plugin_id) || (track && track.instrumentId);
if (!instrumentId || !isVstTrackEngine(eng)) return Promise.resolve(null);
const notes = (item && item.notes) || [];
if (!notes.length) return Promise.resolve(null);
return window.SonicAPI.midiRender({
instrument_id: instrumentId,
notes: notes.map(function (n) { return { pitch: n.pitch || 60, start_beat: n.start_beat || 0, duration_beats: n.duration_beats || 1, velocity: n.velocity != null ? n.velocity : 0.8 }; }),
bpm: parseFloat(bpm) || 120,
preset_id: (eng && eng.preset_id) || undefined,
preset_path: (eng && eng.preset_path) || undefined,
preset_data: (eng && eng.preset_data) || undefined,
}).then(function (res) {
if (!res || !res.success || !res.url) return null;
return API_BASE_URL + res.url;
}).catch(function () { return null; });
} catch (e) { return Promise.resolve(null); }
};
// Dedupe in-flight render: play trong lúc pre-render đang chạy → chờ cùng promise,
// không bắn duplicate request (tránh queue sau RENDER_LOCK + render 2 lần).
const _nativeVstItemPending = {};
const ensureRenderNativeVstItem = (track, item, bpm) => {
const key = _nativeVstItemKey(track, item, bpm);
if (!key) return _renderNativeVstItem(track, item, bpm);
if (_nativeVstItemUrls[key]) return Promise.resolve(_nativeVstItemUrls[key]);
if (_nativeVstItemPending[key]) return _nativeVstItemPending[key];
const p = _renderNativeVstItem(track, item, bpm).then(function (url) {
if (url) _nativeVstItemUrls[key] = url;
return url;
}).then(function (url) {
delete _nativeVstItemPending[key];
return url;
});
_nativeVstItemPending[key] = p;
return p;
};
let _prerenderNativeVstTimer = null;
const prerenderNativeVstItems = (tracks, bpm) => {
try {
if (!isStandaloneSf()) return;
if (_prerenderNativeVstTimer) { clearTimeout(_prerenderNativeVstTimer); _prerenderNativeVstTimer = null; }
_prerenderNativeVstTimer = setTimeout(function () {
_prerenderNativeVstTimer = null;
const seen = {};
(tracks || []).forEach(function (track) {
const nativeVst = isStandaloneSf() && isVstTrackEngine(track && track.synth_engine) && !(window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive());
if (!nativeVst) return;
(track.midiItems || []).forEach(function (item) {
const key = _nativeVstItemKey(track, item, bpm);
if (!key || seen[key] || _nativeVstItemUrls[key]) return;
seen[key] = true;
ensureRenderNativeVstItem(track, item, bpm).then(function () {});
});
});
}, 800);
} catch (e) {}
};
// ── Native VSTi item render (standalone, transport) ────────────────────────
// Render TOÀN BỘ MIDI item bằng pedalboard (VST3) → decode AudioBuffer →
@@ -363,16 +436,10 @@ const scheduleNativeVstItem = (track, item, offsetTime, context, destNode, bpm,
const end = itemStartAbs + ((n.start_beat || 0) + (n.duration_beats || 1)) * secPerBeat;
if (end > itemEndAbs) itemEndAbs = end;
});
window.SonicAPI.midiRender({
instrument_id: instrumentId,
notes: notes.map(function (n) { return { pitch: n.pitch || 60, start_beat: n.start_beat || 0, duration_beats: n.duration_beats || 1, velocity: n.velocity != null ? n.velocity : 0.8 }; }),
bpm: parseFloat(bpm) || 120,
preset_id: (eng && eng.preset_id) || undefined,
preset_path: (eng && eng.preset_path) || undefined,
preset_data: (eng && eng.preset_data) || undefined,
}).then(function (res) {
if (!res || !res.success || !res.url) return;
fetch(API_BASE_URL + res.url).then(function (r) { return r.arrayBuffer(); }).then(function (buf) {
const key = _nativeVstItemKey(track, item, bpm);
const scheduleUrl = function (url) {
if (!url) return;
fetch(url).then(function (r) { return r.arrayBuffer(); }).then(function (buf) {
context.decodeAudioData(buf, function (audioBuf) {
try {
if (opts && typeof opts.isActive === 'function' && !opts.isActive()) return;
@@ -393,7 +460,12 @@ const scheduleNativeVstItem = (track, item, offsetTime, context, destNode, bpm,
} catch (e) { console.warn('[NativeVST] schedule decode error:', e); }
}, function () {});
}).catch(function () {});
}).catch(function () {});
};
if (key && _nativeVstItemUrls[key]) { scheduleUrl(_nativeVstItemUrls[key]); return; }
ensureRenderNativeVstItem(track, item, bpm).then(function (url) {
if (!url) return;
scheduleUrl(url);
});
} catch (e) { console.warn('[NativeVST] scheduleNativeVstItem error:', e); }
};
@@ -17506,6 +17578,7 @@ const App = () => {
loadAudioBuffersForTracks(restoredTracks).catch(function(err) { console.warn('loadAudioBuffersForTracks error:', err); });
trackMidiChannelsRef.current = {};
preloadTrackInstruments(restoredTracks).catch(function(err) { console.warn('preloadTrackInstruments error:', err); });
prerenderNativeVstItems(restoredTracks, restoredBpm);
setBpm(restoredBpm.toString());
setSelectedTrackId(restoredTracks[0]?.id || '1');
setProjectName(lastName);
@@ -17644,6 +17717,7 @@ const App = () => {
if (restoredTracks.length > 0) {
setTracks(restoredTracks);
setBpm(restoredBpm.toString());
prerenderNativeVstItems(restoredTracks, restoredBpm);
if (restoredSessionTabs.length > 0) {
setSessionTabs(restoredSessionTabs);
}