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