Commit Graph

22 Commits

Author SHA1 Message Date
admin 9c58e35546 feat: drop rms_display from /api/analyze
The UI no longer draws waveforms, so the server stops computing and
sending the ~800-point RMS preview; the payload is now just sections,
duration, window. Old analysis caches stay valid: rms/rms_display are
popped when serving a cache hit (same pattern as the earlier full-RMS
removal), so no DETECTOR_VERSION bump.

Verified: 63 tests pass; endpoint smoke test (fresh + cached analyze)
confirms no RMS keys and correct section detection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:02:47 +02:00
admin 91701ce4d3 feat: remove per-file waveform SVG from the analyse view
Same rationale as the day timeline: purely visual, useless via screen
reader. The section count it carried in its aria-label moved into the
meta line ("N loud sections - margin: 12 dB - gap: 2s - min: 0.5s").
drawWave() and the svg.wave CSS are gone; the UI now renders no SVG at
all. /api/analyze still returns rms_display for API stability, but the
bundled UI no longer reads it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 11:58:14 +02:00
admin 41d921a42a feat: remove decorative day-timeline SVG, linear highlights panel
The user is blind; the day activity timeline (and its hour labels) was
purely visual, non-interactive, and carried no information not already
in the chips/summary. The highlights panel is now linear text and
buttons in reading order: summary line ("N files analysed - M loud
sections"), key-hint note (now always shown, J/K/U/I is the primary
interface), chips toggle, chips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 11:49:59 +02:00
admin 653084e90b feat: cleaner day highlights panel
- Day timeline axis now labels round wall-clock hours with tick marks
  on the bar (start/end fallback when the span has <2 round hours),
  replacing the arbitrary start/midpoint/end labels.
- Long chip lists (>12) collapse behind an aria-expanded toggle button
  so the panel is not a wall of 50 buttons; J/K/U/I are unaffected.
- Chips group aria-labels shortened to "Day loud sections" / "Loud
  sections" - the key-binding explanation lives only in the visible
  note, not repeated in the group name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 11:39:00 +02:00
admin 98d2d7085d feat: auto-advance radio, J/K decoupled from highlights, U/I ranked stepping
- J/K and Prev/Next now always step the clip queue in time order; the old
  "Highlights only" checkbox could silently change what J/K did.
- Auto-advance is a three-way radio: don't auto-advance / auto-advance
  (next in time) / auto-advance highlights only (next by loudness). It
  only affects what plays when a clip ends.
- The "Top" highlight-count input is gone. U/I (and highlights-only
  auto-advance) walk the full loudest-first ranking from scoreOrder()
  with no top-N cutoff - review simply stops when the user stops.
- In-player U/I (full-file playback) step the same ranking, anchored on
  the section under the playhead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:35:31 +02:00
admin 2b0403d05d feat: wall-clock clip labels, collapsible day Highlights with analysed marker
- Clip bar label is now the wall-clock time of occurrence plus queue
  position ("03:46:20 to 03:46:22 (73 / 187)"); filename and score moved
  to the hover tooltip. Works for both per-file and day queues via an
  absStart epoch on every queue entry, derived from the filename clock
  (listing date), falling back to in-file offsets for non-standard names.
- Day Highlights button toggles the panel; re-expanding reuses the
  already-built results (re-armed J/K queue from dayHlSections) and only
  recomputes when margin/gap/min-duration changed. A "analysed" suffix
  marks days where every file has a cached analysis for the current
  params; fetchAnalysis keeps f.cached_analysis fresh client-side.
- Day timeline now positions files by the filename clock instead of
  mtime-duration, and the three axis labels (span start / midpoint /
  end) carry explanatory tooltips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:28:23 +02:00
admin 9f1a6ff711 feat: O key opens the current clip in the full file
Extracts the "Open in file" button handler into openClipInFile() and
binds O in the shared keydown listener as a keyboard alternative, so
clip review never needs the mouse: J/K/U/I to step, O to drop into the
full recording for context.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:24:30 +02:00
admin f6031cfa16 feat: onset-aware section scoring so slow swells rank at the bottom
A section's score is now its peak dB above the noise floor capped by
the sharpest rise within ONSET_SECONDS (0.5 s). Real events (voices,
impacts, barks) rise fast and keep their full prominence; a gradual
swell that outruns the 30 s floor blocks (gusts, distant approaching
cars) still flags but scores near zero, so score-ranked review (chips,
U/I highlights, "Highlights only" mode) surfaces events first. A
section starting in a file's first 0.5 s is scored against the floor
instead, so events cut off by a file split are not punished as swells.

