Files
SonicForgeStudio/native_bridge/include/SharedMemoryIPC.h
T
admin 6a3b430da0 A14: MIDI-out từ plugin (VST2/VST3) → SHM → Rust emit → DAW track
- Vst2Instrument: audioMaster callback thu VstEvents, push SHM midiOutQueue
- Vst3Instrument: IEventList output bus (kAudioMasterWantMidi) — check
- SharedMemoryIPC.h: midiOutQueue[64] + midiOutCount (offset 27556/28324, sizeof 28328)
- shm.rs: MidiEventIPC Serialize, drain_midi_out() emit 'bridge-midi-out'
- lib.rs: pump emit midi-out events
- nativeBridgeService.js: onMidiOut API
- app.jsx: route MIDI-out về selected track qua SonicMidiRouter (feedback guard)
- TASKS.md: A14 ✅
2026-08-19 21:12:07 +07:00

107 lines
5.0 KiB
C++

// native_bridge/include/SharedMemoryIPC.h
#pragma once
#include <cstdint>
#include <cstddef>
#ifdef _WIN32
#include <windows.h>
#endif
#define AUDIO_BLOCK_SIZE 256
// WARNING: volatile is NOT a sync primitive. Real impl should use an
// interlocked/index-flag pair or a Win32 event (SetEvent) signalled by the
// writer. This struct follows the spec layout so Rust/JS mapping stays in sync.
struct SharedAudioBufferIPC {
// Synchronization Flags
volatile uint32_t clientReadIndex;
volatile uint32_t bridgeWriteIndex;
// PCM Float32 Audio Buffers
float masterLeft[AUDIO_BLOCK_SIZE];
float masterRight[AUDIO_BLOCK_SIZE];
// Latency probe: bridge stamps every rendered block (QueryPerformanceCounter)
volatile uint64_t blockTimestamp;
// MIDI Event Exchange Queue
struct MidiEventIPC {
uint8_t command; // 0x9 note on / 0x8 note off / 0xB CC / 0xC program / 0xE pitch bend
uint8_t channel;
uint8_t pitch; // note number (0x9/0x8) or CC number (0xB)
uint8_t velocity; // 0-127 (0x9 + vel=0 == note off)
uint8_t data2; // CC value / program number / PB LSB
uint8_t data3; // PB MSB (0xE only)
uint8_t reserved[2]; // reserved[0]: live flag (1 = live input, bypass transportStopped guard); reserved[1]: 0 — keeps C/Rust layout stable
uint32_t sampleOffset;
} midiQueue[64];
volatile uint32_t midiQueueCount;
// Transport / Control (added vs spec: Stop/Play flush, instrument switch)
struct ControlEventIPC {
uint32_t type; // 0 = NONE, 1 = PANIC/ALL_NOTES_OFF, 2 = LOAD_INSTRUMENT,
// 3 = TRANSPORT, 4 = OPEN_GUI
uint32_t arg0; // LOAD: instrument type (InstrumentType);
// TRANSPORT: 0=STOP, 1=PLAY, 2=SET_POSITION
uint32_t arg1; // LOAD: string length (bytes) of path;
// TRANSPORT: playheadSamples (for timeline sync)
uint32_t channel; // LOAD: MIDI channel to assign instrument to (A10)
char arg2[1024]; // LOAD: UTF-8 path
} controlQueue[8];
volatile uint32_t controlQueueCount;
// G0.3: set by the bridge while an openGUI attach job is in flight
// (reload/attachView on a ChannelWorker). The Tauri watchdog reads this
// to extend its stall deadline — a long Nexus attach must not look like
// a dead bridge. Append-only field: existing offsets stay stable.
volatile uint32_t attachInFlight;
// G3.1: dedicated heartbeat, incremented every 100ms by a bridge-side
// thread INDEPENDENT of the audio loop. The Tauri watchdog keys bridge
// liveness on this, so a slow (but alive) audio loop no longer looks dead.
// Append-only field: existing offsets stay stable.
volatile uint32_t heartbeat;
// VSTi crackle fix: multi-slot audio ring (appended after heartbeat — all
// offsets above stay stable). The bridge renders block N into slot
// (bridgeWriteIndex & ringMask), then increments bridgeWriteIndex (publish).
// The reader drains EVERY slot between its last index and writeIndex, in
// order, so a delayed reader no longer loses a whole block to single-slot
// overwrite (the old 1-slot gap). ringMask = slot count - 1 (power of 2);
// 8 slots ~= 42ms of audio @ 48k — covers a pump/WebView jank window.
volatile uint32_t ringMask;
float ringLeft[8][AUDIO_BLOCK_SIZE];
float ringRight[8][AUDIO_BLOCK_SIZE];
// A14: MIDI OUT from plugins (VST2 audioMaster opcode=8 VstEvents,
// VST3 IEventList output bus). The bridge writes events the plugin
// generated; the Tauri pump drains them to `bridge-midi-out` and the
// JS routes them to DAW tracks. Append-only field: all offsets above
// stay stable.
MidiEventIPC midiOutQueue[64];
volatile uint32_t midiOutCount;
};
// G4.3: per-plugin sandbox SHM — double-buffered audio handoff.
// The child renders into the slot it is NOT publishing (writeSlot flips),
// then publishes; the bridge reads the published slot. A stale slot is safe
// to read (bridge keeps last block), so a slow child never tears audio.
struct SandboxHostIPC {
SharedAudioBufferIPC base; // MIDI/control/heartbeat, offsets unchanged
volatile uint32_t writeSlot; // 0 or 1: slot the child finished writing
float audioLeft[2][AUDIO_BLOCK_SIZE]; // 2 full blocks
float audioRight[2][AUDIO_BLOCK_SIZE];
};
// Shared-memory helpers (impl in SharedMemoryIPC.cpp).
struct ShmHandle;
ShmHandle* shm_open(const char* name, size_t size = sizeof(SharedAudioBufferIPC));
ShmHandle* shm_create(const char* name, size_t size = sizeof(SharedAudioBufferIPC));
SharedAudioBufferIPC* shm_ptr(ShmHandle* h);
SandboxHostIPC* shm_sandbox_ptr(ShmHandle* h);
void shm_close(ShmHandle* h);
bool shm_write_midi(ShmHandle* h, uint8_t cmd, uint8_t channel, uint8_t pitch,
uint8_t velocity, uint32_t sampleOffset, uint8_t data2 = 0,
uint8_t data3 = 0);