I’ve been working on an analyzer app, but I’m getting sample drops, and that introduces noise in the visualization making it barely usable.
Here is an example of a minimal implementation of an oscilloscope, using some code from the processing audio inputs and the dsp tutorial for the abstract fifo implementation and plotting:
#include "../JuceLibraryCode/JuceHeader.h"
#include <array>
#pragma once
//==============================================================================
class MainContentComponent : public AudioAppComponent, private Timer
{
public:
//==============================================================================
MainContentComponent()
{
setSize (600, 100);
setAudioChannels (2, 2);
startTimerHz(30);
}
~MainContentComponent()
{
shutdownAudio();
}
void prepareToPlay(int, double) override { audioProcessLoadMesurer.reset(); }
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
auto* device = deviceManager.getCurrentAudioDevice();
auto activeInputChannels = device->getActiveInputChannels();
auto activeOutputChannels = device->getActiveOutputChannels();
auto maxInputChannels = activeInputChannels .getHighestBit() + 1;
auto maxOutputChannels = activeOutputChannels.getHighestBit() + 1;
for (auto channel = 0; channel < 1; ++channel)
{
if ((! activeOutputChannels[channel]) || maxInputChannels == 0)
{
bufferToFill.buffer->clear (channel, bufferToFill.startSample, bufferToFill.numSamples);
}
else
{
auto actualInputChannel = channel % maxInputChannels; // [1]
if (! activeInputChannels[channel]) // [2]
{
bufferToFill.buffer->clear (channel, bufferToFill.startSample, bufferToFill.numSamples);
}
else // [3]
{
auto* inBuffer = bufferToFill.buffer->getReadPointer (actualInputChannel,
bufferToFill.startSample);
auto* outBuffer = bufferToFill.buffer->getWritePointer (channel, bufferToFill.startSample);
auto count = 0; // counter of dropped samples
scopeDataCollector.process(inBuffer, bufferToFill.numSamples);
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
if (inBuffer[sample - 1] == inBuffer[sample] && inBuffer[sample] == 0.0f)
count++;
}
if (count>1)
DBG("Dropped samples = " << count << ", buffer size: " << bufferToFill.numSamples << ", xRun: " << audioProcessLoadMesurer.getXRunCount());
for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
{
outBuffer[sample] = 0.0f;
}
}
}
}
}
//==============================================================================
void paint(Graphics& g) override
{
g.fillAll(juce::Colours::black);
g.setColour(juce::Colours::white);
auto area = getLocalBounds();
auto h = (float)area.getHeight();
auto w = (float)area.getWidth();
// Oscilloscope
auto scopeRect = Rectangle<float>{ float(0), float(0), w, h / 2 };
plot(sampleData.data(), sampleData.size(), g, scopeRect, float(1), h / 4);
}
//==============================================================================
static void plot(const float* data,
size_t numSamples,
Graphics& g,
juce::Rectangle<float> rect,
float scaler = float(1),
float offset = float(0))
{
auto w = rect.getWidth();
auto h = rect.getHeight();
auto right = rect.getRight();
auto center = rect.getBottom() - offset;
auto gain = h * scaler;
for (size_t i = 1; i < numSamples; ++i)
g.drawLine({ jmap(float(i - 1), float(0), float(numSamples - 1), float(right - w), float(right)),
center - gain * data[i - 1],
jmap(float(i), float(0), float(numSamples - 1), float(right - w), float(right)),
center - gain * data[i] });
}
void releaseResources() override {}
void resized() override {}
private:
template <typename SampleType>
class AudioBufferQueue
{
public:
//==============================================================================
static constexpr size_t order = 14;
static constexpr size_t bufferSize = 1U << order;
static constexpr size_t numBuffers = 5;
//==============================================================================
void push(const SampleType* dataToPush, size_t numSamples)
{
jassert(numSamples <= bufferSize);
int start1, size1, start2, size2;
abstractFifo.prepareToWrite(1, start1, size1, start2, size2);
jassert(size1 <= 1);
jassert(size2 == 0);
if (size1 > 0)
FloatVectorOperations::copy(buffer[(size_t) start1].data(), dataToPush, (int) jmin(bufferSize, numSamples));
abstractFifo.finishedWrite(size1);
}
//==============================================================================
void pop(SampleType* outputBuffer)
{
int start1, size1, start2, size2;
abstractFifo.prepareToRead(1, start1, size1, start2, size2);
jassert(size1 <= 1);
jassert(size2 == 0);
if (size1 > 0)
FloatVectorOperations::copy(outputBuffer, buffer[(size_t) start1].data(), (int) bufferSize);
abstractFifo.finishedRead(size1);
}
private:
//==============================================================================
AbstractFifo abstractFifo{ numBuffers };
std::array<std::array<SampleType, bufferSize>, numBuffers> buffer;
};
template <typename SampleType>
class ScopeDataCollector
{
public:
ScopeDataCollector(AudioBufferQueue<SampleType>& queueToUse)
: audioBufferQueue(queueToUse)
{}
void process(const SampleType* data, size_t numSamples)
{
size_t index = 0;
if (state == State::waitingForTrigger)
{
while (index++ < numSamples)
{
auto currentSample = *data++;
if (currentSample >= triggerLevel && prevSample < triggerLevel)
{
numCollected = 0;
state = State::collecting;
break;
}
prevSample = currentSample;
}
}
if (state == State::collecting)
{
while (index++ < numSamples)
{
buffer[numCollected++] = *data++;
if (numCollected == buffer.size())
{
audioBufferQueue.push(buffer.data(), buffer.size());
state = State::waitingForTrigger;
prevSample = SampleType(100);
break;
}
}
}
}
private:
//==============================================================================
AudioBufferQueue<SampleType>& audioBufferQueue;
std::array<SampleType, AudioBufferQueue<SampleType>::bufferSize> buffer;
size_t numCollected=0;
SampleType prevSample = SampleType(100);
static constexpr auto triggerLevel = SampleType(0.05);
enum class State { waitingForTrigger, collecting } state{ State::waitingForTrigger };
};
void timerCallback() override
{
audioBufferQueue.pop(sampleData.data());
repaint();
}
AudioBufferQueue<float> audioBufferQueue;
ScopeDataCollector<float> scopeDataCollector{ audioBufferQueue };
std::array<float, AudioBufferQueue<float>::bufferSize> sampleData;
AudioProcessLoadMeasurer audioProcessLoadMesurer;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};
To use it I generate a signal with an external app and redirect the output to the input with a virtual cable (virtual audio cable on windows).
I’m using wasapi to be able to share the driver between apps using the internal soundcard, but you can also try it with Asio and an external souncard with a physical loopback cable.
In my app I have my own signal generator but I’ve not included any of that for simplicity.
This is how it looks:

I don’t think they are caused by concurrency problems, the problem seems to happen in the buffer given by getNextAudioBlock.
I am streaming an output message to the debug console there (I know this is a no no, it is only for debugging purposes to show the problem).
In the message I also show if there is an Xrun using AudioProcessLoadMeasurer but it is always zero, although I don’t know if I am using it correctly.
![]()
This happens very randomly and it is difficult to see it in a minimal example like this, although it deffinitively can be seen with patience, and in a more complex app they are more frequent.
Any idea of what can be the cause of this?
Thanks in advance!


