// 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 #include #include #include #include #include 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 m_head {0}; // producer index (next write) std::atomic m_tail {0}; // consumer index (next read) std::mutex m_producerMutex; MidiEvent m_drained[kMaxEventsPerBlock]; std::atomic m_running {false}; std::atomic m_underruns {0}; std::atomic m_latency {0}; std::atomic 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