diff --git a/src/audio.c b/src/audio.c index 71c47e0..d7111e2 100644 --- a/src/audio.c +++ b/src/audio.c @@ -243,6 +243,14 @@ void PlaySelectedRegion(void) float* regionSamples = BuildSelectionAudio(&numSamples); if (!regionSamples) return; + // Snapshot what we're about to play so the playhead tracks THIS region even + // if the user moves the selection mid-playback. Duration comes from the + // buffer we actually built, not from app.signal.duration. + app.playSelStart = app.sel.timeStart; + app.playSelEnd = app.sel.timeEnd; + app.playDuration = (app.signal.sampleRate > 0) + ? (float)numSamples / (float)app.signal.sampleRate : 0.0f; + EnsureAudioDevice(); // opened on demand; released again once playback ends if (AudioPlaybackSound.frameCount != 0) UnloadSound(AudioPlaybackSound); diff --git a/src/render.c b/src/render.c index 22e382b..6fb5988 100644 --- a/src/render.c +++ b/src/render.c @@ -1685,7 +1685,8 @@ void DrawPlayhead(Rectangle bounds) { if (!app.isPlaying || app.playheadT < 0.0f || app.playheadT > 1.0f) return; - float timePos = app.sel.timeStart + app.playheadT * (app.sel.timeEnd - app.sel.timeStart); + // Against the snapshot of the playing region, not the live selection. + float timePos = app.playSelStart + app.playheadT * (app.playSelEnd - app.playSelStart); float viewWidth = app.view.end - app.view.start; float t = (timePos - app.view.start) / viewWidth; float x = bounds.x + t * bounds.width; diff --git a/src/spectrogram.c b/src/spectrogram.c index ef872e3..337e82e 100644 --- a/src/spectrogram.c +++ b/src/spectrogram.c @@ -945,11 +945,14 @@ int main(int argc, char* argv[]) app.isPlaying = false; app.playbackFinished = true; } - // Track playhead position manually + // Track playhead position manually, against the length of the buffer + // that's actually playing (see playDuration) rather than a length + // re-derived from the live selection — the user can move the + // selection mid-playback without the marker jumping. app.playheadElapsed += GetFrameTime(); - float selectionDuration = (app.sel.timeEnd - app.sel.timeStart) * app.signal.duration; - if (selectionDuration > 0) { - app.playheadT = app.playheadElapsed / selectionDuration; + if (app.playDuration > 0.0f) { + app.playheadT = app.playheadElapsed / app.playDuration; + if (app.playheadT > 1.0f) app.playheadT = 1.0f; } } @@ -1324,8 +1327,13 @@ int main(int argc, char* argv[]) app.sel.freqStart = app.sel.freqEnd; app.sel.freqEnd = tmp; } - } else { - // Drag too small - revert to full range + } else if (!hoverInsideSelection) { + // Sub-threshold drag outside any existing selection: treat + // as a click on empty space and reset to full range. A + // stray click *inside* the current box leaves it alone — + // silently clearing it there changes what Space plays. + // (hoverInsideSelection, not clickInsideSelection: the + // latter is press-frame-only and is always false here.) ClearSelection(); } app.sel.isTimeSelecting = false; @@ -1715,7 +1723,7 @@ int main(int argc, char* argv[]) app.scopeView.data.sampleRate = app.signal.sampleRate; // Show playhead if playing if (app.isPlaying) { - DrawScopeView(&app.scopeView, app.sel.timeStart + app.playheadT * (app.sel.timeEnd - app.sel.timeStart)); + DrawScopeView(&app.scopeView, app.playSelStart + app.playheadT * (app.playSelEnd - app.playSelStart)); } else { DrawScopeView(&app.scopeView, -1.0f); } diff --git a/src/spectrogram_types.h b/src/spectrogram_types.h index 9a7e580..fbb8a83 100644 --- a/src/spectrogram_types.h +++ b/src/spectrogram_types.h @@ -138,9 +138,19 @@ typedef struct { bool stftComputed; // Playback state - float playheadT; // 0-1 normalized position in selection + float playheadT; // 0-1 normalized position within the PLAYING region float playheadElapsed; // Elapsed seconds since play started + // Snapshot of the region actually handed to the audio device, captured at + // PlaySelectedRegion time. The playhead must be measured against this, not + // against the live app.sel — the user can move or resize the selection while + // audio is still playing, and the marker has to keep tracking the sound + // that's really coming out. playDuration comes from the buffer's own sample + // count / sampleRate, so it can't drift from app.signal.duration (which is + // derived pre-mono-downmix and disagrees for stereo files). + float playSelStart, playSelEnd; // sel.timeStart/End when playback began + float playDuration; // true length of the playing buffer, seconds + // Time + frequency box selection and its drag/move interaction state. Selection sel;