// 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" #include "AudioEngine.h" #include namespace { // Callback param (T11): plugin đổi param (audioMasterAutomate) → host. typedef void (VSTCALLBACK* SF_ParamChangedCallback)(int32 handle, int32 paramIndex, double valueNormalized, void* userdata); 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; int32 handle = 0; SF_ParamChangedCallback paramCb = nullptr; void* paramCbUserdata = nullptr; sonicforge::AudioEngine audio; // T12: native audio loop (SPSC + WASAPI) std::atomic audioRunning {false}; }; std::mutex g_mutex; std::mutex g_effect_map_mutex; // riêng: audioMasterAutomate gọi từ audio thread std::map g_effect_map; 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) index; (void) value; (void) ptr; 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 audioMasterAutomate: { // Plugin đổi param (user kéo knob trong editor) → callback lên host. // Effect → instance qua map riêng (audio thread, không giữ g_mutex). Instance* owner = nullptr; { std::lock_guard lock (g_effect_map_mutex); auto it = g_effect_map.find (effect); if (it != g_effect_map.end ()) owner = it->second; } if (owner && owner->paramCb) owner->paramCb (owner->handle, index, static_cast (opt), owner->paramCbUserdata); return 1; } 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; } } // T12: audio thread callback — effProcessEvents (MIDI drain tu SPSC) + // processReplacing. Chay tren WASAPI render thread: KHONG lock, KHONG cap phat. bool vst2AudioProcess (const sonicforge::MidiEvent* events, int32 eventCount, float* outL, float* outR, int32 frames, void* userdata) { Instance* inst = static_cast (userdata); if (!inst || !inst->effect) return false; if (eventCount > 0) { int32 n = eventCount < 256 ? eventCount : 256; VstMidiEvent midi[256]; VstEvent* ptrs[256]; struct VstEventsBuf { int32 numEvents; intptr_t reserved; VstEvent* events[256]; }; VstEventsBuf buf = {}; buf.numEvents = n; for (int32 i = 0; i < n; ++i) { VstMidiEvent& m = midi[i]; std::memset (&m, 0, sizeof (m)); m.type = kVstMidiType; m.byteSize = static_cast (sizeof (VstMidiEvent)); m.deltaFrames = 0; m.flags = kVstMidiEventIsRealtime; m.midiData[0] = static_cast ((events[i].noteOn ? 0x90 : 0x80) | (events[i].channel & 0x0F)); m.midiData[1] = static_cast (events[i].pitch & 0x7F); m.midiData[2] = events[i].noteOn ? static_cast (static_cast (events[i].velocity * 127.0f) & 0x7F) : 0; ptrs[i] = reinterpret_cast (&m); buf.events[i] = ptrs[i]; } inst->effect->dispatcher (inst->effect, effProcessEvents, 0, 0, reinterpret_cast (&buf), 0.0f); } if (inst->effect->processReplacing) { float* outs[2] = { outL, outR }; inst->effect->processReplacing (inst->effect, nullptr, outs, frames); return true; } return false; } } // 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++; inst->handle = handle; { std::lock_guard lock (g_effect_map_mutex); g_effect_map[inst->effect] = inst.get (); } 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 (); // T12: dung audio loop (join render thread) truoc khi pha huy plugin. inst->audioRunning.store (false, std::memory_order_release); inst->audio.stop (); if (inst->effect) { { std::lock_guard lock (g_effect_map_mutex); g_effect_map.erase (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; // T12: audio loop dang chay -> day vao SPSC (audio thread xu ly). if (inst->audioRunning.load (std::memory_order_acquire)) { sonicforge::MidiEvent ev = {}; ev.channel = channel; ev.pitch = pitch; ev.velocity = static_cast (velocity) / 127.0f; ev.noteOn = true; inst->audio.pushMidi (ev); return 0; } 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; // T12: audio loop dang chay -> day vao SPSC. if (inst->audioRunning.load (std::memory_order_acquire)) { sonicforge::MidiEvent ev = {}; ev.channel = channel; ev.pitch = pitch; ev.velocity = 0.0f; ev.noteOn = false; inst->audio.pushMidi (ev); return 0; } 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; } // Đăng ký callback param (T11): plugin đổi param (audioMasterAutomate) → // cb(handle, paramIndex, valueNormalized, userdata). __declspec (dllexport) int32 SF_VST2_SetParamCallback (int32 handle, SF_ParamChangedCallback cb, void* userdata) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; it->second->paramCb = cb; it->second->paramCbUserdata = userdata; return 0; } // JS automation → setParameter (T11). __declspec (dllexport) int32 SF_VST2_SetParam (int32 handle, int32 paramIndex, double valueNormalized) { 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->effect->setParameter) return -2; inst->effect->setParameter (inst->effect, paramIndex, static_cast (valueNormalized)); return 0; } // Số tham số plugin. __declspec (dllexport) int32 SF_VST2_GetParamCount (int32 handle) { 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->effect->numParams) return -2; return inst->effect->numParams (inst->effect); } // Giá trị param (0..1) — JS đọc để dựng UI. __declspec (dllexport) int32 SF_VST2_GetParam (int32 handle, int32 paramIndex, double* out_value) { 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->effect->getParameter) return -2; if (out_value) *out_value = static_cast (inst->effect->getParameter (inst->effect, paramIndex)); return 0; } // T12: bat native audio loop (SPSC + WASAPI) cho instance nay. Audio thread // goi vst2AudioProcess (effProcessEvents + processReplacing) moi block. __declspec (dllexport) int32 SF_VST2_AudioStart (int32 handle, int32 sampleRate, int32 blockSize, char* err, int32 err_cap) { 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; inst->audioRunning.store (true, std::memory_order_release); // truoc khi thread chay if (!inst->audio.start (sampleRate, blockSize, &vst2AudioProcess, inst, err, err_cap)) { inst->audioRunning.store (false, std::memory_order_release); return -3; } return 0; } __declspec (dllexport) int32 SF_VST2_AudioStop (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; it->second->audioRunning.store (false, std::memory_order_release); it->second->audio.stop (); return 0; } __declspec (dllexport) int32 SF_VST2_AudioUnderruns (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; return it->second->audio.underruns (); } __declspec (dllexport) int32 SF_VST2_AudioLatency (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; return it->second->audio.latencySamples (); } __declspec (dllexport) int32 SF_VST2_AudioBlocks (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; return it->second->audio.blocksRendered (); } } // extern "C"