Commit Graph
639 Commits
Author SHA1 Message Date
iamdoubz f2c945838c docs(plan): mark dual-channel capture implemented (FR-SPK) 2026-07-14 12:22:59 -05:00
iamdoubz 0e0f3a09f1 docs(architecture): per-stream diarization via split channels in stop data flow (FR-SPK) 2026-07-14 12:22:57 -05:00
iamdoubz 3d183fdd7c docs(data-model): audio.wav split layout + audio_layout column; drop mic_activity.json (FR-SPK) 2026-07-14 12:22:56 -05:00
iamdoubz 32bb4057ec docs(adr-0005): Phase 3.5 dual-channel capture supersedes the masked-mono timeline (FR-SPK) 2026-07-14 12:22:55 -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 e449c194ef docs(plan): draft dual-channel (mic-L/loopback-R) capture plan + ADR-0005 amendment for review (FR-SPK) 2026-07-14 11:28:16 -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 60ae97c771 docs(plan): mark Phase 3 slices a–d landed (FR-SPK) 2026-07-14 00:53:07 -05:00
iamdoubz d8587b8e93 docs(architecture): note per-stream diarization in the stop data flow (FR-SPK) 2026-07-14 00:53:06 -05:00
iamdoubz 695a12dc2d docs(data-model): add mic_activity.json to the meeting dir list (FR-SPK) 2026-07-14 00:53:05 -05:00
iamdoubz 6385e38b33 docs(adr-0005): amend with Phase 3 per-stream 'You' attribution (FR-SPK) 2026-07-14 00:53:04 -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 41c2c79797 docs(plan): finalize Phase 3 design — mic=You, diarize loopback-masked (FR-SPK) 2026-07-13 21:15:48 -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 bb6e222101 docs(plan): Phase 3 per-stream attribution design sketch + ADR-0005 review (FR-SPK) 2026-07-13 19:32:15 -05:00
iamdoubz 85ed3b5587 docs(api-contracts): note transcript://segment re-emit + live diarization://updated (FR-SPK) 2026-07-13 19:25:31 -05:00
iamdoubz 68d692fbe8 fix(transcript): resolve live speaker names incl 'You' in the recording view (FR-SPK) 2026-07-13 19:25:30 -05:00
iamdoubz fa664e5147 feat(recording): live speakers state from diarization://updated (FR-SPK) 2026-07-13 19:25:29 -05:00
iamdoubz b5c99621b5 feat(api): onDiarizationUpdated event listener for live speaker updates (FR-SPK) 2026-07-13 19:25:28 -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 319e7a31cc docs(data-model): add voiceprint.wav to the meeting dir file list (FR-SPK) 2026-07-13 19:19:06 -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 bd6519b65e docs(plan): phased fix plan for diarization speaker accuracy (FR-SPK) 2026-07-13 18:40:44 -05:00
iamdoubz 4f3af98bfa Add Clippy marketing image v0.5.2 2026-07-13 16:15:18 -05:00
iamdoubz 77f9a8b518 docs(readme): bump status to v0.5.2 and add UI-localization comparison row 2026-07-13 16:07:12 -05:00
iamdoubz 2a7e6229f4 docs(readme): list the 20 available interface languages and RTL support 2026-07-13 16:05:12 -05:00
iamdoubz 64e7baad66 docs(release): v0.5.2 release notes with SHA-256 checksums 2026-07-13 16:00:00 -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