Skip to content

Commit 02cad90

Browse files
committed
Super responsive FFT with overlap processing
1 parent d9340f5 commit 02cad90

File tree

5 files changed

+102
-18
lines changed

5 files changed

+102
-18
lines changed

examples/graphics/source/examples/SpectrumAnalyzer.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,16 @@ class SpectrumAnalyzerDemo
441441
analyzerComponent.setReleaseTimeSeconds (value);
442442
};
443443
addAndMakeVisible (*releaseSlider);
444+
445+
// Overlap control for responsiveness
446+
overlapSlider = std::make_unique<yup::Slider> (yup::Slider::LinearBarHorizontal, "Overlap");
447+
overlapSlider->setRange ({ 0.0, 0.95 });
448+
overlapSlider->setValue (0.75);
449+
overlapSlider->onValueChanged = [this] (float value)
450+
{
451+
analyzerComponent.setOverlapFactor (value);
452+
};
453+
addAndMakeVisible (*overlapSlider);
444454

445455
// Status labels with appropriate font size
446456
auto statusFont = font.withHeight (11.0f);
@@ -469,12 +479,13 @@ class SpectrumAnalyzerDemo
469479
analyzerComponent.setDecibelRange (-100.0f, 10.0f);
470480
analyzerComponent.setUpdateRate (30);
471481
analyzerComponent.setSampleRate (44100.0);
482+
analyzerComponent.setOverlapFactor (0.75f); // 75% overlap for better responsiveness
472483
addAndMakeVisible (analyzerComponent);
473484

474485
// Create parameter labels with proper font sizing
475486
auto labelFont = font.withHeight (12.0f);
476487

