T12: native audio loop SPSC + WASAPI (exclusive/shared, zero-lock audio thread, SendNote -> SPSC)
This commit is contained in:
@@ -30,6 +30,11 @@
|
||||
#include "pluginterfaces/vst/ivstaudioprocessor.h"
|
||||
#include "pluginterfaces/vst/ivstcomponent.h"
|
||||
#include "pluginterfaces/vst/ivsteditcontroller.h"
|
||||
#include "pluginterfaces/vst/ivstevents.h"
|
||||
|
||||
#include "AudioEngine.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
using namespace Steinberg;
|
||||
using namespace Steinberg::Vst;
|
||||
@@ -87,11 +92,106 @@ struct Instance
|
||||
{
|
||||
VST3::Hosting::Module::Ptr module;
|
||||
IPtr<PlugProvider> plugProvider;
|
||||
IPtr<IComponent> component;
|
||||
IPtr<IAudioProcessor> processor;
|
||||
IPtr<IEditController> controller;
|
||||
IPtr<IPlugView> view;
|
||||
ComponentHandler handler;
|
||||
sonicforge::AudioEngine audio; // T12: native audio loop (SPSC + WASAPI)
|
||||
std::atomic<bool> audioRunning {false};
|
||||
};
|
||||
|
||||
// T12: IEventList cố định (không cấp phát trên audio thread).
|
||||
class FixedEventList : public IEventList
|
||||
{
|
||||
public:
|
||||
FixedEventList () = default;
|
||||
tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override
|
||||
{
|
||||
if (FUnknownPrivate::iidEqual (_iid, IEventList::iid) ||
|
||||
FUnknownPrivate::iidEqual (_iid, FUnknown::iid))
|
||||
{
|
||||
*obj = this;
|
||||
addRef ();
|
||||
return kResultOk;
|
||||
}
|
||||
*obj = nullptr;
|
||||
return kNoInterface;
|
||||
}
|
||||
uint32 PLUGIN_API addRef () override { return 1; }
|
||||
uint32 PLUGIN_API release () override { return 1; }
|
||||
int32 PLUGIN_API getEventCount () override { return count; }
|
||||
tresult PLUGIN_API getEvent (int32 index, Event& e) override
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
return kInvalidArgument;
|
||||
e = events[index];
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API addEvent (Event& e) override
|
||||
{
|
||||
if (count >= 512)
|
||||
return kOutOfMemory;
|
||||
events[count++] = e;
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
Event events[512];
|
||||
int32 count = 0;
|
||||
};
|
||||
|
||||
// T12: audio thread callback — build EventList + process(). Render thread:
|
||||
// KHÔNG lock, KHÔNG cấp phát (EventList cố định, buffer pre-alloc).
|
||||
bool vst3AudioProcess (const sonicforge::MidiEvent* events, int32 eventCount,
|
||||
float* outL, float* outR, int32 frames, void* userdata)
|
||||
{
|
||||
Instance* inst = static_cast<Instance*> (userdata);
|
||||
IAudioProcessor* proc = inst ? inst->processor.get () : nullptr;
|
||||
if (!proc)
|
||||
return false;
|
||||
|
||||
FixedEventList list;
|
||||
for (int32 i = 0; i < eventCount; ++i)
|
||||
{
|
||||
Event e = {};
|
||||
e.sampleOffset = 0;
|
||||
e.ppqPosition = 0.0;
|
||||
e.flags = 0;
|
||||
if (events[i].noteOn)
|
||||
{
|
||||
e.type = Event::kNoteOnEvent;
|
||||
e.noteOn.channel = events[i].channel;
|
||||
e.noteOn.pitch = events[i].pitch;
|
||||
e.noteOn.velocity = events[i].velocity;
|
||||
e.noteOn.noteId = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.type = Event::kNoteOffEvent;
|
||||
e.noteOff.channel = events[i].channel;
|
||||
e.noteOff.pitch = events[i].pitch;
|
||||
e.noteOff.velocity = 0.0f;
|
||||
e.noteOff.noteId = -1;
|
||||
}
|
||||
list.addEvent (e);
|
||||
}
|
||||
|
||||
ProcessData data = {};
|
||||
data.processMode = kRealtime;
|
||||
data.symbolicSampleSize = kSample32;
|
||||
data.numSamples = frames;
|
||||
data.numInputs = 0;
|
||||
data.numOutputs = 1;
|
||||
AudioBusBuffers outBus = {};
|
||||
float* chans[2] = { outL, outR };
|
||||
outBus.numChannels = 2;
|
||||
outBus.channelBuffers32 = chans;
|
||||
data.outputs = &outBus;
|
||||
data.inputEvents = &list;
|
||||
|
||||
return proc->process (data) == kResultOk;
|
||||
}
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::map<int32, std::unique_ptr<Instance>> g_instances;
|
||||
int32 g_next_handle = 1;
|
||||
@@ -160,6 +260,22 @@ __declspec (dllexport) int32 SF_VST3_Attach (const char* module_path_utf8,
|
||||
return 0;
|
||||
}
|
||||
|
||||
inst->component = inst->plugProvider->getComponent (); // giữ ref
|
||||
if (!inst->component)
|
||||
{
|
||||
set_err (err, err_cap, "plugin has no component");
|
||||
return 0;
|
||||
}
|
||||
IAudioProcessor* rawProc = nullptr;
|
||||
if (inst->component->queryInterface (IAudioProcessor::iid,
|
||||
reinterpret_cast<void**> (&rawProc)) != kResultOk ||
|
||||
!rawProc)
|
||||
{
|
||||
set_err (err, err_cap, "plugin has no audio processor");
|
||||
return 0;
|
||||
}
|
||||
inst->processor = IPtr<IAudioProcessor> (rawProc);
|
||||
|
||||
IEditController* rawController = inst->plugProvider->getController (); // +1 ref
|
||||
if (!rawController)
|
||||
{
|
||||
@@ -207,6 +323,11 @@ __declspec (dllexport) int32 SF_VST3_Close (int32 handle, char* err, int32 err_c
|
||||
set_err (err, err_cap, "bad handle");
|
||||
return -1;
|
||||
}
|
||||
// T12: dung audio loop (join render thread) truoc khi pha huy plugin.
|
||||
it->second->audioRunning.store (false, std::memory_order_release);
|
||||
it->second->audio.stop ();
|
||||
if (it->second->processor)
|
||||
it->second->processor->setProcessing (false);
|
||||
if (it->second->view)
|
||||
{
|
||||
it->second->view->removed ();
|
||||
@@ -324,4 +445,106 @@ __declspec (dllexport) int32 SF_VST3_GetParamInfo (int32 handle, int32 index,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// T12: bat native audio loop (SPSC + WASAPI) cho instance nay. Audio thread
|
||||
// goi vst3AudioProcess (EventList + process) moi block.
|
||||
__declspec (dllexport) int32 SF_VST3_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->processor)
|
||||
return -2;
|
||||
|
||||
ProcessSetup setup = {};
|
||||
setup.processMode = kRealtime;
|
||||
setup.symbolicSampleSize = kSample32;
|
||||
setup.maxSamplesPerBlock = blockSize;
|
||||
setup.sampleRate = static_cast<SampleRate> (sampleRate);
|
||||
inst->processor->setupProcessing (setup);
|
||||
inst->processor->setProcessing (true);
|
||||
|
||||
inst->audioRunning.store (true, std::memory_order_release);
|
||||
if (!inst->audio.start (sampleRate, blockSize, &vst3AudioProcess, inst, err, err_cap))
|
||||
{
|
||||
inst->audioRunning.store (false, std::memory_order_release);
|
||||
inst->processor->setProcessing (false);
|
||||
return -3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec (dllexport) int32 SF_VST3_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 ();
|
||||
if (it->second->processor)
|
||||
it->second->processor->setProcessing (false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec (dllexport) int32 SF_VST3_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_VST3_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_VST3_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 ();
|
||||
}
|
||||
|
||||
// T12: MIDI → SPSC (audio thread xu ly). Khong co duong direct nhu VST2 —
|
||||
// offline render (T16) se drain SPSC rieng.
|
||||
__declspec (dllexport) int32 SF_VST3_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;
|
||||
sonicforge::MidiEvent ev = {};
|
||||
ev.channel = channel;
|
||||
ev.pitch = pitch;
|
||||
ev.velocity = static_cast<float> (velocity) / 127.0f;
|
||||
ev.noteOn = true;
|
||||
return it->second->audio.pushMidi (ev) ? 0 : -2;
|
||||
}
|
||||
|
||||
__declspec (dllexport) int32 SF_VST3_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;
|
||||
sonicforge::MidiEvent ev = {};
|
||||
ev.channel = channel;
|
||||
ev.pitch = pitch;
|
||||
ev.velocity = 0.0f;
|
||||
ev.noteOn = false;
|
||||
return it->second->audio.pushMidi (ev) ? 0 : -2;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
Reference in New Issue
Block a user