···2121// SOFTWARE.
22222323#include "core/core.h"
2424-#include "cqtdata.h"
2424+#include "vqtdata.h"
25252626#include <stdlib.h>
2727#include <lua.h>
···15961596 return 0;
15971597}
1598159815991599-static s32 lua_cqt(lua_State* lua)
15991599+static s32 lua_vqt(lua_State* lua)
16001600{
16011601 s32 top = lua_gettop(lua);
16021602···16051605 s32 bin = getLuaNumber(lua, 1);
1606160616071607 // Validate bin range
16081608- if (bin < 0 || bin >= CQT_BINS)
16081608+ if (bin < 0 || bin >= VQT_BINS)
16091609 {
16101610- luaL_error(lua, "cqt bin out of range (0-%d)\n", CQT_BINS - 1);
16101610+ luaL_error(lua, "vqt bin out of range (0-%d)\n", VQT_BINS - 1);
16111611 return 0;
16121612 }
1613161316141614- // Return normalized CQT data
16151615- lua_pushnumber(lua, cqtNormalizedData[bin]);
16141614+ // Return normalized VQT data
16151615+ lua_pushnumber(lua, vqtNormalizedData[bin]);
16161616 return 1;
16171617 }
1618161816191619- luaL_error(lua, "invalid params, cqt(bin)\n");
16191619+ luaL_error(lua, "invalid params, vqt(bin)\n");
16201620 return 0;
16211621}
16221622···16741674 registerLuaFunction(core, lua_dofile, "dofile");
16751675 registerLuaFunction(core, lua_loadfile, "loadfile");
1676167616771677- // Register CQT function
16781678- registerLuaFunction(core, lua_cqt, "cqt");
16771677+ // Register VQT function
16781678+ registerLuaFunction(core, lua_vqt, "vqt");
16791679}
1680168016811681void luaapi_close(tic_mem* tic)
+8-8
src/core/core.c
···22222323#include "fftdata.h"
2424#include "../ext/fft.h"
2525-#include "cqtdata.h"
2626-#include "../ext/cqt.h"
2525+#include "vqtdata.h"
2626+#include "../ext/vqt.h"
27272828#include "api.h"
2929#include "core.h"
···445445 {
446446 FFT_GetFFT(fftData);
447447448448- // Process CQT using raw audio buffer
449449- // For now, tie CQT to FFT enable flag
450450- cqtEnabled = fftEnabled;
451451- if (cqtEnabled)
448448+ // Process VQT using raw audio buffer
449449+ // For now, tie VQT to FFT enable flag
450450+ vqtEnabled = fftEnabled;
451451+ if (vqtEnabled)
452452 {
453453- // Process CQT from the shared audio buffer
454454- CQT_ProcessAudio();
453453+ // Process VQT from the shared audio buffer
454454+ VQT_ProcessAudio();
455455 }
456456 }
457457 if (!core->state.initialized)
-71
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-// Spectral whitening data
2121-float cqtBinAverages[CQT_BINS];
2222-float cqtWhitenedData[CQT_BINS];
2323-2424-void CQT_Init(void)
2525-{
2626- // Zero all data arrays
2727- memset(cqtData, 0, sizeof(cqtData));
2828- memset(cqtSmoothingData, 0, sizeof(cqtSmoothingData));
2929- memset(cqtNormalizedData, 0, sizeof(cqtNormalizedData));
3030-3131- // Initialize peak values
3232- cqtPeakValue = 1.0f;
3333- cqtPeakSmoothValue = 1.0f;
3434-3535- // Zero kernel pointers
3636- memset(cqtKernels, 0, sizeof(cqtKernels));
3737-3838- // Initialize spectral whitening arrays
3939- memset(cqtBinAverages, 0, sizeof(cqtBinAverages));
4040- memset(cqtWhitenedData, 0, sizeof(cqtWhitenedData));
4141-4242- // Start with small initial averages to avoid divide-by-zero
4343- for (int i = 0; i < CQT_BINS; i++)
4444- {
4545- cqtBinAverages[i] = CQT_WHITENING_FLOOR;
4646- }
4747-}
4848-4949-void CQT_Cleanup(void)
5050-{
5151- // Free all allocated kernel memory
5252- for (int i = 0; i < CQT_BINS; i++)
5353- {
5454- if (cqtKernels[i].real)
5555- {
5656- free(cqtKernels[i].real);
5757- cqtKernels[i].real = NULL;
5858- }
5959- if (cqtKernels[i].imag)
6060- {
6161- free(cqtKernels[i].imag);
6262- cqtKernels[i].imag = NULL;
6363- }
6464- if (cqtKernels[i].indices)
6565- {
6666- free(cqtKernels[i].indices);
6767- cqtKernels[i].indices = NULL;
6868- }
6969- cqtKernels[i].length = 0;
7070- }
7171-}
-66
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 8192 // 8K FFT - optimized variable-Q for responsive visualization, ~5.4 fps
88-99-// CQT frequency range
1010-#define CQT_MIN_FREQ 20.0f // Sub-bass for electronic music
1111-#define CQT_MAX_FREQ 20480.0f // Nearest note to 20kHz
1212-1313-// Smoothing parameters
1414-#define CQT_SMOOTHING_FACTOR 0.3f // Reduced from 0.7f for more responsive display
1515-#define CQT_SPARSITY_THRESHOLD 0.01f
1616-1717-// Variable-Q configuration (optimized for 8K FFT)
1818-#define CQT_VARIABLE_Q_ENABLED 1
1919-#define CQT_8K_OPTIMIZED 1 // Uses Q values that fit within 8K FFT
2020-#define CQT_BASS_Q_MIN 7.4f // Minimum Q at 20 Hz (constrained by 8K)
2121-#define CQT_BASS_Q_MAX 17.0f // Full Q achieved at 80+ Hz
2222-#define CQT_TREBLE_Q_FACTOR 11.0f // Smoother for high frequencies
2323-2424-// Spectral whitening toggle (set to 0 to disable)
2525-#define CQT_SPECTRAL_WHITENING_ENABLED 1
2626-2727-// Spectral whitening parameters
2828-#define CQT_WHITENING_DECAY 0.99f // Running average decay (0.98-0.995 for 1-2 second adaptation)
2929-#define CQT_WHITENING_FLOOR 0.001f // Minimum average to prevent divide-by-zero
3030-3131-// Raw CQT magnitude data
3232-extern float cqtData[CQT_BINS];
3333-3434-// Smoothed CQT data for visual stability
3535-extern float cqtSmoothingData[CQT_BINS];
3636-3737-// Normalized CQT data (0-1 range)
3838-extern float cqtNormalizedData[CQT_BINS];
3939-4040-// Peak tracking for auto-gain
4141-extern float cqtPeakValue;
4242-extern float cqtPeakSmoothValue;
4343-4444-// Enable flag (tied to fftEnabled initially)
4545-extern bool cqtEnabled;
4646-4747-// Spectral whitening data
4848-extern float cqtBinAverages[CQT_BINS]; // Long-term running averages per bin
4949-extern float cqtWhitenedData[CQT_BINS]; // Whitened CQT data
5050-5151-// Sparse kernel storage structures
5252-typedef struct {
5353- float* real; // Real parts of kernel (sparse)
5454- float* imag; // Imaginary parts of kernel (sparse)
5555- int* indices; // Non-zero FFT bin indices
5656- int length; // Number of non-zero elements
5757-} CqtKernel;
5858-5959-// Array of kernels, one per CQT bin
6060-extern CqtKernel cqtKernels[CQT_BINS];
6161-6262-// Initialize CQT data structures
6363-void CQT_Init(void);
6464-6565-// Free CQT kernel memory
6666-void CQT_Cleanup(void);
-441
src/ext/cqt.c
···11-#include "cqt.h"
22-#include "cqt_kernel.h"
33-#include "../cqtdata.h"
44-#include "../fftdata.h"
55-#include "fft.h"
66-#include "kiss_fftr.h"
77-#include <math.h>
88-#include <string.h>
99-#include <stdbool.h>
1010-#include <stdio.h>
1111-#include <time.h>
1212-1313-#define CQT_DEBUG
1414-1515-// FFT configuration for CQT
1616-static kiss_fftr_cfg cqtFftCfg = NULL;
1717-static float* cqtAudioBuffer = NULL;
1818-static kiss_fft_cpx* cqtFftOutput = NULL;
1919-2020-// Benchmark different FFT sizes
2121-static void CQT_BenchmarkFFT(void)
2222-{
2323- printf("\nCQT FFT Benchmark on this CPU:\n");
2424- printf("================================\n");
2525-2626- int sizes[] = {4096, 6144, 8192, 12288, 16384, 24576, 32768};
2727- int numSizes = 7;
2828-2929- for (int s = 0; s < numSizes; s++)
3030- {
3131- int fftSize = sizes[s];
3232-3333- // Allocate buffers
3434- float* testInput = (float*)calloc(fftSize, sizeof(float));
3535- kiss_fft_cpx* testOutput = (kiss_fft_cpx*)malloc((fftSize/2 + 1) * sizeof(kiss_fft_cpx));
3636- kiss_fftr_cfg testCfg = kiss_fftr_alloc(fftSize, 0, NULL, NULL);
3737-3838- if (!testInput || !testOutput || !testCfg)
3939- {
4040- printf("Failed to allocate for size %d\n", fftSize);
4141- continue;
4242- }
4343-4444- // Fill with test signal
4545- for (int i = 0; i < fftSize; i++)
4646- {
4747- testInput[i] = sin(2.0 * M_PI * 440.0 * i / 44100.0);
4848- }
4949-5050- // Warm up
5151- for (int i = 0; i < 10; i++)
5252- {
5353- kiss_fftr(testCfg, testInput, testOutput);
5454- }
5555-5656- // Time 100 iterations
5757- clock_t start = clock();
5858- for (int i = 0; i < 100; i++)
5959- {
6060- kiss_fftr(testCfg, testInput, testOutput);
6161- }
6262- clock_t end = clock();
6363-6464- double avgTime = (double)(end - start) / CLOCKS_PER_SEC * 1000.0 / 100.0;
6565- printf("%5d-point FFT: %.3f ms\n", fftSize, avgTime);
6666-6767- // Cleanup
6868- free(testInput);
6969- free(testOutput);
7070- free(testCfg);
7171- }
7272-7373- printf("================================\n\n");
7474-}
7575-7676-// Initialize CQT processing
7777-bool CQT_Open(void)
7878-{
7979- // Initialize data structures
8080- CQT_Init();
8181-8282- // Allocate FFT buffers
8383- cqtAudioBuffer = (float*)malloc(CQT_FFT_SIZE * sizeof(float));
8484- cqtFftOutput = (kiss_fft_cpx*)malloc((CQT_FFT_SIZE/2 + 1) * sizeof(kiss_fft_cpx));
8585-8686- if (!cqtAudioBuffer || !cqtFftOutput)
8787- {
8888- CQT_Close();
8989- return false;
9090- }
9191-9292- // Create FFT configuration
9393- cqtFftCfg = kiss_fftr_alloc(CQT_FFT_SIZE, 0, NULL, NULL);
9494- if (!cqtFftCfg)
9595- {
9696- CQT_Close();
9797- return false;
9898- }
9999-100100- // Configure CQT kernels
101101- CqtKernelConfig config = {
102102- .fftSize = CQT_FFT_SIZE,
103103- .numBins = CQT_BINS,
104104- .minFreq = CQT_MIN_FREQ,
105105- .maxFreq = CQT_MAX_FREQ,
106106- .sampleRate = 44100.0f, // Standard TIC-80 sample rate
107107- .windowType = CQT_WINDOW_HAMMING,
108108- .sparsityThreshold = CQT_SPARSITY_THRESHOLD
109109- };
110110-111111- // Generate kernels
112112- if (!CQT_GenerateKernels(cqtKernels, &config))
113113- {
114114- CQT_Close();
115115- return false;
116116- }
117117-118118- // Run FFT benchmark on first initialization
119119- static bool benchmarkRun = false;
120120- if (!benchmarkRun)
121121- {
122122- CQT_BenchmarkFFT();
123123- benchmarkRun = true;
124124- }
125125-126126- // Debug: Print variable Q values across frequency spectrum
127127- #ifdef CQT_DEBUG
128128- float centerFreqs[CQT_BINS];
129129- CQT_GenerateCenterFrequencies(centerFreqs, CQT_BINS, CQT_MIN_FREQ, CQT_MAX_FREQ);
130130-131131- printf("\nCQT Variable-Q Implementation (8K FFT Optimized):\n");
132132- printf("================================================\n");
133133-134134- // Show Q values for key frequency ranges
135135- float testFreqs[] = {20, 25, 30, 40, 50, 65, 80, 120, 160, 240, 320, 440, 640, 1000, 2000, 4000};
136136- printf("Frequency | Design Q | Window Length | Effective Q | Resolution\n");
137137- printf("----------|----------|---------------|-------------|------------\n");
138138-139139- for (int i = 0; i < 16; i++)
140140- {
141141- float freq = testFreqs[i];
142142- // Find closest CQT bin
143143- int closestBin = 0;
144144- float minDiff = fabs(centerFreqs[0] - freq);
145145- for (int j = 1; j < CQT_BINS; j++)
146146- {
147147- float diff = fabs(centerFreqs[j] - freq);
148148- if (diff < minDiff)
149149- {
150150- minDiff = diff;
151151- closestBin = j;
152152- }
153153- }
154154-155155- // Calculate Q values using 8K-optimized function
156156- float designQ;
157157- if (freq < 25.0f) designQ = 7.4f;
158158- else if (freq < 30.0f) designQ = 9.2f;
159159- else if (freq < 40.0f) designQ = 11.5f;
160160- else if (freq < 50.0f) designQ = 14.5f;
161161- else if (freq < 65.0f) designQ = 16.0f;
162162- else if (freq < 80.0f) designQ = 17.0f;
163163- else if (freq < 160.0f) designQ = 17.0f;
164164- else if (freq < 320.0f) designQ = 15.0f;
165165- else if (freq < 640.0f) designQ = 13.0f;
166166- else designQ = 11.0f;
167167-168168- int windowLength = (int)(designQ * 44100.0f / freq);
169169- if (windowLength > CQT_FFT_SIZE) windowLength = CQT_FFT_SIZE;
170170- float effectiveQ = windowLength * freq / 44100.0f;
171171- float bandwidth = freq / effectiveQ;
172172-173173- printf("%7.0f Hz | %7.1f | %13d | %11.1f | %7.1f Hz\n",
174174- freq, designQ, windowLength, effectiveQ, bandwidth);
175175- }
176176-177177- printf("\nFirst 10 CQT bins:\n");
178178- for (int i = 0; i < 10 && i < CQT_BINS; i++)
179179- {
180180- int expectedBin = (int)(centerFreqs[i] * CQT_FFT_SIZE / 44100.0f);
181181- printf(" Bin %d: %.2f Hz -> FFT bin %d\n", i, centerFreqs[i], expectedBin);
182182- }
183183- #endif
184184-185185- return true;
186186-}
187187-188188-// Apply CQT kernels to FFT output
189189-void CQT_ApplyKernels(const float* fftReal, const float* fftImag)
190190-{
191191- // Clear CQT output
192192- memset(cqtData, 0, sizeof(cqtData));
193193-194194- // Apply each kernel to compute CQT bins
195195- for (int bin = 0; bin < CQT_BINS; bin++)
196196- {
197197- CqtKernel* kernel = &cqtKernels[bin];
198198-199199- // Check if kernel is valid
200200- if (!kernel->real || !kernel->imag || !kernel->indices || kernel->length == 0)
201201- {
202202- cqtData[bin] = 0.0f;
203203- continue;
204204- }
205205-206206- float real = 0.0f;
207207- float imag = 0.0f;
208208-209209- // Sparse matrix multiplication
210210- for (int k = 0; k < kernel->length; k++)
211211- {
212212- int idx = kernel->indices[k];
213213- // Ensure index is within bounds
214214- if (idx < 0 || idx >= CQT_FFT_SIZE/2 + 1)
215215- continue;
216216-217217- // Complex multiplication: (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
218218- real += fftReal[idx] * kernel->real[k] - fftImag[idx] * kernel->imag[k];
219219- imag += fftReal[idx] * kernel->imag[k] + fftImag[idx] * kernel->real[k];
220220- }
221221-222222- // Calculate magnitude with gain boost
223223- cqtData[bin] = sqrt(real * real + imag * imag) * 2.0f; // Match FFT gain factor
224224-225225- // Check for NaN or Inf
226226- if (!isfinite(cqtData[bin]))
227227- cqtData[bin] = 0.0f;
228228- }
229229-}
230230-231231-// Process CQT from audio data
232232-void CQT_ProcessAudio(void)
233233-{
234234- if (!cqtFftCfg || !cqtEnabled) return;
235235-236236- // Check if kernels are initialized
237237- bool kernelsValid = false;
238238- for (int i = 0; i < CQT_BINS; i++)
239239- {
240240- if (cqtKernels[i].real && cqtKernels[i].length > 0)
241241- {
242242- kernelsValid = true;
243243- break;
244244- }
245245- }
246246-247247- if (!kernelsValid)
248248- {
249249- // Kernels not initialized, set all output to zero
250250- memset(cqtData, 0, sizeof(cqtData));
251251- memset(cqtSmoothingData, 0, sizeof(cqtSmoothingData));
252252- memset(cqtNormalizedData, 0, sizeof(cqtNormalizedData));
253253- return;
254254- }
255255-256256- // Copy audio data from the shared buffer
257257- // sampleBuf is defined in fft.c as extern
258258- extern float sampleBuf[];
259259- memcpy(cqtAudioBuffer, sampleBuf, CQT_FFT_SIZE * sizeof(float));
260260-261261- // Check if we have any audio data
262262- float audioSum = 0.0f;
263263- for (int i = 0; i < CQT_FFT_SIZE; i++)
264264- {
265265- audioSum += fabs(cqtAudioBuffer[i]);
266266- }
267267-268268- if (audioSum < 0.0001f)
269269- {
270270- // No audio data, set output to zero
271271- memset(cqtData, 0, sizeof(cqtData));
272272- memset(cqtSmoothingData, 0, sizeof(cqtSmoothingData));
273273- memset(cqtNormalizedData, 0, sizeof(cqtNormalizedData));
274274- return;
275275- }
276276-277277- // Profiling variables
278278- static double totalFftTime = 0.0;
279279- static double totalKernelTime = 0.0;
280280- static int profileCount = 0;
281281-282282- // Perform 16384-point FFT with timing
283283- clock_t fftStart = clock();
284284- kiss_fftr(cqtFftCfg, cqtAudioBuffer, cqtFftOutput);
285285- clock_t fftEnd = clock();
286286-287287- // Extract real and imaginary components for kernel application
288288- float fftReal[CQT_FFT_SIZE/2 + 1];
289289- float fftImag[CQT_FFT_SIZE/2 + 1];
290290-291291- for (int i = 0; i <= CQT_FFT_SIZE/2; i++)
292292- {
293293- fftReal[i] = cqtFftOutput[i].r;
294294- fftImag[i] = cqtFftOutput[i].i;
295295- }
296296-297297- // Apply CQT kernels with timing
298298- clock_t kernelStart = clock();
299299- CQT_ApplyKernels(fftReal, fftImag);
300300- clock_t kernelEnd = clock();
301301-302302- // Calculate times in milliseconds
303303- double fftTime = (double)(fftEnd - fftStart) / CLOCKS_PER_SEC * 1000.0;
304304- double kernelTime = (double)(kernelEnd - kernelStart) / CLOCKS_PER_SEC * 1000.0;
305305-306306- totalFftTime += fftTime;
307307- totalKernelTime += kernelTime;
308308- profileCount++;
309309-310310- // Print profiling info every 60 frames (~1 second)
311311- if (profileCount % 60 == 0)
312312- {
313313- printf("CQT Performance (16K FFT):\n");
314314- printf(" FFT avg: %.3fms\n", totalFftTime / profileCount);
315315- printf(" Kernels avg: %.3fms\n", totalKernelTime / profileCount);
316316- printf(" Total avg: %.3fms\n", (totalFftTime + totalKernelTime) / profileCount);
317317- printf(" Samples: %d\n\n", profileCount);
318318- }
319319-320320- #ifdef CQT_DEBUG
321321- // Reduced debug output - CQT is working correctly now
322322- static int debugCounter = 0;
323323- if (++debugCounter % 300 == 0) // Print once every 5 seconds
324324- {
325325- // Find peak CQT bin
326326- int peakBin = 0;
327327- float peakVal = 0.0f;
328328- for (int i = 0; i < CQT_BINS; i++)
329329- {
330330- if (cqtData[i] > peakVal)
331331- {
332332- peakVal = cqtData[i];
333333- peakBin = i;
334334- }
335335- }
336336-337337- if (peakVal > 1.0f)
338338- {
339339- float peakFreq = CQT_MIN_FREQ * pow(2.0f, peakBin / 12.0f);
340340- printf("CQT: Peak at bin %d (%.1f Hz) with magnitude %.1f\n",
341341- peakBin, peakFreq, peakVal);
342342- }
343343- }
344344- #endif
345345-346346- // Apply spectral whitening if enabled
347347- #if CQT_SPECTRAL_WHITENING_ENABLED
348348- for (int i = 0; i < CQT_BINS; i++)
349349- {
350350- // Update running average for this bin
351351- cqtBinAverages[i] = cqtBinAverages[i] * CQT_WHITENING_DECAY +
352352- cqtData[i] * (1.0f - CQT_WHITENING_DECAY);
353353-354354- // Ensure average doesn't go below floor
355355- if (cqtBinAverages[i] < CQT_WHITENING_FLOOR)
356356- cqtBinAverages[i] = CQT_WHITENING_FLOOR;
357357-358358- // Apply whitening by dividing by the average
359359- cqtWhitenedData[i] = cqtData[i] / cqtBinAverages[i];
360360-361361- // Check for NaN or Inf
362362- if (!isfinite(cqtWhitenedData[i]))
363363- cqtWhitenedData[i] = 0.0f;
364364- }
365365-366366- // Apply smoothing to whitened data
367367- for (int i = 0; i < CQT_BINS; i++)
368368- {
369369- cqtSmoothingData[i] = cqtSmoothingData[i] * CQT_SMOOTHING_FACTOR +
370370- cqtWhitenedData[i] * (1.0f - CQT_SMOOTHING_FACTOR);
371371- }
372372- #else
373373- // Apply smoothing to raw data (spectral whitening disabled)
374374- for (int i = 0; i < CQT_BINS; i++)
375375- {
376376- cqtSmoothingData[i] = cqtSmoothingData[i] * CQT_SMOOTHING_FACTOR +
377377- cqtData[i] * (1.0f - CQT_SMOOTHING_FACTOR);
378378- }
379379- #endif
380380-381381- // Find peak for normalization
382382- float currentPeak = 0.0f;
383383- for (int i = 0; i < CQT_BINS; i++)
384384- {
385385- if (cqtSmoothingData[i] > currentPeak)
386386- currentPeak = cqtSmoothingData[i];
387387- }
388388-389389- // Initialize peak value if needed
390390- if (cqtPeakSmoothValue <= 0.0f)
391391- cqtPeakSmoothValue = 0.1f;
392392-393393- // Smooth peak value
394394- if (currentPeak > cqtPeakSmoothValue)
395395- cqtPeakSmoothValue = currentPeak;
396396- else
397397- cqtPeakSmoothValue = cqtPeakSmoothValue * 0.99f + currentPeak * 0.01f;
398398-399399- // Ensure peak value doesn't go too low
400400- if (cqtPeakSmoothValue < 0.0001f)
401401- cqtPeakSmoothValue = 0.0001f;
402402-403403- // Normalize data
404404- float normalizer = 1.0f / cqtPeakSmoothValue;
405405- for (int i = 0; i < CQT_BINS; i++)
406406- {
407407- cqtNormalizedData[i] = cqtSmoothingData[i] * normalizer;
408408- if (cqtNormalizedData[i] > 1.0f)
409409- cqtNormalizedData[i] = 1.0f;
410410-411411- // Final NaN check
412412- if (!isfinite(cqtNormalizedData[i]))
413413- cqtNormalizedData[i] = 0.0f;
414414- }
415415-}
416416-417417-418418-// Close CQT processing and free resources
419419-void CQT_Close(void)
420420-{
421421- if (cqtFftCfg)
422422- {
423423- free(cqtFftCfg);
424424- cqtFftCfg = NULL;
425425- }
426426-427427- if (cqtAudioBuffer)
428428- {
429429- free(cqtAudioBuffer);
430430- cqtAudioBuffer = NULL;
431431- }
432432-433433- if (cqtFftOutput)
434434- {
435435- free(cqtFftOutput);
436436- cqtFftOutput = NULL;
437437- }
438438-439439- // Clean up kernels
440440- CQT_Cleanup();
441441-}
-16
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 audio buffer (uses shared audio capture buffer)
1010-void CQT_ProcessAudio(void);
1111-1212-// Close CQT processing and free resources
1313-void CQT_Close(void);
1414-1515-// Apply CQT kernels to FFT output
1616-void CQT_ApplyKernels(const float* fftReal, const float* fftImag);
+17-17
src/ext/cqt_kernel.c
src/ext/vqt_kernel.c
···11-#include "cqt_kernel.h"
22-#include "../cqtdata.h"
11+#include "vqt_kernel.h"
22+#include "../vqtdata.h"
33#include <math.h>
44#include <stdlib.h>
55#include <string.h>
···1010#endif
11111212// Generate logarithmically spaced center frequencies for musical notes
1313-void CQT_GenerateCenterFrequencies(float* frequencies, int numBins, float minFreq, float maxFreq)
1313+void VQT_GenerateCenterFrequencies(float* frequencies, int numBins, float minFreq, float maxFreq)
1414{
1515- // For musical CQT with 12 bins per octave, we want equal temperament spacing
1515+ // For musical VQT with 12 bins per octave, we want equal temperament spacing
1616 // Each semitone is a factor of 2^(1/12) ≈ 1.0594631
1717 const float semitone = pow(2.0f, 1.0f / 12.0f);
1818···3535}
36363737// Calculate Q factor for constant-Q transform
3838-float CQT_CalculateQ(int binsPerOctave)
3838+float VQT_CalculateQ(int binsPerOctave)
3939{
4040 // Q = 1 / (2^(1/binsPerOctave) - 1)
4141 // For 12 bins/octave, Q ≈ 17.0
···5353 else if (centerFreq < 50.0f) return 14.5f; // 40-50 Hz: Near ideal
5454 else if (centerFreq < 65.0f) return 16.0f; // 50-65 Hz: Almost full Q
5555 else if (centerFreq < 80.0f) return 17.0f; // 65-80 Hz: Full standard Q
5656- else if (centerFreq < 160.0f) return 17.0f; // 80-160 Hz: Standard CQT
5656+ else if (centerFreq < 160.0f) return 17.0f; // 80-160 Hz: Standard VQT
5757 else if (centerFreq < 320.0f) return 15.0f; // 160-320 Hz: Slightly wider
5858 else if (centerFreq < 640.0f) return 13.0f; // 320-640 Hz: Smoother
5959 else return 11.0f; // 640+ Hz: Very smooth
···9595 }
9696}
97979898-// Generate a single CQT kernel
9898+// Generate a single VQT kernel
9999static bool generateSingleKernel(
100100- CqtKernel* kernel,
100100+ VqtKernel* kernel,
101101 kiss_fftr_cfg fftCfg,
102102 int fftSize,
103103 float centerFreq,
104104 float minFreq,
105105 float sampleRate,
106106- CqtWindowType windowType,
106106+ VqtWindowType windowType,
107107 float sparsityThreshold)
108108{
109109 // Use variable Q optimized for 8K FFT
···124124 windowLength = fftSize;
125125 // Calculate effective Q after truncation
126126 float effectiveQ = windowLength * centerFreq / sampleRate;
127127- #ifdef CQT_DEBUG
127127+ #ifdef VQT_DEBUG
128128 printf("Freq %.1f Hz: Q designed=%.1f, window=%d, Q effective=%.1f (truncated)\n",
129129 centerFreq, Q, windowLength, effectiveQ);
130130 #endif
···158158159159 switch (windowType)
160160 {
161161- case CQT_WINDOW_HAMMING:
161161+ case VQT_WINDOW_HAMMING:
162162 generateHammingWindow(tempWindow, windowLength);
163163 break;
164164- case CQT_WINDOW_GAUSSIAN:
164164+ case VQT_WINDOW_GAUSSIAN:
165165 generateGaussianWindow(tempWindow, windowLength);
166166 break;
167167 }
···235235 }
236236 }
237237238238- #ifdef CQT_DEBUG
238238+ #ifdef VQT_DEBUG
239239 // Debug output for specific frequencies
240240 if (fabs(centerFreq - 110.0f) < 1.0f || fabs(centerFreq - 440.0f) < 1.0f)
241241 {
···249249 return true;
250250}
251251252252-// Generate CQT kernels for all bins
253253-bool CQT_GenerateKernels(CqtKernel* kernels, const CqtKernelConfig* config)
252252+// Generate VQT kernels for all bins
253253+bool VQT_GenerateKernels(VqtKernel* kernels, const VqtKernelConfig* config)
254254{
255255 // Generate center frequencies
256256 float* centerFreqs = (float*)malloc(config->numBins * sizeof(float));
257257 if (!centerFreqs) return false;
258258259259- CQT_GenerateCenterFrequencies(centerFreqs, config->numBins,
259259+ VQT_GenerateCenterFrequencies(centerFreqs, config->numBins,
260260 config->minFreq, config->maxFreq);
261261262262 // Create FFT configuration
···267267 return false;
268268 }
269269270270- // Generate kernel for each CQT bin
270270+ // Generate kernel for each VQT bin
271271 bool success = true;
272272 for (int i = 0; i < config->numBins; i++)
273273 {
-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);
+5-5
src/ext/fft.c
···1919kiss_fftr_cfg fftcfg;
2020ma_context context;
2121ma_device captureDevice;
2222-// Include CQT header to get CQT_FFT_SIZE
2323-#include "../cqtdata.h"
2222+// Include VQT header to get VQT_FFT_SIZE
2323+#include "../vqtdata.h"
24242525-// Shared audio buffer - must be large enough for both FFT and CQT
2626-// FFT needs FFT_SIZE * 2 (2048) samples, CQT needs CQT_FFT_SIZE samples
2727-#define AUDIO_BUFFER_SIZE (CQT_FFT_SIZE > (FFT_SIZE * 2) ? CQT_FFT_SIZE : (FFT_SIZE * 2))
2525+// Shared audio buffer - must be large enough for both FFT and VQT
2626+// FFT needs FFT_SIZE * 2 (2048) samples, VQT needs VQT_FFT_SIZE samples
2727+#define AUDIO_BUFFER_SIZE (VQT_FFT_SIZE > (FFT_SIZE * 2) ? VQT_FFT_SIZE : (FFT_SIZE * 2))
2828float sampleBuf[AUDIO_BUFFER_SIZE];
29293030void miniaudioLogCallback(void* userData, ma_uint32 level, const char* message)
+1-1
src/ext/fft.h
···88void FFT_GetFFT(float* _samples);
99void FFT_Close();
10101111-// CQT needs access to the raw audio buffer
1111+// VQT needs access to the raw audio buffer
1212extern float sampleBuf[];
13131414//////////////////////////////////////////////////////////////////////////
+441
src/ext/vqt.c
···11+#include "vqt.h"
22+#include "vqt_kernel.h"
33+#include "../vqtdata.h"
44+#include "../fftdata.h"
55+#include "fft.h"
66+#include "kiss_fftr.h"
77+#include <math.h>
88+#include <string.h>
99+#include <stdbool.h>
1010+#include <stdio.h>
1111+#include <time.h>
1212+1313+#define VQT_DEBUG
1414+1515+// FFT configuration for VQT
1616+static kiss_fftr_cfg vqtFftCfg = NULL;
1717+static float* vqtAudioBuffer = NULL;
1818+static kiss_fft_cpx* vqtFftOutput = NULL;
1919+2020+// Benchmark different FFT sizes
2121+static void VQT_BenchmarkFFT(void)
2222+{
2323+ printf("\nVQT FFT Benchmark on this CPU:\n");
2424+ printf("================================\n");
2525+2626+ int sizes[] = {4096, 6144, 8192, 12288, 16384, 24576, 32768};
2727+ int numSizes = 7;
2828+2929+ for (int s = 0; s < numSizes; s++)
3030+ {
3131+ int fftSize = sizes[s];
3232+3333+ // Allocate buffers
3434+ float* testInput = (float*)calloc(fftSize, sizeof(float));
3535+ kiss_fft_cpx* testOutput = (kiss_fft_cpx*)malloc((fftSize/2 + 1) * sizeof(kiss_fft_cpx));
3636+ kiss_fftr_cfg testCfg = kiss_fftr_alloc(fftSize, 0, NULL, NULL);
3737+3838+ if (!testInput || !testOutput || !testCfg)
3939+ {
4040+ printf("Failed to allocate for size %d\n", fftSize);
4141+ continue;
4242+ }
4343+4444+ // Fill with test signal
4545+ for (int i = 0; i < fftSize; i++)
4646+ {
4747+ testInput[i] = sin(2.0 * M_PI * 440.0 * i / 44100.0);
4848+ }
4949+5050+ // Warm up
5151+ for (int i = 0; i < 10; i++)
5252+ {
5353+ kiss_fftr(testCfg, testInput, testOutput);
5454+ }
5555+5656+ // Time 100 iterations
5757+ clock_t start = clock();
5858+ for (int i = 0; i < 100; i++)
5959+ {
6060+ kiss_fftr(testCfg, testInput, testOutput);
6161+ }
6262+ clock_t end = clock();
6363+6464+ double avgTime = (double)(end - start) / CLOCKS_PER_SEC * 1000.0 / 100.0;
6565+ printf("%5d-point FFT: %.3f ms\n", fftSize, avgTime);
6666+6767+ // Cleanup
6868+ free(testInput);
6969+ free(testOutput);
7070+ free(testCfg);
7171+ }
7272+7373+ printf("================================\n\n");
7474+}
7575+7676+// Initialize VQT processing
7777+bool VQT_Open(void)
7878+{
7979+ // Initialize data structures
8080+ VQT_Init();
8181+8282+ // Allocate FFT buffers
8383+ vqtAudioBuffer = (float*)malloc(VQT_FFT_SIZE * sizeof(float));
8484+ vqtFftOutput = (kiss_fft_cpx*)malloc((VQT_FFT_SIZE/2 + 1) * sizeof(kiss_fft_cpx));
8585+8686+ if (!vqtAudioBuffer || !vqtFftOutput)
8787+ {
8888+ VQT_Close();
8989+ return false;
9090+ }
9191+9292+ // Create FFT configuration
9393+ vqtFftCfg = kiss_fftr_alloc(VQT_FFT_SIZE, 0, NULL, NULL);
9494+ if (!vqtFftCfg)
9595+ {
9696+ VQT_Close();
9797+ return false;
9898+ }
9999+100100+ // Configure VQT kernels
101101+ VqtKernelConfig config = {
102102+ .fftSize = VQT_FFT_SIZE,
103103+ .numBins = VQT_BINS,
104104+ .minFreq = VQT_MIN_FREQ,
105105+ .maxFreq = VQT_MAX_FREQ,
106106+ .sampleRate = 44100.0f, // Standard TIC-80 sample rate
107107+ .windowType = VQT_WINDOW_HAMMING,
108108+ .sparsityThreshold = VQT_SPARSITY_THRESHOLD
109109+ };
110110+111111+ // Generate kernels
112112+ if (!VQT_GenerateKernels(vqtKernels, &config))
113113+ {
114114+ VQT_Close();
115115+ return false;
116116+ }
117117+118118+ // Run FFT benchmark on first initialization
119119+ static bool benchmarkRun = false;
120120+ if (!benchmarkRun)
121121+ {
122122+ VQT_BenchmarkFFT();
123123+ benchmarkRun = true;
124124+ }
125125+126126+ // Debug: Print variable Q values across frequency spectrum
127127+ #ifdef VQT_DEBUG
128128+ float centerFreqs[VQT_BINS];
129129+ VQT_GenerateCenterFrequencies(centerFreqs, VQT_BINS, VQT_MIN_FREQ, VQT_MAX_FREQ);
130130+131131+ printf("\nVQT Variable-Q Implementation (8K FFT Optimized):\n");
132132+ printf("================================================\n");
133133+134134+ // Show Q values for key frequency ranges
135135+ float testFreqs[] = {20, 25, 30, 40, 50, 65, 80, 120, 160, 240, 320, 440, 640, 1000, 2000, 4000};
136136+ printf("Frequency | Design Q | Window Length | Effective Q | Resolution\n");
137137+ printf("----------|----------|---------------|-------------|------------\n");
138138+139139+ for (int i = 0; i < 16; i++)
140140+ {
141141+ float freq = testFreqs[i];
142142+ // Find closest VQT bin
143143+ int closestBin = 0;
144144+ float minDiff = fabs(centerFreqs[0] - freq);
145145+ for (int j = 1; j < VQT_BINS; j++)
146146+ {
147147+ float diff = fabs(centerFreqs[j] - freq);
148148+ if (diff < minDiff)
149149+ {
150150+ minDiff = diff;
151151+ closestBin = j;
152152+ }
153153+ }
154154+155155+ // Calculate Q values using 8K-optimized function
156156+ float designQ;
157157+ if (freq < 25.0f) designQ = 7.4f;
158158+ else if (freq < 30.0f) designQ = 9.2f;
159159+ else if (freq < 40.0f) designQ = 11.5f;
160160+ else if (freq < 50.0f) designQ = 14.5f;
161161+ else if (freq < 65.0f) designQ = 16.0f;
162162+ else if (freq < 80.0f) designQ = 17.0f;
163163+ else if (freq < 160.0f) designQ = 17.0f;
164164+ else if (freq < 320.0f) designQ = 15.0f;
165165+ else if (freq < 640.0f) designQ = 13.0f;
166166+ else designQ = 11.0f;
167167+168168+ int windowLength = (int)(designQ * 44100.0f / freq);
169169+ if (windowLength > VQT_FFT_SIZE) windowLength = VQT_FFT_SIZE;
170170+ float effectiveQ = windowLength * freq / 44100.0f;
171171+ float bandwidth = freq / effectiveQ;
172172+173173+ printf("%7.0f Hz | %7.1f | %13d | %11.1f | %7.1f Hz\n",
174174+ freq, designQ, windowLength, effectiveQ, bandwidth);
175175+ }
176176+177177+ printf("\nFirst 10 VQT bins:\n");
178178+ for (int i = 0; i < 10 && i < VQT_BINS; i++)
179179+ {
180180+ int expectedBin = (int)(centerFreqs[i] * VQT_FFT_SIZE / 44100.0f);
181181+ printf(" Bin %d: %.2f Hz -> FFT bin %d\n", i, centerFreqs[i], expectedBin);
182182+ }
183183+ #endif
184184+185185+ return true;
186186+}
187187+188188+// Apply VQT kernels to FFT output
189189+void VQT_ApplyKernels(const float* fftReal, const float* fftImag)
190190+{
191191+ // Clear VQT output
192192+ memset(vqtData, 0, sizeof(vqtData));
193193+194194+ // Apply each kernel to compute VQT bins
195195+ for (int bin = 0; bin < VQT_BINS; bin++)
196196+ {
197197+ VqtKernel* kernel = &vqtKernels[bin];
198198+199199+ // Check if kernel is valid
200200+ if (!kernel->real || !kernel->imag || !kernel->indices || kernel->length == 0)
201201+ {
202202+ vqtData[bin] = 0.0f;
203203+ continue;
204204+ }
205205+206206+ float real = 0.0f;
207207+ float imag = 0.0f;
208208+209209+ // Sparse matrix multiplication
210210+ for (int k = 0; k < kernel->length; k++)
211211+ {
212212+ int idx = kernel->indices[k];
213213+ // Ensure index is within bounds
214214+ if (idx < 0 || idx >= VQT_FFT_SIZE/2 + 1)
215215+ continue;
216216+217217+ // Complex multiplication: (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
218218+ real += fftReal[idx] * kernel->real[k] - fftImag[idx] * kernel->imag[k];
219219+ imag += fftReal[idx] * kernel->imag[k] + fftImag[idx] * kernel->real[k];
220220+ }
221221+222222+ // Calculate magnitude with gain boost
223223+ vqtData[bin] = sqrt(real * real + imag * imag) * 2.0f; // Match FFT gain factor
224224+225225+ // Check for NaN or Inf
226226+ if (!isfinite(vqtData[bin]))
227227+ vqtData[bin] = 0.0f;
228228+ }
229229+}
230230+231231+// Process VQT from audio data
232232+void VQT_ProcessAudio(void)
233233+{
234234+ if (!vqtFftCfg || !vqtEnabled) return;
235235+236236+ // Check if kernels are initialized
237237+ bool kernelsValid = false;
238238+ for (int i = 0; i < VQT_BINS; i++)
239239+ {
240240+ if (vqtKernels[i].real && vqtKernels[i].length > 0)
241241+ {
242242+ kernelsValid = true;
243243+ break;
244244+ }
245245+ }
246246+247247+ if (!kernelsValid)
248248+ {
249249+ // Kernels not initialized, set all output to zero
250250+ memset(vqtData, 0, sizeof(vqtData));
251251+ memset(vqtSmoothingData, 0, sizeof(vqtSmoothingData));
252252+ memset(vqtNormalizedData, 0, sizeof(vqtNormalizedData));
253253+ return;
254254+ }
255255+256256+ // Copy audio data from the shared buffer
257257+ // sampleBuf is defined in fft.c as extern
258258+ extern float sampleBuf[];
259259+ memcpy(vqtAudioBuffer, sampleBuf, VQT_FFT_SIZE * sizeof(float));
260260+261261+ // Check if we have any audio data
262262+ float audioSum = 0.0f;
263263+ for (int i = 0; i < VQT_FFT_SIZE; i++)
264264+ {
265265+ audioSum += fabs(vqtAudioBuffer[i]);
266266+ }
267267+268268+ if (audioSum < 0.0001f)
269269+ {
270270+ // No audio data, set output to zero
271271+ memset(vqtData, 0, sizeof(vqtData));
272272+ memset(vqtSmoothingData, 0, sizeof(vqtSmoothingData));
273273+ memset(vqtNormalizedData, 0, sizeof(vqtNormalizedData));
274274+ return;
275275+ }
276276+277277+ // Profiling variables
278278+ static double totalFftTime = 0.0;
279279+ static double totalKernelTime = 0.0;
280280+ static int profileCount = 0;
281281+282282+ // Perform 16384-point FFT with timing
283283+ clock_t fftStart = clock();
284284+ kiss_fftr(vqtFftCfg, vqtAudioBuffer, vqtFftOutput);
285285+ clock_t fftEnd = clock();
286286+287287+ // Extract real and imaginary components for kernel application
288288+ float fftReal[VQT_FFT_SIZE/2 + 1];
289289+ float fftImag[VQT_FFT_SIZE/2 + 1];
290290+291291+ for (int i = 0; i <= VQT_FFT_SIZE/2; i++)
292292+ {
293293+ fftReal[i] = vqtFftOutput[i].r;
294294+ fftImag[i] = vqtFftOutput[i].i;
295295+ }
296296+297297+ // Apply VQT kernels with timing
298298+ clock_t kernelStart = clock();
299299+ VQT_ApplyKernels(fftReal, fftImag);
300300+ clock_t kernelEnd = clock();
301301+302302+ // Calculate times in milliseconds
303303+ double fftTime = (double)(fftEnd - fftStart) / CLOCKS_PER_SEC * 1000.0;
304304+ double kernelTime = (double)(kernelEnd - kernelStart) / CLOCKS_PER_SEC * 1000.0;
305305+306306+ totalFftTime += fftTime;
307307+ totalKernelTime += kernelTime;
308308+ profileCount++;
309309+310310+ // Print profiling info every 60 frames (~1 second)
311311+ if (profileCount % 60 == 0)
312312+ {
313313+ printf("VQT Performance (16K FFT):\n");
314314+ printf(" FFT avg: %.3fms\n", totalFftTime / profileCount);
315315+ printf(" Kernels avg: %.3fms\n", totalKernelTime / profileCount);
316316+ printf(" Total avg: %.3fms\n", (totalFftTime + totalKernelTime) / profileCount);
317317+ printf(" Samples: %d\n\n", profileCount);
318318+ }
319319+320320+ #ifdef VQT_DEBUG
321321+ // Reduced debug output - VQT is working correctly now
322322+ static int debugCounter = 0;
323323+ if (++debugCounter % 300 == 0) // Print once every 5 seconds
324324+ {
325325+ // Find peak VQT bin
326326+ int peakBin = 0;
327327+ float peakVal = 0.0f;
328328+ for (int i = 0; i < VQT_BINS; i++)
329329+ {
330330+ if (vqtData[i] > peakVal)
331331+ {
332332+ peakVal = vqtData[i];
333333+ peakBin = i;
334334+ }
335335+ }
336336+337337+ if (peakVal > 1.0f)
338338+ {
339339+ float peakFreq = VQT_MIN_FREQ * pow(2.0f, peakBin / 12.0f);
340340+ printf("VQT: Peak at bin %d (%.1f Hz) with magnitude %.1f\n",
341341+ peakBin, peakFreq, peakVal);
342342+ }
343343+ }
344344+ #endif
345345+346346+ // Apply spectral whitening if enabled
347347+ #if VQT_SPECTRAL_WHITENING_ENABLED
348348+ for (int i = 0; i < VQT_BINS; i++)
349349+ {
350350+ // Update running average for this bin
351351+ vqtBinAverages[i] = vqtBinAverages[i] * VQT_WHITENING_DECAY +
352352+ vqtData[i] * (1.0f - VQT_WHITENING_DECAY);
353353+354354+ // Ensure average doesn't go below floor
355355+ if (vqtBinAverages[i] < VQT_WHITENING_FLOOR)
356356+ vqtBinAverages[i] = VQT_WHITENING_FLOOR;
357357+358358+ // Apply whitening by dividing by the average
359359+ vqtWhitenedData[i] = vqtData[i] / vqtBinAverages[i];
360360+361361+ // Check for NaN or Inf
362362+ if (!isfinite(vqtWhitenedData[i]))
363363+ vqtWhitenedData[i] = 0.0f;
364364+ }
365365+366366+ // Apply smoothing to whitened data
367367+ for (int i = 0; i < VQT_BINS; i++)
368368+ {
369369+ vqtSmoothingData[i] = vqtSmoothingData[i] * VQT_SMOOTHING_FACTOR +
370370+ vqtWhitenedData[i] * (1.0f - VQT_SMOOTHING_FACTOR);
371371+ }
372372+ #else
373373+ // Apply smoothing to raw data (spectral whitening disabled)
374374+ for (int i = 0; i < VQT_BINS; i++)
375375+ {
376376+ vqtSmoothingData[i] = vqtSmoothingData[i] * VQT_SMOOTHING_FACTOR +
377377+ vqtData[i] * (1.0f - VQT_SMOOTHING_FACTOR);
378378+ }
379379+ #endif
380380+381381+ // Find peak for normalization
382382+ float currentPeak = 0.0f;
383383+ for (int i = 0; i < VQT_BINS; i++)
384384+ {
385385+ if (vqtSmoothingData[i] > currentPeak)
386386+ currentPeak = vqtSmoothingData[i];
387387+ }
388388+389389+ // Initialize peak value if needed
390390+ if (vqtPeakSmoothValue <= 0.0f)
391391+ vqtPeakSmoothValue = 0.1f;
392392+393393+ // Smooth peak value
394394+ if (currentPeak > vqtPeakSmoothValue)
395395+ vqtPeakSmoothValue = currentPeak;
396396+ else
397397+ vqtPeakSmoothValue = vqtPeakSmoothValue * 0.99f + currentPeak * 0.01f;
398398+399399+ // Ensure peak value doesn't go too low
400400+ if (vqtPeakSmoothValue < 0.0001f)
401401+ vqtPeakSmoothValue = 0.0001f;
402402+403403+ // Normalize data
404404+ float normalizer = 1.0f / vqtPeakSmoothValue;
405405+ for (int i = 0; i < VQT_BINS; i++)
406406+ {
407407+ vqtNormalizedData[i] = vqtSmoothingData[i] * normalizer;
408408+ if (vqtNormalizedData[i] > 1.0f)
409409+ vqtNormalizedData[i] = 1.0f;
410410+411411+ // Final NaN check
412412+ if (!isfinite(vqtNormalizedData[i]))
413413+ vqtNormalizedData[i] = 0.0f;
414414+ }
415415+}
416416+417417+418418+// Close VQT processing and free resources
419419+void VQT_Close(void)
420420+{
421421+ if (vqtFftCfg)
422422+ {
423423+ free(vqtFftCfg);
424424+ vqtFftCfg = NULL;
425425+ }
426426+427427+ if (vqtAudioBuffer)
428428+ {
429429+ free(vqtAudioBuffer);
430430+ vqtAudioBuffer = NULL;
431431+ }
432432+433433+ if (vqtFftOutput)
434434+ {
435435+ free(vqtFftOutput);
436436+ vqtFftOutput = NULL;
437437+ }
438438+439439+ // Clean up kernels
440440+ VQT_Cleanup();
441441+}
+16
src/ext/vqt.h
···11+#pragma once
22+33+#include <stdbool.h>
44+55+// Initialize VQT processing
66+// Returns true on success, false on failure
77+bool VQT_Open(void);
88+99+// Process VQT from audio buffer (uses shared audio capture buffer)
1010+void VQT_ProcessAudio(void);
1111+1212+// Close VQT processing and free resources
1313+void VQT_Close(void);
1414+1515+// Apply VQT kernels to FFT output
1616+void VQT_ApplyKernels(const float* fftReal, const float* fftImag);
+31
src/ext/vqt_kernel.h
···11+#pragma once
22+33+#include <stdbool.h>
44+#include "../vqtdata.h"
55+66+// Window types for VQT kernels
77+typedef enum {
88+ VQT_WINDOW_HAMMING = 0,
99+ VQT_WINDOW_GAUSSIAN
1010+} VqtWindowType;
1111+1212+// VQT kernel configuration
1313+typedef struct {
1414+ int fftSize; // FFT size (4096)
1515+ int numBins; // Number of VQT bins (120)
1616+ float minFreq; // Minimum frequency (20 Hz)
1717+ float maxFreq; // Maximum frequency (20480 Hz)
1818+ float sampleRate; // Sample rate (44100 Hz)
1919+ VqtWindowType windowType; // Window function type
2020+ float sparsityThreshold; // Threshold for sparse matrix (0.01)
2121+} VqtKernelConfig;
2222+2323+// Generate VQT kernels for all bins
2424+// Returns true on success, false on failure
2525+bool VQT_GenerateKernels(VqtKernel* kernels, const VqtKernelConfig* config);
2626+2727+// Calculate Q factor for given parameters
2828+float VQT_CalculateQ(int binsPerOctave);
2929+3030+// Generate center frequencies for VQT bins
3131+void VQT_GenerateCenterFrequencies(float* frequencies, int numBins, float minFreq, float maxFreq);