Skip to content

Commit cc4dd60

Browse files
committed
FFT buffer size independent from audio service buffer size
1 parent 81eb8af commit cc4dd60

6 files changed

Lines changed: 45 additions & 43 deletions

File tree

system_modules/napfft/src/fftaudionodecomponent.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ RTTI_BEGIN_CLASS(nap::FFTAudioNodeComponent)
1717
RTTI_PROPERTY("Input", &nap::FFTAudioNodeComponent::mInput, nap::rtti::EPropertyMetaData::Required)
1818
RTTI_PROPERTY("Overlaps", &nap::FFTAudioNodeComponent::mOverlaps, nap::rtti::EPropertyMetaData::Default)
1919
RTTI_PROPERTY("Channel", &nap::FFTAudioNodeComponent::mChannel, nap::rtti::EPropertyMetaData::Default)
20+
RTTI_PROPERTY("FFTBufferSize", &nap::FFTAudioNodeComponent::mFFTBufferSize, nap::rtti::EPropertyMetaData::Default)
2021
RTTI_END_CLASS
2122

2223
RTTI_BEGIN_CLASS_NO_DEFAULT_CONSTRUCTOR(nap::FFTAudioNodeComponentInstance)
@@ -38,8 +39,15 @@ namespace nap
3839
if (!errorState.check(mResource->mChannel < mInput->getChannelCount(), "%s: Channel exceeds number of input channels", mResource->mID.c_str()))
3940
return false;
4041

42+
// Validate FFT buffer size
43+
if (!(mResource->mFFTBufferSize & (mResource->mFFTBufferSize - 1)) == 0)
44+
{
45+
errorState.fail("FFT buffer size needs to be a power of 2.");
46+
return false;
47+
}
48+
4149
auto& node_manager = mAudioService->getNodeManager();
42-
mFFTNode = node_manager.makeSafe<FFTNode>(node_manager);
50+
mFFTNode = node_manager.makeSafe<FFTNode>(node_manager, mResource->mFFTBufferSize);
4351
mFFTNode->mInput.connect(*mInput->getOutputForChannel(mResource->mChannel));
4452
mFFTBuffer = &mFFTNode->getFFTBuffer();
4553

system_modules/napfft/src/fftaudionodecomponent.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace nap
3333
nap::ComponentPtr<audio::AudioComponentBase> mInput; ///< Property: 'Input' The component whose audio output will be measured.
3434
FFTBuffer::EOverlap mOverlaps = FFTBuffer::EOverlap::One; ///< Property: 'Overlaps' Number of overlaps, more increases fft precision in exchange for performance
3535
int mChannel = 0; ///< Property: 'Channel' Channel of the input that will be analyzed.
36+
int mFFTBufferSize = 2048; ///< Property: 'FFTBufferSize' Number of samples being analyzed per window.
3637
};
3738

3839

system_modules/napfft/src/fftbuffer.cpp

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ namespace nap
8787
// Create sample buffer
8888
mSampleBufferFormatted.resize(data_size * 2);
8989
mSampleBufferWindowed.resize(data_size);
90+
mCircularBuffer.resize(data_size * 4);
9091

9192
// Compute hamming window
9293
mForwardHammingWindow.resize(data_size);
@@ -114,31 +115,31 @@ namespace nap
114115

115116
void FFTBuffer::supply(const std::vector<float>& samples)
116117
{
117-
// Try to lock and copy the audio buffer for FFT analysis.
118-
// This prevents the audio thread from stalling when the analysis thread is working on a (previous) set at the same time.
119-
// We do however attempt to store it for the most up to date, accurate image.
120-
std::unique_lock<std::mutex> lock(mSampleBufferMutex, std::defer_lock);
121-
if (lock.try_lock())
118+
auto writePosition = mWritePosition.load();
119+
int index = writePosition % mCircularBuffer.size();
120+
for (auto& sample : samples)
122121
{
123-
mSampleBufferA = samples;
124-
mSampleData = true;
122+
mCircularBuffer[index++] = sample;
123+
if (index >= mCircularBuffer.size())
124+
index = 0;
125125
}
126+
mWritePosition.store(writePosition + samples.size());
126127
}
127128

128129

129130
void FFTBuffer::transform()
130131
{
131-
// Check if there's something to consume -> atomic dirty check first to minimize overhead
132-
// Note that we could use a try_lock construction for both producer and consumer threads,
133-
// But it's safer to always transform available sample data, instead of potentially not consuming *any* data.
134-
if (mSampleData.load())
132+
auto writePosition = mWritePosition.load();
133+
auto readPosition = mReadPosition.load();
134+
135+
// Check if there is a new FFT buffer of data available
136+
if (writePosition - readPosition >= mContext->getSize())
135137
{
136-
// Lock when available and swap buffer memory locations -> making the previously transformed buffer available for storage.
137-
{
138-
std::lock_guard<std::mutex> lock(mSampleBufferMutex);
139-
std::swap(mSampleBufferA, mSampleBufferB);
140-
mSampleData = false;
141-
}
138+
// If we are more than two FFT buffers behind proceed to the newest one available
139+
// This way we don't create latency when transform() is not called regularly enough.
140+
while (readPosition + mContext->getSize() < writePosition - mContext->getSize())
141+
readPosition += mContext->getSize();
142+
142143
createImage();
143144
}
144145
}
@@ -160,22 +161,16 @@ namespace nap
160161
// Copy second half to first half
161162
std::memcpy(mSampleBufferFormatted.data(), half_ptr, data_bytes);
162163

