Refactor BufferConsumer to use a double buffer that results in significantly less audio loss

pull/261/head
James H Ball 2024-10-27 12:02:54 +00:00 zatwierdzone przez James H Ball
rodzic 8296014272
commit 6054d81541
10 zmienionych plików z 45 dodań i 48 usunięć

Wyświetl plik

@ -754,7 +754,6 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
juce::SpinLock::ScopedLockType scope(consumerLock);
for (auto consumer : consumers) {
consumer->write(OsciPoint(x, y, 1));
consumer->notifyIfFull();
}
}

Wyświetl plik

@ -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());

Wyświetl plik

@ -22,7 +22,6 @@ private:
juce::CriticalSection consumerLock;
std::shared_ptr<BufferConsumer> consumer;
std::vector<OsciPoint> buffer = std::vector<OsciPoint>(fftSize);
juce::dsp::FFT forwardFFT{fftOrder};
std::array<float, fftSize * 2> fftData;
OscirenderAudioProcessor& audioProcessor;

Wyświetl plik

@ -153,14 +153,10 @@ void VisualiserComponent::run() {
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<float> area
void VisualiserComponent::resetBuffer() {
sampleRate = (int) sampleRateManager.getSampleRate();
tempBuffer = std::vector<OsciPoint>(sampleRate * BUFFER_LENGTH_SECS);
{
juce::CriticalSection::ScopedLockType scope(consumerLock);
consumerManager.consumerStop(consumer);
consumer = consumerManager.consumerRegister(sampleRate * BUFFER_LENGTH_SECS);
}
}
void VisualiserComponent::toggleRecording() {

Wyświetl plik

@ -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<OsciPoint> tempBuffer;
int precision = 4;
juce::CriticalSection consumerLock;

Wyświetl plik

@ -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<OsciPoint>& 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<OsciPoint>& 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,13 +460,11 @@ 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
return { textureID, width, height };
@ -477,7 +478,6 @@ void VisualiserOpenGLComponent::drawLineTexture(const std::vector<float>& 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<Texture> 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);
}

Wyświetl plik

@ -102,16 +102,10 @@ 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<OsciPoint>(BUFFER_DURATION_SECS * sampleRate);
{
juce::CriticalSection::ScopedLockType scope(consumerLock);
audioProcessor.consumerStop(consumer);
consumer = audioProcessor.consumerRegister(BUFFER_DURATION_SECS * sampleRate);
}
}

Wyświetl plik

@ -76,7 +76,6 @@ private:
const double BUFFER_DURATION_SECS = 0.02;
int sampleRate = DEFAULT_SAMPLE_RATE;
std::vector<OsciPoint> buffer = std::vector<OsciPoint>(BUFFER_DURATION_SECS * DEFAULT_SAMPLE_RATE);
std::atomic<float> leftVolume = 0;
std::atomic<float> rightVolume = 0;

Wyświetl plik

@ -42,7 +42,10 @@ public:
class BufferConsumer {
public:
BufferConsumer(std::vector<OsciPoint>& buffer) : buffer(buffer) {}
BufferConsumer(std::size_t size) {
buffer1.resize(size);
buffer2.resize(size);
}
~BufferConsumer() {}
@ -50,12 +53,6 @@ public:
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.
void forceNotify() {
@ -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<OsciPoint>& getFullBuffer() {
return buffer == &buffer1 ? buffer2 : buffer1;
}
private:
std::vector<OsciPoint>& buffer;
std::vector<OsciPoint> buffer1;
std::vector<OsciPoint> buffer2;
std::vector<OsciPoint>* buffer = &buffer1;
Semaphore sema{0};
int offset = 0;
};

Wyświetl plik

@ -10,8 +10,8 @@ public:
ConsumerManager() {}
~ConsumerManager() {}
std::shared_ptr<BufferConsumer> consumerRegister(std::vector<OsciPoint>& buffer) {
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(buffer);
std::shared_ptr<BufferConsumer> consumerRegister(std::size_t size) {
std::shared_ptr<BufferConsumer> consumer = std::make_shared<BufferConsumer>(size);
juce::SpinLock::ScopedLockType scope(consumerLock);
consumers.push_back(consumer);
@ -20,8 +20,6 @@ public:
void consumerRead(std::shared_ptr<BufferConsumer> consumer) {
consumer->waitUntilFull();
juce::SpinLock::ScopedLockType scope(consumerLock);
consumers.erase(std::remove(consumers.begin(), consumers.end(), consumer), consumers.end());
}
void consumerStop(std::shared_ptr<BufferConsumer> consumer) {