Old cached analyses carry now-wrong scores, so the cache gains a
leading "detector" version key (DETECTOR_VERSION = 2) checked by both
_cached_analysis_params() and the /api/analyze cache hit path; v1
caches never match and are recomputed on the next analyse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:57:19 +02:00
admin f52eb62215 feat: U/I keys and "Highlights only" mode to review top-scored sections
J/K still steps through every queued section in time order; U/I steps
through only the highlights, defined as the top-N sections by score
(new "Top" input in the clip bar, default 50, matching the day chips).
A "Highlights only" checkbox makes J/K, Prev/Next, and Auto-advance
skip non-highlights too, so a day with thousands of detections plays
as a short reel of just the loudest events. Both key pairs also work
during full-file playback, and modified keypresses (Ctrl+K, Ctrl+U)
are no longer hijacked from the browser.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:38:56 +02:00
admin 5e7620627b feat: name cut clips by wall-clock time; fix recording filename format
Cut downloads were named by byte offsets (`..._cut_740s-750s.flac`). They are
now named by the actual recording time the slice covers, e.g.
`20260523_22-31-30_22-32-30.flac` for a 22:31:30->22:32:30 cut of a recording
started at 22:00:00.

To make this reliable, the recording filename is now a fixed
`%Y%m%d_%H%M%S` start-time format (`FILENAME_FORMAT`) shared by isr.py and
web.py, replacing the user-configurable `filename_pattern` (web.py never reads
config.ini, so a custom pattern could not be parsed back). web.py parses the
start time out of the filename via `_recording_start()` and builds cut names
with `_cut_filename()`. The DATE column now also comes from the filename
(falling back to mtime only for non-standard names), since mtime is the last
write, not the start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 14:30:30 +02:00
admin f3716d3ff1 feat: minimum section duration filter (--min-duration, default 0.5 s)
A single 100 ms RMS window above the noise floor used to become its own
section, so isolated pops (clicks, single raindrops) flooded a day with
thousands of sub-second clips like "21:18 to 21:18". Sections shorter
than min_duration (measured after min_gap merging, so a cluster of blips
spanning longer still flags) are now discarded.

Wired through all coupled places: CLI flag, /api/config, controls-bar
input, /api/analyze query param, and the analysis-cache head keys (old
two-key caches no longer match and are recomputed on next analyse).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 09:00:37 +02:00
admin e4d82483b5 docs: code map, regression findings, and HTTP API reference
CLAUDE.md gains a function-level code map of web.py/webui.html, a
"verifying changes" section (test scope, no-node UI checks, endpoint
smoke pattern, Windows commit-message gotcha), and findings recorded as
guard rails: why detection must stay adaptive (fixed threshold produced
~600 useless sections/day), why section playback must stay clip-based
(libsndfile FLACs have no SEEKTABLE so browser seeks bisect the file),
and the five places analysis params are coupled.

README gains the adaptive-detection and clip-review features up top, an
HTTP API table for scripting, and a corrected Docker analyses-cache
paragraph (the rw mount is ./recordings/analyses layered over the ro
recordings mount, not a separate ./analyses dir).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:21:37 +02:00
admin 119e631faf feat: instant section playback via server-rendered clips
Add /api/clip: decodes a WAV/FLAC slice server-side and returns a small
standalone 16-bit WAV with exact Content-Length (capped at 600s, cached
client-side since finished recordings are immutable). Active recordings
are refused like analyse/cut/delete.

