The null sink and its two loopbacks were loaded at startup and stayed
loaded forever. While loaded they link the microphone and the speaker
device into a single PipeWire driver group: the microphone drives the
graph and the speaker device runs as a clock follower, so every playback
stream pays for continuous cross-device resampling.
Nothing needs the chain while idle — recording is triggered manually.
Build it in toggle_recording() and drop it as soon as the audio is
captured, before transcription runs for minutes.
Module ids are now derived from pactl instead of tracked in a state file,
so a crashed process cannot leave stale ids behind. The state file only
holds the device selection. Startup tears down any leftover chain.
Loopbacks get latency_msec=200; transcription is offline, and a small
buffer would put these nodes on the graph's realtime deadline.
- llm: punctuate() adds punctuation/capitalisation without changing words
- llm: _strip_code_fences() handles markdown-wrapped JSON from gemma3
- llm: filter string 'null' from identify_speakers result
- pipeline: punctuate raw_text in parallel with refine for solo recordings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- llm: generate_title_and_tldr() returns concise title and 2-3 sentence summary
- output: index in root, transkript+zusammenfassung in {base}/ subdir with backlinks
- pipeline: call generate_title_and_tldr for both solo and meeting recordings
- router: mirror subdir structure when copying to Obsidian vault
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Config: obsidian.vault path. On Obsidian button click, file is copied to
vault dir then opened via obsidian:// URI. Vault path configurable in settings.
- /audio/devices now returns sounddevice device names (not pactl source names)
so the stored device name works directly with sd.InputStream
- /audio/combined maps sounddevice names back to pactl source names via
description matching for the loopback commands
- Combined sink description set to 'transkriptor-combined' (no spaces) so
sounddevice name matches the value stored in config
- Add _pactl_source_for_sd_name() helper for the mapping
Replaces cfg.update(body) with _deep_merge so partial updates (e.g.
setting whisper.base_url) no longer wipe sibling keys. Also persists
the merged config back to disk via tomli_w. Adds test_put_config_deep_merges.