Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion system_modules/napfft/src/fftaudionodecomponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ RTTI_BEGIN_CLASS(nap::FFTAudioNodeComponent)
RTTI_PROPERTY("Input", &nap::FFTAudioNodeComponent::mInput, nap::rtti::EPropertyMetaData::Required)
RTTI_PROPERTY("Overlaps", &nap::FFTAudioNodeComponent::mOverlaps, nap::rtti::EPropertyMetaData::Default)
RTTI_PROPERTY("Channel", &nap::FFTAudioNodeComponent::mChannel, nap::rtti::EPropertyMetaData::Default)
RTTI_PROPERTY("FFTBufferSize", &nap::FFTAudioNodeComponent::mFFTBufferSize, nap::rtti::EPropertyMetaData::Default)
RTTI_END_CLASS

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

// Validate FFT buffer size
if (!(mResource->mFFTBufferSize & (mResource->mFFTBufferSize - 1)) == 0)
{
errorState.fail("FFT buffer size needs to be a power of 2.");
return false;
}

auto& node_manager = mAudioService->getNodeManager();
mFFTNode = node_manager.makeSafe<FFTNode>(node_manager);
mFFTNode = node_manager.makeSafe<FFTNode>(node_manager, mResource->mFFTBufferSize);
mFFTNode->mInput.connect(*mInput->getOutputForChannel(mResource->mChannel));
mFFTBuffer = &mFFTNode->getFFTBuffer();

Expand Down
1 change: 1 addition & 0 deletions system_modules/napfft/src/fftaudionodecomponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace nap
nap::ComponentPtr<audio::AudioComponentBase> mInput; ///< Property: 'Input' The component whose audio output will be measured.
FFTBuffer::EOverlap mOverlaps = FFTBuffer::EOverlap::One; ///< Property: 'Overlaps' Number of overlaps, more increases fft precision in exchange for performance
int mChannel = 0; ///< Property: 'Channel' Channel of the input that will be analyzed.
int mFFTBufferSize = 2048; ///< Property: 'FFTBufferSize' Number of samples being analyzed per window.
};


Expand Down
57 changes: 26 additions & 31 deletions system_modules/napfft/src/fftbuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ namespace nap
// Create sample buffer
mSampleBufferFormatted.resize(data_size * 2);
mSampleBufferWindowed.resize(data_size);
mCircularBuffer.resize(data_size * 4);

// Compute hamming window
mForwardHammingWindow.resize(data_size);
Expand Down Expand Up @@ -114,31 +115,31 @@ namespace nap

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


void FFTBuffer::transform()
{
// Check if there's something to consume -> atomic dirty check first to minimize overhead
// Note that we could use a try_lock construction for both producer and consumer threads,
// But it's safer to always transform available sample data, instead of potentially not consuming *any* data.
if (mSampleData.load())
auto writePosition = mWritePosition.load();
auto readPosition = mReadPosition.load();

// Check if there is a new FFT buffer of data available
if (writePosition - readPosition >= mContext->getSize())
{
// Lock when available and swap buffer memory locations -> making the previously transformed buffer available for storage.
{
std::lock_guard<std::mutex> lock(mSampleBufferMutex);
std::swap(mSampleBufferA, mSampleBufferB);
mSampleData = false;
}
// If we are more than two FFT buffers behind proceed to the newest one available
// This way we don't create latency when transform() is not called regularly enough.
while (readPosition + mContext->getSize() < writePosition - mContext->getSize())
readPosition += mContext->getSize();

createImage();
}
}
Expand All @@ -160,22 +161,16 @@ namespace nap
// Copy second half to first half
std::memcpy(mSampleBufferFormatted.data(), half_ptr, data_bytes);

// Copy new samples to second half
if (mSampleBufferB.size() == data_size)
{
std::memcpy(half_ptr, mSampleBufferB.data(), data_bytes);
}
else if (mSampleBufferB.size() > data_size)
{
// Zero-padding
std::fill(mSampleBufferFormatted.begin(), mSampleBufferFormatted.end(), 0.0f);
std::memcpy(half_ptr, mSampleBufferB.data(), data_bytes);
}
else
// Copy data from circular buffer to the second half of the formatted buffer
auto readPosition = mReadPosition.load();
int circularBufferIndex = readPosition % mCircularBuffer.size();
for (int i = 0; i < data_size; ++i)
{
NAP_ASSERT_MSG(false, "Specified sample buffer size too small");
return;
half_ptr[i] = mCircularBuffer[circularBufferIndex++];
if (circularBufferIndex >= mCircularBuffer.size())
circularBufferIndex = 0;
}
mReadPosition.store(readPosition + data_size);

// Copy data to windowed array
const uint hop_count = static_cast<uint>(mOverlap);
Expand Down
10 changes: 5 additions & 5 deletions system_modules/napfft/src/fftbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <mutex>
#include <atomic>

#include "audio/utility/audiotypes.h"

namespace nap
{
/**
Expand Down Expand Up @@ -88,10 +90,9 @@ namespace nap
float mHammingWindowSum; //< The sum of all window function coefficients
float mNormalizationFactor; //< Inverse of the window sum (2/sum)


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

Expand All @@ -100,6 +101,5 @@ namespace nap

EOverlap mOverlap; //< The number of audio buffer overlaps for FFT analysis (hops)
uint mHopSize; //< The number of bins of a single hop
std::atomic<bool> mSampleData = { false }; //< Amplitudes dirty checking flag, prevents redundant FFT analyses
};
}
7 changes: 2 additions & 5 deletions system_modules/napfft/src/fftnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ RTTI_END_CLASS

namespace nap
{
FFTNode::FFTNode(audio::NodeManager& nodeManager, FFTBuffer::EOverlap overlaps) :
FFTNode::FFTNode(audio::NodeManager& nodeManager, int fftBufferSize, FFTBuffer::EOverlap overlaps) :
audio::Node(nodeManager)
{
const auto buffer_size = getNodeManager().getInternalBufferSize();
assert(buffer_size >= 2);

mFFTBuffer = std::make_unique<FFTBuffer>(buffer_size, overlaps);
mFFTBuffer = std::make_unique<FFTBuffer>(fftBufferSize, overlaps);
getNodeManager().registerRootProcess(*this);
}

Expand Down
3 changes: 2 additions & 1 deletion system_modules/napfft/src/fftnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ namespace nap
/**
* @param audioService the NAP audio service.
* @param nodeManager the node manager this node must be registered to.
* @param fftBufferSize size of the fft analysis window in samples.
* @param overlaps the number of overlaps
*/
FFTNode(audio::NodeManager& nodeManager, FFTBuffer::EOverlap overlaps = FFTBuffer::EOverlap::One);
FFTNode(audio::NodeManager& nodeManager, int fftBufferSize, FFTBuffer::EOverlap overlaps = FFTBuffer::EOverlap::One);

// Destructor
virtual ~FFTNode();
Expand Down