Files
SonicForgeStudio/native_host/VST2AudioEngine.cpp
T

376 lines
12 KiB
C++

// 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"