T12: native audio loop SPSC + WASAPI (exclusive/shared, zero-lock audio thread, SendNote -> SPSC)
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// AudioEngine.h — native audio loop (T12, spec vsti_native_audio_pipeline_spec)
|
||||
//
|
||||
// Lõi chung cho cả 2 bridge DLL (vst2_host_bridge, vst3_host_bridge): SPSC
|
||||
// ring buffer chuyển MIDI từ UI thread → audio thread, render thread WASAPI
|
||||
// (exclusive trước, fallback shared) gọi ProcessFn mỗi block. Audio thread
|
||||
// TUYỆT ĐỐI không lock / không cấp phát (golden rule spec III).
|
||||
//
|
||||
// QUYẾT ĐỊNH (T12): gộp "VST3AudioEngine.cpp" thành AudioEngine.cpp dùng
|
||||
// chung 2 plugin kind (spec cho phép "hoặc gộp với T9/T10"); WASAPI đủ cho
|
||||
// milestone, ASIO bỏ qua (ghi chú, thêm khi có yêu cầu rõ ràng).
|
||||
#pragma once
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
namespace sonicforge {
|
||||
|
||||
using int32 = int32_t;
|
||||
using uint32 = uint32_t;
|
||||
|
||||
// MIDI event từ UI thread → audio thread (SPSC). velocity 0..1.
|
||||
struct MidiEvent
|
||||
{
|
||||
int32 channel;
|
||||
int32 pitch;
|
||||
float velocity;
|
||||
bool noteOn;
|
||||
};
|
||||
|
||||
constexpr int32 kEventCapacity = 4096;
|
||||
constexpr int32 kMaxEventsPerBlock = 256;
|
||||
|
||||
// ProcessFn chạy trên audio thread (render thread). events/count là các MIDI
|
||||
// event đã drain từ SPSC trong block này. outL/outR: frames mẫu mỗi kênh,
|
||||
// pre-alloc (audio thread không cấp phát). Trả true nếu đã ghi output (false
|
||||
// → engine zero-fill).
|
||||
typedef bool (*ProcessFn)(const MidiEvent* events, int32 eventCount,
|
||||
float* outL, float* outR, int32 frames, void* userdata);
|
||||
|
||||
class AudioEngine
|
||||
{
|
||||
public:
|
||||
AudioEngine ();
|
||||
~AudioEngine ();
|
||||
|
||||
// Khởi tạo WASAPI + render thread. Exclusive trước, fallback shared.
|
||||
// err/err_cap để trả message lỗi (có thể null). Idempotent.
|
||||
bool start (int32 sampleRate, int32 blockSize, ProcessFn fn, void* userdata,
|
||||
char* err, int32 err_cap);
|
||||
void stop (); // join thread + giải phóng WASAPI
|
||||
|
||||
bool running () const { return m_running.load (std::memory_order_acquire); }
|
||||
|
||||
// Producer (UI thread) — có mutex riêng để an toàn với nhiều producer
|
||||
// (Tauri command thread pool); audio thread KHÔNG lock.
|
||||
bool pushMidi (const MidiEvent& ev);
|
||||
|
||||
int32 underruns () const { return m_underruns.load (std::memory_order_relaxed); }
|
||||
int32 latencySamples () const { return m_latency.load (std::memory_order_relaxed); }
|
||||
int32 blocksRendered () const { return m_blocks.load (std::memory_order_relaxed); }
|
||||
int32 sampleRate () const { return m_sampleRate; }
|
||||
|
||||
private:
|
||||
static DWORD WINAPI renderThreadEntry (LPVOID self);
|
||||
void renderLoop ();
|
||||
static uint32 nextIdx (uint32 i) { return (i + 1) % kEventCapacity; }
|
||||
|
||||
// T12: activate IAudioClient cho 1 device + Initialize exclusive→shared.
|
||||
// Trả true nếu OK (m_client/m_render/m_event/m_bufferFrames set).
|
||||
bool initOnDevice (IMMDevice* device, int32 sampleRate, int32 blockSize,
|
||||
char* err, int32 err_cap);
|
||||
|
||||
// SPSC ring (single consumer = audio thread; producer guarded by mutex)
|
||||
MidiEvent m_events[kEventCapacity];
|
||||
std::atomic<uint32> m_head {0}; // producer index (next write)
|
||||
std::atomic<uint32> m_tail {0}; // consumer index (next read)
|
||||
std::mutex m_producerMutex;
|
||||
MidiEvent m_drained[kMaxEventsPerBlock];
|
||||
|
||||
std::atomic<bool> m_running {false};
|
||||
std::atomic<int32> m_underruns {0};
|
||||
std::atomic<int32> m_latency {0};
|
||||
std::atomic<int32> m_blocks {0};
|
||||
|
||||
HANDLE m_thread = nullptr;
|
||||
HANDLE m_event = nullptr;
|
||||
|
||||
IAudioClient* m_client = nullptr;
|
||||
IAudioRenderClient* m_render = nullptr;
|
||||
UINT32 m_bufferFrames = 0;
|
||||
int32 m_sampleRate = 44100;
|
||||
int32 m_blockSize = 128;
|
||||
bool m_comInit = false;
|
||||
|
||||
ProcessFn m_fn = nullptr;
|
||||
void* m_userdata = nullptr;
|
||||
|
||||
float* m_outL = nullptr;
|
||||
float* m_outR = nullptr;
|
||||
};
|
||||
|
||||
} // namespace sonicforge
|
||||
Reference in New Issue
Block a user