···2452455. **Hook into tic_core_tick** after FFT processing
2462466. **Test with simple Lua visualization**
247247248248-#### Phase 2: Full API (Test in 3-4 hours)
249249-1. Add smoothing to CQT processing
250250-2. Implement remaining API functions: `cqts()`, `cqto()`, `cqtos()`
251251-3. Add normalization and auto-gain
252252-4. Create demo comparing FFT vs CQT
248248+#### Phase 1 Status: COMPLETE ✓
249249+- [x] cqtdata.h - Basic structure and constants
250250+- [x] cqtdata.c - Global data instance
251251+- [x] cqt_kernel.h - Kernel generation header
252252+- [x] cqt_kernel.c - Adapt ESP32 kernel generation
253253+- [x] cqt.h - Main CQT header
254254+- [x] cqt.c - Basic CQT processing (with placeholder FFT mapping)
255255+- [x] luaapi.c - Add cqt() function
256256+- [x] tic80.c - Hook into core tick
257257+- [x] CMakeLists.txt - Add new files to build
258258+259259+**Test Results**: Basic API working, visualization shows 10 octaves with test data mapping
260260+261261+#### Phase 2: Real CQT Processing (Priority Tasks)
262262+1. **Access raw audio buffer** from FFT capture system
263263+2. **Implement 4096-point FFT** for CQT (separate from main FFT)
264264+3. **Apply CQT kernels** to FFT output (currently stubbed)
265265+4. **Fix kernel initialization** - kernels not being generated on startup
266266+5. **Add remaining API functions**: `cqts()`, `cqto()`, `cqtos()`
267267+6. **Create comparison demo** showing FFT vs CQT side-by-side
253268254269#### Phase 3: Optimization (If needed)
2552701. Profile and identify bottlenecks
···266281 local val = cqt(i) * 100 -- scale up for visibility
267282 rect(i*2, 136-val, 2, val, 12)
268283 end
284284+285285+ -- Draw octave markers
286286+ for oct=0,9 do
287287+ local x = oct * 12 * 2
288288+ line(x, 0, x, 136, 8)
289289+ print(oct, x+2, 2, 12, false, 1, true)
290290+ end
291291+292292+ -- Show info
293293+ print("CQT TEST - 10 octaves x 12 notes", 2, 120, 12, false, 1, true)
269294end
270295```
271296···282307- Platform-specific code should be isolated in src/system/
283308- The studio uses immediate mode GUI principles
284309- Cartridge format is documented in wiki and src/studio/project.c
285285-- PRO version enables additional features like extra memory banks310310+- PRO version enables additional features like extra memory banks
···2121// SOFTWARE.
22222323#include "core/core.h"
2424+#include "cqtdata.h"
24252526#include <stdlib.h>
2627#include <lua.h>
···15951596 return 0;
15961597}
1597159815991599+static s32 lua_cqt(lua_State* lua)
16001600+{
16011601+ s32 top = lua_gettop(lua);
16021602+16031603+ if (top >= 1)
16041604+ {
16051605+ s32 bin = getLuaNumber(lua, 1);
16061606+16071607+ // Validate bin range
16081608+ if (bin < 0 || bin >= CQT_BINS)
16091609+ {
16101610+ luaL_error(lua, "cqt bin out of range (0-%d)\n", CQT_BINS - 1);
16111611+ return 0;
16121612+ }
16131613+16141614+ // Return normalized CQT data
16151615+ lua_pushnumber(lua, cqtNormalizedData[bin]);
16161616+ return 1;
16171617+ }
16181618+16191619+ luaL_error(lua, "invalid params, cqt(bin)\n");
16201620+ return 0;
16211621+}
16221622+15981623static int lua_dofile(lua_State *lua)
15991624{
16001625 luaL_error(lua, "unknown method: \"dofile\"\n");
···1648167316491674 registerLuaFunction(core, lua_dofile, "dofile");
16501675 registerLuaFunction(core, lua_loadfile, "loadfile");
16761676+16771677+ // Register CQT function
16781678+ registerLuaFunction(core, lua_cqt, "cqt");
16511679}
1652168016531681void luaapi_close(tic_mem* tic)
+34
src/core/core.c
···22222323#include "fftdata.h"
2424#include "../ext/fft.h"
2525+#include "cqtdata.h"
2626+#include "../ext/cqt.h"
25272628#include "api.h"
2729#include "core.h"
···442444 if (fftEnabled)
443445 {
444446 FFT_GetFFT(fftData);
447447+448448+ // Process CQT using existing FFT data
449449+ // For now, tie CQT to FFT enable flag
450450+ cqtEnabled = fftEnabled;
451451+ if (cqtEnabled)
452452+ {
453453+ // TODO: In a full implementation, we would:
454454+ // 1. Get raw audio samples
455455+ // 2. Perform our own 4096-point FFT
456456+ // 3. Apply CQT kernels
457457+ // For now, we'll use a simplified approach with existing FFT data
458458+459459+ // TEMPORARY: Generate test data for Phase 1 testing
460460+ // This creates a simple frequency sweep pattern
461461+ for (int i = 0; i < CQT_BINS; i++)
462462+ {
463463+ // Use existing FFT data to create some movement
464464+ int fftIndex = (i * FFT_SIZE) / CQT_BINS;
465465+ if (fftIndex < FFT_SIZE)
466466+ {
467467+ // Map FFT data to CQT bins with some scaling
468468+ cqtData[i] = fftData[fftIndex] * 0.5f;
469469+470470+ // Apply smoothing
471471+ cqtSmoothingData[i] = cqtSmoothingData[i] * 0.8f + cqtData[i] * 0.2f;
472472+473473+ // Normalize
474474+ cqtNormalizedData[i] = cqtSmoothingData[i];
475475+ if (cqtNormalizedData[i] > 1.0f) cqtNormalizedData[i] = 1.0f;
476476+ }
477477+ }
478478+ }
445479 }
446480 if (!core->state.initialized)
447481 {
+57
src/cqtdata.c
···11+#include "cqtdata.h"
22+#include <string.h>
33+#include <stdlib.h>
44+55+// Global CQT data arrays
66+float cqtData[CQT_BINS];
77+float cqtSmoothingData[CQT_BINS];
88+float cqtNormalizedData[CQT_BINS];
99+1010+// Peak tracking for auto-gain
1111+float cqtPeakValue = 1.0f;
1212+float cqtPeakSmoothValue = 1.0f;
1313+1414+// Enable flag (tied to fftEnabled initially)
1515+bool cqtEnabled = false;
1616+1717+// Array of kernels, one per CQT bin
1818+CqtKernel cqtKernels[CQT_BINS];
1919+2020+void CQT_Init(void)
2121+{
2222+ // Zero all data arrays
2323+ memset(cqtData, 0, sizeof(cqtData));
2424+ memset(cqtSmoothingData, 0, sizeof(cqtSmoothingData));
2525+ memset(cqtNormalizedData, 0, sizeof(cqtNormalizedData));
2626+2727+ // Initialize peak values
2828+ cqtPeakValue = 1.0f;
2929+ cqtPeakSmoothValue = 1.0f;
3030+3131+ // Zero kernel pointers
3232+ memset(cqtKernels, 0, sizeof(cqtKernels));
3333+}
3434+3535+void CQT_Cleanup(void)
3636+{
3737+ // Free all allocated kernel memory
3838+ for (int i = 0; i < CQT_BINS; i++)
3939+ {
4040+ if (cqtKernels[i].real)
4141+ {
4242+ free(cqtKernels[i].real);
4343+ cqtKernels[i].real = NULL;
4444+ }
4545+ if (cqtKernels[i].imag)
4646+ {
4747+ free(cqtKernels[i].imag);
4848+ cqtKernels[i].imag = NULL;
4949+ }
5050+ if (cqtKernels[i].indices)
5151+ {
5252+ free(cqtKernels[i].indices);
5353+ cqtKernels[i].indices = NULL;
5454+ }
5555+ cqtKernels[i].length = 0;
5656+ }
5757+}
+48
src/cqtdata.h
···11+#pragma once
22+#include <stdbool.h>
33+44+#define CQT_BINS 120
55+#define CQT_OCTAVES 10
66+#define CQT_BINS_PER_OCTAVE 12
77+#define CQT_FFT_SIZE 4096 // Larger FFT for better sub-bass resolution
88+99+// CQT frequency range
1010+#define CQT_MIN_FREQ 20.0f // Below piano's lowest A
1111+#define CQT_MAX_FREQ 20480.0f // Nearest note to 20kHz
1212+1313+// Smoothing parameters
1414+#define CQT_SMOOTHING_FACTOR 0.7f
1515+#define CQT_SPARSITY_THRESHOLD 0.01f
1616+1717+// Raw CQT magnitude data
1818+extern float cqtData[CQT_BINS];
1919+2020+// Smoothed CQT data for visual stability
2121+extern float cqtSmoothingData[CQT_BINS];
2222+2323+// Normalized CQT data (0-1 range)
2424+extern float cqtNormalizedData[CQT_BINS];
2525+2626+// Peak tracking for auto-gain
2727+extern float cqtPeakValue;
2828+extern float cqtPeakSmoothValue;
2929+3030+// Enable flag (tied to fftEnabled initially)
3131+extern bool cqtEnabled;
3232+3333+// Sparse kernel storage structures
3434+typedef struct {
3535+ float* real; // Real parts of kernel (sparse)
3636+ float* imag; // Imaginary parts of kernel (sparse)
3737+ int* indices; // Non-zero FFT bin indices
3838+ int length; // Number of non-zero elements
3939+} CqtKernel;
4040+4141+// Array of kernels, one per CQT bin
4242+extern CqtKernel cqtKernels[CQT_BINS];
4343+4444+// Initialize CQT data structures
4545+void CQT_Init(void);
4646+4747+// Free CQT kernel memory
4848+void CQT_Cleanup(void);
+152
src/ext/cqt.c
···11+#include "cqt.h"
22+#include "cqt_kernel.h"
33+#include "../cqtdata.h"
44+#include "../fftdata.h"
55+#include "kiss_fftr.h"
66+#include <math.h>
77+#include <string.h>
88+99+// FFT configuration for CQT
1010+static kiss_fftr_cfg cqtFftCfg = NULL;
1111+static float* cqtAudioBuffer = NULL;
1212+static kiss_fft_cpx* cqtFftOutput = NULL;
1313+1414+// Initialize CQT processing
1515+bool CQT_Open(void)
1616+{
1717+ // Initialize data structures
1818+ CQT_Init();
1919+2020+ // Allocate FFT buffers
2121+ cqtAudioBuffer = (float*)malloc(CQT_FFT_SIZE * sizeof(float));
2222+ cqtFftOutput = (kiss_fft_cpx*)malloc((CQT_FFT_SIZE/2 + 1) * sizeof(kiss_fft_cpx));
2323+2424+ if (!cqtAudioBuffer || !cqtFftOutput)
2525+ {
2626+ CQT_Close();
2727+ return false;
2828+ }
2929+3030+ // Create FFT configuration
3131+ cqtFftCfg = kiss_fftr_alloc(CQT_FFT_SIZE, 0, NULL, NULL);
3232+ if (!cqtFftCfg)
3333+ {
3434+ CQT_Close();
3535+ return false;
3636+ }
3737+3838+ // Configure CQT kernels
3939+ CqtKernelConfig config = {
4040+ .fftSize = CQT_FFT_SIZE,
4141+ .numBins = CQT_BINS,
4242+ .minFreq = CQT_MIN_FREQ,
4343+ .maxFreq = CQT_MAX_FREQ,
4444+ .sampleRate = 44100.0f, // Standard TIC-80 sample rate
4545+ .windowType = CQT_WINDOW_HAMMING,
4646+ .sparsityThreshold = CQT_SPARSITY_THRESHOLD
4747+ };
4848+4949+ // Generate kernels
5050+ if (!CQT_GenerateKernels(cqtKernels, &config))
5151+ {
5252+ CQT_Close();
5353+ return false;
5454+ }
5555+5656+ return true;
5757+}
5858+5959+// Apply CQT kernels to FFT output
6060+void CQT_ApplyKernels(const float* fftReal, const float* fftImag)
6161+{
6262+ // Clear CQT output
6363+ memset(cqtData, 0, sizeof(cqtData));
6464+6565+ // Apply each kernel to compute CQT bins
6666+ for (int bin = 0; bin < CQT_BINS; bin++)
6767+ {
6868+ CqtKernel* kernel = &cqtKernels[bin];
6969+ float real = 0.0f;
7070+ float imag = 0.0f;
7171+7272+ // Sparse matrix multiplication
7373+ for (int k = 0; k < kernel->length; k++)
7474+ {
7575+ int idx = kernel->indices[k];
7676+ // Complex multiplication: (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
7777+ real += fftReal[idx] * kernel->real[k] - fftImag[idx] * kernel->imag[k];
7878+ imag += fftReal[idx] * kernel->imag[k] + fftImag[idx] * kernel->real[k];
7979+ }
8080+8181+ // Calculate magnitude
8282+ cqtData[bin] = sqrt(real * real + imag * imag);
8383+ }
8484+}
8585+8686+// Process CQT from audio data (using existing FFT data from fftdata.h)
8787+void CQT_Process(const float* fftReal, const float* fftImag)
8888+{
8989+ if (!cqtFftCfg || !cqtEnabled) return;
9090+9191+ // Note: In the full implementation, we would:
9292+ // 1. Get audio data from the shared buffer
9393+ // 2. Perform our own 4096-point FFT
9494+ // For now, we'll process using provided FFT data
9595+9696+ // Apply CQT kernels
9797+ CQT_ApplyKernels(fftReal, fftImag);
9898+9999+ // Apply smoothing
100100+ for (int i = 0; i < CQT_BINS; i++)
101101+ {
102102+ cqtSmoothingData[i] = cqtSmoothingData[i] * CQT_SMOOTHING_FACTOR +
103103+ cqtData[i] * (1.0f - CQT_SMOOTHING_FACTOR);
104104+ }
105105+106106+ // Find peak for normalization
107107+ float currentPeak = 0.0f;
108108+ for (int i = 0; i < CQT_BINS; i++)
109109+ {
110110+ if (cqtSmoothingData[i] > currentPeak)
111111+ currentPeak = cqtSmoothingData[i];
112112+ }
113113+114114+ // Smooth peak value
115115+ if (currentPeak > cqtPeakSmoothValue)
116116+ cqtPeakSmoothValue = currentPeak;
117117+ else
118118+ cqtPeakSmoothValue = cqtPeakSmoothValue * 0.99f + currentPeak * 0.01f;
119119+120120+ // Normalize data
121121+ float normalizer = (cqtPeakSmoothValue > 0.0001f) ? (1.0f / cqtPeakSmoothValue) : 1.0f;
122122+ for (int i = 0; i < CQT_BINS; i++)
123123+ {
124124+ cqtNormalizedData[i] = cqtSmoothingData[i] * normalizer;
125125+ if (cqtNormalizedData[i] > 1.0f) cqtNormalizedData[i] = 1.0f;
126126+ }
127127+}
128128+129129+// Close CQT processing and free resources
130130+void CQT_Close(void)
131131+{
132132+ if (cqtFftCfg)
133133+ {
134134+ free(cqtFftCfg);
135135+ cqtFftCfg = NULL;
136136+ }
137137+138138+ if (cqtAudioBuffer)
139139+ {
140140+ free(cqtAudioBuffer);
141141+ cqtAudioBuffer = NULL;
142142+ }
143143+144144+ if (cqtFftOutput)
145145+ {
146146+ free(cqtFftOutput);
147147+ cqtFftOutput = NULL;
148148+ }
149149+150150+ // Clean up kernels
151151+ CQT_Cleanup();
152152+}
+17
src/ext/cqt.h
···11+#pragma once
22+33+#include <stdbool.h>
44+55+// Initialize CQT processing
66+// Returns true on success, false on failure
77+bool CQT_Open(void);
88+99+// Process CQT from FFT data
1010+// fftData: Complex FFT output (size should be CQT_FFT_SIZE/2 + 1)
1111+void CQT_Process(const float* fftReal, const float* fftImag);
1212+1313+// Close CQT processing and free resources
1414+void CQT_Close(void);
1515+1616+// Apply CQT kernels to FFT output
1717+void CQT_ApplyKernels(const float* fftReal, const float* fftImag);
+203
src/ext/cqt_kernel.c
···11+#include "cqt_kernel.h"
22+#include <math.h>
33+#include <stdlib.h>
44+#include <string.h>
55+#include "../ext/kiss_fftr.h"
66+77+#ifndef M_PI
88+#define M_PI 3.14159265358979323846
99+#endif
1010+1111+// Generate logarithmically spaced center frequencies
1212+void CQT_GenerateCenterFrequencies(float* frequencies, int numBins, float minFreq, float maxFreq)
1313+{
1414+ float logRatio = log(maxFreq / minFreq);
1515+ for (int i = 0; i < numBins; i++)
1616+ {
1717+ frequencies[i] = minFreq * exp(logRatio * i / (numBins - 1));
1818+ }
1919+}
2020+2121+// Calculate Q factor for constant-Q transform
2222+float CQT_CalculateQ(int binsPerOctave)
2323+{
2424+ // Q = 1 / (2^(1/binsPerOctave) - 1)
2525+ // For 12 bins/octave, Q ≈ 17.0
2626+ return 1.0f / (pow(2.0, 1.0 / binsPerOctave) - 1.0f);
2727+}
2828+2929+// Generate Hamming window
3030+static void generateHammingWindow(float* window, int length)
3131+{
3232+ const float a0 = 0.54f;
3333+ const float a1 = 0.46f;
3434+3535+ for (int i = 0; i < length; i++)
3636+ {
3737+ window[i] = a0 - a1 * cos(2.0 * M_PI * i / (length - 1));
3838+ }
3939+}
4040+4141+// Generate Gaussian window
4242+static void generateGaussianWindow(float* window, int length)
4343+{
4444+ const float sigma = 0.5f; // Standard deviation factor
4545+ float center = (length - 1) / 2.0f;
4646+ float normFactor = sigma * length / 2.0f;
4747+4848+ for (int i = 0; i < length; i++)
4949+ {
5050+ float x = (i - center) / normFactor;
5151+ window[i] = exp(-0.5f * x * x);
5252+ }
5353+}
5454+5555+// Generate a single CQT kernel
5656+static bool generateSingleKernel(
5757+ CqtKernel* kernel,
5858+ kiss_fftr_cfg fftCfg,
5959+ int fftSize,
6060+ float centerFreq,
6161+ float minFreq,
6262+ float sampleRate,
6363+ CqtWindowType windowType,
6464+ float sparsityThreshold)
6565+{
6666+ // Calculate window length based on frequency (inverse relationship)
6767+ float factor = centerFreq / minFreq;
6868+ int windowLength = (int)(fftSize / factor);
6969+ if (windowLength < 1) windowLength = 1;
7070+ if (windowLength > fftSize) windowLength = fftSize;
7171+7272+ // Allocate temporary arrays
7373+ float* timeKernel = (float*)calloc(fftSize, sizeof(float));
7474+ kiss_fft_cpx* freqKernel = (kiss_fft_cpx*)malloc((fftSize/2 + 1) * sizeof(kiss_fft_cpx));
7575+7676+ if (!timeKernel || !freqKernel)
7777+ {
7878+ free(timeKernel);
7979+ free(freqKernel);
8080+ return false;
8181+ }
8282+8383+ // Generate window in the center of the kernel
8484+ int windowStart = (fftSize - windowLength) / 2;
8585+ float* window = &timeKernel[windowStart];
8686+8787+ switch (windowType)
8888+ {
8989+ case CQT_WINDOW_HAMMING:
9090+ generateHammingWindow(window, windowLength);
9191+ break;
9292+ case CQT_WINDOW_GAUSSIAN:
9393+ generateGaussianWindow(window, windowLength);
9494+ break;
9595+ }
9696+9797+ // Modulate window with complex exponential
9898+ for (int i = 0; i < fftSize; i++)
9999+ {
100100+ float phase = 2.0f * M_PI * centerFreq / sampleRate * (i - fftSize / 2);
101101+ timeKernel[i] *= cos(phase);
102102+ }
103103+104104+ // Normalize by window length
105105+ for (int i = 0; i < fftSize; i++)
106106+ {
107107+ timeKernel[i] /= windowLength;
108108+ }
109109+110110+ // Perform FFT
111111+ kiss_fftr(fftCfg, timeKernel, freqKernel);
112112+113113+ // Count non-zero elements (above sparsity threshold)
114114+ int nonZeroCount = 0;
115115+ for (int i = 0; i <= fftSize/2; i++)
116116+ {
117117+ float magnitude = sqrt(freqKernel[i].r * freqKernel[i].r +
118118+ freqKernel[i].i * freqKernel[i].i);
119119+ if (magnitude > sparsityThreshold)
120120+ {
121121+ nonZeroCount++;
122122+ }
123123+ }
124124+125125+ // Allocate sparse storage
126126+ kernel->real = (float*)malloc(nonZeroCount * sizeof(float));
127127+ kernel->imag = (float*)malloc(nonZeroCount * sizeof(float));
128128+ kernel->indices = (int*)malloc(nonZeroCount * sizeof(int));
129129+ kernel->length = nonZeroCount;
130130+131131+ if (!kernel->real || !kernel->imag || !kernel->indices)
132132+ {
133133+ free(kernel->real);
134134+ free(kernel->imag);
135135+ free(kernel->indices);
136136+ free(timeKernel);
137137+ free(freqKernel);
138138+ return false;
139139+ }
140140+141141+ // Store non-zero elements
142142+ int sparseIndex = 0;
143143+ for (int i = 0; i <= fftSize/2; i++)
144144+ {
145145+ float magnitude = sqrt(freqKernel[i].r * freqKernel[i].r +
146146+ freqKernel[i].i * freqKernel[i].i);
147147+ if (magnitude > sparsityThreshold)
148148+ {
149149+ kernel->real[sparseIndex] = freqKernel[i].r;
150150+ kernel->imag[sparseIndex] = freqKernel[i].i;
151151+ kernel->indices[sparseIndex] = i;
152152+ sparseIndex++;
153153+ }
154154+ }
155155+156156+ free(timeKernel);
157157+ free(freqKernel);
158158+ return true;
159159+}
160160+161161+// Generate CQT kernels for all bins
162162+bool CQT_GenerateKernels(CqtKernel* kernels, const CqtKernelConfig* config)
163163+{
164164+ // Generate center frequencies
165165+ float* centerFreqs = (float*)malloc(config->numBins * sizeof(float));
166166+ if (!centerFreqs) return false;
167167+168168+ CQT_GenerateCenterFrequencies(centerFreqs, config->numBins,
169169+ config->minFreq, config->maxFreq);
170170+171171+ // Create FFT configuration
172172+ kiss_fftr_cfg fftCfg = kiss_fftr_alloc(config->fftSize, 0, NULL, NULL);
173173+ if (!fftCfg)
174174+ {
175175+ free(centerFreqs);
176176+ return false;
177177+ }
178178+179179+ // Generate kernel for each CQT bin
180180+ bool success = true;
181181+ for (int i = 0; i < config->numBins; i++)
182182+ {
183183+ if (!generateSingleKernel(&kernels[i], fftCfg, config->fftSize,
184184+ centerFreqs[i], config->minFreq,
185185+ config->sampleRate, config->windowType,
186186+ config->sparsityThreshold))
187187+ {
188188+ // Clean up on failure
189189+ for (int j = 0; j < i; j++)
190190+ {
191191+ free(kernels[j].real);
192192+ free(kernels[j].imag);
193193+ free(kernels[j].indices);
194194+ }
195195+ success = false;
196196+ break;
197197+ }
198198+ }
199199+200200+ free(fftCfg);
201201+ free(centerFreqs);
202202+ return success;
203203+}
+31
src/ext/cqt_kernel.h
···11+#pragma once
22+33+#include <stdbool.h>
44+#include "../cqtdata.h"
55+66+// Window types for CQT kernels
77+typedef enum {
88+ CQT_WINDOW_HAMMING = 0,
99+ CQT_WINDOW_GAUSSIAN
1010+} CqtWindowType;
1111+1212+// CQT kernel configuration
1313+typedef struct {
1414+ int fftSize; // FFT size (4096)
1515+ int numBins; // Number of CQT bins (120)
1616+ float minFreq; // Minimum frequency (20 Hz)
1717+ float maxFreq; // Maximum frequency (20480 Hz)
1818+ float sampleRate; // Sample rate (44100 Hz)
1919+ CqtWindowType windowType; // Window function type
2020+ float sparsityThreshold; // Threshold for sparse matrix (0.01)
2121+} CqtKernelConfig;
2222+2323+// Generate CQT kernels for all bins
2424+// Returns true on success, false on failure
2525+bool CQT_GenerateKernels(CqtKernel* kernels, const CqtKernelConfig* config);
2626+2727+// Calculate Q factor for given parameters
2828+float CQT_CalculateQ(int binsPerOctave);
2929+3030+// Generate center frequencies for CQT bins
3131+void CQT_GenerateCenterFrequencies(float* frequencies, int numBins, float minFreq, float maxFreq);