3a8f20b783
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>
223 lines
6.9 KiB
C
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
|