T12: native audio loop SPSC + WASAPI (exclusive/shared, zero-lock audio thread, SendNote -> SPSC)

This commit is contained in:
2026-08-11 16:40:30 +07:00
parent 9eec33cc79
commit 8f1e89ce50
5 changed files with 828 additions and 0 deletions
+138
View File
@@ -23,6 +23,9 @@
#include <vector>
#include "vestige.h"
#include "AudioEngine.h"
#include <atomic>
namespace {
@@ -45,6 +48,8 @@ struct Instance
int32 handle = 0;
SF_ParamChangedCallback paramCb = nullptr;
void* paramCbUserdata = nullptr;
sonicforge::AudioEngine audio; // T12: native audio loop (SPSC + WASAPI)
std::atomic<bool> audioRunning {false};
};
std::mutex g_mutex;
@@ -116,6 +121,53 @@ VstIntPtr VSTCALLBACK hostAudioMasterImpl (AEffect* effect, int32 opcode, int32
}
}
// 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);
return true;
}
return false;
}
} // namespace
extern "C" {
@@ -244,6 +296,9 @@ __declspec (dllexport) int32 SF_VST2_Close (int32 handle, char* err, int32 err_c
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)
{
{
@@ -320,6 +375,18 @@ __declspec (dllexport) int32 SF_VST2_SendNoteOn (int32 handle, int32 channel, in
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);
@@ -352,6 +419,18 @@ __declspec (dllexport) int32 SF_VST2_SendNoteOff (int32 handle, int32 channel, i
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);
@@ -464,4 +543,63 @@ __declspec (dllexport) int32 SF_VST2_GetParam (int32 handle, int32 paramIndex, d
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 ();
}
} // extern "C"