CQT Experiment: Tossed in a branch for later
This experiment with Constant Q Transform was interesting, and it's nice to see that it actually functions, but it's just too damn slow to really be useful for visualization purposes. It uses nearly a full core on an M1 processor, and I'd hate to see what it does on an Intel machine. Stashing this in a branch and discarding it from the main tree, in case somebody finds this code useful some day. Signed-off-by: Christopher Snowhill <kode54@gmail.com>CQTexperiment
parent
bf54c45242
commit
024e36963a
|
@ -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 = "<group>"; };
|
||||
8377C64D27B8C54400E8BC0F /* fft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fft.h; sourceTree = "<group>"; };
|
||||
8377C65027B8CAD100E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisualizationController.h; sourceTree = "<group>"; };
|
||||
8377C65127B8CAD100E8BC0F /* VisualizationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VisualizationController.m; sourceTree = "<group>"; };
|
||||
8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
|
||||
|
@ -208,6 +206,8 @@
|
|||
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
|
||||
8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
|
||||
8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
|
||||
83D44DBE2839C60A00D4DD10 /* cqt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cqt.h; sourceTree = "<group>"; };
|
||||
83D44DBF2839C60A00D4DD10 /* cqt.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = cqt.c; sourceTree = "<group>"; };
|
||||
83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPHBDownsampler.h; sourceTree = "<group>"; };
|
||||
83F18AE127D1E8EF00385946 /* pffft_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_double.h; sourceTree = "<group>"; };
|
||||
83F18AE327D1E8EF00385946 /* pf_neon_double_from_avx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double_from_avx.h; sourceTree = "<group>"; };
|
||||
|
@ -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 = "<group>";
|
||||
};
|
||||
8377C64A27B8C51500E8BC0F /* deadbeef */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8377C64D27B8C54400E8BC0F /* fft.h */,
|
||||
8377C64B27B8C51500E8BC0F /* fft_accelerate.c */,
|
||||
);
|
||||
path = deadbeef;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8377C64F27B8CAAB00E8BC0F /* Visualization */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8377C65027B8CAD100E8BC0F /* VisualizationController.h */,
|
||||
8377C65127B8CAD100E8BC0F /* VisualizationController.m */,
|
||||
83D44DBE2839C60A00D4DD10 /* cqt.h */,
|
||||
83D44DBF2839C60A00D4DD10 /* cqt.c */,
|
||||
);
|
||||
path = Visualization;
|
||||
sourceTree = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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
|
|
@ -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 <Accelerate/Accelerate.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#import "VisualizationController.h"
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
//
|
||||
// cqt.c
|
||||
// CogAudio Framework
|
||||
//
|
||||
// Created by Christopher Snowhill on 5/21/22.
|
||||
//
|
||||
|
||||
#include "cqt.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <Accelerate/Accelerate.h>
|
||||
|
||||
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);
|
||||
}
|
|
@ -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 */
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
- (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
|
||||
|
|
|
@ -27,34 +27,14 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue