From 6054d81541f94cdd8a51937eace3d450572e1c75 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Sun, 27 Oct 2024 12:02:54 +0000 Subject: [PATCH] Refactor BufferConsumer to use a double buffer that results in significantly less audio loss --- Source/PluginProcessor.cpp | 1 - Source/audio/PitchDetector.cpp | 7 ++--- Source/audio/PitchDetector.h | 1 - Source/components/VisualiserComponent.cpp | 15 +++++----- Source/components/VisualiserComponent.h | 1 - .../components/VisualiserOpenGLComponent.cpp | 16 +++++------ Source/components/VolumeComponent.cpp | 17 ++++++----- Source/components/VolumeComponent.h | 1 - Source/concurrency/BufferConsumer.h | 28 ++++++++++++------- Source/concurrency/ConsumerManager.h | 6 ++-- 10 files changed, 45 insertions(+), 48 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 871bbd6..7e73aae 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -754,7 +754,6 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer& buffer, ju juce::SpinLock::ScopedLockType scope(consumerLock); for (auto consumer : consumers) { consumer->write(OsciPoint(x, y, 1)); - consumer->notifyIfFull(); } } diff --git a/Source/audio/PitchDetector.cpp b/Source/audio/PitchDetector.cpp index e0fe17a..80c7fef 100644 --- a/Source/audio/PitchDetector.cpp +++ b/Source/audio/PitchDetector.cpp @@ -2,6 +2,7 @@ #include "../PluginProcessor.h" PitchDetector::PitchDetector(OscirenderAudioProcessor& audioProcessor) : juce::Thread("PitchDetector"), audioProcessor(audioProcessor) { + consumer = audioProcessor.consumerRegister(fftSize); startThread(); } @@ -15,15 +16,11 @@ PitchDetector::~PitchDetector() { void PitchDetector::run() { while (!threadShouldExit()) { - { - juce::CriticalSection::ScopedLockType scope(consumerLock); - consumer = audioProcessor.consumerRegister(buffer); - } audioProcessor.consumerRead(consumer); // buffer is for 2 channels, so we need to only use one for (int i = 0; i < fftSize; i++) { - fftData[i] = buffer[i].x; + fftData[i] = consumer->getFullBuffer()[i].x; } forwardFFT.performFrequencyOnlyForwardTransform(fftData.data()); diff --git a/Source/audio/PitchDetector.h b/Source/audio/PitchDetector.h index 5e745a1..00c948c 100644 --- a/Source/audio/PitchDetector.h +++ b/Source/audio/PitchDetector.h @@ -22,7 +22,6 @@ private: juce::CriticalSection consumerLock; std::shared_ptr consumer; - std::vector buffer = std::vector(fftSize); juce::dsp::FFT forwardFFT{fftOrder}; std::array fftData; OscirenderAudioProcessor& audioProcessor; diff --git a/Source/components/VisualiserComponent.cpp b/Source/components/VisualiserComponent.cpp index a8d6811..4fbb9ad 100644 --- a/Source/components/VisualiserComponent.cpp +++ b/Source/components/VisualiserComponent.cpp @@ -152,15 +152,11 @@ void VisualiserComponent::run() { if (sampleRate != (int) sampleRateManager.getSampleRate()) { resetBuffer(); } - - { - juce::CriticalSection::ScopedLockType scope(consumerLock); - consumer = consumerManager.consumerRegister(tempBuffer); - } + consumerManager.consumerRead(consumer); // TODO: Find a way to immediately call consumerRegister after consumerRead so that no audio is missed - setBuffer(tempBuffer); + setBuffer(consumer->getFullBuffer()); } } @@ -247,7 +243,12 @@ void VisualiserComponent::paintXY(juce::Graphics& g, juce::Rectangle area void VisualiserComponent::resetBuffer() { sampleRate = (int) sampleRateManager.getSampleRate(); - tempBuffer = std::vector(sampleRate * BUFFER_LENGTH_SECS); + + { + juce::CriticalSection::ScopedLockType scope(consumerLock); + consumerManager.consumerStop(consumer); + consumer = consumerManager.consumerRegister(sampleRate * BUFFER_LENGTH_SECS); + } } void VisualiserComponent::toggleRecording() { diff --git a/Source/components/VisualiserComponent.h b/Source/components/VisualiserComponent.h index c4a4396..7e1e8c6 100644 --- a/Source/components/VisualiserComponent.h +++ b/Source/components/VisualiserComponent.h @@ -80,7 +80,6 @@ private: SvgButton popOutButton{ "popOut", BinaryData::open_in_new_svg, juce::Colours::white, juce::Colours::white }; SvgButton settingsButton{ "settings", BinaryData::cog_svg, juce::Colours::white, juce::Colours::white }; - std::vector tempBuffer; int precision = 4; juce::CriticalSection consumerLock; diff --git a/Source/components/VisualiserOpenGLComponent.cpp b/Source/components/VisualiserOpenGLComponent.cpp index a8c4eec..62783a8 100644 --- a/Source/components/VisualiserOpenGLComponent.cpp +++ b/Source/components/VisualiserOpenGLComponent.cpp @@ -15,6 +15,8 @@ VisualiserOpenGLComponent::~VisualiserOpenGLComponent() { void VisualiserOpenGLComponent::newOpenGLContextCreated() { using namespace juce::gl; + juce::CriticalSection::ScopedLockType lock(samplesLock); + juce::OpenGLHelpers::clear(juce::Colours::black); glColorMask(true, true, true, true); @@ -304,8 +306,7 @@ void VisualiserOpenGLComponent::openGLContextClosing() { } void VisualiserOpenGLComponent::updateBuffer(std::vector& buffer) { - // TODO: Figure out whether locking on samplesLock is required - //juce::CriticalSection::ScopedLockType lock(samplesLock); + juce::CriticalSection::ScopedLockType lock(samplesLock); if (xSamples.size() != buffer.size()) { needsReattach = true; @@ -323,6 +324,8 @@ void VisualiserOpenGLComponent::updateBuffer(std::vector& buffer) { } void VisualiserOpenGLComponent::handleAsyncUpdate() { + juce::CriticalSection::ScopedLockType lock(samplesLock); + int newResampledSize = xSamples.size() * RESAMPLE_RATIO; smoothedXSamples.resize(newResampledSize); @@ -350,7 +353,7 @@ void VisualiserOpenGLComponent::handleAsyncUpdate() { void VisualiserOpenGLComponent::renderOpenGL() { if (openGLContext.isActive()) { - //juce::CriticalSection::ScopedLockType lock(samplesLock); + juce::CriticalSection::ScopedLockType lock(samplesLock); if (graticuleEnabled != settings.getGraticuleEnabled() || smudgesEnabled != settings.getSmudgesEnabled()) { graticuleEnabled = settings.getGraticuleEnabled(); @@ -457,12 +460,10 @@ Texture VisualiserOpenGLComponent::makeTexture(int width, int height) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_FLOAT, nullptr); // Set texture filtering and wrapping - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); // Unbind @@ -477,7 +478,6 @@ void VisualiserOpenGLComponent::drawLineTexture(const std::vector& xPoint fade(); drawLine(xPoints, yPoints, zPoints); glBindTexture(GL_TEXTURE_2D, targetTexture.value().id); - glGenerateMipmap(GL_TEXTURE_2D); } void VisualiserOpenGLComponent::saveTextureToFile(GLuint textureID, int width, int height, const juce::File& file) { @@ -582,7 +582,6 @@ void VisualiserOpenGLComponent::drawTexture(std::optional texture0, std if (targetTexture.has_value()) { glBindTexture(GL_TEXTURE_2D, targetTexture.value().id); - glGenerateMipmap(GL_TEXTURE_2D); } } @@ -782,7 +781,6 @@ Texture VisualiserOpenGLComponent::createScreenTexture() { glLineWidth(1.0f); glDrawArrays(GL_LINES, 0, data.size()); glBindTexture(GL_TEXTURE_2D, targetTexture.value().id); - glGenerateMipmap(GL_TEXTURE_2D); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } diff --git a/Source/components/VolumeComponent.cpp b/Source/components/VolumeComponent.cpp index bce1c85..db03b83 100644 --- a/Source/components/VolumeComponent.cpp +++ b/Source/components/VolumeComponent.cpp @@ -102,15 +102,9 @@ void VolumeComponent::run() { resetBuffer(); } - if (buffer.size() == 0) { - continue; - } - - { - juce::CriticalSection::ScopedLockType lock(consumerLock); - consumer = audioProcessor.consumerRegister(buffer); - } audioProcessor.consumerRead(consumer); + + auto buffer = consumer->getFullBuffer(); float leftVolume = 0; float rightVolume = 0; @@ -149,5 +143,10 @@ void VolumeComponent::resized() { void VolumeComponent::resetBuffer() { sampleRate = (int) audioProcessor.currentSampleRate; - buffer = std::vector(BUFFER_DURATION_SECS * sampleRate); + + { + juce::CriticalSection::ScopedLockType scope(consumerLock); + audioProcessor.consumerStop(consumer); + consumer = audioProcessor.consumerRegister(BUFFER_DURATION_SECS * sampleRate); + } } diff --git a/Source/components/VolumeComponent.h b/Source/components/VolumeComponent.h index d524423..e3c9d85 100644 --- a/Source/components/VolumeComponent.h +++ b/Source/components/VolumeComponent.h @@ -76,7 +76,6 @@ private: const double BUFFER_DURATION_SECS = 0.02; int sampleRate = DEFAULT_SAMPLE_RATE; - std::vector buffer = std::vector(BUFFER_DURATION_SECS * DEFAULT_SAMPLE_RATE); std::atomic leftVolume = 0; std::atomic rightVolume = 0; diff --git a/Source/concurrency/BufferConsumer.h b/Source/concurrency/BufferConsumer.h index b70430f..f6b15be 100644 --- a/Source/concurrency/BufferConsumer.h +++ b/Source/concurrency/BufferConsumer.h @@ -42,19 +42,16 @@ public: class BufferConsumer { public: - BufferConsumer(std::vector& buffer) : buffer(buffer) {} + BufferConsumer(std::size_t size) { + buffer1.resize(size); + buffer2.resize(size); + } ~BufferConsumer() {} void waitUntilFull() { sema.acquire(); } - - void notifyIfFull() { - if (offset >= buffer.size()) { - sema.release(); - } - } // to be used when the audio thread is being destroyed to // make sure that everything waiting on it stops waiting. @@ -63,13 +60,24 @@ public: } void write(OsciPoint point) { - if (offset < buffer.size()) { - buffer[offset++] = point; + if (offset >= buffer->size()) { + buffer = buffer == &buffer1 ? &buffer2 : &buffer1; + offset = 0; + sema.release(); } + + (*buffer)[offset++] = point; + } + + // whatever buffer is not currently being written to + std::vector& getFullBuffer() { + return buffer == &buffer1 ? buffer2 : buffer1; } private: - std::vector& buffer; + std::vector buffer1; + std::vector buffer2; + std::vector* buffer = &buffer1; Semaphore sema{0}; int offset = 0; }; diff --git a/Source/concurrency/ConsumerManager.h b/Source/concurrency/ConsumerManager.h index f40e025..e8bf41e 100644 --- a/Source/concurrency/ConsumerManager.h +++ b/Source/concurrency/ConsumerManager.h @@ -10,8 +10,8 @@ public: ConsumerManager() {} ~ConsumerManager() {} - std::shared_ptr consumerRegister(std::vector& buffer) { - std::shared_ptr consumer = std::make_shared(buffer); + std::shared_ptr consumerRegister(std::size_t size) { + std::shared_ptr consumer = std::make_shared(size); juce::SpinLock::ScopedLockType scope(consumerLock); consumers.push_back(consumer); @@ -20,8 +20,6 @@ public: void consumerRead(std::shared_ptr consumer) { consumer->waitUntilFull(); - juce::SpinLock::ScopedLockType scope(consumerLock); - consumers.erase(std::remove(consumers.begin(), consumers.end(), consumer), consumers.end()); } void consumerStop(std::shared_ptr consumer) {