feat: native host bridge integration — C++ bridge, Rust SHM, JS routing, build scripts, docs
- native_bridge/: InstrumentEngineManager multi-channel, sample-accurate, CC/program/pitchbend, transport, Vst3Instrument stub (HAVE_VST3SDK) - src-tauri: shm.rs, bridge spawn + audio pump + health monitor, open_vst_gui, externalBin, commands - app: UnifiedMidiRouter, NativeBridgeService, bridgeAudioNode, audioRoutingEngine, Plugin Manager UI, Bridge/WASM indicator, set_position sync - build: 3 ps1 (force-added, build/ ignored), verify_bundle --check-bridge, CI workflow - docs: TASKS.md, TEST_NOTES.md (Windows verify checklist), install/report updates
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// app/static/js/services/unifiedMidiRouter.js
|
||||
// Single MIDI entry point for Web MIDI keyboard + timeline playback.
|
||||
// bridge connected -> NativeBridgeService.dispatchMidiEvent (Rust SHM -> C++ bridge)
|
||||
// bridge down -> onFallback callback (app.jsx wires SonicSF/Carla path).
|
||||
(function () {
|
||||
var MELODIC_CHANNELS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]; // skip 9 (percussion)
|
||||
|
||||
var router = {
|
||||
bridgeConnected: false,
|
||||
onFallback: null, // function(cmd, channel, pitch, velocity) — set by app.jsx
|
||||
forceWasm: false, // debug: D10 "Ép dùng WASM"
|
||||
_channels: {}, // trackId -> { ch, percussion }
|
||||
_nextMelodicIdx: 0,
|
||||
|
||||
setBridgeConnected: function (flag) {
|
||||
this.bridgeConnected = !!flag;
|
||||
},
|
||||
|
||||
setForceWasm: function (flag) { this.forceWasm = !!flag; },
|
||||
|
||||
isBridgeActive: function () {
|
||||
return this.bridgeConnected && !this.forceWasm && !!window.NativeBridgeService;
|
||||
},
|
||||
|
||||
/** trackId -> MIDI channel; percussion (bank 128) -> ch 9. */
|
||||
allocateChannel: function (trackId, isPercussion) {
|
||||
if (this._channels[trackId]) return this._channels[trackId].ch;
|
||||
var ch;
|
||||
if (isPercussion) {
|
||||
ch = 9;
|
||||
} else {
|
||||
ch = MELODIC_CHANNELS[this._nextMelodicIdx % MELODIC_CHANNELS.length];
|
||||
this._nextMelodicIdx++;
|
||||
}
|
||||
this._channels[trackId] = { ch: ch, percussion: !!isPercussion };
|
||||
return ch;
|
||||
},
|
||||
|
||||
resetChannels: function () {
|
||||
this._channels = {};
|
||||
this._nextMelodicIdx = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* cmd: 'NOTE_ON' | 'NOTE_OFF' | 'CC' | 'PROGRAM' | 'PITCH_BEND'
|
||||
* channel: MIDI channel (or trackId -> allocateChannel first)
|
||||
* velocity: 0..1 (normalized); sampleOffset: samples within current block.
|
||||
* data2/data3 (A12): CC value / program / PB LSB|MSB — passed to bridge only.
|
||||
*/
|
||||
pushEvent: function (opts) {
|
||||
var cmd = opts.cmd, ch = opts.channel, pitch = opts.pitch;
|
||||
var vel = (opts.velocity === undefined ? 1.0 : opts.velocity);
|
||||
var sampleOffset = opts.sampleOffset || 0;
|
||||
if (typeof ch === 'string') ch = this.allocateChannel(ch, opts.percussion);
|
||||
if (ch === undefined || ch === null) ch = 0;
|
||||
if (this.isBridgeActive()) {
|
||||
window.NativeBridgeService.dispatchMidiEvent(cmd, ch, pitch, vel, sampleOffset, opts.data2, opts.data3);
|
||||
return;
|
||||
}
|
||||
if (this.onFallback) this.onFallback(cmd, ch, pitch, vel);
|
||||
},
|
||||
|
||||
/** Stop/panic -> bridge transport panic (flush all notes) + fallback local stopAll. */
|
||||
panic: function () {
|
||||
if (this.isBridgeActive() && window.NativeBridgeService.transport) {
|
||||
window.NativeBridgeService.transport('panic');
|
||||
}
|
||||
if (this.onFallback) this.onFallback('PANIC', 0, 0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
window.SonicMidiRouter = router;
|
||||
})();
|
||||
Reference in New Issue
Block a user