#pragma once #include #include #include #include #ifdef __APPLE__ #include #include #include #include #include #include #endif // Shared memory layout for audio transport between Plugin and Helper // Zero-copy: both processes mmap the same region struct SharedAudioHeader { std::atomic sampleRate{44100}; std::atomic blockSize{512}; std::atomic numChannels{2}; std::atomic state{0}; // 0=idle, 1=input_ready, 2=output_ready std::atomic helperAlive{0}; // heartbeat from helper uint32_t padding[3]; // align to 32 bytes }; // State machine: // Parent: writes input → sets state=1 → signals helper // Helper: reads input → processBlock → writes output → sets state=2 → signals parent // Parent: reads output → sets state=0 static constexpr uint32_t kStateIdle = 0; static constexpr uint32_t kStateInputReady = 1; static constexpr uint32_t kStateOutputReady = 2; // Max supported: stereo, 4096 samples per block static constexpr size_t kMaxChannels = 2; static constexpr size_t kMaxBlockSize = 4096; static constexpr size_t kAudioDataSize = kMaxChannels * kMaxBlockSize * sizeof(float); static constexpr size_t kSharedMemSize = sizeof(SharedAudioHeader) + (kAudioDataSize * 2); // input + output class SharedAudioBuffer { public: SharedAudioBuffer() = default; ~SharedAudioBuffer() { destroy(); } // Create (parent) or open (helper) shared memory bool create(const std::string& name) { shmName = "/" + name; // Remove if exists from previous crash shm_unlink(shmName.c_str()); fd = shm_open(shmName.c_str(), O_CREAT | O_RDWR, 0666); if (fd < 0) return false; if (ftruncate(fd, kSharedMemSize) != 0) { close(fd); return false; } ptr = (uint8_t*)mmap(nullptr, kSharedMemSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { close(fd); return false; } // Zero the whole region memset(ptr, 0, kSharedMemSize); header = reinterpret_cast(ptr); inputBuffer = reinterpret_cast(ptr + sizeof(SharedAudioHeader)); outputBuffer = reinterpret_cast(ptr + sizeof(SharedAudioHeader) + kAudioDataSize); isOwner = true; return true; } bool open(const std::string& name) { shmName = "/" + name; fd = shm_open(shmName.c_str(), O_RDWR, 0666); if (fd < 0) return false; ptr = (uint8_t*)mmap(nullptr, kSharedMemSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { close(fd); return false; } header = reinterpret_cast(ptr); inputBuffer = reinterpret_cast(ptr + sizeof(SharedAudioHeader)); outputBuffer = reinterpret_cast(ptr + sizeof(SharedAudioHeader) + kAudioDataSize); isOwner = false; return true; } void destroy() { if (ptr && ptr != MAP_FAILED) { munmap(ptr, kSharedMemSize); ptr = nullptr; } if (fd >= 0) { close(fd); fd = -1; } if (isOwner && !shmName.empty()) { shm_unlink(shmName.c_str()); } } // --- Audio data access --- SharedAudioHeader* getHeader() { return header; } void writeInput(const float* const* channelData, int numChannels, int numSamples) { int ch = std::min(numChannels, (int)kMaxChannels); int ns = std::min(numSamples, (int)kMaxBlockSize); for (int c = 0; c < ch; ++c) memcpy(inputBuffer + c * kMaxBlockSize, channelData[c], ns * sizeof(float)); } void readOutput(float* const* channelData, int numChannels, int numSamples) { int ch = std::min(numChannels, (int)kMaxChannels); int ns = std::min(numSamples, (int)kMaxBlockSize); for (int c = 0; c < ch; ++c) memcpy(channelData[c], outputBuffer + c * kMaxBlockSize, ns * sizeof(float)); } void readInput(float* const* channelData, int numChannels, int numSamples) { int ch = std::min(numChannels, (int)kMaxChannels); int ns = std::min(numSamples, (int)kMaxBlockSize); for (int c = 0; c < ch; ++c) memcpy(channelData[c], inputBuffer + c * kMaxBlockSize, ns * sizeof(float)); } void writeOutput(const float* const* channelData, int numChannels, int numSamples) { int ch = std::min(numChannels, (int)kMaxChannels); int ns = std::min(numSamples, (int)kMaxBlockSize); for (int c = 0; c < ch; ++c) memcpy(outputBuffer + c * kMaxBlockSize, channelData[c], ns * sizeof(float)); } bool isValid() const { return ptr != nullptr && ptr != MAP_FAILED; } private: uint8_t* ptr = nullptr; int fd = -1; bool isOwner = false; std::string shmName; SharedAudioHeader* header = nullptr; float* inputBuffer = nullptr; float* outputBuffer = nullptr; }; // --- Semaphore wrapper (macOS uses dispatch semaphores since POSIX named sems are deprecated) --- class SharedSemaphore { public: SharedSemaphore() = default; ~SharedSemaphore() { destroy(); } bool create(const std::string& name) { semName = name; // macOS: use named POSIX semaphore (still works despite deprecation warning) sem = sem_open(("/" + name).c_str(), O_CREAT, 0666, 0); return sem != SEM_FAILED; } bool open(const std::string& name) { semName = name; sem = sem_open(("/" + name).c_str(), 0); return sem != SEM_FAILED; } void signal() { if (sem != SEM_FAILED) sem_post(sem); } bool wait(int timeoutMs = 100) { if (sem == SEM_FAILED) return false; // macOS doesn't support sem_timedwait, use polling for (int i = 0; i < timeoutMs; ++i) { if (sem_trywait(sem) == 0) return true; usleep(1000); // 1ms } return false; } bool tryWait() { if (sem == SEM_FAILED) return false; return sem_trywait(sem) == 0; } void destroy() { if (sem != SEM_FAILED) { sem_close(sem); sem_unlink(("/" + semName).c_str()); sem = SEM_FAILED; } } private: sem_t* sem = SEM_FAILED; std::string semName; };