feat: live cursor readout (time / frequency / STFT level)
Floating tag follows the pointer over the spectrogram showing the time, frequency, and STFT magnitude (dB) under the cursor — the standard spectrum-analyzer probe. Suppressed while selecting/panning (which have their own readout) and when a modal is open. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -481,6 +481,55 @@ void DrawSelectionDrag(Rectangle bounds)
|
||||
DrawStatPanel(bounds, (Rectangle){ x, y, w, h });
|
||||
}
|
||||
|
||||
// Floating time/frequency/level tag that follows the cursor over the
|
||||
// spectrogram (suppressed while selecting/panning, which have their own
|
||||
// readout). The level is the STFT magnitude at that bin, in dB.
|
||||
void DrawCursorReadout(Rectangle bounds)
|
||||
{
|
||||
if (!app.loaded || !app.stftComputed || app.signal.samples == NULL) return;
|
||||
if (app.sel.isTimeSelecting || app.sel.isFreqSelecting || app.sel.isDragging ||
|
||||
app.view.isPanning) return;
|
||||
Vector2 m = GetMousePosition();
|
||||
if (!CheckCollisionPointRec(m, bounds)) return;
|
||||
|
||||
float tFrac = app.view.start + ((m.x - bounds.x) / bounds.width) * (app.view.end - app.view.start);
|
||||
float fFrac = app.view.freqStart + (1.0f - (m.y - bounds.y) / bounds.height) * (app.view.freqEnd - app.view.freqStart);
|
||||
float timeSec = tFrac * app.signal.duration;
|
||||
float nyquist = app.signal.sampleRate * 0.5f;
|
||||
float freqHz = fFrac * nyquist;
|
||||
|
||||
// Sample the STFT level at this (time, freq).
|
||||
char level[32] = "--";
|
||||
if (app.stft.numSegments > 0) {
|
||||
int seg = (int)(tFrac * app.stft.numSegments);
|
||||
if (seg < 0) seg = 0;
|
||||
if (seg >= app.stft.numSegments) seg = app.stft.numSegments - 1;
|
||||
const StftSegment* s = &app.stft.segments[seg];
|
||||
if (s->spectrum && s->numBins > 1) {
|
||||
float binHz = nyquist / (float)(s->numBins - 1);
|
||||
int bin = (int)(freqHz / binHz + 0.5f);
|
||||
if (bin < 0) bin = 0;
|
||||
if (bin >= s->numBins) bin = s->numBins - 1;
|
||||
sprintf(level, "%.1f dB", AmplitudeToDecibels(s->spectrum[bin].amplitude));
|
||||
}
|
||||
}
|
||||
|
||||
char text[80];
|
||||
sprintf(text, "%.3fs %.0f Hz %s", timeSec, freqHz, level);
|
||||
int fontSize = 10;
|
||||
int tw = MeasureText(text, fontSize);
|
||||
int boxW = tw + 12, boxH = fontSize + 8;
|
||||
|
||||
// Offset up-right of the cursor; flip to keep it inside the viewport.
|
||||
float bx = m.x + 14, by = m.y - boxH - 6;
|
||||
if (bx + boxW > bounds.x + bounds.width) bx = m.x - boxW - 14;
|
||||
if (by < bounds.y) by = m.y + 14;
|
||||
|
||||
DrawRectangle((int)bx, (int)by, boxW, boxH, (Color){ 0, 0, 0, 200 });
|
||||
DrawRectangleLines((int)bx, (int)by, boxW, boxH, Fade(SKYBLUE, 0.6f));
|
||||
DrawText(text, (int)bx + 6, (int)by + 4, fontSize, (Color){ 180, 220, 255, 255 });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Playhead
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user