T10: VST2 engine bridge (vestige.h clean-room, VST2AudioEngine.cpp, fake_vst2 test plugin)
This commit is contained in:
@@ -32,3 +32,14 @@ target_compile_definitions(vst3_host_bridge PRIVATE
|
|||||||
NOMINMAX
|
NOMINMAX
|
||||||
WIN32_LEAN_AND_MEAN
|
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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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 <windows.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<int32, std::unique_ptr<Instance>> 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<size_t> (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<char*> (ptr), "SonicForgeStudio", 64);
|
||||||
|
return 1;
|
||||||
|
case audioMasterGetProductString:
|
||||||
|
if (ptr)
|
||||||
|
std::strncpy (static_cast<char*> (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<std::mutex> lock (g_mutex);
|
||||||
|
if (!module_path_utf8)
|
||||||
|
{
|
||||||
|
set_err (err, err_cap, "null arg");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inst = std::make_unique<Instance> ();
|
||||||
|
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<wchar_t> wpath (static_cast<size_t> (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<VSTPluginMainFn> (GetProcAddress (inst->module, "VSTPluginMain"));
|
||||||
|
if (!mainFn)
|
||||||
|
{
|
||||||
|
auto oldFn = reinterpret_cast<VSTPluginMainOldFn> (GetProcAddress (inst->module, "main"));
|
||||||
|
if (oldFn)
|
||||||
|
{
|
||||||
|
tls_loading = inst.get ();
|
||||||
|
inst->effect = oldFn (reinterpret_cast<void*> (&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<void*> (&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<float> (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<void*> (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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<char> (0x90 | (channel & 0x0F));
|
||||||
|
midiData[1] = static_cast<char> (pitch & 0x7F);
|
||||||
|
midiData[2] = static_cast<char> (velocity & 0x7F);
|
||||||
|
|
||||||
|
VstMidiEvent midiEvent = {};
|
||||||
|
midiEvent.type = kVstMidiType;
|
||||||
|
midiEvent.byteSize = static_cast<int32> (sizeof (VstMidiEvent));
|
||||||
|
midiEvent.deltaFrames = 0;
|
||||||
|
midiEvent.flags = kVstMidiEventIsRealtime;
|
||||||
|
std::memcpy (midiEvent.midiData, midiData, 4);
|
||||||
|
|
||||||
|
VstEvent* eventPtr = reinterpret_cast<VstEvent*> (&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<std::mutex> 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<char> (0x80 | (channel & 0x0F));
|
||||||
|
midiData[1] = static_cast<char> (pitch & 0x7F);
|
||||||
|
midiData[2] = 0;
|
||||||
|
|
||||||
|
VstMidiEvent midiEvent = {};
|
||||||
|
midiEvent.type = kVstMidiType;
|
||||||
|
midiEvent.byteSize = static_cast<int32> (sizeof (VstMidiEvent));
|
||||||
|
midiEvent.deltaFrames = 0;
|
||||||
|
midiEvent.flags = kVstMidiEventIsRealtime;
|
||||||
|
std::memcpy (midiEvent.midiData, midiData, 4);
|
||||||
|
|
||||||
|
VstEvent* eventPtr = reinterpret_cast<VstEvent*> (&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<std::mutex> 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<float*> ptrs (static_cast<size_t> (total));
|
||||||
|
for (int32 i = 0; i < total; ++i)
|
||||||
|
ptrs[static_cast<size_t> (i)] = buffers + static_cast<size_t> (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"
|
||||||
@@ -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 <windows.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#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<int32> (opt);
|
||||||
|
return 1;
|
||||||
|
case effMainsChanged:
|
||||||
|
return 1;
|
||||||
|
case effStartProcess:
|
||||||
|
case effStopProcess:
|
||||||
|
return 1;
|
||||||
|
case effEditGetRect:
|
||||||
|
{
|
||||||
|
static ERect rect = {0, 0, 300, 400};
|
||||||
|
*reinterpret_cast<ERect**> (ptr) = ▭
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case effEditOpen:
|
||||||
|
return 1;
|
||||||
|
case effEditClose:
|
||||||
|
return 1;
|
||||||
|
case effProcessEvents:
|
||||||
|
return 1;
|
||||||
|
case effGetEffectName:
|
||||||
|
std::strncpy (static_cast<char*> (ptr), "FakeVST2", 64);
|
||||||
|
return 1;
|
||||||
|
case effGetVendorString:
|
||||||
|
std::strncpy (static_cast<char*> (ptr), "SonicForgeStudio", 64);
|
||||||
|
return 1;
|
||||||
|
case effGetProductString:
|
||||||
|
std::strncpy (static_cast<char*> (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<float> (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);
|
||||||
|
}
|
||||||
@@ -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 <cstdint>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
Reference in New Issue
Block a user