fix(export): offline render VSTi qua engine khi chua play — fill midiCacheRef khong can play truoc
This commit is contained in:
+68
-9
@@ -27996,6 +27996,63 @@ const App = () => {
|
||||
};
|
||||
|
||||
// ── Server-side Export ──
|
||||
// Render MIDI offline qua engine khi chưa play (midiCacheRef rỗng → export
|
||||
// offline câm MIDI/VSTi). Render từng track qua native_bridge --render (VSTi/
|
||||
// SF2 trong bộ nhớ, preset đúng bridge channel), decode WAV → đổ vào
|
||||
// midiCacheRef cùng cấu trúc capture preview (chunks interleaved stereo) →
|
||||
// clientSideExport có âm không cần play trước. Schema per-track giữ NGUYÊN
|
||||
// mọi melodic track (channel round-robin phụ thuộc thứ tự melodic), chỉ clear
|
||||
// items track khác; items track target giữ MIDI_ITEM (audio clips client đã
|
||||
// schedule riêng qua t.buffer — không nhân đôi âm).
|
||||
const ensureMidiCacheViaEngine = async (midiTracks) => {
|
||||
if (serverStatus !== 'connected' || !window.SonicAPI || !window.SonicAPI.renderProject) return false;
|
||||
const baseSchema = serializeProjectToSchema(currentProjectId || 'temp_project', projectName || 'Dự án tạm', bpm, tracks, subTabs, sessionTabs, masteringSettings, masterVolume, { volL: masterVolL, volR: masterVolR, link: masterLink, muted: masterMuted });
|
||||
let okCount = 0;
|
||||
for (const t of midiTracks) {
|
||||
const schema = JSON.parse(JSON.stringify(baseSchema));
|
||||
(schema.main_session.tracks || []).forEach(st => {
|
||||
if (st.id === t.id) {
|
||||
st.items = (st.items || []).filter(it => it.type === 'MIDI_ITEM');
|
||||
} else {
|
||||
st.items = [];
|
||||
}
|
||||
});
|
||||
// Section store: bỏ hết items — âm section không được lẫn vào cache track MIDI.
|
||||
Object.keys(schema.section_store || {}).forEach(sk => {
|
||||
((schema.section_store[sk].tracks) || []).forEach(st => { st.items = []; });
|
||||
});
|
||||
try {
|
||||
const rate = parseInt(exportSettings.sampleRate) || 44100;
|
||||
const res = await window.SonicAPI.renderProject(schema, 'midicache_' + t.id + '_' + Date.now() + '.wav', {
|
||||
bit_depth: 16,
|
||||
sample_rate: rate,
|
||||
dither: false,
|
||||
normalize: false
|
||||
});
|
||||
const br = await fetch(res.url);
|
||||
if (!br.ok) throw new Error('HTTP ' + br.status);
|
||||
const ab = await br.arrayBuffer();
|
||||
const dctx = new OfflineAudioContext(2, 1, rate);
|
||||
const buf = await dctx.decodeAudioData(ab);
|
||||
if (!buf || buf.length < 100) continue;
|
||||
const L = buf.getChannelData(0);
|
||||
const R = buf.numberOfChannels > 1 ? buf.getChannelData(1) : L;
|
||||
const chunks = [];
|
||||
const CH = 4096;
|
||||
for (let i = 0; i < L.length; i += CH) {
|
||||
const n = Math.min(CH, L.length - i);
|
||||
const ch = new Float32Array(n * 2);
|
||||
for (let j = 0; j < n; j++) { ch[j * 2] = L[i + j]; ch[j * 2 + 1] = R[i + j]; }
|
||||
chunks.push(ch);
|
||||
}
|
||||
midiCacheRef.current[t.id] = { chunks, duration: buf.duration, sampleRate: buf.sampleRate };
|
||||
okCount++;
|
||||
} catch (e) {
|
||||
console.warn('[MidiCache] engine render track', t.id, 'fail:', e);
|
||||
}
|
||||
}
|
||||
return okCount > 0;
|
||||
};
|
||||
const triggerWavExport = async () => {
|
||||
// Stop transport ngay khi bắt đầu export offline → tắt âm thanh đang phát.
|
||||
try { if (typeof stopAllPlayback === 'function') stopAllPlayback(); } catch (e) { }
|
||||
@@ -28031,15 +28088,17 @@ const App = () => {
|
||||
return c && c.chunks.length > 0 && c.duration >= needDur - 0.3 && cacheHasEnergy(c);
|
||||
});
|
||||
// sfEntry → file offline đủ duration nhưng KHÔNG CÓ ÂM THANH. Khi cache
|
||||
// không đủ → TỰ chuyển sang bounce REALTIME (capture masterBus.output =
|
||||
// đúng âm đang nghe) thay vì xuất file câm. Áp dụng Export Project (mix).
|
||||
if (midiTracks.length > 0 && !cacheOK && exportSettings.source === 'project' && exportSettings.format === 'wav') {
|
||||
showToast("Cache MIDI chưa đủ — tự chuyển sang bounce REALTIME để giữ nguyên âm thanh...", "info");
|
||||
await triggerBounceExport();
|
||||
return;
|
||||
}
|
||||
if (midiTracks.length > 0 && !cacheOK) {
|
||||
showToast("Project có MIDI items chưa đủ cache preview — file offline có thể thiếu âm MIDI. Bật Realtime để bounce trung thực.", "warning");
|
||||
// không đủ (chưa play preview) → render MIDI qua engine ngay (VSTi/SF2
|
||||
// trong bộ nhớ, preset đúng bridge channel) đổ vào midiCacheRef — export
|
||||
// offline có âm KHÔNG cần phát trước. Chỉ khi engine không render được
|
||||
// mới cảnh báo (file có thể thiếu âm MIDI) rồi vẫn xuất.
|
||||
if (midiTracks.length > 0 && !cacheOK && exportSettings.source !== 'active_clip' && exportSettings.source !== 'clip_selection') {
|
||||
const filled = await ensureMidiCacheViaEngine(midiTracks);
|
||||
if (filled) {
|
||||
showToast("Đã render MIDI/VSTi offline qua engine — export không cần phát trước.", "success");
|
||||
} else {
|
||||
showToast("Project có MIDI items chưa đủ cache preview — file offline có thể thiếu âm MIDI. Bật Realtime để bounce trung thực.", "warning");
|
||||
}
|
||||
}
|
||||
}
|
||||
let exportTracks;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user