···2323#include "../vqtdata.h"
24242525// 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))
2826float sampleBuf[AUDIO_BUFFER_SIZE];
29273028void miniaudioLogCallback(void* userData, ma_uint32 level, const char* message)
···335333#else
336334337335 kiss_fft_cpx out[FFT_SIZE + 1];
338338- // Align FFT and VQT to start from the same temporal position
339339- kiss_fftr(fftcfg, sampleBuf + AUDIO_BUFFER_SIZE - VQT_FFT_SIZE, out);
336336+ // Align FFT and VQT to the same recent audio window
337337+ // Use the last 2048 samples for the 2K FFT, not the start of the 8K VQT window
338338+ kiss_fftr(fftcfg, sampleBuf + AUDIO_BUFFER_SIZE - (FFT_SIZE * 2), out);
340339341340 float peakValue = fPeakMinValue;
342341 for (int i = 0; i < FFT_SIZE; i++)
+6-4
src/ext/vqt.c
···1717#include <stdio.h>
1818#include <time.h>
19192020+// Enable verbose VQT debug prints only for debug builds unless explicitly forced
2121+#if !defined(NDEBUG) && !defined(VQT_DEBUG)
2022#define VQT_DEBUG
2323+#endif
21242225// FFT configuration for VQT
2326static kiss_fftr_cfg vqtFftCfg = NULL;
···7477 // Cleanup
7578 free(testInput);
7679 free(testOutput);
7777- free(testCfg);
8080+ kiss_fft_free(testCfg);
7881 }
79828083 printf("================================\n\n");
···251254 // Copy audio data from the shared buffer
252255 // sampleBuf is defined in fft.c as extern
253256 extern float sampleBuf[];
254254- // Align FFT and VQT to start from the same temporal position
255255- #define AUDIO_BUFFER_SIZE (VQT_FFT_SIZE > (FFT_SIZE * 2) ? VQT_FFT_SIZE : (FFT_SIZE * 2))
257257+ // Align to the most recent VQT window using centralized AUDIO_BUFFER_SIZE
256258 memcpy(vqtAudioBuffer, sampleBuf + AUDIO_BUFFER_SIZE - VQT_FFT_SIZE, VQT_FFT_SIZE * sizeof(float));
257259258260 // Profiling variables
···373375{
374376 if (vqtFftCfg)
375377 {
376376- free(vqtFftCfg);
378378+ kiss_fft_free(vqtFftCfg);
377379 vqtFftCfg = NULL;
378380 }
379381
+23-19
src/ext/vqt_kernel.c
···8899#include <stdlib.h>
1010#include <string.h>
1111-#include "../ext/kiss_fftr.h"
1111+#include "kiss_fftr.h"
12121313// Generate logarithmically spaced center frequencies for musical notes
1414+// Keeps equal-tempered semitone spacing exact (2^(1/12)) without scaling the ladder.
1515+// If the last bin slightly exceeds maxFreq due to floating-point error, adjust the
1616+// base frequency minimally so that the last bin matches maxFreq exactly.
1417void VQT_GenerateCenterFrequencies(float* frequencies, int numBins, float minFreq, float maxFreq)
1518{
1616- // For musical VQT with 12 bins per octave, we want equal temperament spacing
1717- // Each semitone is a factor of 2^(1/12) ≈ 1.0594631
1818- const float semitone = pow(2.0f, 1.0f / 12.0f);
1919-2020- for (int i = 0; i < numBins; i++)
1919+ const double step = pow(2.0, 1.0 / 12.0); // semitone ratio
2020+ const double stepsToTop = (double)(numBins - 1) / 12.0; // octaves to the top bin
2121+2222+ // Compute the ideal top frequency using high precision
2323+ double base = (double)minFreq;
2424+ double idealTop = base * pow(2.0, stepsToTop);
2525+2626+ // Allow a tiny tolerance for FP error before correcting the base
2727+ const double eps = 1e-7; // relative tolerance
2828+ if (idealTop > (double)maxFreq * (1.0 + eps))
2129 {
2222- // Calculate frequency for each bin based on semitone spacing
2323- frequencies[i] = minFreq * pow(semitone, i);
3030+ // Adjust base so that the top bin lands exactly at maxFreq
3131+ base = (double)maxFreq / pow(2.0, stepsToTop);
2432 }
2525-2626- // Verify we don't exceed maxFreq
2727- if (frequencies[numBins - 1] > maxFreq)
3333+3434+ // Fill frequencies using exact semitone spacing from the (possibly adjusted) base
3535+ for (int i = 0; i < numBins; i++)
2836 {
2929- // Scale down if necessary
3030- float scale = maxFreq / frequencies[numBins - 1];
3131- for (int i = 0; i < numBins; i++)
3232- {
3333- frequencies[i] *= scale;
3434- }
3737+ double f = base * pow(2.0, (double)i / 12.0);
3838+ frequencies[i] = (float)f;
3539 }
3640}
3741···206210 free(kernel->indices);
207211 free(timeKernel);
208212 free(freqKernel);
209209- free(tempWindow);
213213+ // tempWindow was already freed earlier
210214 return false;
211215 }
212216···283287 }
284288 }
285289286286- free(fftCfg);
290290+ kiss_fft_free(fftCfg);
287291 free(centerFreqs);
288292 return success;
289293}
+8
src/fftdata.h
···11#pragma once
22#include <stdbool.h>
33+44+// FFT configuration
35#define FFT_SIZE 1024
66+77+// For shared audio buffer size, factor in the larger of
88+// the FFT window (2*FFT_SIZE) and the VQT window (VQT_FFT_SIZE).
99+// Keep this definition centralized to avoid mismatches.
1010+#include "vqtdata.h"
1111+#define AUDIO_BUFFER_SIZE (VQT_FFT_SIZE > (FFT_SIZE * 2) ? VQT_FFT_SIZE : (FFT_SIZE * 2))
412extern float fPeakMinValue;
513extern float fPeakSmoothing;
614extern float fPeakSmoothValue;
+5-2
src/vqtdata.h
···55#define VQT_FFT_SIZE 8192 // 8K FFT - optimized variable-Q for responsive visualization, ~5.4 fps
6677// VQT frequency range
88-#define VQT_MIN_FREQ 20.0f // Sub-bass for electronic music
99-#define VQT_MAX_FREQ 20480.0f // Nearest note to 20kHz
88+// Use a musical base note for exact semitone alignment across bins.
99+// D#0/Eb0 ≈ 19.445 Hz; over 10 octaves (120 semitones) this reaches ≈19.9 kHz.
1010+// This keeps every bin on a real note and fits within the audible band.
1111+#define VQT_MIN_FREQ 19.445f // D#0 / Eb0 base (A4=440)
1212+#define VQT_MAX_FREQ 20480.0f // Upper reference (not used to scale bins)
10131114// Smoothing parameters
1215#define VQT_SMOOTHING_FACTOR 0.3f // Reduced from 0.7f for more responsive display