gelyk/Source/EqualizerDSP.cpp

205 lines
5.5 KiB
C++
Raw Normal View History

2026-08-29 13:17:11 +02:00
#include "EqualizerDSP.h"
namespace gelyk
{
// ---------------------------------------------------------------------------
// FilterBand
// ---------------------------------------------------------------------------
void FilterBand::prepare(double sampleRate, int blockSize)
{
juce::dsp::ProcessSpec spec;
spec.sampleRate = sampleRate;
spec.maximumBlockSize = (juce::uint32) blockSize;
spec.numChannels = 2;
left.prepare(spec);
right.prepare(spec);
Coeffs::Ptr c;
const float db = juce::jlimit(-24.0f, 24.0f, gainDb);
switch (type)
{
case Type::lowShelf:
c = Coeffs::makeLowShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db));
2026-08-29 13:17:11 +02:00
break;
case Type::highShelf:
c = Coeffs::makeHighShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db));
2026-08-29 13:17:11 +02:00
break;
case Type::peak:
default:
c = Coeffs::makePeakFilter(sampleRate, freq, q, juce::Decibels::decibelsToGain(db));
2026-08-29 13:17:11 +02:00
break;
}
left.coefficients = c;
right.coefficients = c;
}
void FilterBand::reset()
{
left.reset();
right.reset();
}
void FilterBand::process(juce::dsp::AudioBlock<float>& block)
{
if (block.getNumChannels() < 2)
return;
// Smooth the coefficients to avoid zipper noise, then process.
left.snapToZero();
right.snapToZero();
left.process(juce::dsp::ProcessContextReplacing<float>(block));
right.process(juce::dsp::ProcessContextReplacing<float>(block));
}
// ---------------------------------------------------------------------------
// SpectrumAnalyser
// ---------------------------------------------------------------------------
SpectrumAnalyser::SpectrumAnalyser()
: fft(12) // 4096 point FFT
2026-08-29 13:17:11 +02:00
{
const int size = fft.getSize();
fftBuffer.resize((size_t) (size * 2), 0.0f);
2026-08-29 13:17:11 +02:00
// The timer drives periodic background analysis.
startTimerHz(30);
}
void SpectrumAnalyser::prepare(double sr, int maxBlockSize)
{
const int size = 4096;
(void) maxBlockSize;
2026-08-29 13:17:11 +02:00
sampleRate = sr;
ring.setSize(2, size);
ring.clear();
ringFill = 0;
ringChannels = 2;
}
void SpectrumAnalyser::push(const float* const* channels, int numChannels, int numSamples)
{
if (!enabled.load() || ring.getNumSamples() == 0)
return;
const int n = (int) ring.getNumSamples();
const int c = juce::jmin(ringChannels, numChannels);
for (int i = 0; i < numSamples; ++i)
{
const int dst = (ringFill + i) % n;
for (int ch = 0; ch < c; ++ch)
ring.setSample(ch, dst, channels[ch][i]);
}
ringFill = (ringFill + numSamples) % n;
}
void SpectrumAnalyser::startBackground()
{
startTimerHz(30);
}
void SpectrumAnalyser::stopBackground()
{
stopTimer();
}
void SpectrumAnalyser::timerCallback()
{
if (!enabled.load())
return;
runAnalysis();
}
void SpectrumAnalyser::runAnalysis()
{
const int n = (int) ring.getNumSamples();
if (n == 0)
return;
// Mesa of the two channels, Hann-windowed into the FFT buffer.
const auto fftSize = fft.getSize();
for (int i = 0; i < fftSize; ++i)
2026-08-29 13:17:11 +02:00
{
const int src = (ringFill - fftSize + i + n) % n; // contiguously oldest of last fftSize
float v = ring.getSample(0, src);
if (ringChannels > 1)
v = 0.5f * (v + ring.getSample(1, src));
const float win = 0.5f * (1.0f - std::cos(2.0f * juce::MathConstants<float>::pi * i / (fftSize - 1)));
fftBuffer[(size_t) i] = v * win;
2026-08-29 13:17:11 +02:00
}
for (size_t i = (size_t) fftSize; i < fftBuffer.size(); ++i)
fftBuffer[i] = 0.0f;
2026-08-29 13:17:11 +02:00
fft.performFrequencyOnlyForwardTransform(fftBuffer.data());
// Magnitude values (non-negative frequencies) are in the first size/2+1 entries.
const int numBins = juce::jmin((int) fftBuffer.size(), fftSize / 2 + 1);
2026-08-29 13:17:11 +02:00
juce::Array<float> mags;
mags.ensureStorageAllocated(numBins);
for (int i = 0; i < numBins; ++i)
2026-08-29 13:17:11 +02:00
{
float mag = fftBuffer[(size_t) i];
2026-08-29 13:17:11 +02:00
float db = 20.0f * std::log10(mag + 1.0e-9f);
mags.add(db);
}
{
juce::ScopedLock sl(spectrumLock);
readySpectrum = mags;
}
}
int SpectrumAnalyser::copySpectrum(juce::Array<float>& dest, juce::Array<float>& freqBins,
int maxBins) const
{
juce::Array<float> snapshot;
{
juce::ScopedLock sl(spectrumLock);
snapshot = readySpectrum;
}
if (snapshot.size() == 0)
{
dest.clear();
freqBins.clear();
return 0;
}
// Log-spaced aggregation from ~20 Hz to 20 kHz.
const float fMin = 20.0f;
const float fMax = 20000.0f;
const double nfft = (double)(snapshot.size() * 2);
const float binFreq = (float)(sampleRate / nfft);
dest.clear();
freqBins.clear();
const int bands = juce::jmin(maxBins, snapshot.size());
for (int b = 0; b < bands; ++b)
{
const float f = fMin * std::pow(fMax / fMin, (float) b / (float) (bands - 1));
freqBins.add(f);
const int startBin = juce::jmax(1, (int) std::floor(f / binFreq));
const int endBin = juce::jmin(snapshot.size() - 1, (int) std::ceil((f * 1.5f) / binFreq));
float total = 0.0f;
int count = 0;
for (int i = startBin; i <= endBin; ++i)
{
total += snapshot[i];
++count;
}
dest.add(count > 0 ? total / count : -96.0f);
}
return dest.size();
}
void SpectrumAnalyser::setEnabled(bool e)
{
enabled.store(e);
}
} // namespace gelyk