5c3c88dc22
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
103 lines
4.0 KiB
Bash
Executable File
103 lines
4.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Usage: shot_input.sh <output.png> [action ...]
|
|
# Launches rspektrum under Xvfb, runs input actions, captures the screen, cleans up.
|
|
#
|
|
# Actions (each runs in order):
|
|
# "sleep N" sleep N seconds locally (let the app settle / animate).
|
|
# Capture at "sleep 12" for a SETTLED render: the background
|
|
# high-res STFT fill runs over the first several seconds, so
|
|
# earlier grabs are nondeterministic run-to-run.
|
|
# "click X Y" left-click at (X,Y): move, press, hold ~0.2s, release.
|
|
# "drag X1 Y1 X2 Y2" left-drag from (X1,Y1) to (X2,Y2).
|
|
# "rdrag X1 Y1 X2 Y2" same but with Alt held (the app's pan gesture).
|
|
# "key NAME" xdotool keysym, e.g. "key F1", "key p", "key Home".
|
|
# anything else passed verbatim to xdotool (mousemove, mousedown, ...).
|
|
#
|
|
# IMPORTANT timing note: a left button's press and release MUST land in different
|
|
# 60 Hz frames or raylib's IsMouseButtonPressed edge-detect (current && !previous)
|
|
# never fires. The click/drag helpers below insert that gap; a bare "click 1" via
|
|
# xdotool is too fast and silently no-ops. Keys reach GLFW fine under Xvfb; so do
|
|
# synthetic mouse buttons (the pointer just has to be over a live target).
|
|
#
|
|
# Layout cheat-sheet at the default 1280x800 window (UI scale = 1.0):
|
|
# spectrogram viewport ~ x:[385,1242] y:[50,468] (drags/selections go here)
|
|
# sidebar ~ x:[0,320] (buttons; click centers)
|
|
# a selection box needs a >5 px drag or it reverts to "no selection".
|
|
set -u
|
|
OUT="${1:-/tmp/rspek_shot.png}"; shift || true
|
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DISP=:99
|
|
WAV="${RSPEK_WAV:-/tmp/mlink_bulk_drop.wav}"
|
|
|
|
pkill -f "Xvfb $DISP" 2>/dev/null
|
|
sleep 1
|
|
Xvfb $DISP -screen 0 1280x800x24 >/tmp/xvfb.log 2>&1 &
|
|
XVFB=$!
|
|
sleep 1
|
|
|
|
cd "$REPO" || exit 1
|
|
DISPLAY=$DISP ./bin/Debug/rspektrum "$WAV" >/tmp/rspek.log 2>&1 &
|
|
APP=$!
|
|
sleep 5 # initial load
|
|
|
|
# Make sure the app window is focused so key events land on it.
|
|
WID=$(DISPLAY=$DISP xdotool search --name "Spectrogram" | head -1)
|
|
[ -n "$WID" ] && DISPLAY=$DISP xdotool windowactivate "$WID" 2>/dev/null
|
|
sleep 0.3
|
|
|
|
xd() { DISPLAY=$DISP xdotool "$@" 2>>/tmp/xdotool.log; }
|
|
|
|
for act in "$@"; do
|
|
# shellcheck disable=SC2086
|
|
set -- $act
|
|
case "$1" in
|
|
sleep) sleep "$2" ;;
|
|
click)
|
|
xd mousemove "$2" "$3"; sleep 0.1
|
|
xd mousedown 1; sleep 0.2 # hold across >=1 frame
|
|
xd mouseup 1; sleep 0.15
|
|
;;
|
|
drag)
|
|
xd mousemove "$2" "$3"; sleep 0.1
|
|
xd mousedown 1; sleep 0.1
|
|
xd mousemove "$4" "$5"; sleep 0.2
|
|
xd mouseup 1; sleep 0.15
|
|
;;
|
|
rdrag)
|
|
xd keydown alt; sleep 0.05
|
|
xd mousemove "$2" "$3"; sleep 0.1
|
|
xd mousedown 1; sleep 0.1
|
|
xd mousemove "$4" "$5"; sleep 0.2
|
|
xd mouseup 1; sleep 0.1
|
|
xd keyup alt; sleep 0.15
|
|
;;
|
|
wheel)
|
|
# "wheel X Y N [mod]" — N wheel clicks at (X,Y); N<0 scrolls down.
|
|
# X11 maps wheel up/down to buttons 4/5. Each click needs its own
|
|
# frame, same edge-detect reason as the click helper above.
|
|
xd mousemove "$2" "$3"; sleep 0.1
|
|
_n="$4"; _btn=4
|
|
if [ "$_n" -lt 0 ]; then _btn=5; _n=$(( -_n )); fi
|
|
_mod="${5:-}"
|
|
[ -n "$_mod" ] && { xd keydown "$_mod"; sleep 0.05; }
|
|
_i=0
|
|
while [ "$_i" -lt "$_n" ]; do
|
|
xd click "$_btn"; sleep 0.05
|
|
_i=$(( _i + 1 ))
|
|
done
|
|
[ -n "$_mod" ] && { xd keyup "$_mod"; sleep 0.05; }
|
|
sleep 0.15
|
|
;;
|
|
*) xd "$@"; sleep 0.15 ;;
|
|
esac
|
|
done
|
|
sleep 0.3
|
|
|
|
DISPLAY=$DISP import -window root "$OUT" 2>/tmp/import.log
|
|
RC=$?
|
|
kill $APP 2>/dev/null
|
|
kill $XVFB 2>/dev/null
|
|
wait 2>/dev/null
|
|
echo "capture rc=$RC -> $OUT"
|
|
ls -la "$OUT" 2>/dev/null
|