#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 = new Coeffs(Coeffs::makeLowShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db))); break; case Type::highShelf: c = new Coeffs(Coeffs::makeHighShelf(sampleRate, freq, q, juce::Decibels::decibelsToGain(db))); break; case Type::peak: default: c = new Coeffs(Coeffs::makePeakFilter(sampleRate, freq, q, juce::Decibels::decibelsToGain(db))); break; } left.coefficients = c; right.coefficients = c; } void FilterBand::reset() { left.reset(); right.reset(); } void FilterBand::process(juce::dsp::AudioBlock& block) { if (block.getNumChannels() < 2) return; // Smooth the coefficients to avoid zipper noise, then process. left.snapToZero(); right.snapToZero(); left.process(juce::dsp::ProcessContextReplacing(block)); right.process(juce::dsp::ProcessContextReplacing(block)); } // --------------------------------------------------------------------------- // SpectrumAnalyser // --------------------------------------------------------------------------- SpectrumAnalyser::SpectrumAnalyser() { // The timer drives periodic background analysis. startTimerHz(30); } void SpectrumAnalyser::prepare(double sr, int maxBlockSize) { const int size = 4096; 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; juce::AudioBuffer block(1, n); // Mesa of the two channels and window with a Hann. block.copyFrom(0, 0, ring, 0, 0, n); if (ringChannels > 1) block.addFrom(0, 0, ring, 1, 0, n); juce::dsp::FFT fft(12); // 4096 point juce::HeapBlock> fftData; fftData.calloc((size_t) n); for (int i = 0; i < n; ++i) { float v = block.getSample(0, i) * 0.5f; const float win = 0.5f * (1.0f - std::cos(2.0f * juce::MathConstants::pi * i / (n - 1))); fftData[i] = std::complex(v * win, 0.0f); } fft.perform((juce::dsp::Complex*) fftData.getData(), false); const int numBins = n / 2; juce::Array mags; mags.ensureStorageAllocated(numBins); for (int i = 1; i < numBins; ++i) { float re = fftData[i].real(); float im = fftData[i].imag(); float mag = std::sqrt(re * re + im * im); float db = 20.0f * std::log10(mag + 1.0e-9f); mags.add(db); } { juce::ScopedLock sl(spectrumLock); readySpectrum = mags; } } int SpectrumAnalyser::copySpectrum(juce::Array& dest, juce::Array& freqBins, int maxBins) const { juce::Array 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