isr.py carried a plugin architecture — AudioBackend ABC, a _backend_classes
registry, _discover_backends, available_backends/get_backend, a
preferred_backend config knob — for exactly one implementation. It was
also the source of three real bugs:
* find_device() returned None before reaching the ALSA PCM-name fallback
whenever `arecord -l` enumerated nothing, so the documented
`device = shared_mic` (dsnoop from asound.conf) case failed outright on
any host with no hardware card. The fallback is the whole point: those
PCMs never appear in `arecord -l`.
* `device = monitor` could never resolve. ALSA has no generic monitor
source and list_devices hardcoded is_monitor=False, so the keyword —
advertised in config.example.ini as the "EASY" way to capture system
audio — only ever produced "device not found". Removed; it is a
PulseAudio idea that outlived the PulseAudio backend.
* list_all_devices() deduplicated by lowercased name "in case a device
appears in multiple backends". With one backend what it actually did
was delete the second of two identical USB cards. Dedup dropped.
find_device() now always returns a device, so _resolve_device's
unreachable "Available devices:" error is gone; an unknown spec is
passed to ALSA verbatim and logged. AudioDevice loses backend/
is_monitor plus channels/sample_rate — the latter were hardcoded 2/44100
and only ever printed by --list-devices, i.e. numbers with no bearing on
what gets recorded.
--list-devices now says outright that it lists hardware cards only, which
is where the shared_mic confusion started.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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.