diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index 708eeb6ef..c218e3b2f 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -67,8 +67,6 @@ 835FAC5F27BCA14D00BA8562 /* BadSampleCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */; }; 83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7B27AA0D8A0003F694 /* Accelerate.framework */; }; 83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */; }; - 8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */ = {isa = PBXBuildFile; fileRef = 8377C64B27B8C51500E8BC0F /* fft_accelerate.c */; }; - 8377C64E27B8C54400E8BC0F /* fft.h in Headers */ = {isa = PBXBuildFile; fileRef = 8377C64D27B8C54400E8BC0F /* fft.h */; }; 8377C65227B8CAD100E8BC0F /* VisualizationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8377C65027B8CAD100E8BC0F /* VisualizationController.h */; }; 8377C65327B8CAD100E8BC0F /* VisualizationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C65127B8CAD100E8BC0F /* VisualizationController.m */; }; 8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; }; @@ -76,6 +74,8 @@ 839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; }; 8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */ = {isa = PBXBuildFile; fileRef = 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */; }; 8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */; }; + 83D44DC02839C60A00D4DD10 /* cqt.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D44DBE2839C60A00D4DD10 /* cqt.h */; }; + 83D44DC12839C60A00D4DD10 /* cqt.c in Sources */ = {isa = PBXBuildFile; fileRef = 83D44DBF2839C60A00D4DD10 /* cqt.c */; }; 83F18B1E27D1E8EF00385946 /* CDSPHBDownsampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */; }; 83F18B1F27D1E8EF00385946 /* pffft_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AE127D1E8EF00385946 /* pffft_double.h */; }; 83F18B2027D1E8EF00385946 /* pf_neon_double_from_avx.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F18AE327D1E8EF00385946 /* pf_neon_double_from_avx.h */; }; @@ -199,8 +199,6 @@ 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BadSampleCleaner.m; path = Utils/BadSampleCleaner.m; sourceTree = SOURCE_ROOT; }; 83725A7B27AA0D8A0003F694 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - 8377C64B27B8C51500E8BC0F /* fft_accelerate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fft_accelerate.c; sourceTree = ""; }; - 8377C64D27B8C54400E8BC0F /* fft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fft.h; sourceTree = ""; }; 8377C65027B8CAD100E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisualizationController.h; sourceTree = ""; }; 8377C65127B8CAD100E8BC0F /* VisualizationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VisualizationController.m; sourceTree = ""; }; 8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = ""; }; @@ -208,6 +206,8 @@ 839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = ""; }; 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = ""; }; 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = ""; }; + 83D44DBE2839C60A00D4DD10 /* cqt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cqt.h; sourceTree = ""; }; + 83D44DBF2839C60A00D4DD10 /* cqt.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cqt.c; sourceTree = ""; }; 83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPHBDownsampler.h; sourceTree = ""; }; 83F18AE127D1E8EF00385946 /* pffft_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_double.h; sourceTree = ""; }; 83F18AE327D1E8EF00385946 /* pf_neon_double_from_avx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double_from_avx.h; sourceTree = ""; }; @@ -400,7 +400,6 @@ isa = PBXGroup; children = ( 83F18ADE27D1E8EF00385946 /* r8brain-free-src */, - 8377C64A27B8C51500E8BC0F /* deadbeef */, 835C88AE279811A500E28EAE /* hdcd */, 835C88A22797D4D400E28EAE /* lvqcl */, 17D21DC40B8BE79700D1EBDE /* CoreAudioUtils */, @@ -482,20 +481,13 @@ name = Frameworks; sourceTree = ""; }; - 8377C64A27B8C51500E8BC0F /* deadbeef */ = { - isa = PBXGroup; - children = ( - 8377C64D27B8C54400E8BC0F /* fft.h */, - 8377C64B27B8C51500E8BC0F /* fft_accelerate.c */, - ); - path = deadbeef; - sourceTree = ""; - }; 8377C64F27B8CAAB00E8BC0F /* Visualization */ = { isa = PBXGroup; children = ( 8377C65027B8CAD100E8BC0F /* VisualizationController.h */, 8377C65127B8CAD100E8BC0F /* VisualizationController.m */, + 83D44DBE2839C60A00D4DD10 /* cqt.h */, + 83D44DBF2839C60A00D4DD10 /* cqt.c */, ); path = Visualization; sourceTree = ""; @@ -561,6 +553,7 @@ 17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */, 17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */, 17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */, + 83D44DC02839C60A00D4DD10 /* cqt.h in Headers */, 83F18B3427D1E8EF00385946 /* r8butil.h in Headers */, 8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */, 17D21CA90B8BE4BA00D1EBDE /* OutputNode.h in Headers */, @@ -607,7 +600,6 @@ 835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */, 8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */, 8384912718080FF100E7332D /* Logging.h in Headers */, - 8377C64E27B8C54400E8BC0F /* fft.h in Headers */, 83F18B5027D1E8F000385946 /* r8bconf.h in Headers */, 835FAC5E27BCA14D00BA8562 /* BadSampleCleaner.h in Headers */, 83F18B2327D1E8EF00385946 /* pf_sse2_double.h in Headers */, @@ -712,11 +704,11 @@ 17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */, 83F18B2727D1E8EF00385946 /* pffft_double.c in Sources */, 8328995327CB511000D7F028 /* RedundantPlaylistDataStore.m in Sources */, - 8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */, 839366681815923C006DD712 /* CogPluginMulti.m in Sources */, 835C88AA2797D4D400E28EAE /* lpc.c in Sources */, 17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */, 17F94DD60B8D0F7000A34E87 /* PluginController.mm in Sources */, + 83D44DC12839C60A00D4DD10 /* cqt.c in Sources */, 17A2D3C60B8D1D37000778C4 /* AudioDecoder.m in Sources */, 8328995827CB51B700D7F028 /* SHA256Digest.m in Sources */, 17C940240B900909008627D6 /* AudioMetadataReader.m in Sources */, diff --git a/Audio/ThirdParty/deadbeef/fft.h b/Audio/ThirdParty/deadbeef/fft.h deleted file mode 100644 index dba7f8f4d..000000000 --- a/Audio/ThirdParty/deadbeef/fft.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - DeaDBeeF -- the music player - Copyright (C) 2009-2021 Alexey Yakovenko and other contributors - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source distribution. -*/ - -#ifndef FFT_H -#define FFT_H - -#ifdef __cplusplus -extern "C" { -} -#endif - -void fft_calculate(const float *data, float *freq, int fft_size); - -void fft_free(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/Audio/ThirdParty/deadbeef/fft_accelerate.c b/Audio/ThirdParty/deadbeef/fft_accelerate.c deleted file mode 100644 index 320731c1f..000000000 --- a/Audio/ThirdParty/deadbeef/fft_accelerate.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - DeaDBeeF -- the music player - Copyright (C) 2009-2021 Alexey Yakovenko and other contributors - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source distribution. -*/ - -#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; - -static vDSP_DFT_Setup _dft_setup; - -static void -_init_buffers(int fft_size) { - if(fft_size != _fft_size) { - fft_free(); - - _input_real = calloc(fft_size * 2, sizeof(float)); - _input_imaginary = calloc(fft_size * 2, sizeof(float)); - _hamming = calloc(fft_size * 2, sizeof(float)); - _sq_mags = calloc(fft_size, sizeof(float)); - _output_real = calloc(fft_size * 2, sizeof(float)); - _output_imaginary = calloc(fft_size * 2, sizeof(float)); - - _dft_setup = vDSP_DFT_zop_CreateSetup(NULL, fft_size * 2, FFT_FORWARD); - vDSP_hamm_window(_hamming, fft_size * 2, 0); - - _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); - - vDSP_DFT_Execute(_dft_setup, _input_real, _input_imaginary, _output_real, _output_imaginary); - - DSPSplitComplex split_complex = { - .realp = _output_real, - .imagp = _output_imaginary - }; - vDSP_zvmags(&split_complex, 1, _sq_mags, 1, fft_size); - - int sq_count = fft_size; - vvsqrtf(_sq_mags, _sq_mags, &sq_count); - - float mult = 2.f / fft_size; - vDSP_vsmul(_sq_mags, 1, &mult, freq, 1, fft_size); -} - -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); - } - _input_real = NULL; - _input_imaginary = NULL; - _hamming = NULL; - _sq_mags = NULL; - _dft_setup = NULL; - _output_real = NULL; - _output_imaginary = NULL; -} diff --git a/Audio/Visualization/VisualizationController.h b/Audio/Visualization/VisualizationController.h index 247433673..3fbf6c607 100644 --- a/Audio/Visualization/VisualizationController.h +++ b/Audio/Visualization/VisualizationController.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)postSampleRate:(double)sampleRate; - (void)postVisPCM:(const float *)inPCM amount:(int)amount; - (double)readSampleRate; -- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT; +- (void)copyVisPCM:(float *)outPCM visCQT:(float *)outCQT; @end diff --git a/Audio/Visualization/VisualizationController.m b/Audio/Visualization/VisualizationController.m index 8c6e62d72..4bd232cc3 100644 --- a/Audio/Visualization/VisualizationController.m +++ b/Audio/Visualization/VisualizationController.m @@ -8,7 +8,7 @@ #import "VisualizationController.h" #import -#import "fft.h" +#import "cqt.h" @implementation VisualizationController @@ -32,7 +32,7 @@ static VisualizationController *_sharedController = nil; } - (void)dealloc { - fft_free(); + cqt_free(); } - (void)postSampleRate:(double)sampleRate { @@ -59,10 +59,10 @@ static VisualizationController *_sharedController = nil; } } -- (void)copyVisPCM:(float *)outPCM visFFT:(float *)outFFT { +- (void)copyVisPCM:(float *)outPCM visCQT:(float *)outCQT { @synchronized(self) { cblas_scopy(4096, visAudio, 1, outPCM, 1); - fft_calculate(visAudio, outFFT, 2048); + cqt_calculate(visAudio, sampleRate, outCQT, 4096); } } diff --git a/Audio/Visualization/cqt.c b/Audio/Visualization/cqt.c new file mode 100644 index 000000000..289e23a58 --- /dev/null +++ b/Audio/Visualization/cqt.c @@ -0,0 +1,186 @@ +// +// cqt.c +// CogAudio Framework +// +// Created by Christopher Snowhill on 5/21/22. +// + +#include "cqt.h" + +#include + +#include + +struct CQTState { + float m_minFreq; + float m_maxFreq; + int m_bins; + float m_sampleFreq; + // enum WindowFunction; // Always Hamm + + float m_Q; + FFTSetup m_fftSetup; + int m_fftLength; + int m_fftLogLength; + DSPSplitComplex m_fftLengthMatrix; + DSPSplitComplex m_kernel; + int m_K; + DSPSplitComplex m_KVector; +}; + +static struct CQTState m_state = { 0 }; + +static void *malloc_aligned(size_t size) { + void *ret = NULL; + if(posix_memalign(&ret, 8, size)) { + return NULL; + } + return ret; +} + +static int dspsplit_alloc(DSPSplitComplex *out, size_t size) { + size_t realSize = size * sizeof(float); + out->realp = malloc_aligned(realSize); + out->imagp = malloc_aligned(realSize); + if(!out->realp || !out->imagp) return -1; + bzero(out->realp, realSize); + bzero(out->imagp, realSize); + return 0; +} + +static void dspsplit_free(DSPSplitComplex *in) { + if(in->realp) free(in->realp); + if(in->imagp) free(in->imagp); + in->realp = NULL; + in->imagp = NULL; +} + +static int cqtstate_alloc(struct CQTState *state, float minFreq, float maxFreq, int binsPerOctave, float sampleFreq /*, enum WindowFunction */) { + state->m_minFreq = minFreq; + state->m_maxFreq = maxFreq; + state->m_bins = binsPerOctave; + state->m_sampleFreq = sampleFreq; + /* state->m_windowFunction = func; */ + + const int K = (int)(ceilf(binsPerOctave * log2f(maxFreq / minFreq))); + state->m_K = K; + float Q = state->m_Q = 1 / (powf(2.0, 1.0f / binsPerOctave) - 1.0); + const int fftLogLength = state->m_fftLogLength = (int)(ceilf(log2f(Q * sampleFreq / minFreq))); + const int fftLength = state->m_fftLength = (int)(powf(2.0, fftLogLength)); + + if(dspsplit_alloc(&state->m_kernel, K * fftLength) < 0) + return -1; + + state->m_fftSetup = vDSP_create_fftsetup(fftLogLength, FFT_RADIX2); + if(!state->m_fftSetup) + return -1; + + const int maxN = (int)(ceilf(Q * sampleFreq / minFreq)); + + DSPSplitComplex window; + if(dspsplit_alloc(&window, maxN) < 0) + return -1; + + DSPSplitComplex exponents; + if(dspsplit_alloc(&exponents, maxN) < 0) { + dspsplit_free(&window); + return -1; + } + + for(int k = K; k > 0; --k) { + const int N = (int)(ceilf(Q * sampleFreq / (minFreq * pow(2.0, (float)(k - 1) / binsPerOctave)))); + assert(N <= maxN); + + bzero(window.imagp, sizeof(float) * N); + + vDSP_hamm_window(window.realp, N, 0); + + for(int i = 0; i < N; ++i) { + float inner = 2 * M_PI * Q * (float)(i) / (float)(N); + exponents.realp[i] = inner; + exponents.imagp[i] = inner; + } + + vvcosf(exponents.realp, exponents.realp, &N); + vvsinf(exponents.imagp, exponents.imagp, &N); + + const float nFloat = (float)(N); + vDSP_vsdiv(exponents.realp, 1, &nFloat, exponents.realp, 1, N); + vDSP_vsdiv(exponents.imagp, 1, &nFloat, exponents.imagp, 1, N); + + DSPSplitComplex complexPointer; + complexPointer.realp = &state->m_kernel.realp[(k - 1) * fftLength]; + complexPointer.imagp = &state->m_kernel.imagp[(k - 1) * fftLength]; + + vDSP_zvmul(&window, 1, &exponents, 1, &complexPointer, 1, N < fftLength ? N : fftLength, 1); + vDSP_fft_zip(state->m_fftSetup, &complexPointer, 1, fftLogLength, FFT_FORWARD); + } + + dspsplit_free(&window); + dspsplit_free(&exponents); + + vDSP_mtrans(state->m_kernel.realp, 1, state->m_kernel.realp, 1, fftLength, K); + vDSP_mtrans(state->m_kernel.imagp, 1, state->m_kernel.imagp, 1, fftLength, K); + + vDSP_zvconj(&state->m_kernel, 1, &state->m_kernel, 1, fftLength * K); + + const float lengthFloat = (float)(fftLength); + vDSP_vsdiv(state->m_kernel.realp, 1, &lengthFloat, state->m_kernel.realp, 1, fftLength * K); + vDSP_vsdiv(state->m_kernel.imagp, 1, &lengthFloat, state->m_kernel.imagp, 1, fftLength * K); + + if(dspsplit_alloc(&state->m_fftLengthMatrix, fftLength) < 0) + return -1; + if(dspsplit_alloc(&state->m_KVector, K) < 0) + return -1; + + return 0; +} + +static void cqtstate_free(struct CQTState *in) { + if(in) { + dspsplit_free(&in->m_KVector); + dspsplit_free(&in->m_kernel); + dspsplit_free(&in->m_fftLengthMatrix); + + if(in->m_fftSetup) vDSP_destroy_fftsetup(in->m_fftSetup); + + in->m_sampleFreq = 0.0; + } +} + +void cqt_calculate(const float *data, const float sampleRate, float *freq, int samples_in) { + if(!sampleRate) { + bzero(freq, 88 * sizeof(float)); + return; + } + if(sampleRate != m_state.m_sampleFreq) { + cqtstate_free(&m_state); + if(cqtstate_alloc(&m_state, 27.5f, 4434.92f, 12, sampleRate) < 0) { + cqtstate_free(&m_state); + bzero(freq, 88 * sizeof(float)); + return; + } + } + + const int fftLength = m_state.m_fftLength; + const int K = m_state.m_K; + + if(samples_in < fftLength) { + bzero(m_state.m_fftLengthMatrix.realp, sizeof(float) * fftLength); + } + + cblas_scopy(samples_in > fftLength ? fftLength : samples_in, data, 1, m_state.m_fftLengthMatrix.realp, 1); + bzero(m_state.m_fftLengthMatrix.imagp, sizeof(float) * fftLength); + + vDSP_fft_zip(m_state.m_fftSetup, &m_state.m_fftLengthMatrix, 1, m_state.m_fftLogLength, FFT_FORWARD); + + vDSP_zmmul(&m_state.m_fftLengthMatrix, 1, &m_state.m_kernel, 1, &m_state.m_KVector, 1, 1, K, fftLength); + + vDSP_zvmags(&m_state.m_KVector, 1, freq, 1, K); + + vvsqrtf(freq, freq, &K); +} + +void cqt_free() { + cqtstate_free(&m_state); +} diff --git a/Audio/Visualization/cqt.h b/Audio/Visualization/cqt.h new file mode 100644 index 000000000..7b09203c8 --- /dev/null +++ b/Audio/Visualization/cqt.h @@ -0,0 +1,23 @@ +// +// cqt.h +// CogAudio Framework +// +// Created by Christopher Snowhill on 5/21/22. +// + +#ifndef cqt_h +#define cqt_h + +#ifdef __cplusplus +extern "C" { +#endif + +void cqt_calculate(const float *data, const float sampleRate, float *freq, int samples_in); + +void cqt_free(void); + +#ifdef __cplusplus +} +#endif + +#endif /* cqt_h */ diff --git a/Visualization/SpectrumView.m b/Visualization/SpectrumView.m index 64f4347be..dccf2dbe6 100644 --- a/Visualization/SpectrumView.m +++ b/Visualization/SpectrumView.m @@ -63,19 +63,13 @@ extern NSString *CogPlaybackDidStopNotficiation; [self colorsDidChange:nil]; - BOOL freqMode = [[NSUserDefaults standardUserDefaults] boolForKey:@"spectrumFreqMode"]; - ddb_analyzer_init(&_analyzer); _analyzer.db_lower_bound = LOWER_BOUND; - _analyzer.min_freq = 10; - _analyzer.max_freq = 22000; _analyzer.peak_hold = 10; _analyzer.view_width = 64; _analyzer.fractional_bars = 1; _analyzer.octave_bars_step = 2; - _analyzer.max_of_stereo_data = 1; _analyzer.freq_is_log = 0; - _analyzer.mode = freqMode ? DDB_ANALYZER_MODE_FREQUENCIES : DDB_ANALYZER_MODE_OCTAVE_NOTE_BANDS; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(colorsDidChange:) @@ -222,11 +216,7 @@ extern NSString *CogPlaybackDidStopNotficiation; } - (void)drawAnalyzer { - if(_analyzer.mode == DDB_ANALYZER_MODE_FREQUENCIES) { - [self drawAnalyzerDescreteFrequencies]; - } else { - [self drawAnalyzerOctaveBands]; - } + [self drawAnalyzerOctaveBands]; } - (void)drawRect:(NSRect)dirtyRect { @@ -248,25 +238,15 @@ extern NSString *CogPlaybackDidStopNotficiation; if(stopped) return; - float visAudio[4096], visFFT[2048]; + float visAudio[4096], visCQT[88]; - [self->visController copyVisPCM:&visAudio[0] visFFT:&visFFT[0]]; + [self->visController copyVisPCM:&visAudio[0] visCQT:&visCQT[0]]; - ddb_analyzer_process(&_analyzer, [self->visController readSampleRate] / 2.0, 1, visFFT, 2048); + ddb_analyzer_process(&_analyzer, [self->visController readSampleRate] / 2.0, 1, visCQT, 88); ddb_analyzer_tick(&_analyzer); ddb_analyzer_get_draw_data(&_analyzer, self.bounds.size.width, self.bounds.size.height, &_draw_data); [self drawAnalyzer]; } -- (void)mouseDown:(NSEvent *)event { - BOOL freqMode = ![[NSUserDefaults standardUserDefaults] boolForKey:@"spectrumFreqMode"]; - [[NSUserDefaults standardUserDefaults] setBool:freqMode forKey:@"spectrumFreqMode"]; - - _analyzer.mode = freqMode ? DDB_ANALYZER_MODE_FREQUENCIES : DDB_ANALYZER_MODE_OCTAVE_NOTE_BANDS; - _analyzer.mode_did_change = 1; - - [self repaint]; -} - @end diff --git a/Visualization/ThirdParty/deadbeef/analyzer.c b/Visualization/ThirdParty/deadbeef/analyzer.c index 3e153a283..1f064646b 100644 --- a/Visualization/ThirdParty/deadbeef/analyzer.c +++ b/Visualization/ThirdParty/deadbeef/analyzer.c @@ -27,34 +27,14 @@ #include #include -#define OCTAVES 11 -#define STEPS 24 -#define ROOT24 1.0293022366 // pow(2, 1.0 / STEPS) -#define C0 16.3515978313 // 440 * pow(ROOT24, -114); - #pragma mark - Forward declarations static float _get_bar_height(ddb_analyzer_t *analyzer, float normalized_height, int view_height); -static void -_generate_frequency_labels(ddb_analyzer_t *analyzer); - -static void -_generate_frequency_bars(ddb_analyzer_t *analyzer); - static void _generate_octave_note_bars(ddb_analyzer_t *analyzer); -static void -_tempered_scale_bands_precalc(ddb_analyzer_t *analyzer); - -static float _bin_for_freq_floor(ddb_analyzer_t *analyzer, float freq); - -static float _bin_for_freq_round(ddb_analyzer_t *analyzer, float freq); - -static float _freq_for_bin(ddb_analyzer_t *analyzer, int bin); - static float _interpolate_bin_with_ratio(float *fft_data, int bin, float ratio, int fft_size); @@ -67,21 +47,16 @@ ddb_analyzer_alloc(void) { ddb_analyzer_t * ddb_analyzer_init(ddb_analyzer_t *analyzer) { - analyzer->mode = DDB_ANALYZER_MODE_FREQUENCIES; - analyzer->min_freq = 50; - analyzer->max_freq = 22000; analyzer->view_width = 1000; analyzer->peak_hold = 10; analyzer->peak_speed_scale = 1000.f; analyzer->db_lower_bound = -80; analyzer->octave_bars_step = 1; - analyzer->freq_is_log = 1; analyzer->bar_gap_denominator = 3; return analyzer; } void ddb_analyzer_dealloc(ddb_analyzer_t *analyzer) { - free(analyzer->tempered_scale_bands); free(analyzer->fft_data); memset(analyzer, 0, sizeof(ddb_analyzer_t)); } @@ -97,11 +72,7 @@ void ddb_analyzer_process(ddb_analyzer_t *analyzer, int samplerate, int channels channels = 2; } - if(!analyzer->max_of_stereo_data) { - channels = 1; - } - - if(analyzer->mode_did_change || channels != analyzer->channels || fft_size != analyzer->fft_size || samplerate != analyzer->samplerate) { + if(channels != analyzer->channels || fft_size != analyzer->fft_size || samplerate != analyzer->samplerate) { analyzer->channels = channels; analyzer->fft_size = fft_size; analyzer->samplerate = samplerate; @@ -114,16 +85,7 @@ void ddb_analyzer_process(ddb_analyzer_t *analyzer, int samplerate, int channels memcpy(analyzer->fft_data, fft_data, fft_size * channels * sizeof(float)); if(need_regenerate) { - switch(analyzer->mode) { - case DDB_ANALYZER_MODE_FREQUENCIES: - _generate_frequency_bars(analyzer); - break; - case DDB_ANALYZER_MODE_OCTAVE_NOTE_BANDS: - _generate_octave_note_bars(analyzer); - break; - } - - _generate_frequency_labels(analyzer); + _generate_octave_note_bars(analyzer); } } @@ -137,15 +99,7 @@ void ddb_analyzer_tick(ddb_analyzer_t *analyzer) { float *fft_data = analyzer->fft_data + ch * analyzer->fft_size; ddb_analyzer_bar_t *bar = analyzer->bars; for(int i = 0; i < analyzer->bar_count; i++, bar++) { - float norm_h = _interpolate_bin_with_ratio(fft_data, bar->bin, bar->ratio, analyzer->fft_size); - - // if the bar spans more than one bin, find the max value - for(int b = bar->bin + 1; b <= bar->last_bin; b++) { - float val = analyzer->fft_data[b]; - if(val > norm_h) { - norm_h = val; - } - } + float norm_h = fft_data[bar->bin]; float bound = -analyzer->db_lower_bound; float height = (20 * log10(norm_h) + bound) / bound; @@ -182,9 +136,7 @@ void ddb_analyzer_get_draw_data(ddb_analyzer_t *analyzer, int view_width, int vi draw_data->bar_count = analyzer->bar_count; } - if(analyzer->mode == DDB_ANALYZER_MODE_FREQUENCIES) { - draw_data->bar_width = 1; - } else if(analyzer->mode == DDB_ANALYZER_MODE_OCTAVE_NOTE_BANDS) { + { if(analyzer->fractional_bars) { float width = (float)view_width / analyzer->bar_count; float gap = analyzer->bar_gap_denominator > 0 ? width / analyzer->bar_gap_denominator : 0; @@ -212,12 +164,6 @@ void ddb_analyzer_get_draw_data(ddb_analyzer_t *analyzer, int view_width, int vi draw_bar->xpos = bar->xpos * view_width; draw_bar->peak_ypos = _get_bar_height(analyzer, bar->peak, view_height); } - - memcpy(draw_data->label_freq_texts, analyzer->label_freq_texts, sizeof(analyzer->label_freq_texts)); - for(int i = 0; i < analyzer->label_freq_count; i++) { - draw_data->label_freq_positions[i] = analyzer->label_freq_positions[i] * view_width; - } - draw_data->label_freq_count = analyzer->label_freq_count; } void ddb_analyzer_draw_data_dealloc(ddb_analyzer_draw_data_t *draw_data) { @@ -239,113 +185,20 @@ _get_bar_height(ddb_analyzer_t *analyzer, float normalized_height, int view_heig return height; } -static void -_generate_frequency_labels(ddb_analyzer_t *analyzer) { - float min_freq_log = log10(analyzer->min_freq); - float max_freq_log = log10(analyzer->max_freq); - float view_width = analyzer->view_width; - float width_log = view_width / (max_freq_log - min_freq_log); - - // calculate the distance between any 2 neighbour labels - float freq = 64000; - float freq2 = 32000; - float pos = width_log * (log10(freq) - min_freq_log) / view_width; - float pos2 = width_log * (log10(freq2) - min_freq_log) / view_width; - float dist = pos - pos2; - - // generate position and text for each label - int index = 0; - while(freq > 30 && index < DDB_ANALYZER_MAX_LABEL_FREQS) { - analyzer->label_freq_positions[index] = pos; - - if(freq < 1000) { - snprintf(analyzer->label_freq_texts[index], sizeof(analyzer->label_freq_texts[index]), "%d", (int)round(freq)); - } else { - snprintf(analyzer->label_freq_texts[index], sizeof(analyzer->label_freq_texts[index]), "%dk", ((int)freq) / 1000); - } - - pos -= dist; - freq /= 2; - index += 1; - } - analyzer->label_freq_count = index; -} - -static void -_generate_frequency_bars(ddb_analyzer_t *analyzer) { - float min_freq = analyzer->min_freq; - float min_freq_log; - float view_width = analyzer->view_width; - float width; - if(analyzer->freq_is_log) { - min_freq_log = log10(analyzer->min_freq); - float max_freq_log = log10(analyzer->max_freq); - width = view_width / (max_freq_log - min_freq_log); - } else { - min_freq = analyzer->min_freq; - width = view_width / (analyzer->max_freq - min_freq); - } - - float minIndex = _bin_for_freq_floor(analyzer, analyzer->min_freq); - float maxIndex = _bin_for_freq_round(analyzer, analyzer->max_freq); - - int prev = -1; - - analyzer->bar_count = 0; - - if(analyzer->bar_count_max != analyzer->view_width) { - free(analyzer->bars); - analyzer->bars = calloc(analyzer->view_width, sizeof(ddb_analyzer_bar_t)); - analyzer->bar_count_max = analyzer->view_width; - } - - for(int i = minIndex; i <= maxIndex; i++) { - float freq = _freq_for_bin(analyzer, i); - - // FIXME: only int position! - int pos; - if(analyzer->freq_is_log) - pos = width * (log10(freq) - min_freq_log); - else - pos = width * (freq - min_freq); - - if(pos > prev && pos >= 0) { - // start accumulating frequencies for the new band - ddb_analyzer_bar_t *bar = analyzer->bars + analyzer->bar_count; - - bar->xpos = pos / view_width; // normalized position - bar->bin = i; - bar->ratio = 0; - analyzer->bar_count += 1; - - prev = pos; - } - } -} - static void _generate_octave_note_bars(ddb_analyzer_t *analyzer) { analyzer->bar_count = 0; - _tempered_scale_bands_precalc(analyzer); - - if(analyzer->bar_count_max != OCTAVES * STEPS) { + if(analyzer->bar_count_max != 88) { free(analyzer->bars); - analyzer->bars = calloc(OCTAVES * STEPS, sizeof(ddb_analyzer_bar_t)); - analyzer->bar_count_max = OCTAVES * STEPS; + analyzer->bars = calloc(88, sizeof(ddb_analyzer_bar_t)); + analyzer->bar_count_max = 88; } int minBand = -1; int maxBand = -1; - ddb_analyzer_bar_t *prev_bar = NULL; - for(int i = 0; i < OCTAVES * STEPS; i += analyzer->octave_bars_step) { - ddb_analyzer_band_t *band = &analyzer->tempered_scale_bands[i]; - - if(band->freq < analyzer->min_freq || band->freq > analyzer->max_freq) { - continue; - } - + for(int i = 0; i < 88; i += analyzer->octave_bars_step) { if(minBand == -1) { minBand = i; } @@ -354,30 +207,11 @@ _generate_octave_note_bars(ddb_analyzer_t *analyzer) { ddb_analyzer_bar_t *bar = analyzer->bars + analyzer->bar_count; - int bin = _bin_for_freq_floor(analyzer, band->freq); + int bin = i; bar->bin = bin; - bar->last_bin = 0; - bar->ratio = 0; - - // interpolation ratio of next bin of previous bar to the first bin of this bar - if(prev_bar && bin - 1 > prev_bar->bin) { - prev_bar->last_bin = bin - 1; - } analyzer->bar_count += 1; - - // get interpolation ratio to the next bin - int bin2 = bin + 1; - if(bin2 < analyzer->fft_size) { - float p = log10(band->freq); - float p1 = log10(_freq_for_bin(analyzer, bin)); - float p2 = log10(_freq_for_bin(analyzer, bin2)); - float d = p2 - p1; - bar->ratio = (p - p1) / d; - } - - prev_bar = bar; } for(int i = 0; i < analyzer->bar_count; i++) { @@ -385,44 +219,6 @@ _generate_octave_note_bars(ddb_analyzer_t *analyzer) { } } -static float _bin_for_freq_floor(ddb_analyzer_t *analyzer, float freq) { - float max = analyzer->fft_size - 1; - float bin = floor(freq * analyzer->fft_size / analyzer->samplerate); - return bin < max ? bin : max; -} - -static float _bin_for_freq_round(ddb_analyzer_t *analyzer, float freq) { - float max = analyzer->fft_size - 1; - float bin = round(freq * analyzer->fft_size / analyzer->samplerate); - return bin < max ? bin : max; -} - -static float _freq_for_bin(ddb_analyzer_t *analyzer, int bin) { - return (int64_t)bin * analyzer->samplerate / analyzer->fft_size; -} - -// Precalculate data for tempered scale -static void -_tempered_scale_bands_precalc(ddb_analyzer_t *analyzer) { - if(analyzer->tempered_scale_bands != NULL) { - return; - } - - analyzer->tempered_scale_bands = calloc(OCTAVES * STEPS, sizeof(ddb_analyzer_band_t)); - - for(int i = 0; i < OCTAVES * STEPS; i++) { - float f = C0 * pow(ROOT24, i); - float bin = _bin_for_freq_floor(analyzer, f); - float binf = _freq_for_bin(analyzer, bin); - float fn = _freq_for_bin(analyzer, bin + 1); - float ratio = (f - binf) / (fn - binf); - - analyzer->tempered_scale_bands[i].bin = bin; - analyzer->tempered_scale_bands[i].freq = f; - analyzer->tempered_scale_bands[i].ratio = ratio; - } -} - static float _interpolate_bin_with_ratio(float *fft_data, int bin, float ratio, int fft_size) { return bin < fft_size ? (bin + 1 < fft_size ? (fft_data[bin] + (fft_data[bin + 1] - fft_data[bin]) * ratio) : fft_data[bin]) : 0.0; diff --git a/Visualization/ThirdParty/deadbeef/analyzer.h b/Visualization/ThirdParty/deadbeef/analyzer.h index a1d2e2714..1775e10a8 100644 --- a/Visualization/ThirdParty/deadbeef/analyzer.h +++ b/Visualization/ThirdParty/deadbeef/analyzer.h @@ -27,19 +27,9 @@ extern "C" { #endif -#define DDB_ANALYZER_MAX_LABEL_FREQS 20 - -typedef struct { - float freq; - float ratio; - int bin; -} ddb_analyzer_band_t; - typedef struct { // interpolation data int bin; - int last_bin; - float ratio; // normalized position float xpos; @@ -58,35 +48,17 @@ typedef struct { int bar_count; ddb_analyzer_draw_bar_t *bars; float bar_width; - - // freq label drawing positions in view space - float label_freq_positions[DDB_ANALYZER_MAX_LABEL_FREQS]; - char label_freq_texts[DDB_ANALYZER_MAX_LABEL_FREQS][4]; - int label_freq_count; } ddb_analyzer_draw_data_t; -typedef enum { - DDB_ANALYZER_MODE_FREQUENCIES, - DDB_ANALYZER_MODE_OCTAVE_NOTE_BANDS, -} ddb_analyzer_mode_t; - typedef struct ddb_analyzer_s { // Settings - float min_freq; - float max_freq; - - ddb_analyzer_mode_t mode; - /// Set to 1 after changing the @c mode or @c octave_bars_step at runtime, to refresh the internal state int mode_did_change; /// Generate fractional bar positions and width. Default is 0. int fractional_bars; - /// Process 2 channels, if available, and use the max value. Default is 0. - int max_of_stereo_data; - /// How to calculate the gap between bars. E.g. 10 means bar_width/10. Default is 3. /// If this value is 0, no gap will be created. /// If the gap is >=1, a gap of at least 1px will be created, unless the bar width itself is 1px. @@ -112,15 +84,6 @@ typedef struct ddb_analyzer_s { int channels; int fft_size; float *fft_data; - - /// Calculated label texts and frequencies - float label_freq_positions[DDB_ANALYZER_MAX_LABEL_FREQS]; - char label_freq_texts[DDB_ANALYZER_MAX_LABEL_FREQS][4]; - int label_freq_count; - - /// Tempered scale data, precalculated from fft pins - ddb_analyzer_band_t *tempered_scale_bands; - } ddb_analyzer_t; ddb_analyzer_t *ddb_analyzer_alloc(void);