The minimum visible time window was a flat 2% of the file, but view.start and view.end are normalized to the whole signal — so the achievable time resolution scaled with file length. A 30-minute recording could never show less than a 36-second span, while a 30-second one reached 0.6 s. Long captures were effectively unreadable at the sample level no matter how far you scrolled. Derive the floor from the STFT hop instead (MinTimeViewWidth): segments sit fftSize/HOP_RATIO samples apart, so the real limit is the point where only a handful of segments span the viewport and further zoom would interpolate rather than reveal. The floor is now a constant ~43 ms at 48 kHz/1024 regardless of duration — an 844x improvement on a 30-minute file, and it tightens further with a smaller FFT. Guards cover the unloaded (sampleRate 0) and shorter-than-the-floor cases. Zooming is also no longer forced to move both axes together. The bare wheel keeps the existing coupled behaviour; Shift+wheel is time-only and Ctrl+wheel frequency-only, so a long capture can be stretched along time without collapsing the frequency range to match. Time-axis labels now pick their precision from the span between adjacent ticks (1 to 4 decimals, and m:ss.sss past a minute). At the spans this change makes reachable the old fixed "%.1fs" printed the same value in every slot, which read as a frozen axis. Adds a `wheel X Y N [mod]` action to shot_input.sh for exercising zoom headlessly, and known_bugs.md for behaviour that is unspecified rather than broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8ZWfr5XZyyDttvkhJUgHN
3.4 KiB
Known bugs & rough edges
Behaviour that is unspecified, awkward, or known-imperfect — as distinct from outright breakage. Each entry says what happens, why, and what a real fix would need to decide.
Playhead vs. a selection edited mid-playback
Status: partially addressed; underlying semantics still undefined.
Playback hands a snapshot of the selected region to the audio device — the samples are copied, bandpassed, and normalised up front, so the sound coming out of the speakers is fixed the moment Space is pressed. The selection box, however, stays live and editable while that audio plays.
Previously the playhead marker was drawn against the live app.sel, so moving
or resizing the selection during playback made the marker jump, run off the end,
or scale to a region that had nothing to do with what was audible. The playhead
is now measured against playSelStart / playSelEnd / playDuration, captured
at PlaySelectedRegion() time, so it tracks the audio that is actually playing.
What remains undefined is the product question, not the drawing math:
- If the user drags the selection somewhere else mid-playback, should the audio follow (restart / re-seek against the new region), or should playback keep going with the old buffer and the marker stay where it is (current behaviour)?
- Should editing the selection during playback simply stop playback?
- Should the playhead remain visible when the region it refers to is scrolled off-screen, or has been replaced by a selection elsewhere in the file?
Current behaviour is the conservative reading: the sound wins. The marker always describes real audio, and a mid-playback edit is treated as staging the next thing to play rather than modifying the current one. That is defensible but was never explicitly chosen, and the UI gives no feedback that the box on screen and the audio in flight have diverged.
Related: a sub-threshold click inside an existing selection deliberately does
not clear it (hoverInsideSelection in spectrogram.c), because silently
clearing changes what Space would play. A click on empty space still resets
to full range.
Touches: audio.c (PlaySelectedRegion), spectrogram.c (playhead
advance, scope cursor), render.c (DrawPlayhead), spectrogram_types.h
(playSelStart / playSelEnd / playDuration).
Long-file zoom sharpness lags the zoom gesture
Status: working as designed, but reads as a bug.
ComputeSkipFactor() (stft.c) strides the initial STFT pass for long files —
every 8th segment past 10 minutes — so the overview loads promptly. The missing
segments are filled at full resolution afterwards: the visible range first, then
a background sweep of the whole file.
The practical effect is that a hard zoom into a 30-minute file can look blocky
for a moment before the foreground fill catches up and it sharpens. The fill is
gated on view.end - view.start <= 0.25f, so it only runs once reasonably zoomed
in. If a view stays blocky indefinitely, the fill is not reaching that range and
that is a real bug worth chasing.
Load time on long files is unbounded and unreported
A 30-minute 48 kHz file spends a long time in Processing… before the UI is
usable, and the percentage indicator advances non-linearly (the strided overview
completes fast, the high-res fill does not). There is no cancel. Headless/scripted
runs must wait this out; see AGENTS.md.