477-
for (const auto& labelText : { "Signal Type:", "Frequency:", "Amplitude:", "Sweep Duration:", "FFT Size:", "Window:", "Display:", "Release:" })
488+
for (const auto& labelText : { "Signal Type:", "Frequency:", "Amplitude:", "Sweep Duration:", "FFT Size:", "Window:", "Display:", "Release:", "Overlap:" })
478489
{
479490
auto label = parameterLabels.add (std::make_unique<yup::Label> (labelText));
480491
label->setText (labelText);
@@ -526,7 +537,7 @@ class SpectrumAnalyzerDemo
526537
auto fftSizeSection = row2.removeFromLeft (colWidth);
527538
auto windowSection = row2.removeFromLeft (colWidth);
528539
auto displaySection = row2.removeFromLeft (colWidth);
529-
row2.removeFromLeft (colWidth); // Skip unused column
540+
auto overlapSection = row2.removeFromLeft (colWidth);
530541

531542
parameterLabels[4]->setBounds (fftSizeSection.removeFromTop (labelHeight));
532543
fftSizeCombo->setBounds (fftSizeSection.removeFromTop (controlHeight));
@@ -536,6 +547,9 @@ class SpectrumAnalyzerDemo
536547

537548
parameterLabels[6]->setBounds (displaySection.removeFromTop (labelHeight));
538549
displayTypeCombo->setBounds (displaySection.removeFromTop (controlHeight));
550+
551+
parameterLabels[8]->setBounds (overlapSection.removeFromTop (labelHeight));
552+
overlapSlider->setBounds (overlapSection.removeFromTop (controlHeight));
539553

540554
// Third row: Status labels
541555
auto row3 = bounds.removeFromTop (30);
@@ -671,6 +685,7 @@ class SpectrumAnalyzerDemo
671685
std::unique_ptr<yup::ComboBox> windowTypeCombo;
672686
std::unique_ptr<yup::ComboBox> displayTypeCombo;
673687
std::unique_ptr<yup::Slider> releaseSlider;
688+
std::unique_ptr<yup::Slider> overlapSlider;
674689

675690
// Status labels
676691
std::unique_ptr<yup::Label> frequencyLabel;

modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,15 @@ void SpectrumAnalyzerComponent::initializeFFTBuffers()
5757
void SpectrumAnalyzerComponent::timerCallback()
5858
{
5959
bool hasNewData = false;
60+
int fftCount = 0;
61+
const int maxFFTsPerFrame = 8; // Limit to prevent blocking UI thread
6062

61-
// Process FFT frames with proper overlap
62-
while (analyzerState.getNumAvailableSamples() >= fftSize && analyzerState.isFFTDataReady())
63+
// Process multiple FFT frames with overlap for better responsiveness
64+
while (analyzerState.isFFTDataReady() && fftCount < maxFFTsPerFrame)
6365
{
6466
processFFT();
6567
hasNewData = true;
68+
++fftCount;
6669
}
6770

6871
// Always update display to maintain smooth animation
@@ -558,6 +561,16 @@ void SpectrumAnalyzerComponent::setReleaseTimeSeconds (float timeSeconds)
558561
releaseTimeSeconds = jmax (0.1f, timeSeconds);
559562
}
560563

564+
void SpectrumAnalyzerComponent::setOverlapFactor (float overlapFactor)
565+
{
566+
analyzerState.setOverlapFactor (overlapFactor);
567+
}
568+
569+
float SpectrumAnalyzerComponent::getOverlapFactor() const noexcept
570+
{
571+
return analyzerState.getOverlapFactor();
572+
}
573+
561574
void SpectrumAnalyzerComponent::setFFTSize (int size)
562575
{
563576
jassert (isPowerOfTwo (size) && size >= 64 && size <= 16384);

modules/yup_audio_gui/displays/yup_SpectrumAnalyzerComponent.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ class YUP_API SpectrumAnalyzerComponent
168168

169169
/** Returns the current release time in seconds. */
170170
float getReleaseTimeSeconds() const noexcept { return releaseTimeSeconds; }
171+
172+
//==============================================================================
173+
/** Sets the overlap factor for more responsive spectrum analysis.
174+
175+
@param overlapFactor overlap factor (0.0 = no overlap, 0.75 = 75% overlap)
176+
*/
177+
void setOverlapFactor (float overlapFactor);
178+
179+
/** Returns the current overlap factor. */
180+
float getOverlapFactor() const noexcept;
171181

172182
//==============================================================================
173183
/** Returns the frequency for a given bin index.

modules/yup_dsp/frequency/yup_SpectrumAnalyzerState.cpp

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ void SpectrumAnalyzerState::initializeFifo()
3838
{
3939
fftDataReady = false;
4040
fifoSize = fftSize * 4;
41+
hopSize = static_cast<int> (fftSize * (1.0f - overlapFactor));
4142

4243
audioFifo = std::make_unique<AbstractFifo> (fifoSize);
4344

@@ -57,7 +58,7 @@ void SpectrumAnalyzerState::pushSample (float sample) noexcept
5758
if (writeScope.blockSize1 > 0)
5859
sampleBuffer[static_cast<size_t> (writeScope.startIndex1)] = sample;
5960

60-
// Check if we have enough samples for FFT processing
61+
// Check if we have enough samples for FFT processing with overlap
6162
if (audioFifo->getNumReady() >= fftSize)
6263
fftDataReady = true;
6364
}
@@ -85,7 +86,7 @@ void SpectrumAnalyzerState::pushSamples (const float* samples, int numSamples) n
8586
std::copy_n (samples + writeScope.blockSize1, writeScope.blockSize2, &sampleBuffer[static_cast<size_t> (writeScope.startIndex2)]);
8687
}
8788

88-
// Check if we have enough samples for FFT processing
89+
// Check if we have enough samples for FFT processing with overlap
8990
if (audioFifo->getNumReady() >= fftSize)
9091
fftDataReady = true;
9192
}
@@ -103,29 +104,42 @@ bool SpectrumAnalyzerState::getFFTData (float* destBuffer) noexcept
103104
if (destBuffer == nullptr || ! isFFTDataReady())
104105
return false;
105106

106-
// Lock-free read from FIFO - safe for UI thread
107-
const auto readScope = audioFifo->read (fftSize);
107+
// Use prepareToRead to get read positions without consuming data
108+
int startIndex1, blockSize1, startIndex2, blockSize2;
109+
audioFifo->prepareToRead (fftSize, startIndex1, blockSize1, startIndex2, blockSize2);
108110

109111
// Copy first block
110-
if (readScope.blockSize1 > 0)
112+
if (blockSize1 > 0)
111113
{
112-
std::copy_n (&sampleBuffer[static_cast<size_t> (readScope.startIndex1)],
113-
readScope.blockSize1,
114+
std::copy_n (&sampleBuffer[static_cast<size_t> (startIndex1)],
115+
blockSize1,
114116
destBuffer);
115117
}
116118

117119
// Copy second block (wrap-around case)
118-
if (readScope.blockSize2 > 0)
120+
if (blockSize2 > 0)
119121
{
120-
std::copy_n (&sampleBuffer[static_cast<size_t> (readScope.startIndex2)],
121-
readScope.blockSize2,
122-
destBuffer + readScope.blockSize1);
122+
std::copy_n (&sampleBuffer[static_cast<size_t> (startIndex2)],
123+
blockSize2,
124+
destBuffer + blockSize1);
123125
}
124126

125-
// Reset the ready flag since we've consumed the data
126-
fftDataReady = false;
127+
// Check if we read the full FFT size
128+
const int actualReadSize = blockSize1 + blockSize2;
129+
if (actualReadSize == fftSize)
130+
{
131+
// Advance read position by hopSize (not full FFT size) for overlap processing
132+
audioFifo->finishedRead (hopSize);
133+
134+
// Check if we still have enough samples for next FFT
135+
fftDataReady = (audioFifo->getNumReady() >= fftSize);
136+
137+
return true;
138+
}
127139

128-
return (readScope.blockSize1 + readScope.blockSize2) == fftSize;
140+
// If we couldn't read the full FFT size, reset flag and return false
141+
fftDataReady = false;
142+
return false;
129143
}
130144

131145
//==============================================================================
@@ -161,4 +175,21 @@ int SpectrumAnalyzerState::getFreeSpace() const noexcept
161175
return audioFifo->getFreeSpace();
162176
}
163177

178+
void SpectrumAnalyzerState::setOverlapFactor (float newOverlapFactor)
179+
{
180+
jassert (newOverlapFactor >= 0.0f && newOverlapFactor < 1.0f);
181+
182+
if (overlapFactor != newOverlapFactor)
183+
{
184+
overlapFactor = jlimit (0.0f, 0.95f, newOverlapFactor);
185+
hopSize = static_cast<int> (fftSize * (1.0f - overlapFactor));
186+
hopSize = jmax (1, hopSize); // Ensure minimum hop size of 1
187+
}
188+
}
189+
190+
int SpectrumAnalyzerState::getHopSize() const noexcept
191+
{
192+
return hopSize;
193+
}
194+
164195
} // namespace yup

modules/yup_dsp/frequency/yup_SpectrumAnalyzerState.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,28 @@ class YUP_API SpectrumAnalyzerState
115115

116116
/** Returns the amount of free space in the FIFO. */
117117
int getFreeSpace() const noexcept;
118+
119+
//==============================================================================
120+
/** Sets the overlap factor for more responsive spectrum analysis.
121+
122+
@param overlapFactor overlap factor (0.0 = no overlap, 0.75 = 75% overlap)
123+
*/
124+
void setOverlapFactor (float overlapFactor);
125+
126+
/** Returns the current overlap factor. */
127+
float getOverlapFactor() const noexcept { return overlapFactor; }
128+
129+
/** Returns the hop size (samples between FFT frames). */
130+
int getHopSize() const noexcept;
118131

119132
private:
120133
//==============================================================================
121134
void initializeFifo();
122135

123136
int fftSize = 2048;
124137
int fifoSize = 8192; // Will be updated in initializeFifo()
138+
float overlapFactor = 0.75f; // 75% overlap by default
139+
int hopSize = 512; // Will be computed from overlap factor
125140

126141
std::unique_ptr<AbstractFifo> audioFifo;
127142
std::vector<float> sampleBuffer;

0 commit comments

Comments
 (0)