From 16f40af10edfe68347222d213605de3283f1c1ea Mon Sep 17 00:00:00 2001 From: struktured Date: Sun, 29 Mar 2026 17:29:04 -0400 Subject: [PATCH] Fix PCM buffer thread safety with proper mutex The circular audio buffer was protected only by a std::atomic on the write index, which is insufficient: a multi-sample AddToBuffer() call can be interrupted mid-write by UpdateFrameAudioData() reading the buffer, producing torn waveform data. Replace the atomic index with a std::mutex that protects both the buffer writes in AddToBuffer() and the bulk copy in UpdateFrameAudioData(). The lock is held only for the buffer access, not for the downstream spectrum analysis. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/libprojectM/Audio/PCM.cpp | 14 ++++++++++---- src/libprojectM/Audio/PCM.hpp | 6 ++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/libprojectM/Audio/PCM.cpp b/src/libprojectM/Audio/PCM.cpp index bc7c83c59a..1deea37ae4 100755 --- a/src/libprojectM/Audio/PCM.cpp +++ b/src/libprojectM/Audio/PCM.cpp @@ -1,5 +1,7 @@ #include "Audio/PCM.hpp" +#include + namespace libprojectM { namespace Audio { @@ -17,6 +19,7 @@ void PCM::AddToBuffer( return; } + std::lock_guard lock(m_pcmMutex); for (size_t i = 0; i < sampleCount; i++) { size_t const bufferOffset = (m_start + i) % AudioBufferSamples; @@ -48,9 +51,12 @@ void PCM::Add(int16_t const* const samples, uint32_t channels, size_t const coun void PCM::UpdateFrameAudioData(double secondsSinceLastFrame, uint32_t frame) { - // 1. Copy audio data from input buffer - CopyNewWaveformData(m_inputBufferL, m_waveformL); - CopyNewWaveformData(m_inputBufferR, m_waveformR); + // 1. Copy audio data from input buffer (lock to prevent tearing with audio thread writes) + { + std::lock_guard lock(m_pcmMutex); + CopyNewWaveformData(m_inputBufferL, m_waveformL); + CopyNewWaveformData(m_inputBufferR, m_waveformR); + } // 2. Update spectrum analyzer data for both channels UpdateSpectrum(m_waveformL, m_spectrumL); @@ -110,7 +116,7 @@ void PCM::UpdateSpectrum(const WaveformBuffer& waveformData, SpectrumBuffer& spe void PCM::CopyNewWaveformData(const WaveformBuffer& source, WaveformBuffer& destination) { - auto const bufferStartIndex = m_start.load(); + auto const bufferStartIndex = m_start; for (size_t i = 0; i < AudioBufferSamples; i++) { diff --git a/src/libprojectM/Audio/PCM.hpp b/src/libprojectM/Audio/PCM.hpp index 26871954da..7727f13ee3 100755 --- a/src/libprojectM/Audio/PCM.hpp +++ b/src/libprojectM/Audio/PCM.hpp @@ -15,9 +15,9 @@ #include -#include #include #include +#include namespace libprojectM { @@ -89,10 +89,12 @@ class PROJECTM_CXX_EXPORT PCM */ void CopyNewWaveformData(const WaveformBuffer& source, WaveformBuffer& destination); + std::mutex m_pcmMutex; //!< Protects the circular input buffer from concurrent access. + // External input buffer WaveformBuffer m_inputBufferL{0.f}; //!< Circular buffer for left-channel PCM data. WaveformBuffer m_inputBufferR{0.f}; //!< Circular buffer for right-channel PCM data. - std::atomic m_start{0}; //!< Circular buffer start index. + size_t m_start{0}; //!< Circular buffer start index. // Frame waveform data WaveformBuffer m_waveformL{0.f}; //!< Left-channel waveform data, aligned. Only the first WaveformSamples number of samples are valid.