feat: add playhead indicator during audio playback
Add a red vertical line that tracks playback position within the selected region. The playhead shows the current position in the spectrogram as audio plays, making it easy to see what part of the signal is currently being heard. - playheadT tracks 0-1 normalized position in the selection - Red line drawn across the spectrogram viewport while playing - Semi-transparent red highlight behind the line for visibility - Reset playhead to 0 when playback stops or is stopped
This commit is contained in:
@@ -102,6 +102,10 @@ typedef struct {
|
|||||||
bool loaded;
|
bool loaded;
|
||||||
bool stftComputed;
|
bool stftComputed;
|
||||||
|
|
||||||
|
// Playback state
|
||||||
|
float playheadT; // 0-1 normalized position in selection
|
||||||
|
float playheadElapsed; // Elapsed seconds since play started
|
||||||
|
|
||||||
// Time selection (0-1 normalized)
|
// Time selection (0-1 normalized)
|
||||||
float timeSelectionStart;
|
float timeSelectionStart;
|
||||||
float timeSelectionEnd;
|
float timeSelectionEnd;
|
||||||
@@ -1238,6 +1242,28 @@ static void DrawSelectionDrag(Rectangle bounds)
|
|||||||
DrawRectangleLinesEx((Rectangle){ x, y, w, h }, 2, YELLOW);
|
DrawRectangleLinesEx((Rectangle){ x, y, w, h }, 2, YELLOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Playhead
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static void DrawPlayhead(Rectangle bounds)
|
||||||
|
{
|
||||||
|
if (!app.isPlaying || app.playheadT < 0.0f || app.playheadT > 1.0f) return;
|
||||||
|
|
||||||
|
float timePos = app.timeSelectionStart + app.playheadT * (app.timeSelectionEnd - app.timeSelectionStart);
|
||||||
|
float viewWidth = app.viewEnd - app.viewStart;
|
||||||
|
float t = (timePos - app.viewStart) / viewWidth;
|
||||||
|
float x = bounds.x + t * bounds.width;
|
||||||
|
|
||||||
|
// Clamp to bounds
|
||||||
|
x = fmaxf(bounds.x, fminf(bounds.x + bounds.width, x));
|
||||||
|
|
||||||
|
// Draw vertical line
|
||||||
|
DrawLine(x, bounds.y, x, bounds.y + bounds.height, RED);
|
||||||
|
// Draw semi-transparent overlay to make it stand out
|
||||||
|
DrawRectangle(x - 2, bounds.y, 4, bounds.height, (Color){ 255, 0, 0, 60 });
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// PNG Export
|
// PNG Export
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1416,6 +1442,8 @@ static void DrawSidebar(void)
|
|||||||
if (app.isPlaying && AudioPlaybackSound.frameCount > 0) {
|
if (app.isPlaying && AudioPlaybackSound.frameCount > 0) {
|
||||||
StopSound(AudioPlaybackSound);
|
StopSound(AudioPlaybackSound);
|
||||||
app.isPlaying = false;
|
app.isPlaying = false;
|
||||||
|
app.playheadElapsed = 0;
|
||||||
|
app.playheadT = 0;
|
||||||
} else {
|
} else {
|
||||||
PlaySelectedRegion();
|
PlaySelectedRegion();
|
||||||
app.isPlaying = true;
|
app.isPlaying = true;
|
||||||
@@ -1665,6 +1693,12 @@ int main(int argc, char* argv[])
|
|||||||
app.isPlaying = false;
|
app.isPlaying = false;
|
||||||
app.playbackFinished = true;
|
app.playbackFinished = true;
|
||||||
}
|
}
|
||||||
|
// Track playhead position manually
|
||||||
|
app.playheadElapsed += GetFrameTime();
|
||||||
|
float selectionDuration = (app.timeSelectionEnd - app.timeSelectionStart) * app.signal.duration;
|
||||||
|
if (selectionDuration > 0) {
|
||||||
|
app.playheadT = app.playheadElapsed / selectionDuration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle window resize
|
// Handle window resize
|
||||||
@@ -1828,6 +1862,8 @@ int main(int argc, char* argv[])
|
|||||||
StopSound(AudioPlaybackSound);
|
StopSound(AudioPlaybackSound);
|
||||||
app.isPlaying = false;
|
app.isPlaying = false;
|
||||||
app.playbackFinished = false;
|
app.playbackFinished = false;
|
||||||
|
app.playheadElapsed = 0;
|
||||||
|
app.playheadT = 0;
|
||||||
} else if (app.playbackFinished) {
|
} else if (app.playbackFinished) {
|
||||||
// Playback finished naturally - restart from beginning
|
// Playback finished naturally - restart from beginning
|
||||||
PlaySelectedRegion();
|
PlaySelectedRegion();
|
||||||
@@ -2233,6 +2269,7 @@ int main(int argc, char* argv[])
|
|||||||
if (app.showGrid) DrawSpectrogramGrid(viewBounds, 10, 8, Fade(GRAY, 0.3f));
|
if (app.showGrid) DrawSpectrogramGrid(viewBounds, 10, 8, Fade(GRAY, 0.3f));
|
||||||
DrawSelection(viewBounds);
|
DrawSelection(viewBounds);
|
||||||
DrawSelectionDrag(viewBounds);
|
DrawSelectionDrag(viewBounds);
|
||||||
|
DrawPlayhead(viewBounds);
|
||||||
DrawLabels(viewBounds);
|
DrawLabels(viewBounds);
|
||||||
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
||||||
float freqMin = app.freqViewStart * maxFreq;
|
float freqMin = app.freqViewStart * maxFreq;
|
||||||
|
|||||||
Reference in New Issue
Block a user