From aaf21ddf89c6a28ee82083daf65d90b94ab0df9c Mon Sep 17 00:00:00 2001 From: locphamtran Date: Tue, 11 Aug 2026 16:18:45 +0700 Subject: [PATCH] T10: VST2 engine bridge (vestige.h clean-room, VST2AudioEngine.cpp, fake_vst2 test plugin) --- native_host/CMakeLists.txt | 11 + native_host/VST2AudioEngine.cpp | 375 ++++++++++++++++++++++++++++++++ native_host/tests/fake_vst2.cpp | 116 ++++++++++ native_host/vestige.h | 194 +++++++++++++++++ 4 files changed, 696 insertions(+) create mode 100644 native_host/VST2AudioEngine.cpp create mode 100644 native_host/tests/fake_vst2.cpp create mode 100644 native_host/vestige.h diff --git a/native_host/CMakeLists.txt b/native_host/CMakeLists.txt index c84e7a1..7906a98 100644 --- a/native_host/CMakeLists.txt +++ b/native_host/CMakeLists.txt @@ -32,3 +32,14 @@ target_compile_definitions(vst3_host_bridge PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN ) + +# VST2 host bridge — chỉ cần vestige.h clean-room, không cần SDK. +add_library(vst2_host_bridge SHARED + VST2AudioEngine.cpp +) +target_include_directories(vst2_host_bridge PRIVATE native_host) +target_compile_definitions(vst2_host_bridge PRIVATE + _CRT_SECURE_NO_WARNINGS + NOMINMAX + WIN32_LEAN_AND_MEAN +) diff --git a/native_host/VST2AudioEngine.cpp b/native_host/VST2AudioEngine.cpp new file mode 100644 index 0000000..8d75471 --- /dev/null +++ b/native_host/VST2AudioEngine.cpp @@ -0,0 +1,375 @@ +// VST2AudioEngine.cpp — VST2 host bridge (T10, spec 5.7 / spec III) +// +// DLL export C API để load .dll VST2, dispatch MIDI, attach GUI vào HWND cha, +// và process float32 qua processReplacing. Host audio master trả lời tối +// thiểu: version, sampleRate, blockSize (opcode bắt buộc theo spec III). +// +// QUYẾT ĐỊNH (T10): KHÔNG JUCE, KHÔNG aeffect.h chính thức — dùng vestige.h +// clean-room (xem header). T12 native audio loop sẽ gọi SF_VST2_Process từ +// audio thread; effProcessEvents chỉ được gọi từ UI/main thread (spec 5.7.3). +// +// Giới hạn hiện tại: chưa xử lý plugin chunk state (effGetChunk/SetChunk) — +// thêm khi T11 cần save/restore preset. +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "vestige.h" + +namespace { + +struct Instance +{ + HMODULE module = nullptr; + AEffect* effect = nullptr; + int32 sampleRate = 44100; + int32 blockSize = 512; + int32 numInputs = 0; + int32 numOutputs = 2; + HWND parentHwnd = nullptr; + ERect* editorRect = nullptr; + bool editorOpen = false; + bool active = false; +}; + +std::mutex g_mutex; +std::map> g_instances; +int32 g_next_handle = 1; + +void set_err (char* err, int32 err_cap, const char* msg) +{ + if (err && err_cap > 0) + { + std::snprintf (err, static_cast (err_cap), "%s", msg); + } +} + +// Host audio master — spec III: phải trả lời version/sampleRate/blockSize. +// thread_local tls_loading cho plugin tra cứu instance đang khởi tạo. +thread_local Instance* tls_loading = nullptr; + +VstIntPtr VSTCALLBACK hostAudioMasterImpl (AEffect* effect, int32 opcode, int32 index, + VstIntPtr value, void* ptr, float opt) +{ + (void) effect; + (void) index; + (void) value; + (void) opt; + Instance* inst = tls_loading; + switch (opcode) + { + case audioMasterVersion: + return 2400; + case audioMasterGetSampleRate: + return inst ? inst->sampleRate : 44100; + case audioMasterGetBlockSize: + return inst ? inst->blockSize : 512; + case audioMasterCurrentId: + case audioMasterGetLanguage: + return 0; + case audioMasterGetVendorString: + if (ptr) + std::strncpy (static_cast (ptr), "SonicForgeStudio", 64); + return 1; + case audioMasterGetProductString: + if (ptr) + std::strncpy (static_cast (ptr), "SonicForgeStudio", 64); + return 1; + case audioMasterGetVendorVersion: + return 1; + case audioMasterCanDo: + return 0; + default: + return 0; + } +} + +} // namespace + +extern "C" { + +// Load .dll VST2, khởi tạo plugin, mở editor (nếu có) vào parent_hwnd. +// module_path_utf8: đường dẫn .dll (UTF-8). Trả handle (>= 1) hoặc 0 + err. +__declspec (dllexport) int32 SF_VST2_Load (const char* module_path_utf8, + HWND parent_hwnd, + int32* out_w, + int32* out_h, + char* err, + int32 err_cap) +{ + std::lock_guard lock (g_mutex); + if (!module_path_utf8) + { + set_err (err, err_cap, "null arg"); + return 0; + } + + auto inst = std::make_unique (); + inst->parentHwnd = parent_hwnd; + + // UTF-8 -> wide cho LoadLibraryW + int wlen = MultiByteToWideChar (CP_UTF8, 0, module_path_utf8, -1, nullptr, 0); + if (wlen <= 0) + { + set_err (err, err_cap, "bad utf8 path"); + return 0; + } + std::vector wpath (static_cast (wlen)); + MultiByteToWideChar (CP_UTF8, 0, module_path_utf8, -1, wpath.data (), wlen); + + inst->module = LoadLibraryW (wpath.data ()); + if (!inst->module) + { + set_err (err, err_cap, "LoadLibrary failed"); + return 0; + } + + auto mainFn = reinterpret_cast (GetProcAddress (inst->module, "VSTPluginMain")); + if (!mainFn) + { + auto oldFn = reinterpret_cast (GetProcAddress (inst->module, "main")); + if (oldFn) + { + tls_loading = inst.get (); + inst->effect = oldFn (reinterpret_cast (&hostAudioMasterImpl), nullptr); + tls_loading = nullptr; + } + else + { + FreeLibrary (inst->module); + set_err (err, err_cap, "no VSTPluginMain/main export"); + return 0; + } + } + else + { + tls_loading = inst.get (); + inst->effect = mainFn (reinterpret_cast (&hostAudioMasterImpl)); + tls_loading = nullptr; + } + + if (!inst->effect || inst->effect->magic != CCONST ('V', 's', 't', 'P')) + { + FreeLibrary (inst->module); + set_err (err, err_cap, "not a valid VST2 effect"); + return 0; + } + + inst->numInputs = inst->effect->numInputs ? inst->effect->numInputs (inst->effect) : 0; + inst->numOutputs = inst->effect->numOutputs ? inst->effect->numOutputs (inst->effect) : 2; + + // Khởi tạo dispatcher theo thứ tự chuẩn + inst->effect->dispatcher (inst->effect, effOpen, 0, 0, nullptr, 0.0f); + inst->effect->dispatcher (inst->effect, effSetSampleRate, 0, 0, nullptr, + static_cast (inst->sampleRate)); + inst->effect->dispatcher (inst->effect, effSetBlockSize, 0, inst->blockSize, nullptr, 0.0f); + inst->effect->dispatcher (inst->effect, effMainsChanged, 0, 1, nullptr, 0.0f); + inst->effect->dispatcher (inst->effect, effStartProcess, 0, 0, nullptr, 0.0f); + inst->active = true; + + // Editor GUI + if (parent_hwnd) + { + VstIntPtr hasEditor = inst->effect->dispatcher (inst->effect, effEditGetRect, 0, 0, + &inst->editorRect, 0.0f); + if (hasEditor == 1 && inst->editorRect) + { + inst->effect->dispatcher (inst->effect, effEditOpen, 0, 0, + reinterpret_cast (parent_hwnd), 0.0f); + inst->editorOpen = true; + if (out_w) + *out_w = inst->editorRect->right - inst->editorRect->left; + if (out_h) + *out_h = inst->editorRect->bottom - inst->editorRect->top; + } + else + { + if (out_w) + *out_w = 0; + if (out_h) + *out_h = 0; + } + } + + int32 handle = g_next_handle++; + g_instances[handle] = std::move (inst); + return handle; +} + +// Đóng: effStopProcess + effMainsChanged(0) + effEditClose + effClose + FreeLibrary. +__declspec (dllexport) int32 SF_VST2_Close (int32 handle, char* err, int32 err_cap) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + { + set_err (err, err_cap, "bad handle"); + return -1; + } + Instance* inst = it->second.get (); + if (inst->effect) + { + if (inst->active) + { + inst->effect->dispatcher (inst->effect, effStopProcess, 0, 0, nullptr, 0.0f); + inst->effect->dispatcher (inst->effect, effMainsChanged, 0, 0, nullptr, 0.0f); + inst->active = false; + } + if (inst->editorOpen) + { + inst->effect->dispatcher (inst->effect, effEditClose, 0, 0, nullptr, 0.0f); + inst->editorOpen = false; + } + inst->effect->dispatcher (inst->effect, effClose, 0, 0, nullptr, 0.0f); + inst->effect = nullptr; + } + if (inst->module) + { + FreeLibrary (inst->module); + inst->module = nullptr; + } + g_instances.erase (it); + return 0; +} + +// Kích thước editor hiện tại. +__declspec (dllexport) int32 SF_VST2_GetSize (int32 handle, int32* out_w, int32* out_h) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + return -1; + Instance* inst = it->second.get (); + if (inst->editorRect) + { + if (out_w) + *out_w = inst->editorRect->right - inst->editorRect->left; + if (out_h) + *out_h = inst->editorRect->bottom - inst->editorRect->top; + return 0; + } + return -2; +} + +// Báo editor: window cha đã resize — plugin cập nhật vị trí nội bộ. +__declspec (dllexport) int32 SF_VST2_Resize (int32 handle, int32 w, int32 h) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + return -1; + Instance* inst = it->second.get (); + if (inst->effect && inst->editorOpen) + { + inst->effect->dispatcher (inst->effect, effSetViewPosition, 0, 0, nullptr, 0.0f); + return 0; + } + return -2; +} + +// MIDI note-on qua effProcessEvents (chỉ gọi từ UI/main thread — spec 5.7.3). +__declspec (dllexport) int32 SF_VST2_SendNoteOn (int32 handle, int32 channel, int32 pitch, + int32 velocity) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + return -1; + Instance* inst = it->second.get (); + if (!inst->effect) + return -2; + + char midiData[4] = {0}; + midiData[0] = static_cast (0x90 | (channel & 0x0F)); + midiData[1] = static_cast (pitch & 0x7F); + midiData[2] = static_cast (velocity & 0x7F); + + VstMidiEvent midiEvent = {}; + midiEvent.type = kVstMidiType; + midiEvent.byteSize = static_cast (sizeof (VstMidiEvent)); + midiEvent.deltaFrames = 0; + midiEvent.flags = kVstMidiEventIsRealtime; + std::memcpy (midiEvent.midiData, midiData, 4); + + VstEvent* eventPtr = reinterpret_cast (&midiEvent); + VstEvents events = {}; + events.numEvents = 1; + events.events[0] = eventPtr; + + inst->effect->dispatcher (inst->effect, effProcessEvents, 0, 0, &events, 0.0f); + return 0; +} + +// MIDI note-off qua effProcessEvents. +__declspec (dllexport) int32 SF_VST2_SendNoteOff (int32 handle, int32 channel, int32 pitch) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + return -1; + Instance* inst = it->second.get (); + if (!inst->effect) + return -2; + + char midiData[4] = {0}; + midiData[0] = static_cast (0x80 | (channel & 0x0F)); + midiData[1] = static_cast (pitch & 0x7F); + midiData[2] = 0; + + VstMidiEvent midiEvent = {}; + midiEvent.type = kVstMidiType; + midiEvent.byteSize = static_cast (sizeof (VstMidiEvent)); + midiEvent.deltaFrames = 0; + midiEvent.flags = kVstMidiEventIsRealtime; + std::memcpy (midiEvent.midiData, midiData, 4); + + VstEvent* eventPtr = reinterpret_cast (&midiEvent); + VstEvents events = {}; + events.numEvents = 1; + events.events[0] = eventPtr; + + inst->effect->dispatcher (inst->effect, effProcessEvents, 0, 0, &events, 0.0f); + return 0; +} + +// Process float32 qua processReplacing. buffers: input[m][n] + output[m][n] +// liền nhau, channels = max(numInputs, numOutputs). Trả 0 nếu OK. +__declspec (dllexport) int32 SF_VST2_Process (int32 handle, float* buffers, int32 channels, + int32 sampleFrames) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + return -1; + Instance* inst = it->second.get (); + if (!inst->effect || !buffers || sampleFrames <= 0) + return -2; + + int32 total = inst->numInputs + inst->numOutputs; + if (channels < total) + return -3; + + std::vector ptrs (static_cast (total)); + for (int32 i = 0; i < total; ++i) + ptrs[static_cast (i)] = buffers + static_cast (i) * sampleFrames; + + if (inst->effect->processReplacing) + { + inst->effect->processReplacing (inst->effect, + inst->numInputs > 0 ? ptrs.data () : nullptr, + ptrs.data () + inst->numInputs, + sampleFrames); + return 0; + } + return -4; +} + +} // extern "C" diff --git a/native_host/tests/fake_vst2.cpp b/native_host/tests/fake_vst2.cpp new file mode 100644 index 0000000..00df9e6 --- /dev/null +++ b/native_host/tests/fake_vst2.cpp @@ -0,0 +1,116 @@ +// fake_vst2.cpp — test plugin VST2 tối thiểu để verify bridge T10. +// Sine 440Hz stereo qua processReplacing; editor trả rect giả. +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include "vestige.h" + +namespace { + +struct FakeState +{ + float phase = 0.0f; + int32 sampleRate = 44100; +}; + +FakeState g_state; + +int32 VSTCALLBACK fakeDispatcher (AEffect* effect, int32 opcode, int32 index, + VstIntPtr value, void* ptr, float opt) +{ + (void) index; + (void) value; + (void) ptr; + (void) opt; + switch (opcode) + { + case effOpen: + return 1; + case effClose: + return 1; + case effSetSampleRate: + g_state.sampleRate = static_cast (opt); + return 1; + case effMainsChanged: + return 1; + case effStartProcess: + case effStopProcess: + return 1; + case effEditGetRect: + { + static ERect rect = {0, 0, 300, 400}; + *reinterpret_cast (ptr) = ▭ + return 1; + } + case effEditOpen: + return 1; + case effEditClose: + return 1; + case effProcessEvents: + return 1; + case effGetEffectName: + std::strncpy (static_cast (ptr), "FakeVST2", 64); + return 1; + case effGetVendorString: + std::strncpy (static_cast (ptr), "SonicForgeStudio", 64); + return 1; + case effGetProductString: + std::strncpy (static_cast (ptr), "FakeVST2", 64); + return 1; + case effGetVendorVersion: + return 1; + case effIdentify: + return CCONST ('N', 'v', 'E', 'f'); + case effCanDo: + return 0; + default: + return 0; + } +} + +void VSTCALLBACK fakeProcessReplacing (AEffect*, float** inputs, float** outputs, + int32 sampleFrames) +{ + (void) inputs; + float* outL = outputs[0]; + float* outR = outputs[1]; + const float freq = 440.0f; + const float dt = freq / static_cast (g_state.sampleRate); + for (int32 i = 0; i < sampleFrames; ++i) + { + float v = 0.25f * std::sin (2.0f * 3.14159265f * g_state.phase); + g_state.phase += dt; + if (g_state.phase >= 1.0f) + g_state.phase -= 1.0f; + outL[i] = v; + outR[i] = v; + } +} + +AEffect g_effect; + +AEffect* VSTCALLBACK createInstance (void*) +{ + std::memset (&g_effect, 0, sizeof (g_effect)); + g_effect.magic = CCONST ('V', 's', 't', 'P'); + g_effect.dispatcher = &fakeDispatcher; + g_effect.processReplacing = &fakeProcessReplacing; + g_effect.numInputs = [] (AEffect*) -> int32 { return 0; }; + g_effect.numOutputs = [] (AEffect*) -> int32 { return 2; }; + g_effect.numParams = [] (AEffect*) -> int32 { return 0; }; + g_effect.numPrograms = [] (AEffect*) -> int32 { return 0; }; + g_effect.flags = [] (AEffect*) -> int32 { return effFlagsCanReplacing | effFlagsIsSynth; }; + g_effect.uniqueID = CCONST ('F', 'k', '2', 'V'); + g_effect.version = 1; + return &g_effect; +} + +} // namespace + +extern "C" __declspec (dllexport) AEffect* VSTPluginMain (void* audioMaster) +{ + (void) audioMaster; + return createInstance (audioMaster); +} diff --git a/native_host/vestige.h b/native_host/vestige.h new file mode 100644 index 0000000..e20758d --- /dev/null +++ b/native_host/vestige.h @@ -0,0 +1,194 @@ +// vestige.h — clean-room VST2 (AEffect) API header (T10, spec 5.7 / spec III) +// +// QUYẾT ĐỊNH (T10): KHÔNG dùng aeffect.h/aeffectx.h chính thức (Steinberg +// ngừng license VST2 10/2018 — spec III). Viết lại từ kiến thức public của +// giao diện AEffect: struct + opcode enum đủ cho host (load, MIDI, GUI, +// processReplacing). Chỉ giữ phần host cần — không phải bản sao SDK. +// +// Nâng cấp nếu cần: thay bằng bridge có sẵn (Carla/Wine-VST) hoặc gộp JUCE. +#pragma once + +#include +#include + +#define VSTCALLBACK __cdecl + +#ifdef _WIN32 +#define CCONST(a, b, c, d) ((int32)((d) << 24 | (c) << 16 | (b) << 8 | (a))) +#else +#define CCONST(a, b, c, d) ((int32)((a) << 24 | (b) << 16 | (c) << 8 | (d))) +#endif + +typedef int32_t int32; +typedef intptr_t VstIntPtr; +typedef float VstParamValue; + +//------------------------------------------------------------------------ +// AEffect — layout công khai của VST2; KHÔNG đổi thứ tự field. +struct AEffect +{ + int32 magic; // kEffectMagic = CCONST('V', 's', 't', 'P') + int32 (VSTCALLBACK* dispatcher)(AEffect*, int32 opcode, int32 index, VstIntPtr value, void* ptr, float opt); + void (VSTCALLBACK* process)(AEffect*, float** inputs, float** outputs, int32 sampleFrames); + void (VSTCALLBACK* setParameter)(AEffect*, int32 index, float parameter); + float (VSTCALLBACK* getParameter)(AEffect*, int32 index); + int32 (VSTCALLBACK* numPrograms)(AEffect*); + int32 (VSTCALLBACK* numParams)(AEffect*); + int32 (VSTCALLBACK* numInputs)(AEffect*); + int32 (VSTCALLBACK* numOutputs)(AEffect*); + int32 (VSTCALLBACK* flags)(AEffect*); + int32 resvd1; + int32 resvd2; + int32 initialDelay; + int32 realQualities; + int32 offQualities; + float ioRatio; + void* object; + void* user; + int32 uniqueID; + int32 version; + void (VSTCALLBACK* processReplacing)(AEffect*, float** inputs, float** outputs, int32 sampleFrames); + void (VSTCALLBACK* processDoubleReplacing)(AEffect*, double** inputs, double** outputs, int32 sampleFrames); +}; + +enum VstAEffectFlags +{ + effFlagsHasEditor = 1 << 0, + effFlagsCanReplacing = 1 << 4, + effFlagsProgramChunks = 1 << 5, + effFlagsIsSynth = 1 << 8, + effFlagsNoSoundInStop = 1 << 9, + effFlagsCanDoubleReplacing = 1 << 12 +}; + +//------------------------------------------------------------------------ +// Dispatcher opcodes (host -> plugin) +enum VstEffectOpcodes +{ + effOpen = 0, + effClose = 1, + effSetProgram = 2, + effGetProgram = 3, + effSetProgramName = 4, + effGetProgramName = 5, + effGetParamLabel = 6, + effGetParamDisplay = 7, + effGetParamName = 8, + effSetSampleRate = 10, + effSetBlockSize = 11, + effMainsChanged = 12, + effEditGetRect = 13, + effEditOpen = 14, + effEditClose = 15, + effEditIdle = 19, + effEditTop = 20, + effEditSleep = 21, + effIdentify = 22, + effGetChunk = 23, + effSetChunk = 24, + effProcessEvents = 25, + effCanBeAutomated = 26, + effGetEffectName = 39, + effGetVendorString = 41, + effGetProductString = 42, + effGetVendorVersion = 43, + effCanDo = 45, + effIdle = 47, + effSetViewPosition = 49, + effGetVstVersion = 52, + effEditKeyDown = 53, + effEditKeyUp = 54, + effStartProcess = 65, + effStopProcess = 66, + effSetProcessPrecision = 71 +}; + +enum VstPluginCanDo +{ + canDoSendVstEvents = 0, + canDoSendVstMidiEvent = 1, + canDoReceiveVstEvents = 2, + canDoReceiveVstMidiEvent = 3 +}; + +//------------------------------------------------------------------------ +// AudioMasterCallback opcodes (plugin -> host) +enum VstAudioMasterOpcodes +{ + audioMasterAutomate = 0, + audioMasterVersion = 1, + audioMasterCurrentId = 2, + audioMasterIdle = 3, + audioMasterWantMidi = 6, + audioMasterGetTime = 7, + audioMasterProcessEvents = 8, + audioMasterGetVendorString = 14, + audioMasterGetProductString = 15, + audioMasterGetVendorVersion = 16, + audioMasterCanDo = 19, + audioMasterGetLanguage = 20, + audioMasterGetDirectory = 21, + audioMasterUpdateDisplay = 22, + audioMasterGetSampleRate = 35, + audioMasterGetBlockSize = 36, + audioMasterGetInputLatency = 29, + audioMasterGetOutputLatency = 30 +}; + +//------------------------------------------------------------------------ +// MIDI events qua effProcessEvents (không dùng giao diện MIDI cũ của AEffect) +struct VstEvent +{ + int32 type; + int32 byteSize; + int32 flags; + intptr_t* data; // 4 bytes padding trên Win64 giữ layout +}; + +struct VstMidiEvent +{ + int32 type; // kVstMidiType + int32 byteSize; // sizeof(VstMidiEvent) + int32 deltaFrames; + int32 flags; // kVstMidiEventIsRealtime + int32 noteLength; // 0 + int32 noteOffset; // 0 + char midiData[4]; // status, data1, data2, pad + char detune; + char noteOffVelocity; + char reserved1; + char reserved2; +}; + +struct VstEvents +{ + int32 numEvents; + intptr_t reserved; + VstEvent* events[1]; +}; + +enum VstEventTypes +{ + kVstMidiType = 1 +}; + +enum VstMidiEventFlags +{ + kVstMidiEventIsRealtime = 1 << 0 +}; + +//------------------------------------------------------------------------ +// Editor rect (effEditGetRect) +struct ERect +{ + short top; + short left; + short bottom; + short right; +}; + +//------------------------------------------------------------------------ +// VST2 entry points (typedef names khác tên export để tránh redefinition) +typedef AEffect* (VSTCALLBACK* VSTPluginMainFn)(void* audioMasterCallback); +typedef AEffect* (VSTCALLBACK* VSTPluginMainOldFn)(void* audioMasterCallback, void* reserved); +typedef VstIntPtr (VSTCALLBACK* audioMasterCallbackFunc)(AEffect* effect, int32 opcode, int32 index, VstIntPtr value, void* ptr, float opt);