d7756b08ab
- 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).
90 lines
3.9 KiB
C++
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
|