110 lines
3.9 KiB
C++
110 lines
3.9 KiB
C++
// 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
|