163-
// Copy new samples to second half
164-
if (mSampleBufferB.size() == data_size)
165-
{
166-
std::memcpy(half_ptr, mSampleBufferB.data(), data_bytes);
167-
}
168-
else if (mSampleBufferB.size() > data_size)
169-
{
170-
// Zero-padding
171-
std::fill(mSampleBufferFormatted.begin(), mSampleBufferFormatted.end(), 0.0f);
172-
std::memcpy(half_ptr, mSampleBufferB.data(), data_bytes);
173-
}
174-
else
164+
// Copy data from circular buffer to the second half of the formatted buffer
165+
auto readPosition = mReadPosition.load();
166+
int circularBufferIndex = readPosition % mCircularBuffer.size();
167+
for (int i = 0; i < data_size; ++i)
175168
{
176-
NAP_ASSERT_MSG(false, "Specified sample buffer size too small");
177-
return;
169+
half_ptr[i] = mCircularBuffer[circularBufferIndex++];
170+
if (circularBufferIndex >= mCircularBuffer.size())
171+
circularBufferIndex = 0;
178172
}
173+
mReadPosition.store(readPosition + data_size);
179174

180175
// Copy data to windowed array
181176
const uint hop_count = static_cast<uint>(mOverlap);

system_modules/napfft/src/fftbuffer.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <mutex>
1212
#include <atomic>
1313

14+
#include "audio/utility/audiotypes.h"
15+
1416
namespace nap
1517
{
1618
/**
@@ -88,10 +90,9 @@ namespace nap
8890
float mHammingWindowSum; //< The sum of all window function coefficients
8991
float mNormalizationFactor; //< Inverse of the window sum (2/sum)
9092

91-
92-
std::mutex mSampleBufferMutex; //< The mutex for accessing the sample buffer
93-
std::vector<float> mSampleBufferA; //< Samples provided by the audio node
94-
std::vector<float> mSampleBufferB; //< Thread safe copy of original samples
93+
std::vector<float> mCircularBuffer; //< Circular buffer that the audio thread writes in and the analysis thread reads from
94+
std::atomic<uint64> mWritePosition = { 0 }; //< Write position on audio thread in the circular buffer
95+
std::atomic<uint64> mReadPosition = { 0 }; //< Read position on analysis thread in the circular buffer
9596
std::vector<float> mSampleBufferFormatted; //< The sample buffer before application of a window function
9697
std::vector<float> mSampleBufferWindowed; //< The sample buffer after application of a window function
9798

@@ -100,6 +101,5 @@ namespace nap
100101

101102
EOverlap mOverlap; //< The number of audio buffer overlaps for FFT analysis (hops)
102103
uint mHopSize; //< The number of bins of a single hop
103-
std::atomic<bool> mSampleData = { false }; //< Amplitudes dirty checking flag, prevents redundant FFT analyses
104104
};
105105
}

system_modules/napfft/src/fftnode.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ RTTI_END_CLASS
1414

1515
namespace nap
1616
{
17-
FFTNode::FFTNode(audio::NodeManager& nodeManager, FFTBuffer::EOverlap overlaps) :
17+
FFTNode::FFTNode(audio::NodeManager& nodeManager, int fftBufferSize, FFTBuffer::EOverlap overlaps) :
1818
audio::Node(nodeManager)
1919
{
20-
const auto buffer_size = getNodeManager().getInternalBufferSize();
21-
assert(buffer_size >= 2);
22-
23-
mFFTBuffer = std::make_unique<FFTBuffer>(buffer_size, overlaps);
20+
mFFTBuffer = std::make_unique<FFTBuffer>(fftBufferSize, overlaps);
2421
getNodeManager().registerRootProcess(*this);
2522
}
2623

system_modules/napfft/src/fftnode.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ namespace nap
3434
/**
3535
* @param audioService the NAP audio service.
3636
* @param nodeManager the node manager this node must be registered to.
37+
* @param fftBufferSize size of the fft analysis window in samples.
3738
* @param overlaps the number of overlaps
3839
*/
39-
FFTNode(audio::NodeManager& nodeManager, FFTBuffer::EOverlap overlaps = FFTBuffer::EOverlap::One);
40+
FFTNode(audio::NodeManager& nodeManager, int fftBufferSize, FFTBuffer::EOverlap overlaps = FFTBuffer::EOverlap::One);
4041

4142
// Destructor
4243
virtual ~FFTNode();

0 commit comments

Comments
 (0)