// app/static/js/services/nativeBridgeService.js // JS client for the C++ Native Host Bridge (daw_vst_bridge.exe). // All IPC goes through Tauri commands (Rust writes the shared memory) — // WebView2 JS cannot map Windows shared memory directly. (function () { // Must match native_bridge/include/INativeInstrument.h enum InstrumentType var INSTRUMENT_TYPE = { VST3: 0, VST2: 1, SF2: 2, SF3: 2, SFZ: 3 }; var service = { isBridgeConnected: false, activeInstrumentType: 'VST3', _audioCb: null, _statusCb: null, _tauri: function () { return window.__TAURI__; }, /** D10: UI subscribes to bridge connection changes. cb({connected}) or null to clear. */ onStatusChange: function (cb) { this._statusCb = cb; }, _notifyStatus: function (connected) { this.isBridgeConnected = !!connected; if (this._statusCb) this._statusCb({ connected: !!connected }); }, _init: function () { if (!this._tauri()) { // Dev mode (Linux / plain browser): no Tauri -> log-only, app uses SonicSF. console.log('[BridgeService] Dev mode: no __TAURI__, native bridge disabled.'); return; } var self = this; try { window.__TAURI__.event.listen('bridge-audio', function (e) { if (self._audioCb) self._audioCb(e.payload.l, e.payload.r); }); window.__TAURI__.event.listen('bridge-down', function () { self._notifyStatus(false); if (window.SonicMidiRouter) window.SonicMidiRouter.setBridgeConnected(false); if (window.AudioRoutingEngine) window.AudioRoutingEngine.disconnect(); console.warn('[BridgeService] bridge-down event received.'); }); } catch (e) { console.warn('[BridgeService] event listen failed:', e); } this.queryStatus(); }, queryStatus: async function () { if (!this._tauri()) return { connected: false }; try { var s = await window.__TAURI__.core.invoke('bridge_status'); this._notifyStatus(!!s.connected); return s; } catch (e) { this._notifyStatus(false); return { connected: false }; } }, /** * 1. LOAD NEW INSTRUMENT INTO BRIDGE (VST3 / VST2 / SF2 / SF3 / SFZ) * instrumentType: 'VST3' | 'VST2' | 'SF2' | 'SF3' | 'SFZ' * channel: MIDI channel to assign this instrument to (A10 multi-instance). */ loadInstrument: async function (filePath, instrumentType, channel) { this.activeInstrumentType = instrumentType; console.log('[BridgeService] Loading ' + instrumentType + ' asset: ' + filePath + ' ch=' + channel); if (!this._tauri()) return false; try { await window.__TAURI__.core.invoke('load_native_instrument', { path: filePath, instrumentType: INSTRUMENT_TYPE[instrumentType] !== undefined ? INSTRUMENT_TYPE[instrumentType] : 0, channel: channel === undefined ? 0 : channel }); return true; } catch (e) { console.warn('[BridgeService] loadInstrument failed:', e); return false; } }, /** * 2. UNIFIED MIDI EVENT DISPATCH * cmd: 'NOTE_ON' | 'NOTE_OFF' | 'CC' | 'PROGRAM' | 'PITCH_BEND' * velocity 0..1; sampleOffset in samples within the current audio block. * data2/data3 (A12): CC value / program / PB LSB|MSB. */ dispatchMidiEvent: function (cmd, channel, pitch, velocity, sampleOffset, data2, data3) { if (!this._tauri()) return false; var safeVelocity = Math.floor(Math.min(1.0, Math.max(0.0, velocity)) * 127); var byteCmd; switch (cmd) { case 'CC': byteCmd = 0xB; break; case 'PROGRAM': byteCmd = 0xC; break; case 'PITCH_BEND': byteCmd = 0xE; break; default: byteCmd = cmd === 'NOTE_ON' ? 0x9 : 0x8; break; } window.__TAURI__.core.invoke('push_midi_event', { command: byteCmd, channel: channel, pitch: pitch, velocity: safeVelocity, data2: data2 === undefined ? 0 : data2, data3: data3 === undefined ? 0 : data3, sampleOffset: sampleOffset || 0 }).catch(function (e) { console.warn('[BridgeService] push_midi_event:', e); }); return true; }, /** * 3. OPEN FLOATING CHILD WINDOW NATIVE GUI */ openNativeGUI: async function (pluginId) { if (!this._tauri()) return false; try { await window.__TAURI__.core.invoke('open_vst_gui', { pluginId: pluginId }); return true; } catch (e) { console.warn('[BridgeService] open_vst_gui:', e); return false; } }, /** * 4. TRANSPORT CONTROL ('play' | 'stop' | 'panic' | 'set_position') * playhead: sample position (A13) — used by 'play' and 'set_position'. */ transport: function (kind, playhead) { if (!this._tauri()) return false; var args = { kind: kind }; if (playhead !== undefined) args.playhead = playhead; window.__TAURI__.core.invoke('transport_control', args) .catch(function (e) { console.warn('[BridgeService] transport_control:', e); }); return true; }, /** * 5. AUDIO SINK: register consumer of PCM frames from the bridge. * cb(l: Float32Array, r: Float32Array) */ onAudio: function (cb) { this._audioCb = cb; } }; window.NativeBridgeService = service; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { service._init(); }); } else { service._init(); } })();