Commit Graph
319 Commits
Author SHA1 Message Date
iamdoubz 2fa3ea6824 feat(app): add tauri-plugin-autostart dependency
Pulls in the official launch-at-login plugin (per-user HKCU\Run, no admin).
Regenerated ACL/schema manifests reflect the new plugin.
2026-07-14 16:21:25 -05:00
iamdoubz 2c8958da01 Bump version 0.6.0 2026-07-14 15:48:50 -05:00
iamdoubz 90a9cbd476 feat(recording): fold split stereo to mono on playback, dual-mono on export (FR-SPK)
fold_wav folds a WAV's channels to their average across N output channels.
serve_recording plays it as mono so a split (mic-left/loopback-right) recording
plays both sources in both ears; bundle export decrypts + folds to dual-mono so
the shared file is a normal stereo file (manifest records it as summed). Test
covers mono + dual-mono.
2026-07-14 12:21:12 -05:00
iamdoubz 5bd76b80e8 feat(diarization): channel-based split attribution; retire masked-mono path (FR-SPK)
stop + reprocess now, for a split recording, diarize the right (loopback)
channel into Speaker N and take You from left-channel (mic) VAD — read back from
the file so both agree. Fixes reprocess collapsing to one speaker. Summed
recordings keep the whole-signal + voiceprint fallback. Removes MicTimelineFile,
phase3_attribute, mask_ranges, mic_activity.json (retirement note retained).
2026-07-14 12:16:03 -05:00
iamdoubz a1d71d1c73 refactor(recording): drop RecordingSession.mic_activity (retired) (FR-SPK) 2026-07-14 12:16:02 -05:00
iamdoubz e287fa5ebe feat(audio): read_wav_channel_16k + vad_spans; retire MicActivity (FR-SPK)
Add a channel-selective 16k reader (share the decode with read_wav_mono_16k via
read_wav_16k) and vad_spans (100ms energy-windowed You detection). Remove the
MicActivity live timeline + its capture wiring — the split layout makes the
mic/far-side separation intrinsic to the file, so You is recomputed from the
left channel instead.
2026-07-14 12:16:01 -05:00
iamdoubz 534f5ca350 feat(commands): mark recordings split/summed at stop from the session layout (FR-SPK) 2026-07-14 12:04:46 -05:00
iamdoubz 1e99c4c747 feat(recording): RecordingSession.audio_layout (split when mic on) (FR-SPK) 2026-07-14 12:04:45 -05:00
iamdoubz 5e5f61f3b7 feat(audio): write_wav_split — stereo mic-left/loopback-right capture (FR-SPK)
New split writer + a `split` flag through the capture path: when the mic is on,
audio.wav is recorded 2ch with the mic on the left channel and the loopback
(downmixed) on the right, keeping the two sources separate in the file. Same
byte size as today's summed stereo. Transcription still downmixes L+R (= the
summed signal). Unit test covers L=mic / R=loopback.
2026-07-14 12:04:44 -05:00
iamdoubz dec9251e7b feat(commands): set audio_layout at each finalize/export site (summed for now) (FR-SPK) 2026-07-14 11:58:25 -05:00
iamdoubz d4e2ab494c feat(models): MeetingBundle.audio_layout (serde default) for portable layout (FR-SPK) 2026-07-14 11:58:23 -05:00
iamdoubz 845903a772 feat(storage): thread audio_layout through FinalizeMeeting + Meeting + get_meeting (FR-SPK) 2026-07-14 11:58:22 -05:00
iamdoubz 998de62df2 feat(storage): migration 0009 — meetings.audio_layout column (FR-SPK/FR-CAP) 2026-07-14 11:58:21 -05:00
iamdoubz 6cea767dec fix(audio): drain mic device-paced during silence to stop playback skips/ticks (FR-CAP-7)
The capture-silence fix metered the mic pull by wall-clock, but the mic fills
the bridge at its own device clock. Over a long silent stretch they drifted, the
bridge backed up to its 0.5s cap and dropped oldest samples -> the recording
skipped/'fast-forwarded' late in the gap. And a 10ms idle threshold punched
silence into the loopback on brief inter-packet stalls -> audible ticks. Now:
drain the whole bridge (device-paced, no overflow) and only synthesize after a
sustained 150ms gap.
2026-07-14 11:10:07 -05:00
iamdoubz 0b5a85f461 feat(diarization): persist mic_activity.json; reprocess uses the Phase 3 timeline (FR-SPK)
Factor the Phase 3 attribution (you_spans -> mask -> diarize far side -> merge +
name) into a shared phase3_attribute() used by both stop and reprocess. stop now
writes mic_activity.json (the 'You' spans) next to a retained audio.wav
(plaintext timing metadata, local-only, lives/dies with audio.wav); reprocess
prefers that timeline over the voiceprint fallback so re-transcription keeps
correct per-stream 'You'/'Speaker' attribution.
2026-07-14 00:51:41 -05:00
iamdoubz fd9311c482 feat(diarization): Phase 3 stop attribution — mic timeline is 'You', diarize far side masked (FR-SPK)
stop_recording now, when the mic is on, takes 'You' spans straight from the
MicActivity timeline and diarizes the recording with those ranges zeroed, so
sherpa only ever clusters the far side into Speaker N. Merges the span lists and
names them via the existing voiceprint map (You + Speaker 2..). Falls back to
the whole-signal pass + voiceprint match when the mic is off. Adds mask_ranges
helper + tests.
2026-07-14 00:04:58 -05:00
iamdoubz 39294adcbc feat(audio): populate MicActivity timeline from the loopback writer (FR-SPK) 2026-07-14 00:04:57 -05:00
iamdoubz fee18ebc59 refactor(diarization): expose first_appearance_order + build_name_map to crate (FR-SPK) 2026-07-14 00:04:56 -05:00
iamdoubz 51f5ff085c feat(diarization): Diarizer::diarize_samples + short-input guard for masked far-side pass (FR-SPK) 2026-07-14 00:04:55 -05:00
iamdoubz 6d4204fb22 feat(recording): RecordingSession.mic_activity for Phase 3 attribution (FR-SPK) 2026-07-14 00:04:54 -05:00
iamdoubz 185489fa18 feat(audio): MicActivity timeline for Phase 3 per-stream 'You' attribution (FR-SPK)
Records, per 100ms of audio.wav frame time, when the mic (the user) was
speech-level — captured in the loopback writer where mic and loopback exist
separately in the recording's own timebase. you_spans() collapses active
windows into merged 'You' ranges. The far side will be diarized with these
ranges masked out, so clustering never sees the mic. Struct + span logic land
first (tested); capture wiring + stop attribution follow.
2026-07-13 21:15:49 -05:00
iamdoubz 6dd6c3c69f fix(audio): capture mic during system-audio silence (FR-CAP-7)
WASAPI loopback delivers no packets when the render endpoint is idle, so the
recording only advanced while system audio played. The bridged mic (the user
talking over silence) was lost to MicBridge overflow, and audio.wav lost those
stretches entirely — corrupting diarization and dropping the user's solo
remarks in real meetings. When loopback is idle, synthesize silence for the
elapsed gap and fold the mic in, keeping audio.wav continuous. Loopback +
mic-enabled only.
2026-07-13 20:58:23 -05:00
iamdoubz 9468781218 fix(audio): gate voiceprint capture on mic energy so it's real speech (FR-SPK)
VoiceSample kept the first N samples unconditionally, so a user silent for the
first 30s got a voiceprint of room noise/a cough. That noise embedding
spuriously matched a cluster and mislabeled every speaker 'You', even during
silence. Skip sub-threshold (VOICE_ENERGY_FLOOR) chunks so the window tracks
actual speech; until ~1s of real voice accumulates, the match returns empty
(no premature 'You').
2026-07-13 20:35:25 -05:00
iamdoubz b39352dbff fix(diarization): clear stale speaker rows on reprocess so Participants pane matches (FR-SPK)
Reprocess re-diarizes and upserts the new labels but never deleted the old
ones, so get_meeting (Participants pane) kept returning the discarded run's
labels (e.g. 83) while the transcript showed the new 5. Clear the meeting's
speaker rows before finalize re-inserts the fresh set.
2026-07-13 20:09:03 -05:00
iamdoubz 0d263637df feat(storage): Store::clear_speakers to drop a meeting's speaker rows (FR-SPK) 2026-07-13 20:09:01 -05:00
iamdoubz db52b9e812 feat(diarization): live tick re-emits relabeled segments + live 'You' (FR-SPK)
Phase 2 backend. The 15s provisional pass now (1) runs the mic voiceprint
match every tick and merges 'You'/'Speaker N' into the session name map without
overwriting user-set names, and (2) re-emits every committed segment whose
speaker changed via transcript://segment (replace-by-id) so the live transcript
reflects the refined labels instead of a frozen 'S1'.
2026-07-13 19:25:27 -05:00
iamdoubz 25011d0f27 fix(diarization): reprocess re-diarizes + persist mic voiceprint.wav (FR-SPK)
Phase 1. stop_recording writes voiceprint.wav next to a retained audio.wav
(ADR-0009 gated, sealed at rest with the vault); reprocess_transcript now
re-diarizes the fresh transcript and rebuilds the speaker name map from the
voiceprint instead of reusing the stale original-run names, fixing the
collapse to a single 'You' speaker on re-transcription.
2026-07-13 19:19:06 -05:00
iamdoubz 7733e86cfd feat(audio): write_wav_mono_16k helper for persisting the mic voiceprint (FR-SPK) 2026-07-13 19:19:05 -05:00
iamdoubz a481f5dcbf fix(diarization): tame cluster explosion with threshold 0.7 + 700ms min span (FR-SPK)
Phase 0 of the speaker-accuracy plan. sherpa fast-clustering threshold 0.5
over summed mic+loopback mono exploded a 2-speaker call into 83 clusters;
raise to 0.7 (larger threshold -> fewer clusters) and drop sub-700ms spans
whose embeddings are unstable and only churn labels in assign_by_overlap.
2026-07-13 19:12:04 -05:00
iamdoubz 804d451bfa chore(release): bump version to 0.5.2 2026-07-13 15:59:58 -05:00
iamdoubz a29e60b47d fix(storage): show native error dialog on startup DB failure instead of panic
A migration-checksum mismatch (an edited-after-shipping migration) made
SqliteStore::connect() return Err, which .expect() turned into a panic=abort
fast-fail: the window never opened and the OS reported only a 0xc0000409 crash.
Replace the expect with a blocking MessageBoxW that names the failure and points
at wa.db, then exit(1) cleanly.
2026-07-13 15:52:55 -05:00
iamdoubz 969ed6022e build(deps): enable Win32_UI_WindowsAndMessaging for startup error dialog 2026-07-13 15:52:54 -05:00
iamdoubz 05e003ab19 chore(release): bump version to 0.5.1 2026-07-12 22:41:45 -05:00
iamdoubz c1de97938e chore(release): bump version to 0.5.0 2026-07-12 13:08:56 -05:00
iamdoubz dbe845e923 feat(export): 'obsidian' format — one vault note (notes+summary+action items+transcript, no audio) (FR-STORE-4) 2026-07-12 09:32:23 -05:00
iamdoubz 2e57ccfab5 feat(export): register import_meeting_bundle command 2026-07-11 22:42:08 -05:00
iamdoubz 4dff7a58b5 feat(export): portable bundle (meeting.json + summary.json) + import_meeting_bundle (FR-STORE-4) 2026-07-11 22:42:07 -05:00
iamdoubz 25204c0235 feat(export): set_meeting_times store method to preserve dates on import 2026-07-11 22:40:00 -05:00
iamdoubz 2b2e120f12 feat(export): MeetingBundle manifest for portable meeting export/import 2026-07-11 22:39:38 -05:00
iamdoubz 70eb182eb4 feat(sync): auto-resync artifacts on edit (notes/summary/transcript/tags/action items) (FR-SYNC-5) 2026-07-11 22:37:51 -05:00
iamdoubz 862ab86860 feat(action-items): cancel reminders for deleted items on confirm 2026-07-11 22:32:15 -05:00
iamdoubz a0faaa94b4 feat(action-items): table-backed action_items on Meeting; reconcile deletes (FR-LLM-3) 2026-07-11 22:32:14 -05:00
iamdoubz 379bdf532f chore: sync Cargo.lock to version 0.4.0 2026-07-11 09:10:17 -05:00
iamdoubz 95f07e1dba fix(transcription): scope streamer-test closures so out isn't borrow-locked 2026-07-11 09:08:20 -05:00
iamdoubz 931a2b78b4 fix(media): blank doc line before the tool list (clippy doc_lazy_continuation) 2026-07-11 09:08:19 -05:00
iamdoubz 89254bc070 fix(audio): update audio_level test calls for the new mic arg 2026-07-11 09:08:18 -05:00
iamdoubz cecfe20eec chore: bump tauri config version to 0.4.0 2026-07-11 08:58:05 -05:00
iamdoubz 6fbf967fdb chore: bump crate version to 0.4.0 2026-07-11 08:58:04 -05:00
iamdoubz a4b2b9a292 feat(transcription): drive live worker with StreamTuning; store only committed segments 2026-07-11 08:42:25 -05:00
iamdoubz 12e23143f6 feat(transcription): streaming growing-window + interim + commit-on-pause (FR-TRX-2) 2026-07-11 08:42:24 -05:00