Add configurable FFT size (512-8192), 75% overlap for smoother display
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
+42
-18
@@ -27,8 +27,10 @@
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
#define FFT_SIZE 2048
|
||||
#define HOP_SIZE (FFT_SIZE / 2)
|
||||
#define FFT_SIZE_DEFAULT 2048
|
||||
#define FFT_SIZE_MAX 8192
|
||||
#define FFT_SIZE_MIN 512
|
||||
#define HOP_RATIO 4 // FFT_SIZE / HOP_SIZE = 4 means 75% overlap
|
||||
#define MAX_SAMPLE_RATE 48000
|
||||
#define LOUDNESS_FLOOR_DB -80.0f
|
||||
|
||||
@@ -113,6 +115,7 @@ typedef struct {
|
||||
float amplitudeCeilingDb;
|
||||
ColormapType colormap;
|
||||
bool showGrid;
|
||||
int fftSize; // Current FFT size (512-8192)
|
||||
|
||||
// File browser state
|
||||
bool showFileBrowser;
|
||||
@@ -259,10 +262,10 @@ static void ApplyHannWindow(float* samples, int n)
|
||||
// STFT Implementation
|
||||
// ============================================================================
|
||||
|
||||
static void ComputeSTFT(AudioSignal* signal, StftResult* result)
|
||||
static void ComputeSTFT(AudioSignal* signal, StftResult* result, int fftSize)
|
||||
{
|
||||
int windowSize = FFT_SIZE, hopSize = HOP_SIZE;
|
||||
int numSegments = (signal->numSamples - windowSize) / hopSize + 1;
|
||||
int hopSize = fftSize / HOP_RATIO; // 75% overlap
|
||||
int numSegments = (signal->numSamples - fftSize) / hopSize + 1;
|
||||
if (numSegments <= 0) numSegments = 1;
|
||||
|
||||
result->numSegments = numSegments;
|
||||
@@ -271,30 +274,30 @@ static void ComputeSTFT(AudioSignal* signal, StftResult* result)
|
||||
result->totalSamples = signal->numSamples;
|
||||
result->useHannWindow = true;
|
||||
|
||||
int numBins = windowSize / 2 + 1;
|
||||
float* windowedSamples = (float*)malloc(windowSize * sizeof(float));
|
||||
float complex *complexInput = (float complex*)malloc(windowSize * sizeof(float complex));
|
||||
float complex* fftOutput = (float complex*)malloc(windowSize * sizeof(float complex));
|
||||
int numBins = fftSize / 2 + 1;
|
||||
float* windowedSamples = (float*)malloc(fftSize * sizeof(float));
|
||||
float complex *complexInput = (float complex*)malloc(fftSize * sizeof(float complex));
|
||||
float complex* fftOutput = (float complex*)malloc(fftSize * sizeof(float complex));
|
||||
|
||||
for (int seg = 0; seg < numSegments; seg++) {
|
||||
int offset = seg * hopSize;
|
||||
int samplesToCopy = windowSize;
|
||||
int samplesToCopy = fftSize;
|
||||
if (offset + samplesToCopy > signal->numSamples) {
|
||||
samplesToCopy = signal->numSamples - offset;
|
||||
memset(windowedSamples, 0, windowSize * sizeof(float));
|
||||
memset(windowedSamples, 0, fftSize * sizeof(float));
|
||||
}
|
||||
memcpy(windowedSamples, signal->samples + offset, samplesToCopy * sizeof(float));
|
||||
ApplyHannWindow(windowedSamples, windowSize);
|
||||
for (int i = 0; i < windowSize; i++) complexInput[i] = windowedSamples[i] + 0.0f * I;
|
||||
FFT(complexInput, fftOutput, windowSize, false);
|
||||
ApplyHannWindow(windowedSamples, fftSize);
|
||||
for (int i = 0; i < fftSize; i++) complexInput[i] = windowedSamples[i] + 0.0f * I;
|
||||
FFT(complexInput, fftOutput, fftSize, false);
|
||||
|
||||
result->segments[seg].numBins = numBins;
|
||||
result->segments[seg].sampleOffset = offset;
|
||||
result->segments[seg].sampleCount = samplesToCopy;
|
||||
result->segments[seg].spectrum = (FrequencyData*)malloc(numBins * sizeof(FrequencyData));
|
||||
for (int bin = 0; bin < numBins; bin++) {
|
||||
result->segments[seg].spectrum[bin].frequency = (float)bin * signal->sampleRate / windowSize;
|
||||
result->segments[seg].spectrum[bin].amplitude = (bin == 0) ? cabsf(fftOutput[bin]) / windowSize : 2.0f * cabsf(fftOutput[bin]) / windowSize;
|
||||
result->segments[seg].spectrum[bin].frequency = (float)bin * signal->sampleRate / fftSize;
|
||||
result->segments[seg].spectrum[bin].amplitude = (bin == 0) ? cabsf(fftOutput[bin]) / fftSize : 2.0f * cabsf(fftOutput[bin]) / fftSize;
|
||||
result->segments[seg].spectrum[bin].phase = cargf(fftOutput[bin]);
|
||||
}
|
||||
}
|
||||
@@ -801,6 +804,7 @@ static void DrawInfo(Rectangle bounds)
|
||||
DrawText(TextFormat("Sample Rate: %d Hz", app.signal.sampleRate), 10, y, fontSize, LIGHTGRAY); y += 20;
|
||||
DrawText(TextFormat("Duration: %.2f sec", app.signal.duration), 10, y, fontSize, LIGHTGRAY); y += 20;
|
||||
DrawText(TextFormat("View: %.1f%%-%.1f%% (%.2f sec)", app.viewStart*100, app.viewEnd*100, (app.viewEnd-app.viewStart)*app.signal.duration), 10, y, fontSize, LIGHTGRAY); y += 20;
|
||||
DrawText(TextFormat("FFT: %d (%.1f Hz/bin, 75%% overlap)", app.fftSize, (float)app.signal.sampleRate / app.fftSize), 10, y, fontSize, LIGHTGRAY); y += 20;
|
||||
DrawText(TextFormat("Max Freq: %.1f kHz", (float)app.signal.sampleRate / 2000.0f), 10, y, fontSize, LIGHTGRAY); y += 20;
|
||||
y += 10;
|
||||
|
||||
@@ -813,7 +817,7 @@ static void DrawInfo(Rectangle bounds)
|
||||
DrawText("No audio file loaded", 10, y, fontSize, RED);
|
||||
}
|
||||
|
||||
y = GetScreenHeight() - 260;
|
||||
y = GetScreenHeight() - 280;
|
||||
DrawText("Controls:", 10, y, fontSize, LIGHTGRAY); y += 20;
|
||||
DrawText(" O - Open file browser", 10, y, fontSize, GRAY); y += 18;
|
||||
DrawText(" Drag & drop WAV file", 10, y, fontSize, GRAY); y += 18;
|
||||
@@ -822,6 +826,7 @@ static void DrawInfo(Rectangle bounds)
|
||||
DrawText(" LMB Drag - Select time region", 10, y, fontSize, GRAY); y += 18;
|
||||
DrawText(" Shift+LMB - Select freq range", 10, y, fontSize, GRAY); y += 18;
|
||||
DrawText(" SPACE - Play selection", 10, y, fontSize, GRAY); y += 18;
|
||||
DrawText(" 1/2 - FFT size (resolution)", 10, y, fontSize, GRAY); y += 18;
|
||||
DrawText(" M - Cycle colormap", 10, y, fontSize, GRAY); y += 18;
|
||||
DrawText(" W/S - Adjust dB floor", 10, y, fontSize, GRAY); y += 18;
|
||||
DrawText(" G - Toggle grid", 10, y, fontSize, GRAY); y += 18;
|
||||
@@ -862,6 +867,7 @@ int main(int argc, char* argv[])
|
||||
app.cachedVisibleStart = -1;
|
||||
app.cachedVisibleEnd = -1;
|
||||
app.visibleTextureValid = false;
|
||||
app.fftSize = FFT_SIZE_DEFAULT;
|
||||
|
||||
GenerateColormapTexture();
|
||||
ScanDirectory(GetWorkingDirectory());
|
||||
@@ -1014,6 +1020,24 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
if (IsKeyPressed(KEY_SPACE) && !app.showFileBrowser) PlaySelectedRegion();
|
||||
|
||||
// FFT size control (1/2 keys)
|
||||
if (IsKeyPressed(KEY_ONE) && !app.showFileBrowser) {
|
||||
app.fftSize /= 2;
|
||||
if (app.fftSize < FFT_SIZE_MIN) app.fftSize = FFT_SIZE_MIN;
|
||||
else {
|
||||
app.stftComputed = false;
|
||||
TraceLog(LOG_INFO, "FFT size: %d", app.fftSize);
|
||||
}
|
||||
}
|
||||
if (IsKeyPressed(KEY_TWO) && !app.showFileBrowser) {
|
||||
app.fftSize *= 2;
|
||||
if (app.fftSize > FFT_SIZE_MAX) app.fftSize = FFT_SIZE_MAX;
|
||||
else {
|
||||
app.stftComputed = false;
|
||||
TraceLog(LOG_INFO, "FFT size: %d", app.fftSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KEY_ESCAPE)) {
|
||||
if (app.showFileBrowser) app.showFileBrowser = false;
|
||||
else {
|
||||
@@ -1076,7 +1100,7 @@ int main(int argc, char* argv[])
|
||||
if (app.loaded && !app.stftComputed) {
|
||||
TraceLog(LOG_INFO, "Computing STFT...");
|
||||
double startTime = GetTime();
|
||||
ComputeSTFT(&app.signal, &app.stft);
|
||||
ComputeSTFT(&app.signal, &app.stft, app.fftSize);
|
||||
TraceLog(LOG_INFO, "STFT computed in %.2f sec (%d segments)", GetTime() - startTime, app.stft.numSegments);
|
||||
GenerateSpectrogramTexture(&app.stft, &app.spectrogramImage, &app.spectrogramTexture);
|
||||
app.stftComputed = true;
|
||||
|
||||
Reference in New Issue
Block a user