Files
SonicForgeStudio/native_bridge/src/SandboxVst2Host.h
T
admin d7756b08ab fix(bridge): đồng đều âm lượng VSTi jbridge, GUI native lên top, modal instrument lọc FX
- calibrate: probe sandbox (jbridge) theo real-time pacing — child render qua SHM
  consumer-sync, probe µs-speed chỉ đo được silence/attack → makeup=1.0 mọi channel.
  Thêm virtual probeNeedsRealtime() (SandboxVst2Host → true), calibrate() Sleep
  ~1 block/5.8ms để child kịp nhận MIDI + render steady-state → makeup đo peak thật
  (3 channel Qin ≈ 2.6, trước đây = 1.0 → âm lượng đồng đều giữa các track/VSTi).
- OPEN_GUI: SetWindowPos(HWND_TOPMOST) + SetForegroundWindow + BringWindowToTop
  → GUI VSTi hiển thị ở top window, tương tác ngay.
- /api/v1/plugins/available: filter vst_instruments + extra theo is_fx của
  fx_vst_bridge --scan (trước merge entry) — modal Select Instrument chỉ còn
  soundfont + VSTi, không hiện VST FX (90 → 33).
2026-08-21 10:18:25 +07:00

90 lines
3.9 KiB
C++

// native_bridge/src/SandboxVst2Host.h
// G4.4: VST2 instrument (jBridge stubs like Qin_RV) hosted in a CHILD process.
// jBridge 1.6b3 allows only ONE instance of a given plugin per host process -
// the second instance's shared audio buffer already exists, so it stays
// silent. Each child process is the "first" Bridger64 instance for its own
// process, so any number of Qin tracks produce audio. The child owns its GUI
// window (opened on control 4 = OPEN_GUI).
#ifndef SANDBOX_VST2_HOST_H
#define SANDBOX_VST2_HOST_H
#include "INativeInstrument.h"
#include "SharedMemoryIPC.h"
#include <atomic>
#include <string>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#endif
class SandboxVst2Host : public INativeInstrument {
public:
SandboxVst2Host();
~SandboxVst2Host() override;
bool loadPlugin(const std::string& path, double sampleRate, uint32_t channel);
bool init(double sampleRate, uint32_t maxBlockSize) override;
void selectProgram(uint32_t channel, uint32_t bank, uint32_t program) override {}
void noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) override;
void noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) override;
void controlChange(uint32_t channel, uint32_t cc, uint32_t value) override;
void programChange(uint32_t channel, uint32_t program) override;
void pitchBend(uint32_t channel, uint32_t bend14) override;
// The child owns its GUI window (plugin_host opens it on OPEN_GUI control).
bool openGUI(void* parentWindowHandle) override;
bool canOpenGUI() const override { return true; }
bool ownsGuiWindow() const override { return true; } // child owns its top-level window
// The child renders at consumer pace and sleeps 15ms while idle - the
// calibration probe must run in real-time or it only sees silence/attack.
bool probeNeedsRealtime() const override { return true; }
bool attachView(void* parentWindowHandle) override; // bridge OPEN_GUI job calls this
bool hasAttachedView() const override { return guiOpen_.load(); }
void closeGUI() override;
void guiIdle() override {} // child pumps effEditIdle itself
// G4.5: preset state lives in the child. The child republishes its
// chunk into presetOut (after load / presetIn apply / GUI edits);
// serializeState reads it. loadSerializedState writes presetIn for
// the child to apply on its next loop pass.
void captureState() override {}
void restoreState() override {}
std::string serializeState() const override;
void loadSerializedState(const std::string& base64) override;
void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) override;
private:
bool spawnChild();
void watchdogLoop();
void pushControl(uint32_t type, uint32_t arg0 = 0, uint32_t arg1 = 0);
void seedDefaultPreset();
void queuePreset(const std::string& base64, bool waitApply = false);
std::string path_;
std::string shmName_;
std::string lastPreset_; // last preset queued (seed or real patch) — re-queued on watchdog respawn
// G4.7: local copy of the last consumed child block. The DAW may call
// processAudioBlock in segments within one block; serve every segment
// from ONE coherent child block (copied on slot change). lastWriteSlot_
// starts 0 == zeroed shm slot 0, so the first child publish (slot 1)
// always triggers the copy; before that we serve silence.
float lastLeft_[AUDIO_BLOCK_SIZE] = {};
float lastRight_[AUDIO_BLOCK_SIZE] = {};
uint32_t lastWriteSlot_ = 0;
double sampleRate_ = 44100.0;
uint32_t block_ = AUDIO_BLOCK_SIZE;
uint32_t channel_ = 0;
ShmHandle* shm_ = nullptr;
SandboxHostIPC* ipc_ = nullptr;
std::atomic<bool> alive_{false};
std::atomic<bool> stop_{false};
std::atomic<bool> guiOpen_{false};
std::thread watchdog_;
#ifdef _WIN32
HANDLE childProc_ = nullptr;
#endif
};
#endif // SANDBOX_VST2_HOST_H