fix: SF2 sound (audio sink FIFO underrun) + VST3 native GUI open + GUI load race
- bridgeAudioNode: ScriptProcessor 4096 drained only 1 of 16 blocks -> ~94% silence; now drains all queued blocks per callback, RING_DEPTH 8->24 - app.jsx: openNativeGUI had NO caller; wire after VST3 loadInstrument OK in track Synth dropdown + Plugin Manager Load Bridge - main.cpp: OPEN_GUI polls up to 10s for LOAD completion (worker thread) before attach - fixes race 'no instrument loaded' - index.html: bump bridgeAudioNode/app.precompiled cache versions
This commit is contained in:
@@ -5725,6 +5725,11 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
|
||||
const ok = await window.NativeBridgeService.loadInstrument(r.path, type, 0);
|
||||
window.showToast && window.showToast(ok ? ('Đã load vào Native Bridge: ' + r.path) : 'Bridge không khả dụng (dev mode?)', ok ? 'success' : 'warning');
|
||||
if (ok) window.SonicAPI.bridgeStatus().then(s => setBridgeStatus(s)).catch(() => {});
|
||||
// Requirement 2: VST3 load → mở native GUI ngay (C++ attach editor vào
|
||||
// cửa sổ bridge tự tạo — control type=4, hwnd=0).
|
||||
if (ok && type === 'VST3' && window.NativeBridgeService.openNativeGUI) {
|
||||
try { await window.NativeBridgeService.openNativeGUI(name); } catch (e) {}
|
||||
}
|
||||
return ok;
|
||||
} catch (err) {
|
||||
window.showToast && window.showToast('Lỗi load bridge: ' + (err.message || err), 'error');
|
||||
@@ -15370,6 +15375,11 @@ const App = () => {
|
||||
if (p) {
|
||||
const ok = await window.NativeBridgeService.loadInstrument(p, btype, bch);
|
||||
console.log('[Bridge] track', trackId, '-> bridge', btype, 'ch', bch, ok ? 'OK' : 'FAIL', p);
|
||||
// Requirement 2: chọn VST3 qua nút Synth → load xong mở native GUI
|
||||
// (C++ attach editor vào cửa sổ bridge tự tạo — control type=4, hwnd=0).
|
||||
if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) {
|
||||
try { await window.NativeBridgeService.openNativeGUI(instrumentId); } catch (e) {}
|
||||
}
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,9 +2,12 @@
|
||||
// Audio sink: consumes 'bridge-audio' PCM frames (native bridge) and plays them
|
||||
// through a ScriptProcessorNode into the WebAudio graph (track FX / master bus).
|
||||
(function () {
|
||||
var RING_DEPTH = 8; // 8 blocks * 256 samples ~= 46ms anti-underrun
|
||||
var SP_BUFFER = 4096; // ScriptProcessor chunk (16 bridge blocks)
|
||||
var _queue = [];
|
||||
// Bridge renders one 256-sample block per ~5.8ms; ScriptProcessor fires once
|
||||
// per 4096 samples (~16 blocks @44.1k). The ring MUST cover one full callback
|
||||
// period or we underrun (silence gaps) — 24 blocks ~= 139ms headroom.
|
||||
var RING_DEPTH = 24;
|
||||
var SP_BUFFER = 4096;
|
||||
var _chunks = []; // [{l: Float32Array(256), r: Float32Array(256)}]
|
||||
var _spn = null;
|
||||
var _gainNode = null;
|
||||
var _ctx = null;
|
||||
@@ -27,10 +30,26 @@
|
||||
_spn.onaudioprocess = function (e) {
|
||||
var L = e.outputBuffer.getChannelData(0);
|
||||
var R = e.outputBuffer.getChannelData(1);
|
||||
var outLen = L.length;
|
||||
L.fill(0); R.fill(0);
|
||||
if (!_queue.length) return;
|
||||
var f = _queue.shift();
|
||||
L.set(f.l); R.set(f.r);
|
||||
if (!_chunks.length) return;
|
||||
// Drain as many queued blocks as fit in this callback (16 max) — filling
|
||||
// only one block per callback previously played 1/16 of the audio.
|
||||
var written = 0;
|
||||
while (written < outLen && _chunks.length) {
|
||||
var blk = _chunks[0];
|
||||
var n = Math.min(blk.l.length, outLen - written);
|
||||
L.set(blk.l.subarray(0, n), written);
|
||||
R.set(blk.r.subarray(0, n), written);
|
||||
written += n;
|
||||
if (n >= blk.l.length) {
|
||||
_chunks.shift();
|
||||
} else {
|
||||
// Partial block (only possible if block size != 256): keep remainder.
|
||||
blk.l = blk.l.subarray(n);
|
||||
blk.r = blk.r.subarray(n);
|
||||
}
|
||||
}
|
||||
};
|
||||
_spn.connect(_gainNode);
|
||||
_initialized = true;
|
||||
@@ -39,11 +58,12 @@
|
||||
|
||||
function onAudio(l, r) {
|
||||
if (!_initialized) init();
|
||||
_queue.push({ l: new Float32Array(l), r: new Float32Array(r) });
|
||||
if (_queue.length > RING_DEPTH) _queue.shift(); // drop oldest on overrun
|
||||
_chunks.push({ l: new Float32Array(l), r: new Float32Array(r) });
|
||||
// Drop oldest on overrun — keeps latency bounded (~139ms max).
|
||||
while (_chunks.length > RING_DEPTH) _chunks.shift();
|
||||
}
|
||||
|
||||
function flush() { _queue = []; }
|
||||
function flush() { _chunks = []; }
|
||||
|
||||
function getOutputNode() { return _gainNode; }
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<script src="/static/js/services/soundfontStorage.js?v=202607271016"></script>
|
||||
<script src="/static/js/services/soundfontPlayer.js?v=202608101800"></script>
|
||||
<script src="/static/js/services/nativeBridgeService.js?v=202608112200"></script>
|
||||
<script src="/static/js/services/bridgeAudioNode.js?v=202608112200"></script>
|
||||
<script src="/static/js/services/bridgeAudioNode.js?v=202608121600"></script>
|
||||
<script src="/static/js/services/unifiedMidiRouter.js?v=202608112200"></script>
|
||||
<script src="/static/js/services/audioRoutingEngine.js?v=202608112200"></script>
|
||||
<script src="/static/js/services/aiGateway.js?v=202608037200"></script>
|
||||
@@ -50,7 +50,7 @@
|
||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608121215" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608121600" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user