Section chips and J/K now play these clips through a bottom player bar
instead of seeking the full recording - FLACs have no seek table, so
browser seeks bisected hundreds of MB with Range requests and playback
lagged or never started. The bar steps through a queue (one file's
sections or a whole day's via Highlights), auto-advances to the next
section on end for continuous review, and "Open in file" jumps to the
same position in the full recording for context.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:13:39 +02:00
admin c84b7d8222 feat: adaptive noise-floor loudness detection with section scoring
Replace the fixed RMS threshold with prominence over a rolling noise
floor (20th percentile per 30s block, min-smoothed so events cannot
raise their own floor, clamped to -54 dBFS). Slow ambience changes such
as rain or daytime traffic hum move the floor instead of flagging
everything; sections now need `margin` dB (default 12) of prominence.

Each section carries a score (peak dB above floor); day-highlight chips
show the top 50 by score when there are too many to list, so the most
striking events are reviewed first.

--threshold is replaced by --margin; analysis caches are now keyed by
margin+min_gap, old threshold-keyed caches never match and are
overwritten on the next analyse. Detector covered by tests/test_web.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:36:48 +02:00
admin 16dd7cbe51 feat: duration and seeking for in-progress FLAC recordings
FLAC duration cannot be derived from byte size (variable compression),
so unlike WAV the header cannot be patched from st_size alone. Instead,
every FLAC frame header carries its own frame/sample number: read the
last 64 KB of the growing file, scan backwards for a frame sync,
CRC-8-verify the header to reject false matches in compressed data,
and compute the exact samples recorded so far. STREAMINFO
total_samples (36 bits at a fixed offset) is rewritten in the served
bytes only - the on-disk file is never touched.

Overhead: one tail read per /stream request, active files only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:37:55 +02:00
admin 8e496ec2c4 perf: faster page loads, live-recording playback and seeking fixes
Server (web.py):
- /api/analyze no longer returns the full per-window RMS array (~45x
  larger than the rms_display the UI actually renders); old caches are
  stripped on read
- /api/files reads only the first 256 bytes of each analysis cache to
  get threshold/min_gap instead of parsing the whole JSON
- durations cached by (mtime, size) instead of re-opening every audio
  header per request; stat() race with deleted files guarded
- /api/storage no longer walks the recordings tree (used bytes now
  computed client-side from the file list)
- HTTP/1.1 keep-alive enabled; short writes force-close the connection;
  client-disconnect tracebacks from aborted seeks silenced
- all file copies bounded by the advertised Content-Length so files
  growing during a response cannot desync the connection

Live recording playback:
- /stream/ patches in-progress WAV headers to the current file size so
  browsers show real duration and can seek (on-disk header says 0
  frames until the recorder closes the file)
- active files served with Cache-Control: no-store
- reopening the player for a recording file reloads the source to pick
  up newly captured audio

UI loading:
- analyses lazy-load only for expanded day groups; collapsed days defer
  fetching until opened, and auto-load only when cached parameters
  match the current controls (no surprise mass recompute)
- client-side analysis cache shared by file rows and day highlights, so
  re-renders and filters never refetch
- filename filter debounced (200 ms)
- file list auto-refreshes when the active recording set changes,
  unless audio is playing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:29:13 +02:00
admin c445eb3e04 docs: slim CLAUDE.md down to rules, file map, and non-obvious internals
Drop class-by-class architecture listings that are derivable from the
code; keep only constraints a model cannot infer (status.json coupling,
header injection, read-only mount, shutdown deadline, dsnoop/ipc).
Add webui.html to the file map.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:50:09 +02:00
admin 77e7e4ca9e fix: resolve NameError on startup, move analyses into recordings, remap port to 8050
- web.py: Python class bodies can't close over a name they also assign;
  use a temporary alias _analyses_dir to break the self-reference
- docker-compose.yml: mount ./recordings/analyses:/recordings/analyses
  instead of a separate ./analyses volume so cache lives inside recordings;
  remove --analyses-dir flag (default now resolves correctly); remap
  external port 8080→8050 for reverse proxy
- README.md / CLAUDE.md: update Docker port references to 8050

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 08:46:49 +02:00
admin e67e27f047 fix: share soundcard between darkice and ISR via ALSA dsnoop
hw:0,0 is an exclusive ALSA device — darkice holding it caused arecord
to fail silently (stderr was /dev/null), leaving all recordings at 0 bytes
with no errors in the log.

asound.conf: defines a dsnoop virtual device 'shared_mic' that opens
hw:0,0 once and lets multiple processes capture simultaneously.

docker-compose.yml: mount asound.conf into the container as
/etc/asound.conf; add ipc: host so the container shares the host IPC
namespace (dsnoop uses System V shared memory which does not cross the
container IPC boundary without this).

config.example.ini: document the dsnoop setup and shared-device pattern.
README, CLAUDE.md: document the full setup procedure.
2026-04-26 14:21:31 +02:00
admin f84b36687b docs: update README for Docker fix, FLAC/streaming features; add README rule to CLAUDE.md
- Remove stale instruction to set output_directory = /recordings in Docker
- Document that docker compose down + up --build is required when updating
- Fix output_directory and log_file table descriptions
- Expand Web UI section: inline playback, FLAC waveform, live REC badge, WCAG
- Fix Docker notes: log_file path and log_file in Docker guidance
- Add rule to CLAUDE.md: always update and commit README.md with code changes
2026-04-26 13:17:26 +02:00
admin da67523170 fix: Docker volume path, graceful shutdown deadline, inline stream headers
docker-compose.yml: mount ./recordings at /app/recordings (matches
output_directory = recordings in config.ini); previously the recorder
wrote to /app/recordings while the web container read from /recordings,
causing all files to appear missing — explaining the 9-byte Not-found
download from /stream/ and the 0-byte recordings in the UI.
Add stop_grace_period: 30s so Docker waits long enough for files to close.

isr.py: replace per-thread join(timeout=5) with a shared 25 s deadline;
with N recorders the old code could exceed Docker's SIGKILL window and
leave WAV/FLAC files unclosed (corrupt headers).

web.py: add Content-Disposition: inline to /stream/ responses so
browsers never treat the audio response as a file download.

CLAUDE.md: document web.py endpoints, status.json lifecycle, corrected
Docker volume layout, and web.py CLI flags.
2026-04-26 13:10:14 +02:00
admin 8254ccde86 Add Docker support, fix stale docs, translate UI to English
- Dockerfile + docker-compose.yml: two services (recorder + web) sharing
  ./recordings bind mount; recorder maps /dev/snd for ALSA soundcard access
- requirements.txt: requests, numpy, soundfile
- .dockerignore, updated .gitignore (add __pycache__, .pytest_cache)
- isr.py: add SIGTERM handler for clean Docker shutdown; fix stale error
  message that referenced removed PulseAudio/PipeWire/PortAudio backends
- web.py: translate all German UI strings to English
- config.example.ini: remove PipeWire/PulseAudio/PortAudio backend refs,
  simplify soundcard tips to ALSA only
- README.md: full rewrite as user guide (quick start, config reference,
  Docker notes, how it works)
- CLAUDE.md: update architecture section to reflect ALSA-only backend
- Delete changelog.txt and guide.md (internal session notes)
2026-04-26 10:56:55 +02:00