fix(latency): threadpool renders + RENDER_LOCK + client pre-render cache
This commit is contained in:
+85
-11
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user