refactor: split spectrogram.c into per-concern modules
spectrogram.c was ~2950 lines holding everything. Break it into cohesive translation units; spectrogram.c keeps only globals + the main frame loop. New modules: - spectrogram_types.h shared types, constants, extern globals, inline math - fft.c/.h FFT, bit-reverse, twiddle (standalone, no app deps) - stft.c/.h STFT compute, adaptive resolution, FFT-size LRU cache - audio.c/.h WAV/ffmpeg load, FreeSignal, bandpass, playback - render.c/.h UI scaling, colormaps, texture gen, on-screen drawing - ui.c/.h file browser, sidebar, sliders, PNG export Also: - utils.c now includes utils.h instead of re-typedef'ing AudioSignal/ SignalStats (they had to be hand-synced before). - Remove dead code: ApplyHannWindow and ComputeSTFTHighResRange were never called (the live high-res path is ComputeNextHighResChunk). - Delete the unused raylib-template main.c. - rspektrum.make: build the new units. premake5.lua: glob src/**.c so a future regen stays correct. Pure code movement otherwise; no behavior change. Builds clean (-Wshadow). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
// spectrogram_types.h - Shared types, constants, globals, and small math helpers.
|
||||
// This is the "spine" header included by every module.
|
||||
#ifndef SPECTROGRAM_TYPES_H
|
||||
#define SPECTROGRAM_TYPES_H
|
||||
|
||||
#include "raylib.h"
|
||||
#include "utils.h" // AudioSignal, SignalStats
|
||||
#include "primitives.h" // ScopeView, WaveformData
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#ifndef CYAN
|
||||
#define CYAN (Color){ 0, 255, 255, 255 }
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
#define FFT_SIZE_DEFAULT 2048
|
||||
#define FFT_SIZE_MAX 2048
|
||||
#define FFT_SIZE_MIN 128
|
||||
#define HOP_RATIO 4 // FFT_SIZE / HOP_SIZE = 4 means 75% overlap
|
||||
#define MAX_SAMPLE_RATE 48000
|
||||
#define LOUDNESS_FLOOR_DB -80.0f
|
||||
|
||||
// Base resolution for proportional UI scaling (see GetUIScale in render.c)
|
||||
#define BASE_WIDTH 1280
|
||||
#define BASE_HEIGHT 800
|
||||
|
||||
#define FFT_CACHE_SIZE 4
|
||||
|
||||
// ============================================================================
|
||||
// Data Structures
|
||||
// ============================================================================
|
||||
|
||||
typedef enum {
|
||||
COLORMAP_GRAYS = 0,
|
||||
COLORMAP_INFERNO,
|
||||
COLORMAP_VIRIDIS,
|
||||
COLORMAP_PLASMA,
|
||||
COLORMAP_HOT,
|
||||
COLORMAP_COOL,
|
||||
COLORMAP_COUNT
|
||||
} ColormapType;
|
||||
|
||||
typedef struct {
|
||||
float frequency;
|
||||
float amplitude;
|
||||
float phase;
|
||||
} FrequencyData;
|
||||
|
||||
typedef struct {
|
||||
FrequencyData* spectrum;
|
||||
FrequencyData* derivativeSpectrum; // STFT with derivative window (for synchrosqueezing)
|
||||
int numBins;
|
||||
int sampleOffset;
|
||||
int sampleCount;
|
||||
} StftSegment;
|
||||
|
||||
typedef struct {
|
||||
StftSegment* segments;
|
||||
int numSegments;
|
||||
int sampleRate;
|
||||
int totalSamples;
|
||||
bool useHannWindow;
|
||||
} StftResult;
|
||||
|
||||
typedef struct {
|
||||
int fftSize;
|
||||
StftResult result;
|
||||
int accessOrder; // lower = more recently accessed
|
||||
} FFTCacheEntry;
|
||||
|
||||
typedef struct {
|
||||
FFTCacheEntry entries[FFT_CACHE_SIZE];
|
||||
int count;
|
||||
int nextOrder;
|
||||
} FFTSizeCache;
|
||||
|
||||
typedef struct {
|
||||
AudioSignal signal;
|
||||
StftResult stft;
|
||||
Image spectrogramImage;
|
||||
Texture2D spectrogramTexture;
|
||||
bool loaded;
|
||||
bool stftComputed;
|
||||
|
||||
// Playback state
|
||||
float playheadT; // 0-1 normalized position in selection
|
||||
float playheadElapsed; // Elapsed seconds since play started
|
||||
|
||||
// Time selection (0-1 normalized)
|
||||
float timeSelectionStart;
|
||||
float timeSelectionEnd;
|
||||
bool isTimeSelecting;
|
||||
|
||||
// Frequency selection (0-1 normalized)
|
||||
float freqSelectionStart;
|
||||
float freqSelectionEnd;
|
||||
bool isFreqSelecting;
|
||||
|
||||
// Export settings
|
||||
float exportScale;
|
||||
char exportDir[4096];
|
||||
char exportMessage[256];
|
||||
|
||||
Vector2 selectStartPos; // For minimum drag distance check
|
||||
bool isDraggingSelection; // Dragging existing selection box
|
||||
Vector2 dragSelectionStartPos; // Mouse position when started dragging selection
|
||||
float dragSelectionTimeStart; // Selection start time when dragging
|
||||
float dragSelectionFreqStart; // Selection freq start when dragging
|
||||
|
||||
// Viewport/zoom controls
|
||||
float viewStart; // 0-1, start of visible time region
|
||||
float viewEnd; // 0-1, end of visible time region
|
||||
float freqViewStart; // 0-1, start of visible frequency region (0 = 0Hz)
|
||||
float freqViewEnd; // 0-1, end of visible frequency region (1 = Nyquist)
|
||||
bool isPanning;
|
||||
float panStartViewStart;
|
||||
float panStartViewEnd;
|
||||
float panStartFreqViewStart;
|
||||
float panStartFreqViewEnd;
|
||||
Vector2 panStartPos;
|
||||
|
||||
// Cached visible texture
|
||||
Texture2D visibleTexture;
|
||||
int cachedVisibleStart;
|
||||
int cachedVisibleEnd;
|
||||
int cachedVisibleStartY;
|
||||
int cachedVisibleEndY;
|
||||
bool visibleTextureValid;
|
||||
|
||||
// Display settings
|
||||
float amplitudeFloorDb;
|
||||
float amplitudeCeilingDb;
|
||||
ColormapType colormap;
|
||||
bool showGrid;
|
||||
int fftSize; // Current FFT size (128-2048)
|
||||
|
||||
// File browser state
|
||||
bool showFileBrowser;
|
||||
char browserPath[512];
|
||||
char** browserFiles;
|
||||
bool* browserIsDir;
|
||||
int browserFileCount;
|
||||
int browserScroll;
|
||||
int browserSelected;
|
||||
bool isBrowsing;
|
||||
|
||||
// Playback state
|
||||
bool isPlaying;
|
||||
bool playbackFinished; // Track if playback completed naturally
|
||||
|
||||
// Loading/processing state
|
||||
int loadingPhase; // 0 = computing STFT, 1 = generating texture
|
||||
float loadingProgress; // 0.0 to 1.0 overall progress
|
||||
int currentSTFTSegment; // Which segment we're on for incremental processing
|
||||
|
||||
// Adaptive resolution: skipFactor=1 means compute all segments, skipFactor=N
|
||||
// means compute every Nth segment (faster initial load, overview-only).
|
||||
// highResFinished tracks whether full-res segments have been computed for
|
||||
// the current view range.
|
||||
int skipFactor;
|
||||
bool highResFinished;
|
||||
|
||||
// Background high-res computation state.
|
||||
// After the overview (skipFactor-strided) loads, missing segments are
|
||||
// filled in at full resolution in the background while the user is idle.
|
||||
int bgHighResSeg; // next segment index to compute at high-res
|
||||
bool bgFinished; // true when all segments are computed at high-res
|
||||
int lastInteractedFrame; // frame counter when last user interaction occurred
|
||||
bool isBgProcessing; // true while background task is actively computing
|
||||
|
||||
// FFT size cache — LRU cache of previously computed STFT results.
|
||||
// When user switches FFT sizes, we check the cache first to avoid
|
||||
// recomputing. When cache is full, we evict the least-recently-used entry.
|
||||
FFTSizeCache fftCache;
|
||||
|
||||
// Waveform scope view (underneath spectrogram viewport)
|
||||
ScopeView scopeView;
|
||||
bool showScope; // Toggle to show/hide scope view
|
||||
|
||||
// Scope view divider
|
||||
float dividerY; // Y position of divider between spectrogram and scope (0-1 normalized)
|
||||
bool isDividing; // True while user is dragging the divider
|
||||
Vector2 dividerStartPos; // Mouse position when started dividing
|
||||
float dividerStartY; // Spectro height when started dividing
|
||||
} SpectrogramApp;
|
||||
|
||||
// ============================================================================
|
||||
// Global State (defined in spectrogram.c)
|
||||
// ============================================================================
|
||||
|
||||
extern SpectrogramApp app;
|
||||
extern Sound AudioPlaybackSound;
|
||||
extern Texture2D colormapTexture;
|
||||
extern Font mainFont;
|
||||
|
||||
// ============================================================================
|
||||
// Small math helpers (header-inline so every module can use them)
|
||||
// ============================================================================
|
||||
|
||||
static inline float AmplitudeToDecibels(float amplitude)
|
||||
{
|
||||
if (amplitude < 0.0001f) amplitude = 0.0001f;
|
||||
return 20.0f * log10f(amplitude);
|
||||
}
|
||||
|
||||
static inline float Clamp(float value, float min, float max)
|
||||
{
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
#endif // SPECTROGRAM_TYPES_H
|
||||
Reference in New Issue
Block a user