feat: add signal stats panel for selection region
Display Duration, Energy, Peak amplitude, RMS amplitude, and PAPR for the current time/frequency selection. The stats box: - Appears next to the selection box (right side, or left if not enough room) - Centers vertically on the selection box - Uses a semi-transparent dark background with yellow border - Updates live while dragging the selection - Computed in utils.c with ComputeSignalStats()
This commit is contained in:
+129
-8
@@ -4,6 +4,7 @@
|
||||
#include "raylib.h"
|
||||
#include "resource_dir.h"
|
||||
#include "platform.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -64,14 +65,6 @@ typedef enum {
|
||||
// Data Structures
|
||||
// ============================================================================
|
||||
|
||||
typedef struct {
|
||||
float* samples;
|
||||
int numSamples;
|
||||
int sampleRate;
|
||||
int channels;
|
||||
float duration;
|
||||
} AudioSignal;
|
||||
|
||||
typedef struct {
|
||||
float frequency;
|
||||
float amplitude;
|
||||
@@ -1210,6 +1203,70 @@ static void DrawSelection(Rectangle bounds)
|
||||
|
||||
// Draw selection box border
|
||||
DrawRectangleLinesEx((Rectangle){ selStartX, selStartY, selEndX - selStartX, selEndY - selStartY }, 2, YELLOW);
|
||||
|
||||
// Display selection stats inside viewport, clamped to fit
|
||||
{
|
||||
int startSample = (int)(app.timeSelectionStart * app.signal.numSamples);
|
||||
int endSample = (int)(app.timeSelectionEnd * app.signal.numSamples);
|
||||
SignalStats stats = ComputeSignalStats(&app.signal, startSample, endSample);
|
||||
|
||||
if (stats.durationSec > 0.0f && app.signal.samples != NULL) {
|
||||
char lines[5][128];
|
||||
int lineCount = 0;
|
||||
int fontSize = 10;
|
||||
int maxTextW = 0;
|
||||
|
||||
sprintf(lines[lineCount++], "Duration: %.3fs", stats.durationSec);
|
||||
sprintf(lines[lineCount++], "Energy: %.2f", stats.energy);
|
||||
sprintf(lines[lineCount++], "Peak: %.3f", stats.peakAmplitude);
|
||||
sprintf(lines[lineCount++], "RMS: %.3f", stats.rmsAmplitude);
|
||||
sprintf(lines[lineCount++], "PAPR: %.1f dB", stats.paprDb);
|
||||
|
||||
// Measure text width
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
int textW = MeasureText(lines[i], fontSize);
|
||||
if (textW > maxTextW) maxTextW = textW;
|
||||
}
|
||||
|
||||
int boxW = maxTextW + 20;
|
||||
int boxH = lineCount * 14 + 12;
|
||||
|
||||
// Center vertically on the selection box, clamp to viewport
|
||||
float selCenterY = (selStartY + selEndY) / 2.0f;
|
||||
float boxY = selCenterY - boxH / 2.0f;
|
||||
if (boxY < bounds.y) boxY = bounds.y;
|
||||
if (boxY + boxH > bounds.y + bounds.height) boxY = bounds.y + bounds.height - boxH;
|
||||
|
||||
// Place to the right of the selection, or left if not enough room
|
||||
float boxX = selEndX + 10;
|
||||
if (boxX + boxW > bounds.x + bounds.width) {
|
||||
boxX = selStartX - boxW - 10;
|
||||
if (boxX < bounds.x) boxX = bounds.x;
|
||||
}
|
||||
|
||||
// Clamp to viewport bounds
|
||||
if (boxX + boxW > bounds.x + bounds.width) {
|
||||
// Not enough room on right — draw to the left of selection
|
||||
if (selStartX > boxW + 20) {
|
||||
boxX = selStartX - boxW - 10;
|
||||
} else {
|
||||
boxX = bounds.x;
|
||||
}
|
||||
}
|
||||
if (boxY + boxH > bounds.y + bounds.height) {
|
||||
boxY = bounds.y + bounds.height - boxH;
|
||||
}
|
||||
|
||||
// Draw background box
|
||||
DrawRectangle((int)boxX, (int)boxY, boxW, boxH, (Color){ 0, 0, 0, 200 });
|
||||
DrawRectangleLines((int)boxX, (int)boxY, boxW, boxH, Fade(YELLOW, 0.6f));
|
||||
|
||||
// Draw text
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
DrawText(lines[i], (int)boxX + 10, (int)boxY + 8 + i * 14, fontSize, LIGHTGRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawSelectionDrag(Rectangle bounds)
|
||||
@@ -1240,6 +1297,70 @@ static void DrawSelectionDrag(Rectangle bounds)
|
||||
float h = fabsf(selEndY - selStartY);
|
||||
|
||||
DrawRectangleLinesEx((Rectangle){ x, y, w, h }, 2, YELLOW);
|
||||
|
||||
// Display live stats while dragging (inside viewport, clamped to fit)
|
||||
{
|
||||
int startSample = (int)(app.timeSelectionStart * app.signal.numSamples);
|
||||
int endSample = (int)(app.timeSelectionEnd * app.signal.numSamples);
|
||||
SignalStats stats = ComputeSignalStats(&app.signal, startSample, endSample);
|
||||
|
||||
if (stats.durationSec > 0.0f && app.signal.samples != NULL) {
|
||||
char lines[5][128];
|
||||
int lineCount = 0;
|
||||
int fontSize = 10;
|
||||
int maxTextW = 0;
|
||||
|
||||
sprintf(lines[lineCount++], "Duration: %.3fs", stats.durationSec);
|
||||
sprintf(lines[lineCount++], "Energy: %.2f", stats.energy);
|
||||
sprintf(lines[lineCount++], "Peak: %.3f", stats.peakAmplitude);
|
||||
sprintf(lines[lineCount++], "RMS: %.3f", stats.rmsAmplitude);
|
||||
sprintf(lines[lineCount++], "PAPR: %.1f dB", stats.paprDb);
|
||||
|
||||
// Measure text width
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
int textW = MeasureText(lines[i], fontSize);
|
||||
if (textW > maxTextW) maxTextW = textW;
|
||||
}
|
||||
|
||||
int boxW = maxTextW + 20;
|
||||
int boxH = lineCount * 14 + 12;
|
||||
|
||||
// Center vertically on the selection box, clamp to viewport
|
||||
float selCenterY = (selStartY + selEndY) / 2.0f;
|
||||
float boxY = selCenterY - boxH / 2.0f;
|
||||
if (boxY < bounds.y) boxY = bounds.y;
|
||||
if (boxY + boxH > bounds.y + bounds.height) boxY = bounds.y + bounds.height - boxH;
|
||||
|
||||
// Place to the right of the selection, or left if not enough room
|
||||
float boxX = selEndX + 10;
|
||||
if (boxX + boxW > bounds.x + bounds.width) {
|
||||
boxX = selStartX - boxW - 10;
|
||||
if (boxX < bounds.x) boxX = bounds.x;
|
||||
}
|
||||
|
||||
// Clamp to viewport bounds
|
||||
if (boxX + boxW > bounds.x + bounds.width) {
|
||||
// Not enough room on right — draw to the left of selection
|
||||
if (x > boxW + 20) {
|
||||
boxX = x - boxW - 10;
|
||||
} else {
|
||||
boxX = bounds.x;
|
||||
}
|
||||
}
|
||||
if (boxY + boxH > bounds.y + bounds.height) {
|
||||
boxY = bounds.y + bounds.height - boxH;
|
||||
}
|
||||
|
||||
// Draw background box
|
||||
DrawRectangle((int)boxX, (int)boxY, boxW, boxH, (Color){ 0, 0, 0, 200 });
|
||||
DrawRectangleLines((int)boxX, (int)boxY, boxW, boxH, Fade(YELLOW, 0.6f));
|
||||
|
||||
// Draw text
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
DrawText(lines[i], (int)boxX + 10, (int)boxY + 8 + i * 14, fontSize, LIGHTGRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user