Add VST2 hosting to native bridge; fix CC7 data2 routing verified via SHM test
VST2 (Steinberg 2.4 ABI) host via Vst2Instrument: aeffect.h, dispatcher, process events/replacing, kEffectStartProcess, headless GUI skip. - create_instrument returns Vst2Instrument for InstrumentType::VST2 - 64B-aligned scratch buffers for SIMD plugins; SEH crash capture module+stack - plugins.py: VST2 resolves .dll/.so; vst_engine.py reports VST2 type - app.jsx: derive instrumentType from scan (VST3 vs VST2), not hardcoded - Install exes refreshed from Release build SF2 FluidSynth audio verified end-to-end through SHM ring (energy 1.33): silence was a test-harness bug (CC7 value written to velocity field, bridge reads data2) — app sends data2 correctly. All temporary diagnostics removed.
This commit is contained in:
@@ -1248,7 +1248,7 @@ def _resolve_bridge_asset(name: str, instrument_type: str, path: Optional[str])
|
||||
(fname.lower() == base.lower() or os.path.splitext(fname)[0].lower() == base_noext):
|
||||
return os.path.join(root, fname)
|
||||
ext = (".vst3" if instrument_type.upper() == "VST3" else
|
||||
".vst2" if instrument_type.upper() == "VST2" else
|
||||
(".dll", ".so") if instrument_type.upper() == "VST2" else
|
||||
".sfz" if instrument_type.upper() == "SFZ" else None)
|
||||
if ext:
|
||||
try:
|
||||
@@ -1267,7 +1267,7 @@ def _resolve_bridge_asset(name: str, instrument_type: str, path: Optional[str])
|
||||
continue
|
||||
for root, _, files in os.walk(base_dir):
|
||||
for fn in files:
|
||||
if fn.lower().endswith(ext) and \
|
||||
if (fn.lower().endswith(ext) if isinstance(ext, tuple) else fn.lower().endswith(ext)) and \
|
||||
(fn.lower() == base.lower() or os.path.splitext(fn)[0].lower() == base_noext):
|
||||
return os.path.join(root, fn)
|
||||
# V9 bug 8: Windows VST3 = FOLDER "X.vst3" (chua X.vst3.dll) —
|
||||
@@ -1277,7 +1277,7 @@ def _resolve_bridge_asset(name: str, instrument_type: str, path: Optional[str])
|
||||
# base_noext.
|
||||
for sub in os.listdir(root):
|
||||
sp = os.path.join(root, sub)
|
||||
if os.path.isdir(sp) and sub.lower().endswith(ext) and \
|
||||
if os.path.isdir(sp) and (sub.lower().endswith(ext) if isinstance(ext, tuple) else sub.lower().endswith(ext)) and \
|
||||
(sub.lower() == base.lower() or os.path.splitext(sub)[0].lower() == base_noext):
|
||||
return sp
|
||||
return None
|
||||
|
||||
@@ -470,10 +470,13 @@ class PluginManager:
|
||||
return presets[:256]
|
||||
|
||||
def list_available(self) -> dict:
|
||||
plugins = self._scan_plugins()
|
||||
return {
|
||||
"vst_instruments": [
|
||||
{"id": k, "name": k, "type": "VST3", "has_native_support": HAS_PEDALBOARD}
|
||||
for k in self._scan_plugins().keys()
|
||||
{"id": k, "name": k,
|
||||
"type": "VST3" if p.lower().endswith(".vst3") else "VST2",
|
||||
"has_native_support": HAS_PEDALBOARD}
|
||||
for k, p in plugins.items()
|
||||
],
|
||||
"soundfonts": self._scan_soundfonts()
|
||||
}
|
||||
|
||||
+15
-4
@@ -6382,9 +6382,16 @@ const loadVstToBridge = async (track) => {
|
||||
// V8 bug 5: engine _resolve_bridge_asset chi match FILE theo filename —
|
||||
// KHONG walk folder VST3 ("X.vst3/X.vst3.dll") → 404. Dung path da luu
|
||||
// (synth_engine.plugin_path) hoac cache tu listPlugins.
|
||||
// VST2 (.dll) scan type 'VST2' - khong hardcode VST3 (se LOAD sai loai).
|
||||
let btype = 'VST3';
|
||||
let knownPath = track.synth_engine && track.synth_engine.plugin_path;
|
||||
if (!knownPath && window.__bridgePluginPaths) knownPath = window.__bridgePluginPaths[instrumentId];
|
||||
const r = await window.SonicAPI.bridgeLoad({ name: instrumentId, path: knownPath || null, instrumentType: 'VST3', channel: bch });
|
||||
if (instrumentSelectorData) {
|
||||
const v0 = (instrumentSelectorData.vst_instruments || []).find(v => v.id === instrumentId || v.name === instrumentId);
|
||||
if (v0 && v0.path) knownPath = v0.path;
|
||||
if (v0 && v0.type) btype = v0.type;
|
||||
}
|
||||
const r = await window.SonicAPI.bridgeLoad({ name: instrumentId, path: knownPath || null, instrumentType: btype, channel: bch });
|
||||
let p = r && r.path ? r.path : null;
|
||||
if (!p && knownPath) p = knownPath;
|
||||
if (!p && window.SonicAPI.listPlugins) {
|
||||
@@ -6393,15 +6400,16 @@ const loadVstToBridge = async (track) => {
|
||||
const v = ((pl && pl.vst_instruments) || []).find(x => x.id === instrumentId || x.name === instrumentId);
|
||||
if (v && v.path) {
|
||||
p = v.path;
|
||||
if (v && v.type) btype = v.type;
|
||||
if (!window.__bridgePluginPaths) window.__bridgePluginPaths = {};
|
||||
window.__bridgePluginPaths[instrumentId] = v.path;
|
||||
}
|
||||
} catch (e2) { console.warn('[Bridge] listPlugins fallback fail:', e2); }
|
||||
}
|
||||
if (!p) return false;
|
||||
const ok = await window.NativeBridgeService.loadInstrument(p, 'VST3', bch);
|
||||
const ok = await window.NativeBridgeService.loadInstrument(p, btype, bch);
|
||||
if (ok) window.__bridgeLoadedChannels[bch] = true;
|
||||
console.log('[Bridge] auto-load track', track.id, '-> VST3 ch', bch, ok ? 'OK' : 'FAIL', p);
|
||||
console.log('[Bridge] auto-load track', track.id, '->', btype, 'ch', bch, ok ? 'OK' : 'FAIL', p);
|
||||
return !!ok;
|
||||
} catch (e) { console.warn('[Bridge] loadVstToBridge fail:', track.id, e); return false; }
|
||||
};
|
||||
@@ -15753,7 +15761,9 @@ const App = () => {
|
||||
// (flag cũ còn true) → OPEN_GUI đến bridge khi channel chưa có instrument
|
||||
// → deferred mãi, nút GUI không mở gì.
|
||||
if (isSf && window.__bridgeLoadedChannels) delete window.__bridgeLoadedChannels[bch];
|
||||
const btype = isSf ? 'SF2' : 'VST3';
|
||||
// VST2 (.dll) scan type la 'VST2' - khong hardcode VST3 (se LOAD sai
|
||||
// loai -> create_instrument nullptr -> fail im lang).
|
||||
let btype = isSf ? 'SF2' : 'VST3';
|
||||
let bpath = null;
|
||||
if (instrumentSelectorData) {
|
||||
if (isSf) {
|
||||
@@ -15763,6 +15773,7 @@ const App = () => {
|
||||
} else {
|
||||
const v0 = (instrumentSelectorData.vst_instruments || []).find(v => v.id === instrumentId || v.name === instrumentId);
|
||||
bpath = v0 && v0.path;
|
||||
if (v0 && v0.type) btype = v0.type;
|
||||
}
|
||||
}
|
||||
(async () => {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -48,6 +48,7 @@ else()
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${FLUIDSYNTH_INCLUDE_DIR}
|
||||
${SFIZZ_INCLUDE_DIR}
|
||||
@@ -58,6 +59,7 @@ add_executable(daw_vst_bridge
|
||||
src/NativeInstrumentEngine.cpp
|
||||
src/SharedMemoryIPC.cpp
|
||||
src/Vst3Instrument.cpp
|
||||
src/Vst2Instrument.cpp
|
||||
src/StateStore.cpp
|
||||
src/SandboxVst3Host.cpp
|
||||
)
|
||||
@@ -88,6 +90,8 @@ add_executable(gui_probe
|
||||
src/NativeInstrumentEngine.cpp
|
||||
src/SharedMemoryIPC.cpp
|
||||
src/Vst3Instrument.cpp
|
||||
src/Vst2Instrument.cpp
|
||||
src/SandboxVst3Host.cpp
|
||||
)
|
||||
if(VST3_SDK_TARGET)
|
||||
target_compile_definitions(gui_probe PRIVATE HAVE_VST3SDK=1)
|
||||
@@ -125,6 +129,9 @@ endif()
|
||||
add_executable(seh_channel_test
|
||||
tests/seh_channel_test.cpp
|
||||
src/Vst3Instrument.cpp
|
||||
src/Vst2Instrument.cpp
|
||||
src/SandboxVst3Host.cpp
|
||||
src/SharedMemoryIPC.cpp
|
||||
)
|
||||
if(VST3_SDK_TARGET)
|
||||
target_compile_definitions(seh_channel_test PRIVATE HAVE_VST3SDK=1)
|
||||
|
||||
@@ -33,6 +33,9 @@ public:
|
||||
|
||||
// Open/close Native GUI window
|
||||
virtual bool openGUI(void* parentWindowHandle) = 0;
|
||||
// Headless engines (VST2 without an editor, SF2/SFZ): the bridge must
|
||||
// not create/reuse a window for them.
|
||||
virtual bool canOpenGUI() const { return true; }
|
||||
virtual void closeGUI() = 0;
|
||||
// VST3 reopen-after-close: the bridge checks before openGUI whether the
|
||||
// instance must be rebuilt; while it is, the engine sets reloading=true
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <fluidsynth.h>
|
||||
#include <sfizz.hpp>
|
||||
#include "Vst3Instrument.h"
|
||||
#include "Vst2Instrument.h"
|
||||
#include "SandboxVst3Host.h"
|
||||
|
||||
// void* members keep fluid types out of the public header; cast here.
|
||||
@@ -173,8 +174,8 @@ std::unique_ptr<INativeInstrument> InstrumentEngineManager::create_instrument(In
|
||||
if (std::getenv("SF_SANDBOX_VST3"))
|
||||
return std::make_unique<SandboxVst3Host>();
|
||||
return std::make_unique<Vst3Instrument>();
|
||||
// ponytail: VST2 host (VST2.4 SDK, Steinberg discontinued) not implemented.
|
||||
case InstrumentType::VST2:
|
||||
return std::make_unique<Vst2Instrument>();
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -196,6 +197,12 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
||||
else if (auto* s = dynamic_cast<SandboxVst3Host*>(inst.get()))
|
||||
loaded = s->loadPlugin(path, sampleRate, channel);
|
||||
}
|
||||
else if (type == InstrumentType::VST2) {
|
||||
if (auto* v2 = dynamic_cast<Vst2Instrument*>(inst.get())) {
|
||||
v2->setChannel(channel);
|
||||
loaded = v2->loadPlugin(path, sampleRate);
|
||||
}
|
||||
}
|
||||
if (type == InstrumentType::VST3) {
|
||||
if (auto* v = dynamic_cast<Vst3Instrument*>(inst.get()))
|
||||
v->setChannel(channel);
|
||||
@@ -394,16 +401,31 @@ void InstrumentEngineManager::allNotesOff() {
|
||||
}
|
||||
}
|
||||
|
||||
// Some commercial plugins (DUNE 3) use aligned SIMD loads/stores on their
|
||||
// audio buffers; plain std::vector is only 16B-aligned. Over-allocate and
|
||||
// align the pointer handed to plugins to 64B.
|
||||
static float* alignF(std::vector<float>& v, size_t n) {
|
||||
if (v.size() < n + 16) v.assign(n + 16, 0.0f);
|
||||
uintptr_t a = reinterpret_cast<uintptr_t>(v.data());
|
||||
return reinterpret_cast<float*>((a + 63) & ~(uintptr_t)63);
|
||||
}
|
||||
|
||||
// G3.3: SEH cannot live inside a function that needs C++ unwinding (C2712),
|
||||
// so per-channel process() runs in this helper. A plugin access violation
|
||||
// (0xC0000005) is caught here — the channel is muted instead of killing the
|
||||
// whole bridge process. /EHa (set in CMakeLists) allows mixing with the
|
||||
// outer C++ try/catch net in main.cpp.
|
||||
static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR, uint32_t n) {
|
||||
static DWORD g_sehCode = 0;
|
||||
static void* g_sehAddr = nullptr;
|
||||
static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR, uint32_t n, DWORD* outCode) {
|
||||
*outCode = 0;
|
||||
__try {
|
||||
inst->processAudioBlock(outL, outR, n);
|
||||
return true;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
} __except (g_sehCode = GetExceptionCode(),
|
||||
g_sehAddr = GetExceptionInformation()->ExceptionRecord->ExceptionAddress,
|
||||
EXCEPTION_EXECUTE_HANDLER) {
|
||||
*outCode = g_sehCode;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -418,27 +440,49 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t
|
||||
std::memset(outputL, 0, numSamples * sizeof(float));
|
||||
std::memset(outputR, 0, numSamples * sizeof(float));
|
||||
if (channels_.empty()) return;
|
||||
if (scratchL_.size() < numSamples) {
|
||||
scratchL_.resize(numSamples);
|
||||
scratchR_.resize(numSamples);
|
||||
}
|
||||
for (auto& [ch, inst] : channels_) {
|
||||
if (channelQuiet(ch)) continue; // editor open / crashed: do not process
|
||||
std::memset(scratchL_.data(), 0, numSamples * sizeof(float));
|
||||
std::memset(scratchR_.data(), 0, numSamples * sizeof(float));
|
||||
if (!SafeProcessChannel(inst.get(), scratchL_.data(), scratchR_.data(), numSamples)) {
|
||||
float* spL = alignF(scratchL_, numSamples);
|
||||
float* spR = alignF(scratchR_, numSamples);
|
||||
std::memset(spL, 0, numSamples * sizeof(float));
|
||||
std::memset(spR, 0, numSamples * sizeof(float));
|
||||
DWORD sehCode = 0;
|
||||
if (!SafeProcessChannel(inst.get(), spL, spR, numSamples, &sehCode)) {
|
||||
// G3.3: plugin fault — mute this channel only, keep the bridge up.
|
||||
if (!crashedCh_[ch]) {
|
||||
crashedCh_[ch] = true;
|
||||
std::cerr << "[NativeBridge] G3.3: channel " << ch
|
||||
<< " plugin crashed in processAudioBlock (SEH) — muted until reload"
|
||||
<< std::endl;
|
||||
{
|
||||
HMODULE m = nullptr;
|
||||
char mod[MAX_PATH] = "?";
|
||||
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
(LPCSTR)g_sehAddr, &m))
|
||||
GetModuleFileNameA(m, mod, MAX_PATH);
|
||||
std::cerr << "[NativeBridge] G3.3: channel " << ch
|
||||
<< " plugin crashed in processAudioBlock (SEH code=0x" << std::hex << sehCode
|
||||
<< " addr=0x" << (uintptr_t)g_sehAddr
|
||||
<< std::dec << " mod=" << mod
|
||||
<< ") — muted until reload"
|
||||
<< std::endl;
|
||||
void* bt[16];
|
||||
USHORT nf = RtlCaptureStackBackTrace(0, 16, bt, nullptr);
|
||||
std::cerr << "[NativeBridge] stack:";
|
||||
for (USHORT fi = 0; fi < nf; ++fi) {
|
||||
HMODULE bm = nullptr;
|
||||
char bmod[MAX_PATH] = "?";
|
||||
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
(LPCSTR)bt[fi], &bm))
|
||||
GetModuleFileNameA(bm, bmod, MAX_PATH);
|
||||
std::cerr << " " << std::hex << (uintptr_t)bt[fi] << std::dec
|
||||
<< "(" << bmod << ")";
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 0; i < numSamples; ++i) {
|
||||
outputL[i] += scratchL_[i];
|
||||
outputR[i] += scratchR_[i];
|
||||
outputL[i] += spL[i];
|
||||
outputR[i] += spR[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
// native_bridge/src/Vst2Instrument.cpp
|
||||
// VST2 (VST 2.4, 64-bit) host implementation.
|
||||
//
|
||||
// Clean-room: ABI facts come from the public VST2.4 specification; the
|
||||
// interface header (vestige/aeffect.h) is written from those facts, not
|
||||
// copied from Steinberg or LMMS source. No GUI editor (headless): the bridge
|
||||
// renders audio; preset state round-trips via effGetChunk/effSetChunk.
|
||||
#include "Vst2Instrument.h"
|
||||
|
||||
#include "vestige/aeffect.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
// Registry so the plugin's audioMaster callback can find its host instance.
|
||||
std::mutex g_v2Mutex;
|
||||
std::unordered_map<const void*, Vst2Instrument*> g_v2Registry;
|
||||
// Set only during VSTPluginMain() — before the plugin returns its AEffect*
|
||||
// the callback may be invoked with a null effect (some plugins query the
|
||||
// sample rate during construction).
|
||||
thread_local Vst2Instrument* t_loading = nullptr;
|
||||
|
||||
// base64 encode/decode (RFC 4648) — local copies; Vst3Instrument.cpp keeps
|
||||
// its own so neither file depends on the other's statics.
|
||||
std::string b64Encode(const void* data, size_t len) {
|
||||
static const char tbl[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const uint8_t* d = static_cast<const uint8_t*>(data);
|
||||
std::string out;
|
||||
out.reserve(((len + 2) / 3) * 4);
|
||||
for (size_t i = 0; i < len; i += 3) {
|
||||
uint32_t n = (uint32_t)d[i] << 16;
|
||||
if (i + 1 < len) n |= (uint32_t)d[i + 1] << 8;
|
||||
if (i + 2 < len) n |= (uint32_t)d[i + 2];
|
||||
out += tbl[(n >> 18) & 63];
|
||||
out += tbl[(n >> 12) & 63];
|
||||
out += (i + 1 < len) ? tbl[(n >> 6) & 63] : '=';
|
||||
out += (i + 2 < len) ? tbl[n & 63] : '=';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int b64Val(unsigned char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool b64Decode(const std::string& in, std::vector<uint8_t>& out) {
|
||||
out.clear();
|
||||
out.reserve((in.size() / 4) * 3);
|
||||
uint32_t acc = 0;
|
||||
int bits = 0;
|
||||
for (unsigned char ch : in) {
|
||||
if (ch == '=' || ch == '\n' || ch == '\r' || ch == ' ') continue;
|
||||
int v = b64Val(ch);
|
||||
if (v < 0) return false;
|
||||
acc = (acc << 6) | (uint32_t)v;
|
||||
bits += 6;
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
out.push_back((uint8_t)((acc >> bits) & 0xFF));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// VST2HostState pimpl: everything aeffect.h needs to stay out of the header.
|
||||
struct Vst2HostState {
|
||||
HMODULE hModule = nullptr;
|
||||
AEffect* effect = nullptr;
|
||||
std::vector<uint8_t> savedChunk; // preset chunk from effGetChunk
|
||||
};
|
||||
|
||||
// audioMaster callback — the plugin's channel to the host. VST2.4: return
|
||||
// version 2400, answer sample-rate/block-size queries, accept our MIDI.
|
||||
intptr_t audioMaster(AEffect* effect, int32_t opcode, int32_t index,
|
||||
intptr_t value, void* ptr, float opt) {
|
||||
(void)index; (void)value; (void)ptr; (void)opt;
|
||||
Vst2Instrument* self = nullptr;
|
||||
if (effect) {
|
||||
std::lock_guard<std::mutex> lock(g_v2Mutex);
|
||||
auto it = g_v2Registry.find(effect);
|
||||
if (it != g_v2Registry.end()) self = it->second;
|
||||
}
|
||||
if (!self) self = t_loading;
|
||||
if (t_loading) {
|
||||
// Construction-time probe dump: plugin asks the host before it returns
|
||||
// its AEffect. NULL from VSTPluginMain usually means we answered a
|
||||
// capability probe wrong — print everything during entry().
|
||||
std::cerr << "[Vst2Instrument] audioMaster op=" << opcode << " idx=" << index
|
||||
<< " during VSTPluginMain" << std::endl;
|
||||
}
|
||||
switch (opcode) {
|
||||
case kAudioMasterVersion: return 2400;
|
||||
case kAudioMasterGetSampleRate:
|
||||
return self ? (intptr_t)self->sampleRate() : 44100;
|
||||
case kAudioMasterGetBlockSize:
|
||||
return self ? (intptr_t)self->maxBlockSize() : 512;
|
||||
case kAudioMasterWantMidi: return 1; // we send MIDI events
|
||||
case kAudioMasterGetTime: {
|
||||
static VstTimeInfo ti;
|
||||
std::memset(&ti, 0, sizeof(ti));
|
||||
ti.sampleRate = self ? self->sampleRate() : 44100.0;
|
||||
ti.tempo = 120.0;
|
||||
ti.flags = kVstTempoValid | kVstTransportPlaying;
|
||||
return (intptr_t)&ti;
|
||||
}
|
||||
case kAudioMasterGetCurrentProcessLevel: return 1; // audio thread
|
||||
case kAudioMasterCurrentId: {
|
||||
const auto* fx = static_cast<const AEffect*>(self ? self->effect() : nullptr);
|
||||
if (fx) return (intptr_t)fx->uniqueID;
|
||||
// During VSTPluginMain no effect exists yet; some commercial
|
||||
// plugins abort when the host answers 0 (they take currentId as
|
||||
// a sanity check). Report a stable host signature instead.
|
||||
return 0x53464F52; // 'SFOR'
|
||||
}
|
||||
case kAudioMasterIdle: return 1;
|
||||
case kAudioMasterCanDo: {
|
||||
if (!ptr) return 0;
|
||||
const char* s = static_cast<const char*>(ptr);
|
||||
if (std::strcmp(s, "receiveVstEvents") == 0 ||
|
||||
std::strcmp(s, "receiveVstMidiEvent") == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Vst2Instrument::Vst2Instrument() : state_(nullptr), sampleRate_(44100.0),
|
||||
maxBlockSize_(512), loaded_(false) {}
|
||||
|
||||
Vst2Instrument::~Vst2Instrument() {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s) return;
|
||||
try {
|
||||
if (s->effect) {
|
||||
s->effect->dispatcher(s->effect, kEffectMainsChanged, 0, 0, nullptr, 0);
|
||||
s->effect->dispatcher(s->effect, kEffectStopProcess, 0, 0, nullptr, 0);
|
||||
s->effect->dispatcher(s->effect, kEffectClose, 0, 0, nullptr, 0);
|
||||
}
|
||||
} catch (...) {}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_v2Mutex);
|
||||
if (s->effect) g_v2Registry.erase(s->effect);
|
||||
}
|
||||
if (s->hModule) FreeLibrary(s->hModule);
|
||||
delete s;
|
||||
state_ = nullptr;
|
||||
}
|
||||
|
||||
bool Vst2Instrument::loadPlugin(const std::string& path, double sampleRate) {
|
||||
HMODULE h = LoadLibraryA(path.c_str());
|
||||
if (!h) {
|
||||
std::cerr << "[Vst2Instrument] LoadLibraryA failed: " << path
|
||||
<< " (err=" << GetLastError() << ")" << std::endl;
|
||||
return false;
|
||||
}
|
||||
using EntryFn = AEffect* (*)(audioMasterCallback);
|
||||
auto* entry = (EntryFn)GetProcAddress(h, "VSTPluginMain");
|
||||
if (!entry) entry = (EntryFn)GetProcAddress(h, "main");
|
||||
if (!entry) {
|
||||
std::cerr << "[Vst2Instrument] no VSTPluginMain/main export: " << path << std::endl;
|
||||
FreeLibrary(h);
|
||||
return false;
|
||||
}
|
||||
auto* s = new Vst2HostState();
|
||||
s->hModule = h;
|
||||
t_loading = this;
|
||||
std::cerr << "[Vst2Instrument] calling VSTPluginMain..." << std::endl;
|
||||
AEffect* fx = entry(&audioMaster);
|
||||
t_loading = nullptr;
|
||||
std::cerr << "[Vst2Instrument] VSTPluginMain returned fx=" << (void*)fx << std::endl;
|
||||
if (!fx) {
|
||||
std::cerr << "[Vst2Instrument] VSTPluginMain returned NULL: " << path << std::endl;
|
||||
delete s;
|
||||
return false;
|
||||
}
|
||||
if (fx->magic != kEffectMagic) {
|
||||
std::cerr << "[Vst2Instrument] bad AEffect magic 0x" << std::hex
|
||||
<< (uint32_t)fx->magic << std::dec << " (want 0x" << std::hex
|
||||
<< kEffectMagic << std::dec << ") in: " << path << std::endl;
|
||||
try { fx->dispatcher(fx, kEffectClose, 0, 0, nullptr, 0); } catch (...) {}
|
||||
delete s;
|
||||
return false;
|
||||
}
|
||||
if (!(fx->flags & kEffectFlagsCanReplacing)) {
|
||||
std::cerr << "[Vst2Instrument] plugin lacks CanReplacing: " << path << std::endl;
|
||||
try { fx->dispatcher(fx, kEffectClose, 0, 0, nullptr, 0); } catch (...) {}
|
||||
delete s;
|
||||
return false;
|
||||
}
|
||||
s->effect = fx;
|
||||
state_ = s;
|
||||
path_ = path;
|
||||
sampleRate_ = sampleRate;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_v2Mutex);
|
||||
g_v2Registry[fx] = this;
|
||||
}
|
||||
loaded_ = true;
|
||||
// Log identity strings — validates the ABI opcode table on real plugins.
|
||||
auto queryString = [&](int32_t op) {
|
||||
char buf[256] = {0};
|
||||
fx->dispatcher(fx, op, 0, 0, buf, 0);
|
||||
return std::string(buf);
|
||||
};
|
||||
std::cout << "[Vst2Instrument] loaded ch=" << channel_ << " "
|
||||
<< queryString(kEffectGetVendorString) << " / "
|
||||
<< queryString(kEffectGetProductString) << " / "
|
||||
<< queryString(kEffectGetEffectName)
|
||||
<< " v" << fx->version
|
||||
<< " id=0x" << std::hex << (uint32_t)fx->uniqueID << std::dec
|
||||
<< " flags=0x" << std::hex << (uint32_t)fx->flags << std::dec
|
||||
<< " ins=" << fx->numInputs << " outs=" << fx->numOutputs
|
||||
<< " progs=" << fx->numPrograms << " params=" << fx->numParams
|
||||
<< " [" << path << "]" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Vst2Instrument::init(double sampleRate, uint32_t maxBlockSize) {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect) return false;
|
||||
sampleRate_ = sampleRate;
|
||||
maxBlockSize_ = maxBlockSize;
|
||||
AEffect* fx = s->effect;
|
||||
try {
|
||||
fx->dispatcher(fx, kEffectOpen, 0, 0, nullptr, 0);
|
||||
fx->dispatcher(fx, kEffectSetSampleRate, 0, 0, nullptr, (float)sampleRate);
|
||||
fx->dispatcher(fx, kEffectSetBlockSize, 0, (intptr_t)maxBlockSize, nullptr, 0);
|
||||
fx->dispatcher(fx, kEffectSetProgram, 0, 0, nullptr, 0);
|
||||
fx->dispatcher(fx, kEffectMainsChanged, 0, 1, nullptr, 0);
|
||||
fx->dispatcher(fx, kEffectStartProcess, 0, 0, nullptr, 0);
|
||||
} catch (...) {
|
||||
std::cerr << "[Vst2Instrument] init dispatcher EXCEPTION ch=" << channel_ << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Vst2Instrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {
|
||||
(void)bank;
|
||||
programChange(channel, program);
|
||||
}
|
||||
|
||||
void Vst2Instrument::noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect) return;
|
||||
VstMidiEvent e;
|
||||
std::memset(&e, 0, sizeof(e));
|
||||
e.type = kVstMidiType;
|
||||
e.byteSize = (int32_t)sizeof(e);
|
||||
e.deltaFrames = (int32_t)sampleOffset;
|
||||
e.midiData[0] = 0x90 | (int32_t)(channel & 0x0F);
|
||||
e.midiData[1] = (int32_t)(pitch & 0x7F);
|
||||
e.midiData[2] = (int32_t)(int)(velocity * 127.0f) & 0x7F;
|
||||
std::cerr << "[Vst2Instrument] noteOn ch=" << channel << " p=" << pitch << " v=" << (int)e.midiData[2] << std::endl;
|
||||
VstEvent* evs[1] = { reinterpret_cast<VstEvent*>(&e) };
|
||||
VstEvents events;
|
||||
std::memset(&events, 0, sizeof(events));
|
||||
events.numEvents = 1;
|
||||
events.reserved = 0;
|
||||
events.events[0] = evs[0];
|
||||
s->effect->dispatcher(s->effect, kEffectProcessEvents, 0, 0, &events, 0);
|
||||
}
|
||||
|
||||
void Vst2Instrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect) return;
|
||||
VstMidiEvent e;
|
||||
std::memset(&e, 0, sizeof(e));
|
||||
e.type = kVstMidiType;
|
||||
e.byteSize = (int32_t)sizeof(e);
|
||||
e.deltaFrames = (int32_t)sampleOffset;
|
||||
e.midiData[0] = 0x80 | (int32_t)(channel & 0x0F);
|
||||
e.midiData[1] = (int32_t)(pitch & 0x7F);
|
||||
e.midiData[2] = 0;
|
||||
VstEvent* evs[1] = { reinterpret_cast<VstEvent*>(&e) };
|
||||
VstEvents events;
|
||||
std::memset(&events, 0, sizeof(events));
|
||||
events.numEvents = 1;
|
||||
events.reserved = 0;
|
||||
events.events[0] = evs[0];
|
||||
s->effect->dispatcher(s->effect, kEffectProcessEvents, 0, 0, &events, 0);
|
||||
}
|
||||
|
||||
void Vst2Instrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect) return;
|
||||
VstMidiEvent e;
|
||||
std::memset(&e, 0, sizeof(e));
|
||||
e.type = kVstMidiType;
|
||||
e.byteSize = (int32_t)sizeof(e);
|
||||
e.midiData[0] = 0xB0 | (int32_t)(channel & 0x0F);
|
||||
e.midiData[1] = (int32_t)(cc & 0x7F);
|
||||
e.midiData[2] = (int32_t)(value & 0x7F);
|
||||
VstEvent* evs[1] = { reinterpret_cast<VstEvent*>(&e) };
|
||||
VstEvents events;
|
||||
std::memset(&events, 0, sizeof(events));
|
||||
events.numEvents = 1;
|
||||
events.reserved = 0;
|
||||
events.events[0] = evs[0];
|
||||
s->effect->dispatcher(s->effect, kEffectProcessEvents, 0, 0, &events, 0);
|
||||
}
|
||||
|
||||
void Vst2Instrument::programChange(uint32_t channel, uint32_t program) {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect) return;
|
||||
VstMidiEvent e;
|
||||
std::memset(&e, 0, sizeof(e));
|
||||
e.type = kVstMidiType;
|
||||
e.byteSize = (int32_t)sizeof(e);
|
||||
e.midiData[0] = 0xC0 | (int32_t)(channel & 0x0F);
|
||||
e.midiData[1] = (int32_t)(program & 0x7F);
|
||||
VstEvent* evs[1] = { reinterpret_cast<VstEvent*>(&e) };
|
||||
VstEvents events;
|
||||
std::memset(&events, 0, sizeof(events));
|
||||
events.numEvents = 1;
|
||||
events.reserved = 0;
|
||||
events.events[0] = evs[0];
|
||||
s->effect->dispatcher(s->effect, kEffectProcessEvents, 0, 0, &events, 0);
|
||||
}
|
||||
|
||||
void Vst2Instrument::pitchBend(uint32_t channel, uint32_t bend14) {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect) return;
|
||||
int32_t bend = (int32_t)bend14 - 8192; // 14-bit signed, center 8192
|
||||
VstMidiEvent e;
|
||||
std::memset(&e, 0, sizeof(e));
|
||||
e.type = kVstMidiType;
|
||||
e.byteSize = (int32_t)sizeof(e);
|
||||
e.midiData[0] = 0xE0 | (int32_t)(channel & 0x0F);
|
||||
e.midiData[1] = bend & 0x7F;
|
||||
e.midiData[2] = (bend >> 7) & 0x7F;
|
||||
VstEvent* evs[1] = { reinterpret_cast<VstEvent*>(&e) };
|
||||
VstEvents events;
|
||||
std::memset(&events, 0, sizeof(events));
|
||||
events.numEvents = 1;
|
||||
events.reserved = 0;
|
||||
events.events[0] = evs[0];
|
||||
s->effect->dispatcher(s->effect, kEffectProcessEvents, 0, 0, &events, 0);
|
||||
}
|
||||
|
||||
bool Vst2Instrument::openGUI(void* parentWindowHandle) {
|
||||
(void)parentWindowHandle;
|
||||
return false; // headless — no editor (deferred per DESIGN_VST2_BACKWARD_COMPAT.md)
|
||||
}
|
||||
|
||||
void Vst2Instrument::closeGUI() {}
|
||||
|
||||
bool Vst2Instrument::hasAttachedView() const { return false; }
|
||||
|
||||
void Vst2Instrument::captureState() {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect) return;
|
||||
if (!(s->effect->flags & kEffectFlagsProgramChunks)) return;
|
||||
void* chunkPtr = nullptr;
|
||||
intptr_t size = s->effect->dispatcher(s->effect, kEffectGetChunk, 0, 1,
|
||||
&chunkPtr, 0);
|
||||
if (size > 0 && chunkPtr)
|
||||
s->savedChunk.assign(static_cast<uint8_t*>(chunkPtr),
|
||||
static_cast<uint8_t*>(chunkPtr) + size);
|
||||
}
|
||||
|
||||
void Vst2Instrument::restoreState() {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect || s->savedChunk.empty()) return;
|
||||
s->effect->dispatcher(s->effect, kEffectSetChunk, 0,
|
||||
(intptr_t)s->savedChunk.size(), s->savedChunk.data(), 0);
|
||||
}
|
||||
|
||||
std::string Vst2Instrument::serializeState() const {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || s->savedChunk.empty()) return std::string();
|
||||
return b64Encode(s->savedChunk.data(), s->savedChunk.size());
|
||||
}
|
||||
|
||||
void Vst2Instrument::loadSerializedState(const std::string& base64) {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
if (!s || !s->effect || base64.empty()) return;
|
||||
if (!b64Decode(base64, s->savedChunk)) {
|
||||
std::cerr << "[Vst2Instrument] loadSerializedState: bad base64 ch=" << channel_ << std::endl;
|
||||
return;
|
||||
}
|
||||
restoreState();
|
||||
std::cout << "[Vst2Instrument] preset restored ch=" << channel_
|
||||
<< " (" << s->savedChunk.size() << "B)" << std::endl;
|
||||
}
|
||||
|
||||
void Vst2Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
|
||||
std::cerr << "[Vst2Instrument] pab START n=" << numSamples << std::endl;
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
std::cerr << "[Vst2Instrument] pab state ok=" << (s && s->effect) << std::endl;
|
||||
if (!s || !s->effect) {
|
||||
std::memset(outputL, 0, numSamples * sizeof(float));
|
||||
std::memset(outputR, 0, numSamples * sizeof(float));
|
||||
return;
|
||||
}
|
||||
std::cerr << "[Vst2Instrument] pab before alignF" << std::endl;
|
||||
AEffect* fx = s->effect;
|
||||
// 64B-align inputs: DUNE 3 etc. use aligned SIMD on audio buffers.
|
||||
auto alignF = [](std::vector<float>& v, size_t n) -> float* {
|
||||
if (v.size() < n + 16) v.assign(n + 16, 0.0f);
|
||||
uintptr_t a = reinterpret_cast<uintptr_t>(v.data());
|
||||
return reinterpret_cast<float*>((a + 63) & ~(uintptr_t)63);
|
||||
};
|
||||
float* inL = alignF(inL_, numSamples);
|
||||
float* inR = alignF(inR_, numSamples);
|
||||
std::memset(inL, 0, numSamples * sizeof(float));
|
||||
std::memset(inR, 0, numSamples * sizeof(float));
|
||||
float* inBufs[2] = { inL, inR };
|
||||
float* outBufs[2] = { outputL, outputR };
|
||||
std::cerr << "[Vst2Instrument] processReplacing IN ch=" << channel_ << " n=" << numSamples << std::endl;
|
||||
fx->processReplacing(fx, inBufs, outBufs, (int32_t)numSamples);
|
||||
std::cerr << "[Vst2Instrument] processReplacing OUT ch=" << channel_ << std::endl;
|
||||
}
|
||||
|
||||
const void* Vst2Instrument::effect() const {
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
return s ? s->effect : nullptr;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// native_bridge/src/Vst2Instrument.h
|
||||
// VST2 (VST 2.4, 64-bit) instrument host. Clean-room ABI header
|
||||
// (native_bridge/vestige/aeffect.h) — no Steinberg SDK, no LMMS code.
|
||||
// Headless: no GUI editor (openGUI returns false); preset state is kept via
|
||||
// effGetChunk/effSetChunk (fxp-style chunks).
|
||||
#ifndef VST2_INSTRUMENT_H
|
||||
#define VST2_INSTRUMENT_H
|
||||
|
||||
#include "INativeInstrument.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Vst2Instrument : public INativeInstrument {
|
||||
public:
|
||||
Vst2Instrument();
|
||||
~Vst2Instrument() override;
|
||||
|
||||
bool loadPlugin(const std::string& path, double sampleRate);
|
||||
void setChannel(uint32_t channel) { channel_ = 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;
|
||||
bool openGUI(void* parentWindowHandle) override;
|
||||
bool canOpenGUI() const override { return false; }
|
||||
void closeGUI() override;
|
||||
bool hasAttachedView() const override;
|
||||
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;
|
||||
|
||||
// Accessors used by the audioMaster callback (same translation unit's
|
||||
// friend-ish helpers) without exposing AEffect* in the header.
|
||||
double sampleRate() const { return sampleRate_; }
|
||||
uint32_t maxBlockSize() const { return maxBlockSize_; }
|
||||
const void* effect() const; // AEffect*; null until loaded
|
||||
|
||||
private:
|
||||
void* state_; // Vst2HostState* (pimpl — aeffect.h stays out of the header)
|
||||
std::string path_;
|
||||
double sampleRate_;
|
||||
uint32_t maxBlockSize_;
|
||||
bool loaded_;
|
||||
uint32_t channel_ = 0;
|
||||
// scratch buffers for silent plugin inputs
|
||||
std::vector<float> inL_;
|
||||
std::vector<float> inR_;
|
||||
};
|
||||
|
||||
#endif // VST2_INSTRUMENT_H
|
||||
@@ -793,6 +793,16 @@ int main(int argc, char* argv[]) {
|
||||
g_pendingGui[guiCh] = { (void*)arg1, pluginId };
|
||||
return;
|
||||
}
|
||||
// VST2 headless (no editor) hoac instrument load fail -> khong tao
|
||||
// window, khong reuse window cua channel khac (window trong voi title
|
||||
// sai plugin). Frontend hien "VST2 headless - no GUI" thay vi window.
|
||||
if (instruments.withInstrument(guiCh, [](INativeInstrument* i0) {
|
||||
return !i0 || !i0->canOpenGUI();
|
||||
})) {
|
||||
std::cerr << "[NativeBridge] GUI skip ch=" << guiCh
|
||||
<< " plugin=" << pluginId << " (headless - no editor)" << std::endl;
|
||||
return;
|
||||
}
|
||||
// Dedupe OPEN_GUI spam (frontend openVstGuiRetry): view dang
|
||||
// attached -> GUI da mo, khong post attach job lan nua. Re-attach
|
||||
// tren view dang attached lam plugin loi (Nexus createView null).
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
// native_bridge/vestige/aeffect.h
|
||||
// Clean-room VST2.4 host ABI header (64-bit Windows).
|
||||
//
|
||||
// Written from the public VST2.4 interface specification (structure layout,
|
||||
// dispatcher opcode numbers, flag bits) — NOT copied from Steinberg's SDK or
|
||||
// from LMMS's vestige (LMMS is GPL-2.0+; reusing it would re-taint the bridge
|
||||
// with GPL, defeating this repo's license cleanup). Only ABI facts are
|
||||
// encoded here; all prose is original.
|
||||
//
|
||||
// A VST2 plugin is a DLL exporting `VSTPluginMain` (modern) or `main`
|
||||
// (legacy): AEffect* VSTPluginMain(audioMasterCallback host).
|
||||
#ifndef VESTIGE_AEFFECT_H
|
||||
#define VESTIGE_AEFFECT_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct AEffect; // forward decl so the callback typedefs below can reference it
|
||||
|
||||
// intptr_t: VST2 ABI uses pointer-sized integers on 64-bit hosts.
|
||||
typedef intptr_t (*dispatcherProc)(AEffect* effect, int32_t opcode,
|
||||
int32_t index, intptr_t value,
|
||||
void* ptr, float opt);
|
||||
typedef void (*processProc)(AEffect* effect, float** inputs, float** outputs,
|
||||
int32_t sampleFrames);
|
||||
typedef void (*setParameterProc)(AEffect* effect, int32_t index, float value);
|
||||
typedef float (*getParameterProc)(AEffect* effect, int32_t index);
|
||||
typedef void (*processReplacingProc)(AEffect* effect, float** inputs,
|
||||
float** outputs, int32_t sampleFrames);
|
||||
typedef void (*processDoubleReplacingProc)(AEffect* effect, double** inputs,
|
||||
double** outputs,
|
||||
int32_t sampleFrames);
|
||||
typedef intptr_t (*audioMasterCallback)(AEffect* effect, int32_t opcode,
|
||||
int32_t index, intptr_t value,
|
||||
void* ptr, float opt);
|
||||
|
||||
struct AEffect {
|
||||
int32_t magic; // 'VstP' == 0x56737450
|
||||
dispatcherProc dispatcher;
|
||||
processProc process;
|
||||
setParameterProc setParameter;
|
||||
getParameterProc getParameter;
|
||||
int32_t numPrograms;
|
||||
int32_t numParams;
|
||||
int32_t numInputs;
|
||||
int32_t numOutputs;
|
||||
int32_t flags;
|
||||
intptr_t resvd1; // pointer-sized per VST2.4 ABI
|
||||
intptr_t resvd2;
|
||||
int32_t initialDelay;
|
||||
int32_t realQualities;
|
||||
int32_t offQualities;
|
||||
float ioRatio;
|
||||
void* object;
|
||||
void* user;
|
||||
int32_t uniqueID;
|
||||
int32_t version;
|
||||
processReplacingProc processReplacing;
|
||||
processDoubleReplacingProc processDoubleReplacing;
|
||||
char future[56]; // reserved for future use (must stay intact)
|
||||
};
|
||||
|
||||
// Plugin capability flag bits (AEffect::flags).
|
||||
#define kEffectFlagsHasEditor (1 << 0)
|
||||
#define kEffectFlagsHasClip (1 << 1)
|
||||
#define kEffectFlagsHasVu (1 << 2)
|
||||
#define kEffectFlagsCanMono (1 << 3)
|
||||
#define kEffectFlagsCanReplacing (1 << 4)
|
||||
#define kEffectFlagsProgramChunks (1 << 5)
|
||||
#define kEffectFlagsIsSynth (1 << 8)
|
||||
#define kEffectFlagsNoSoundInStop (1 << 9)
|
||||
#define kEffectFlagsCanDoubleReplacing (1 << 12)
|
||||
|
||||
// Dispatcher opcodes (VST 2.4 numbering).
|
||||
enum {
|
||||
kEffectOpen = 0, // effOpen
|
||||
kEffectClose = 1, // effClose
|
||||
kEffectSetProgram = 2, // effSetProgram
|
||||
kEffectGetProgram = 3, // effGetProgram
|
||||
kEffectSetSampleRate = 10, // effSetSampleRate
|
||||
kEffectSetBlockSize = 11, // effSetBlockSize
|
||||
kEffectMainsChanged = 12, // effMainsChanged (value 1 = on, 0 = off)
|
||||
kEffectGetChunk = 23, // effGetChunk
|
||||
kEffectSetChunk = 24, // effSetChunk
|
||||
kEffectProcessEvents = 25, // effProcessEvents
|
||||
kEffectGetPlugCategory = 35,
|
||||
kEffectGetEffectName = 45,
|
||||
kEffectGetVendorString = 47,
|
||||
kEffectGetProductString = 48,
|
||||
kEffectGetVendorVersion = 49,
|
||||
kEffectCanDo = 51,
|
||||
kEffectGetVstVersion = 58,
|
||||
kEffectStartProcess = 71,
|
||||
kEffectStopProcess = 72
|
||||
};
|
||||
|
||||
// audioMaster opcodes (host callback).
|
||||
enum {
|
||||
kAudioMasterAutomate = 0,
|
||||
kAudioMasterVersion = 1,
|
||||
kAudioMasterCurrentId = 2,
|
||||
kAudioMasterIdle = 3,
|
||||
kAudioMasterWantMidi = 6,
|
||||
kAudioMasterGetTime = 7,
|
||||
kAudioMasterProcessEvents = 8,
|
||||
kAudioMasterGetSampleRate = 16,
|
||||
kAudioMasterGetBlockSize = 17,
|
||||
kAudioMasterGetCurrentProcessLevel = 23,
|
||||
kAudioMasterGetVendorString = 32,
|
||||
kAudioMasterGetProductString = 33,
|
||||
kAudioMasterGetVendorVersion = 34,
|
||||
kAudioMasterVendorSpecific = 35,
|
||||
kAudioMasterCanDo = 37,
|
||||
kAudioMasterGetLanguage = 38
|
||||
};
|
||||
|
||||
// MIDI event passed via effProcessEvents. The plugin receives a VstEvents
|
||||
// array; each event starts with a VstEvent header, VstMidiEvent extends it.
|
||||
struct VstEvent {
|
||||
int32_t type; // kVstMidiType == 1 for MIDI events
|
||||
int32_t byteSize; // sizeof(VstMidiEvent)
|
||||
int32_t deltaFrames;
|
||||
int32_t flags;
|
||||
};
|
||||
|
||||
struct VstMidiEvent {
|
||||
int32_t type; // kVstMidiType == 1
|
||||
int32_t byteSize; // sizeof(VstMidiEvent)
|
||||
int32_t deltaFrames;
|
||||
int32_t flags;
|
||||
int32_t noteLength; // in sample frames, if available, else 0
|
||||
int32_t noteOffset; // offset into note, if available, else 0
|
||||
char midiData[4]; // 1-3 MIDI bytes; midiData[3] reserved (zero)
|
||||
char detune; // -64..+63 cents
|
||||
char noteOffVelocity;
|
||||
char reserved1;
|
||||
char reserved2;
|
||||
};
|
||||
|
||||
struct VstEvents {
|
||||
int32_t numEvents;
|
||||
intptr_t reserved;
|
||||
VstEvent* events[2];
|
||||
};
|
||||
|
||||
// Time info returned to kAudioMasterGetTime (host fills this).
|
||||
struct VstTimeInfo {
|
||||
double samplePos;
|
||||
double sampleRate;
|
||||
double nanoSeconds;
|
||||
double ppqPos;
|
||||
double tempo;
|
||||
double barStartPos;
|
||||
double cycleStartPos;
|
||||
double cycleEndPos;
|
||||
int32_t timeSigNumerator;
|
||||
int32_t timeSigDenominator;
|
||||
int32_t smpteOffset;
|
||||
int32_t smpteFrameRate;
|
||||
int32_t samplesToNextClock;
|
||||
int32_t flags;
|
||||
};
|
||||
|
||||
#define kVstMidiType 1
|
||||
#define kEffectMagic 0x56737450 // 'VstP'
|
||||
|
||||
// VstTimeInfo::flags bits (kAudioMasterGetTime).
|
||||
#define kVstTransportPlaying 1
|
||||
#define kVstTransportCycleActive 2
|
||||
#define kVstTransportChanged 4
|
||||
#define kVstTempoValid 8
|
||||
|
||||
// Host responses for the plugin's capability probes (kEffectCanDo).
|
||||
#define kCanDoYes 1
|
||||
#define kCanDoNo 0
|
||||
|
||||
#endif // VESTIGE_AEFFECT_H
|
||||
Reference in New Issue
Block a user