Files
rspektrum/src/spectrogram_types.h
T
tyler 3a8f20b783 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>
2026-05-25 01:15:51 -07:00

223 lines
6.9 KiB
C

// 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