a9a1900ee4fdeb1e5fc0bd586703fe2b4c8f93c5
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea7be5f111 |
feat(identity): rename user-visible identity to voculinux with one-time legacy import
The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18 |
||
|
|
e66bd218d9 |
fix(audio): prevent dictation hangs on stalled microphones (#974)
* fix(audio): bound capture reads and support cancellation * fix(recognition): bound and serialize capture shutdown * fix(recognition): cancel retries and report stalled microphones * fix(audio): prefer pulse when the ALSA default is unavailable * fix(recognition): guard late callbacks and finalize capture tests * docs(audio): explain input selection and stall recovery * fix(speech): enforce auto-pause across buffered reload timeouts * fix(speech): recover auto-resume after reload timeout * test(speech): fix auto-pause follow-up test regressions * fix(speech): retain auto-resume after reconfigure timeout * fix(tray): preserve auto-pause recovery across suspend * test(capture-shutdown): give the bare tray the suspend path's shortcut managers _on_system_suspend now releases the main and per-language hotkey listeners before sleep (#1025); the fixture builds TrayIndicator without __init__, so it needs the two attributes present. * fix(tray): release hotkey listeners on suspend even while auto-paused Merging #1025's suspend cleanup with this branch's auto-pause handoff left an early return that skipped shortcut_manager.stop() whenever a configured game was active, keeping EVIOCGRABs held through sleep. The paused branch now only owns the model-lifecycle work; listener release and stopping active recognition run for every suspend. * fix(tray): hold the apply lock only around the download's verify and save Wrapping the whole recommended-model download in _apply_settings_lock blocked every GTK-thread settings apply (e.g. Test Dictation) for the length of the transfer. The engine already serializes reconfigures, so the lock only needs to cover verifying the download still owns the receipt and saving it; an apply that lands mid-download now proceeds instead of freezing the app. --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
4d78246174 |
fix(recognition): queue long dictation segments before the buffer cap (#976)
* fix(recognition): queue long dictation segments before the buffer cap A held push-to-talk key (or a toggle session with no pause) keeps a single segment growing until the 5000-chunk safety cap discards its oldest 25%. The cap is a chunk count, not a duration, so the effective limit depends on the capture format: at 16 kHz a chunk is 64 ms (5 min 20 s), but a device that negotiates 44.1 kHz feeds 23 ms chunks and the same cap is 1 min 56 s. A 3-minute hold therefore lost ~87 s of speech before transcription, with only a log warning to show for it. Flush the current segment onto the existing transcription queue once it reaches _max_segment_seconds (90 s), or 75% of the chunk budget when chunks are shorter, so the destructive trim can never fire on buffered speech. Recording continues while queued segments are transcribed in order; reload sessions keep the existing hold-until-reload behaviour. Adds tests for the mid-hold flush, the buffer headroom, and the reload path. * fix(recognition): hold push-to-talk text until the key is released Text injection while PTT is held can release or modify the key. * fix(recognition): hold PTT text without blocking the recognition worker c7b9bd6 held transcribed text by waiting on the release gate inside _process_audio_buffer, on the single recognition worker. Later segments then sat in the 32-slot queue for the rest of the hold instead of being transcribed: memory grows with every queued segment, and once 32 are waiting (~48 min of 90 s segments) _enqueue_audio_segment starts dropping the oldest speech. Buffer the processed text instead. Every segment is transcribed as it arrives; while the key is down its result is appended to a held list, and the held results are delivered in capture order once the gate opens. The final buffer carries the session gate too, so it cannot overtake text that was transcribed before release. Held text is flushed when the worker exits, at the end of stop_recognition, and after the reload drain. An empty mid-hold result no longer flushes the held text early. Tests assert that every queued segment is transcribed before release and delivered in capture order afterwards. * fix(recognition): transcribe held push-to-talk segments ahead of release Keep transcription running during long push-to-talk holds while retaining each result until key release. Text injection during a hold can release or modify the PTT key. * test(recognition): cover cross-session held text isolation The per-session release gate must release only its own held text: a slow worker from an earlier session finishing after a new push-to-talk session starts must not deliver the new session's text while its key is down. --------- Co-authored-by: Muhannad Abo Hajia <muhannadabohajia@gmail.com> Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
5e531cbac0 |
refactor(audio): extract capture source from recognition manager (#883)
* refactor(audio): extract capture source from recognition manager Move audio capture out of the silence-segmented dictation loop into vocalinux.audio.capture: a reusable PortAudioCaptureSource that owns device resolution, format negotiation, downmixing, resampling, and stream lifecycle (open/read/reopen/close). The dictation loop now consumes 16 kHz mono chunks and keeps only segmentation policy (VAD, silence counters, buffer limits, reconnection backoff). This unblocks alternate capture sources (PipeWire system-audio capture for #751, tdrz long-form output for #756) which need the same capture mechanics without dictation's silence segmentation. Behavior is identical: the same helpers run in the same order, moved verbatim. They are re-exported from recognition_manager for existing imports, and a CaptureSource protocol is declared in common_types.py. Tests repoint their patches of the moved helpers to the new module. Closes #760 * fix(audio): annotate capture API and keep a superseded session's source * fix(audio): reset retry counters when a new capture session starts * fix(audio): keep last-error timestamp across capture sessions The new-session reset wiped _last_audio_error_time, so an error within five seconds of the previous session's error always took the reconnect path instead of failing fast. Only the attempt count is per-session; the timestamp is also the rapid-failure throttle. --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
a89966a744 |
feat(recognition): record while an idle model reloads (#851)
* feat(recognition): buffer audio during model reload * fix(recognition): recover buffered reload failures * test(recognition): cover buffered reload edge cases * fix(recognition): narrow buffered reload errors * fix(settings): wait for IDLE before restoring Test Dictation callbacks Buffered reload's stop_recognition() returns while PROCESSING. Test Dictation must keep its text callbacks until recognition reaches IDLE so the test utterance cannot inject into the focused app. * style: apply Black formatting to PR 851 files * fix(settings): cancel buffered reload before Test Dictation timeout restore On the ~3min idle-wait timeout, do not restore live injector callbacks while a buffered reload worker may still deliver text. Cancel via _cancel_reload_recording off the GTK thread, then restore; if cancel is unavailable or fails, leave test callbacks installed and show an error. Add return/param annotations and regression coverage. * fix(settings): keep Test Dictation waiting if buffered cancel fails After the idle-wait timeout, if cancel is unavailable or raises, keep polling with test callbacks and _test_active instead of marking the test done. Ending early left the test callback as the only text destination, so later normal dictation could land in the hidden test buffer. Restore live injectors only once recognition reaches IDLE. * fix(settings): reset Test Dictation timeout-cancel flag per session Clear _test_timeout_cancel_attempted when a test starts, when entering the buffered idle-wait, and when restoring UI so a later Test Dictation can still cancel after timeout. Narrow the cancel-worker except clause to AttributeError/OSError/RuntimeError/TypeError so unrelated defects are not treated as ordinary cancel failure. * fix(recognition): signal capture completion when buffered capture dies A dead microphone during a buffered model reload left should_record=false with no stop_recognition handoff, so the reload worker blocked forever on _capture_finished.wait(). Signal the event from the audio thread's failure teardown paths instead. Refs VocaHQ/vocalinux#851 --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
092dde105e |
fix(audio): open 3-8ch HDA mics at native layout (#813) (#829)
* fix(audio): open 3-8ch HDA mics at native layout (#813) HDA analog capture often reports 4 channels. Opening below that native count can abort PortAudio after read with free(): corrupted unsorted chunks (same class of bug as #666/#673). Try the reported channel count first when it is between 3 and 8, then 2ch and 1ch. Pulse devices that advertise 32/128 channels still open stereo-first. Downmix N channels to mono so engines keep seeing 1ch. Virtual-device filtering for pipewire/default (#629) is unchanged. * fix(audio): downmix HDA capture from first channel pair Averaging all N HDA channels halves speech when only the front pair is live. Keep stereo mean-of-both, and for 3+ channels average channels 0 and 1. * fix(audio): pick loudest channel for N-ch downmix First-pair-only downmix silenced mics on ch2+. For 3+ channels, select the loudest channel by per-buffer mean-square so speech stays at full level whether it lands on the front or rear pair. Stereo still averages both channels. * fix(audio): sticky N-ch downmix channel for open stream Pin the loudest-channel pick for the life of a capture stream so a noise burst on another input cannot switch the mic mid-utterance. * fix(audio): gate sticky downmix lock on speech energy Don't pin the N-ch sticky channel from a silent/ambient first buffer; only lock once the loudest channel clears a documented mean-square floor. * style: isort sticky-lock import in device config tests |
||
|
|
3f5a5effff |
feat(vad): add Silero VAD and drop silence-only buffers (#447)
* feat(vad): replace amplitude-based VAD with Silero neural network Replace the simple amplitude threshold VAD with Silero VAD, a lightweight neural network model that classifies audio as speech/non-speech. This addresses the root cause of #302 (silence not stopping recording) and - Add silero_vad.py wrapper using ONNX Runtime with bundled Silero v5 model - Accumulate 512-sample chunks with 64-sample context window per official API - Map existing vad_sensitivity (1-5) to Silero thresholds (0.8-0.3) - Use max() across sub-chunks to avoid missing speech onsets - Fall back to amplitude VAD when onnxruntime is unavailable - Add onnxruntime as optional dependency (pip install vocalinux[vad]) Closes #344 * address review: add tests, LICENSE, logging, validation fixes - Add unit tests for SileroVAD (process, reset, fallback, sensitivity mapping) - Add MIT LICENSE for bundled Silero VAD model in data/ - Log which VAD backend is active on init - Validate chunk length in SileroVAD.process() - Skip is_speech check until a full Silero chunk is processed - Add thread safety note to SileroVAD docstring - Document resampling rate effect on VAD resolution - Comment explaining why load_silero_vad() catches broad Exception * address review: bundle license, document [vad] extra, surface backend in UI - Pack data/LICENSE into the wheel (pyproject package-data) so attribution travels with the model - README: short note on enabling neural VAD via vocalinux[vad] - Settings: VAD Sensitivity row subtitle shows the active backend; if onnxruntime is missing, an info-row beneath the group spells out the install command and the restart requirement - silero_vad.is_silero_available() probes onnxruntime + model presence w/o instantiating an InferenceSession - CI installs .[dev,vad] so Silero paths get exercised - Integration test for _record_audio Silero branch (mocked pyaudio + scripted SileroVAD), covering silence flush, speech reset, and sensitivity threshold mapping at sens=1 vs sens=5 - Tests no longer skip when other modules globally mock numpy: pop the top-level numpy ref so a fresh import gives the real package, and swap sys.modules["numpy"] inside the _record_audio call so the lazy import inside the function picks up real numpy too * fix(vad): drop silence-only recording buffers * test(vad): align stop buffer expectations * test(vad): preserve real numpy module * fix(install): install vad support opportunistically * test(vad): cover recognition manager vad edges --------- Co-authored-by: Sbenazar <127324689+Sbenazar@users.noreply.github.com> |