From c231a9097e9bf816a5661e82c43c6e7ed1a49a4e Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 12 Jun 2025 19:56:16 -0700 Subject: [PATCH] Visualization: Rewrite FFT calculation code This code, based on some other vDSP information I found, performs the DFT in a different fashion, and also pre-converts the spectrum data into decibel levels, so the DeaDBeeF analyzer calculation code doesn't need to convert the values itself any more. Maybe this will also get rid of the use after free problem somewhere in the audio code? Who knows? Signed-off-by: Christopher Snowhill --- Audio/ThirdParty/deadbeef/fft_accelerate.c | 108 +++++++++++-------- Visualization/ThirdParty/deadbeef/analyzer.c | 2 +- 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/Audio/ThirdParty/deadbeef/fft_accelerate.c b/Audio/ThirdParty/deadbeef/fft_accelerate.c index cd2e17656..f92c31dba 100644 --- a/Audio/ThirdParty/deadbeef/fft_accelerate.c +++ b/Audio/ThirdParty/deadbeef/fft_accelerate.c @@ -24,17 +24,28 @@ #include "fft.h" #include -static int _fft_size; -static float *_input_real; -static float *_input_imaginary; -static float *_output_real; -static float *_output_imaginary; -static float *_hamming; -static float *_sq_mags; +// Some newer spectrum calculation methodology, adapted but not copied wholesale +// Mostly about a dozen or two lines of Cocoa and vDSP code -static vDSP_DFT_Setup _dft_setup; +// AudioSpectrum: A sample app using Audio Unit and vDSP +// By Keijiro Takahashi, 2013, 2014 +// https://github.com/keijiro/AudioSpectrum + +struct SpectrumData +{ + unsigned long length; + Float32 data[0]; +}; + +static int _fft_size = 0; +static vDSP_DFT_Setup _dftSetup = NULL; +static DSPSplitComplex _dftBuffer = {0}; +static Float32 *_window = NULL; + +static struct SpectrumData *_rawSpectrum = NULL; // Apparently _mm_malloc is Intel-only on newer macOS targets, so use supported posix_memalign +// malloc() is allegedly aligned on macOS, but I don't know for sure static void *_memalign_calloc(size_t count, size_t size, size_t align) { size *= count; void *ret = NULL; @@ -50,57 +61,66 @@ _init_buffers(int fft_size) { if(fft_size != _fft_size) { fft_free(); - _input_real = _memalign_calloc(fft_size * 2, sizeof(float), 16); - _input_imaginary = _memalign_calloc(fft_size * 2, sizeof(float), 16); - _hamming = _memalign_calloc(fft_size * 2, sizeof(float), 16); - _sq_mags = _memalign_calloc(fft_size, sizeof(float), 16); - _output_real = _memalign_calloc(fft_size * 2 + 1, sizeof(float), 16); - _output_imaginary = _memalign_calloc(fft_size * 2 + 1, sizeof(float), 16); + _dftSetup = vDSP_DFT_zrop_CreateSetup(NULL, fft_size * 2, vDSP_DFT_FORWARD); - _dft_setup = vDSP_DFT_zop_CreateSetup(NULL, fft_size * 2, FFT_FORWARD); - vDSP_hamm_window(_hamming, fft_size * 2, 0); + _dftBuffer.realp = _memalign_calloc(fft_size, sizeof(Float32), 16); + _dftBuffer.imagp = _memalign_calloc(fft_size, sizeof(Float32), 16); + + _window = _memalign_calloc(fft_size * 2, sizeof(Float32), 16); + vDSP_blkman_window(_window, fft_size * 2, 0); + + Float32 normFactor = 2.0f / (fft_size * 2); + vDSP_vsmul(_window, 1, &normFactor, _window, 1, fft_size * 2); + + _rawSpectrum = (struct SpectrumData *) _memalign_calloc(sizeof(struct SpectrumData) + sizeof(Float32) * fft_size, 1, 16); + _rawSpectrum->length = fft_size; _fft_size = fft_size; } } void fft_calculate(const float *data, float *freq, int fft_size) { - int dft_size = fft_size * 2; - _init_buffers(fft_size); - vDSP_vmul(data, 1, _hamming, 1, _input_real, 1, dft_size); + // Split the waveform + DSPSplitComplex dest = { _dftBuffer.realp, _dftBuffer.imagp }; + vDSP_ctoz((const DSPComplex*)data, 2, &dest, 1, fft_size); - vDSP_DFT_Execute(_dft_setup, _input_real, _input_imaginary, _output_real, _output_imaginary); + // Apply the window function + vDSP_vmul(_dftBuffer.realp, 1, _window, 2, _dftBuffer.realp, 1, fft_size); + vDSP_vmul(_dftBuffer.imagp, 1, _window + 1, 2, _dftBuffer.imagp, 1, fft_size); - DSPSplitComplex split_complex = { - .realp = _output_real, - .imagp = _output_imaginary - }; - vDSP_zvmags(&split_complex, 1, _sq_mags, 1, fft_size); + // DFT + vDSP_DFT_Execute(_dftSetup, _dftBuffer.realp, _dftBuffer.imagp, _dftBuffer.realp, _dftBuffer.imagp); - int sq_count = fft_size; - vvsqrtf(_sq_mags, _sq_mags, &sq_count); + // Zero out the Nyquist value + _dftBuffer.imagp[0] = 0.0; - float mult = 2.f / fft_size; - vDSP_vsmul(_sq_mags, 1, &mult, freq, 1, fft_size); + // Calculate power spectrum + Float32 *rawSpectrum = _rawSpectrum->data; + vDSP_zvmags(&_dftBuffer, 1, rawSpectrum, 1, fft_size); + + // Add -128dB offset to avoid log(0) + float kZeroOffset = 1.5849e-13; + vDSP_vsadd(rawSpectrum, 1, &kZeroOffset, rawSpectrum, 1, fft_size); + + // Convert power to decibel + float kZeroDB = 0.70710678118f; // 1/sqrt(2) + vDSP_vdbcon(rawSpectrum, 1, &kZeroDB, rawSpectrum, 1, fft_size, 0); + + cblas_scopy(fft_size, rawSpectrum, 1, freq, 1); } void fft_free(void) { - free(_input_real); - free(_input_imaginary); - free(_hamming); - free(_sq_mags); - free(_output_real); - free(_output_imaginary); - if(_dft_setup != NULL) { - vDSP_DFT_DestroySetup(_dft_setup); + free(_dftBuffer.realp); + free(_dftBuffer.imagp); + free(_window); + free(_rawSpectrum); + if(_dftSetup != NULL) { + vDSP_DFT_DestroySetup(_dftSetup); } - _input_real = NULL; - _input_imaginary = NULL; - _hamming = NULL; - _sq_mags = NULL; - _dft_setup = NULL; - _output_real = NULL; - _output_imaginary = NULL; + _dftBuffer.realp = NULL; + _dftBuffer.imagp = NULL; + _window = NULL; + _rawSpectrum = NULL; } diff --git a/Visualization/ThirdParty/deadbeef/analyzer.c b/Visualization/ThirdParty/deadbeef/analyzer.c index 664e51e2f..7e2dc837d 100644 --- a/Visualization/ThirdParty/deadbeef/analyzer.c +++ b/Visualization/ThirdParty/deadbeef/analyzer.c @@ -148,7 +148,7 @@ void ddb_analyzer_tick(ddb_analyzer_t *analyzer) { } float bound = -analyzer->db_lower_bound; - float height = (20 * log10(norm_h) + bound) / bound; + float height = (norm_h + bound) / bound; if(ch == 0) { bar->height = height;