631 lines
22 KiB
C++
631 lines
22 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"
|
|
#include "AudioEngine.h"
|
|
#include "NativeMixer.h"
|
|
|
|
#include <atomic>
|
|
|
|
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<bool> audioRunning {false};
|
|
sonicforge::NativeMixer mixer; // T13: track gain/pan + master limiter
|
|
};
|
|
|
|
std::mutex g_mutex;
|
|
std::mutex g_effect_map_mutex; // riêng: audioMasterAutomate gọi từ audio thread
|
|
std::map<AEffect*, Instance*> g_effect_map;
|
|
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) 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<std::mutex> 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<double> (opt),
|
|
owner->paramCbUserdata);
|
|
return 1;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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<Instance*> (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<int32> (sizeof (VstMidiEvent));
|
|
m.deltaFrames = 0;
|
|
m.flags = kVstMidiEventIsRealtime;
|
|
m.midiData[0] = static_cast<char> ((events[i].noteOn ? 0x90 : 0x80) |
|
|
(events[i].channel & 0x0F));
|
|
m.midiData[1] = static_cast<char> (events[i].pitch & 0x7F);
|
|
m.midiData[2] = events[i].noteOn
|
|
? static_cast<char> (static_cast<int> (events[i].velocity * 127.0f) & 0x7F)
|
|
: 0;
|
|
ptrs[i] = reinterpret_cast<VstEvent*> (&m);
|
|
buf.events[i] = ptrs[i];
|
|
}
|
|
inst->effect->dispatcher (inst->effect, effProcessEvents, 0, 0,
|
|
reinterpret_cast<VstEvents*> (&buf), 0.0f);
|
|
}
|
|
|
|
if (inst->effect->processReplacing)
|
|
{
|
|
float* outs[2] = { outL, outR };
|
|
inst->effect->processReplacing (inst->effect, nullptr, outs, frames);
|
|
inst->mixer.processInPlace (outL, outR, frames); // T13: gain/pan + limiter
|
|
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<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++;
|
|
inst->handle = handle;
|
|
{
|
|
std::lock_guard<std::mutex> 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<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 ();
|
|
// 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<std::mutex> 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<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;
|
|
|
|
// 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<float> (velocity) / 127.0f;
|
|
ev.noteOn = true;
|
|
inst->audio.pushMidi (ev);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
// 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<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;
|
|
}
|
|
|
|
// Đă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<std::mutex> 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<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->effect->setParameter)
|
|
return -2;
|
|
inst->effect->setParameter (inst->effect, paramIndex,
|
|
static_cast<float> (valueNormalized));
|
|
return 0;
|
|
}
|
|
|
|
// Số tham số plugin.
|
|
__declspec (dllexport) int32 SF_VST2_GetParamCount (int32 handle)
|
|
{
|
|
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->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<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->effect->getParameter)
|
|
return -2;
|
|
if (out_value)
|
|
*out_value = static_cast<double> (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<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;
|
|
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock (g_mutex);
|
|
auto it = g_instances.find (handle);
|
|
if (it == g_instances.end ())
|
|
return -1;
|
|
return it->second->audio.blocksRendered ();
|
|
}
|
|
|
|
// T13: track gain/pan + master limiter (NativeMixer). Set truoc AudioStart;
|
|
// audio thread doc state nay (khong lock).
|
|
__declspec (dllexport) int32 SF_VST2_SetTrackGainPan (int32 handle, float gainDb, float pan)
|
|
{
|
|
std::lock_guard<std::mutex> lock (g_mutex);
|
|
auto it = g_instances.find (handle);
|
|
if (it == g_instances.end ())
|
|
return -1;
|
|
it->second->mixer.setTrack (sonicforge::TrackGainPan::fromDbPan (gainDb, pan));
|
|
return 0;
|
|
}
|
|
|
|
__declspec (dllexport) int32 SF_VST2_SetMasterLimiter (int32 handle, int32 active, float thresholdDb)
|
|
{
|
|
std::lock_guard<std::mutex> lock (g_mutex);
|
|
auto it = g_instances.find (handle);
|
|
if (it == g_instances.end ())
|
|
return -1;
|
|
it->second->mixer.setLimiter (active != 0, thresholdDb);
|
|
return 0;
|
|
}
|
|
|
|
} // extern "C"
|