python-voiceio 1.9.3__tar.gz → 1.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_voiceio-1.9.3/python_voiceio.egg-info → python_voiceio-1.10.0}/PKG-INFO +1 -1
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/pyproject.toml +1 -1
- {python_voiceio-1.9.3 → python_voiceio-1.10.0/python_voiceio.egg-info}/PKG-INFO +1 -1
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/python_voiceio.egg-info/SOURCES.txt +3 -0
- python_voiceio-1.10.0/tests/test_mic_gain.py +85 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_service.py +2 -2
- python_voiceio-1.10.0/voiceio/__init__.py +1 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/app.py +34 -20
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/config.py +5 -1
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/feedback.py +13 -1
- python_voiceio-1.10.0/voiceio/mic_gain.py +123 -0
- python_voiceio-1.10.0/voiceio/sounds/alert.wav +0 -0
- python_voiceio-1.9.3/voiceio/__init__.py +0 -1
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/LICENSE +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/README.md +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/setup.cfg +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_app_wiring.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_audio_quality.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_audio_source.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_backend_probes.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_cancel.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_cli.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_clipboard_typer.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_commands.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_config.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_context_terms.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_corrections.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_dead_mic.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_decoders.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_engine.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_evdev_uaccess.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_fallback.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_fcitx_bridge.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_feedback_stream.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_focus.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_health.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_hints.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_history.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_hotkey_modes.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_engine_limits.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_engine_listener.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_pids.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_session.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_textlimit.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_idle_audio.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_clean.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_import.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_language.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_metrics.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_pipeline.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_quiet.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_resume.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_review.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_learn_targets.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_llm.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_llm_api.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_modifier_guard.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_numbers.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_paste_last.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_pipeline.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_platform.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_postcorrect.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_postprocess.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_prebuffer.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_presence.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_prompt.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_quiet_audio.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_retention.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_robustness.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_security_hardening.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_server.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_setup.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_streaming.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_suggest.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_tokens.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_transcriber.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_tts.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_typer_manager.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_vad.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_vocabulary.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_wordfreq.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/tests/test_worker_decode.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/__main__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/audio_source.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/backends.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/configcmd.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/corrections.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/doctor.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/history.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/learn.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/models.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/servicecmd.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/uninstall.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/cli/vocab.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/commands.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/consent.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/context_terms.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/bias.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/base.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/catalog.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/lanes.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/sherpa.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/decoders/whispercpp.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/engine.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/core/pipeline.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/corrections.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/data/60-voiceio-uaccess.rules +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/data/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/demo.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/display.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/focus.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/health.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/hints.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/history.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/ibus/install.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/ibus/session.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/ibus/textlimit.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/idle_audio.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/augment.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/clean.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/evaluate.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/importer.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/metrics.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/quiet.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/review.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/store.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/teacher.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/learn/train.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/llm.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/llm_api.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/models/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/numbers.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/pidlock.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/platform.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/postcorrect.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/postprocess.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/power.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/presence.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/privacy.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/prompt.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/quiet_audio.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/recorder.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/retention.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/server/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/server/__main__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/server/app.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/server/config.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/server/runtime.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/service.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/configfile.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/extras.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/hotkey.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/noninteractive.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/speech.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/steps.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/system.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/setup/ui.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/streaming.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/suggest.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tokens.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tts/base.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tts/chain.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/tts/player.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/base.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/chain.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/collecting.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/fcitx_bridge.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/manager.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/modifier_guard.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/paste.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/ui.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/vad.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/vocabulary.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/watchdog.py +0 -0
- {python_voiceio-1.9.3 → python_voiceio-1.10.0}/voiceio/wordfreq.py +0 -0
|
@@ -52,6 +52,7 @@ tests/test_learn_review.py
|
|
|
52
52
|
tests/test_learn_targets.py
|
|
53
53
|
tests/test_llm.py
|
|
54
54
|
tests/test_llm_api.py
|
|
55
|
+
tests/test_mic_gain.py
|
|
55
56
|
tests/test_modifier_guard.py
|
|
56
57
|
tests/test_numbers.py
|
|
57
58
|
tests/test_paste_last.py
|
|
@@ -101,6 +102,7 @@ voiceio/history.py
|
|
|
101
102
|
voiceio/idle_audio.py
|
|
102
103
|
voiceio/llm.py
|
|
103
104
|
voiceio/llm_api.py
|
|
105
|
+
voiceio/mic_gain.py
|
|
104
106
|
voiceio/numbers.py
|
|
105
107
|
voiceio/pidlock.py
|
|
106
108
|
voiceio/platform.py
|
|
@@ -187,6 +189,7 @@ voiceio/setup/steps.py
|
|
|
187
189
|
voiceio/setup/system.py
|
|
188
190
|
voiceio/setup/ui.py
|
|
189
191
|
voiceio/sounds/__init__.py
|
|
192
|
+
voiceio/sounds/alert.wav
|
|
190
193
|
voiceio/sounds/commit.wav
|
|
191
194
|
voiceio/sounds/start.wav
|
|
192
195
|
voiceio/sounds/stop.wav
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Input gain follows the recordings: down on clipping, up when very quiet,
|
|
2
|
+
never back to a level that clipped."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from voiceio import mic_gain
|
|
6
|
+
from voiceio.mic_gain import MicGain
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FakeSource:
|
|
10
|
+
def __init__(self, volume):
|
|
11
|
+
self.volume = volume
|
|
12
|
+
self.sets = []
|
|
13
|
+
|
|
14
|
+
def get(self):
|
|
15
|
+
return self.volume
|
|
16
|
+
|
|
17
|
+
def set(self, v):
|
|
18
|
+
self.sets.append(v)
|
|
19
|
+
self.volume = v
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _gain(volume=0.5):
|
|
24
|
+
g = MicGain(enabled=False)
|
|
25
|
+
g._source = FakeSource(volume)
|
|
26
|
+
return g
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_clipping_lowers_by_a_fifth():
|
|
30
|
+
g = _gain(0.5)
|
|
31
|
+
change = g.after_recording(peak=1.0, clip_ratio=0.02, secs=10)
|
|
32
|
+
assert change.reason == "clipping" and g._source.volume == 0.4
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_never_below_the_floor():
|
|
36
|
+
g = _gain(0.11)
|
|
37
|
+
g.after_recording(1.0, 0.02, 10)
|
|
38
|
+
assert g._source.volume == mic_gain.FLOOR
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_three_quiet_recordings_raise_but_not_to_a_level_that_clipped():
|
|
42
|
+
g = _gain(0.5)
|
|
43
|
+
g.after_recording(1.0, 0.02, 10) # 0.5 clipped -> 0.4
|
|
44
|
+
for _ in range(2):
|
|
45
|
+
assert g.after_recording(0.05, 0.0, 10) is None
|
|
46
|
+
assert g.after_recording(0.05, 0.0, 10) is None # third quiet one: would reach 0.5
|
|
47
|
+
assert g._source.volume == 0.4 # capped at 0.5 * 0.8: no oscillation
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_quiet_without_any_clipping_history_raises():
|
|
51
|
+
g = _gain(0.3)
|
|
52
|
+
for _ in range(3):
|
|
53
|
+
change = g.after_recording(0.05, 0.0, 10)
|
|
54
|
+
assert change.new == round(0.3 / mic_gain.STEP, 2)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_short_or_dead_recordings_dont_count():
|
|
58
|
+
g = _gain(0.3)
|
|
59
|
+
for _ in range(5):
|
|
60
|
+
assert g.after_recording(0.05, 0.0, 1.0) is None # too short
|
|
61
|
+
assert g.after_recording(0.0, 0.0, 10) is None # muted mic: not a gain problem
|
|
62
|
+
assert g._source.sets == []
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_normal_level_resets_the_quiet_streak():
|
|
66
|
+
g = _gain(0.3)
|
|
67
|
+
g.after_recording(0.05, 0.0, 10)
|
|
68
|
+
g.after_recording(0.05, 0.0, 10)
|
|
69
|
+
g.after_recording(0.5, 0.0, 10)
|
|
70
|
+
assert g.after_recording(0.05, 0.0, 10) is None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_a_chosen_device_is_left_alone():
|
|
74
|
+
assert not MicGain(enabled=True, device="hw:1").active
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_off_without_volume_tools(monkeypatch):
|
|
78
|
+
# conftest: quiet_audio._which finds nothing in tests
|
|
79
|
+
assert not MicGain(enabled=True).active
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_failures_never_raise():
|
|
83
|
+
g = _gain(0.5)
|
|
84
|
+
g._source.get = lambda: (_ for _ in ()).throw(RuntimeError("boom"))
|
|
85
|
+
assert g.after_recording(1.0, 0.02, 10) is None
|
|
@@ -44,7 +44,7 @@ def test_learn_timer_runs_maintain_at_idle_priority(monkeypatch, tmp_path):
|
|
|
44
44
|
"MemoryHigh=25%", "OOMScoreAdjust=500"):
|
|
45
45
|
assert f"\n{line}\n" in unit, line
|
|
46
46
|
timer = service.LEARN_TIMER_PATH.read_text()
|
|
47
|
-
assert "Persistent=true" in timer and "OnCalendar
|
|
47
|
+
assert "Persistent=true" in timer and "OnCalendar=Sun *-*-* 03:30" in timer
|
|
48
48
|
assert ("enable", "--now", service.LEARN_TIMER_NAME) in calls
|
|
49
49
|
|
|
50
50
|
service.uninstall_learn_timer()
|
|
@@ -109,7 +109,7 @@ def test_learn_schedule_comes_from_config(monkeypatch, tmp_path):
|
|
|
109
109
|
monkeypatch.setattr(service, "LEARN_SERVICE_PATH", tmp_path / "s.service")
|
|
110
110
|
monkeypatch.setattr(service, "LEARN_TIMER_PATH", tmp_path / "s.timer")
|
|
111
111
|
service.install_learn_timer()
|
|
112
|
-
assert "OnCalendar
|
|
112
|
+
assert "OnCalendar=Sun *-*-* 03:30" in service.LEARN_TIMER_PATH.read_text() # weekly by default
|
|
113
113
|
|
|
114
114
|
config.set_value("learn", "at", "2:05")
|
|
115
115
|
config.set_value("learn", "every", "week")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.10.0"
|
|
@@ -111,6 +111,8 @@ class VoiceIO:
|
|
|
111
111
|
self._recording_bias = Bias()
|
|
112
112
|
from voiceio.quiet_audio import QuietAudio
|
|
113
113
|
self._quiet = QuietAudio(cfg.audio.pause_media, cfg.audio.duck_volume)
|
|
114
|
+
from voiceio.mic_gain import MicGain
|
|
115
|
+
self._mic_gain = MicGain(cfg.audio.auto_gain, cfg.audio.device)
|
|
114
116
|
|
|
115
117
|
self._command_processor = CommandProcessor(enabled=cfg.commands.enabled, editing=cfg.commands.editing)
|
|
116
118
|
self._streaming = cfg.output.streaming
|
|
@@ -437,10 +439,9 @@ class VoiceIO:
|
|
|
437
439
|
if dead:
|
|
438
440
|
log.warning("Mic still silent after reopening")
|
|
439
441
|
from voiceio import feedback
|
|
440
|
-
feedback.
|
|
442
|
+
feedback.mic_alert(
|
|
441
443
|
"VoiceIO: microphone stopped sending audio",
|
|
442
|
-
"Your words so far are kept.
|
|
443
|
-
urgent=True,
|
|
444
|
+
"Your words so far are kept.",
|
|
444
445
|
)
|
|
445
446
|
else:
|
|
446
447
|
log.info("Mic stream recovered mid-recording")
|
|
@@ -457,10 +458,9 @@ class VoiceIO:
|
|
|
457
458
|
if reason == "no_speech":
|
|
458
459
|
from voiceio import feedback
|
|
459
460
|
if not self.recorder.has_signal():
|
|
460
|
-
feedback.
|
|
461
|
+
feedback.mic_alert(
|
|
461
462
|
"VoiceIO: microphone is muted",
|
|
462
|
-
"Stopped — no audio is reaching voiceio.
|
|
463
|
-
"mute button and that the right input device is selected.",
|
|
463
|
+
"Stopped — no audio is reaching voiceio.",
|
|
464
464
|
)
|
|
465
465
|
else:
|
|
466
466
|
feedback.notify(
|
|
@@ -485,11 +485,9 @@ class VoiceIO:
|
|
|
485
485
|
return
|
|
486
486
|
log.warning("Recording captured no audio at all — mic muted?")
|
|
487
487
|
from voiceio import feedback
|
|
488
|
-
feedback.
|
|
488
|
+
feedback.mic_alert(
|
|
489
489
|
"VoiceIO: microphone is muted — nothing recorded",
|
|
490
|
-
"No audio reached voiceio for that entire recording.
|
|
491
|
-
"mic's mute button and which input device is selected.",
|
|
492
|
-
urgent=True,
|
|
490
|
+
"No audio reached voiceio for that entire recording.",
|
|
493
491
|
)
|
|
494
492
|
|
|
495
493
|
def _request_stop(self) -> None:
|
|
@@ -576,10 +574,9 @@ class VoiceIO:
|
|
|
576
574
|
if not self.recorder.has_signal():
|
|
577
575
|
log.warning("Mic still silent after reopen — muted or wrong device?")
|
|
578
576
|
from voiceio import feedback
|
|
579
|
-
feedback.
|
|
577
|
+
feedback.mic_alert(
|
|
580
578
|
"VoiceIO: microphone appears silent",
|
|
581
|
-
"
|
|
582
|
-
urgent=True,
|
|
579
|
+
"No sound is coming from the input device.",
|
|
583
580
|
)
|
|
584
581
|
elif not self.recorder.has_signal():
|
|
585
582
|
log.warning("Mic appears silent or muted (pre-buffer is all zeros)")
|
|
@@ -634,7 +631,7 @@ class VoiceIO:
|
|
|
634
631
|
audio = self.recorder.stop()
|
|
635
632
|
tray.set_recording(False)
|
|
636
633
|
self._idle_audio.touch()
|
|
637
|
-
self.
|
|
634
|
+
self._check_input_level(time.monotonic() - self._record_start)
|
|
638
635
|
|
|
639
636
|
self.engine.prepare_final(title=self._context_title)
|
|
640
637
|
|
|
@@ -706,13 +703,15 @@ class VoiceIO:
|
|
|
706
703
|
_CLIP_RATIO_WARN = 0.005 # >0.5% of samples in flat-top runs
|
|
707
704
|
_CLIP_WARN_INTERVAL = 300 # seconds between warnings
|
|
708
705
|
|
|
709
|
-
def
|
|
710
|
-
"""
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
before the ADC — so tell the user how to lower it.
|
|
714
|
-
"""
|
|
706
|
+
def _check_input_level(self, secs: float) -> None:
|
|
707
|
+
"""After a recording: fix the input gain when auto_gain can, else
|
|
708
|
+
warn (rate-limited) about clipping. Off this thread: the volume tools
|
|
709
|
+
are subprocesses."""
|
|
715
710
|
meter = self.recorder.get_meter()
|
|
711
|
+
if self._mic_gain.active:
|
|
712
|
+
threading.Thread(target=self._adjust_gain, args=(meter, secs),
|
|
713
|
+
daemon=True, name="mic-gain").start()
|
|
714
|
+
return
|
|
716
715
|
if meter["clip_ratio"] < self._CLIP_RATIO_WARN:
|
|
717
716
|
return
|
|
718
717
|
now = time.monotonic()
|
|
@@ -731,6 +730,21 @@ class VoiceIO:
|
|
|
731
730
|
"Input is clipping — lower your mic gain in system sound settings.",
|
|
732
731
|
)
|
|
733
732
|
|
|
733
|
+
def _adjust_gain(self, meter: dict[str, float], secs: float) -> None:
|
|
734
|
+
change = self._mic_gain.after_recording(meter["peak"], meter["clip_ratio"], secs)
|
|
735
|
+
if change is None or change.reason != "clipping":
|
|
736
|
+
return
|
|
737
|
+
now = time.monotonic()
|
|
738
|
+
if now - self._last_clip_warn < self._CLIP_WARN_INTERVAL:
|
|
739
|
+
return
|
|
740
|
+
self._last_clip_warn = now
|
|
741
|
+
from voiceio import feedback
|
|
742
|
+
feedback.notify(
|
|
743
|
+
"VoiceIO lowered your mic volume",
|
|
744
|
+
f"It was clipping: {change.old:.0%} → {change.new:.0%}. "
|
|
745
|
+
"Turn it off with: voiceio config set audio.auto_gain false",
|
|
746
|
+
)
|
|
747
|
+
|
|
734
748
|
# ── Background work ─────────────────────────────────────────────────
|
|
735
749
|
|
|
736
750
|
def _finalize_streaming(
|
|
@@ -128,6 +128,10 @@ class AudioConfig:
|
|
|
128
128
|
# last dictation; "never": closed between dictations, opened on the hotkey.
|
|
129
129
|
keep_warm: str = "auto"
|
|
130
130
|
warm_minutes: float = 5.0
|
|
131
|
+
# Follow your recordings with the default mic's input volume
|
|
132
|
+
# (voiceio/mic_gain.py): down a fifth after one that clipped, up a fifth
|
|
133
|
+
# after three that were very quiet, never back to a level that clipped.
|
|
134
|
+
auto_gain: bool = True
|
|
131
135
|
|
|
132
136
|
|
|
133
137
|
@dataclass
|
|
@@ -290,7 +294,7 @@ class LearnConfig:
|
|
|
290
294
|
# When nightly learning runs (`voiceio learn schedule on`): a time of day,
|
|
291
295
|
# and "day" or "week" (weekly runs on Sunday).
|
|
292
296
|
at: str = "03:30"
|
|
293
|
-
every: str = "
|
|
297
|
+
every: str = "week"
|
|
294
298
|
# It works only while nobody is using the computer: no keyboard or mouse
|
|
295
299
|
# input for away_minutes (or the screen locked), on mains power, and
|
|
296
300
|
# starts only when the 1-minute load per CPU is at most max_load. When
|
|
@@ -76,7 +76,7 @@ def warm_up() -> None:
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def _load_sounds() -> None:
|
|
79
|
-
for name in ("start", "stop", "commit"):
|
|
79
|
+
for name in ("start", "stop", "commit", "alert"):
|
|
80
80
|
path = _SOUNDS_DIR / f"{name}.wav"
|
|
81
81
|
if name in _sounds or not path.exists():
|
|
82
82
|
continue
|
|
@@ -141,6 +141,18 @@ def play_commit_sound() -> None:
|
|
|
141
141
|
_play("commit")
|
|
142
142
|
|
|
143
143
|
|
|
144
|
+
# The usual cause, by far: a hardware mic switch, a mute key or a muted
|
|
145
|
+
# source. Each look the same to voiceio — a stream that sends only zeros.
|
|
146
|
+
MIC_HINT = "Is the mic switched off or muted (privacy switch, mute key)?"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def mic_alert(title: str, body: str) -> None:
|
|
150
|
+
"""A dead or muted mic: an alert sound as well as the notification, since
|
|
151
|
+
you are talking, not looking at the screen."""
|
|
152
|
+
_play("alert")
|
|
153
|
+
notify(title, f"{body} {MIC_HINT}", urgent=True)
|
|
154
|
+
|
|
155
|
+
|
|
144
156
|
def cue_secs(name: str) -> float:
|
|
145
157
|
"""How long a cue takes to play out; 0 when sound output is off."""
|
|
146
158
|
if _stream is None or name not in _sounds:
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Mic input gain that follows your recordings ([audio] auto_gain).
|
|
2
|
+
|
|
3
|
+
Clipping can't be repaired after the ADC, so the fix is the input volume.
|
|
4
|
+
After each recording:
|
|
5
|
+
|
|
6
|
+
clipped (flat-top runs in >= 0.5% of samples)
|
|
7
|
+
→ lower the default source's volume by a fifth, and remember that the
|
|
8
|
+
old level clipped: it is never raised back to it
|
|
9
|
+
three spoken recordings in a row that peaked below 0.1
|
|
10
|
+
→ raise it by a fifth, staying under the level that clipped
|
|
11
|
+
|
|
12
|
+
Only the default PipeWire/Pulse source is touched (wpctl, else pactl), and
|
|
13
|
+
never below 10% or above 100%. A device chosen in [audio] device is the
|
|
14
|
+
user's to set.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
from voiceio import quiet_audio
|
|
23
|
+
from voiceio.quiet_audio import _Caller
|
|
24
|
+
|
|
25
|
+
log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
CLIP_RATIO = 0.005
|
|
28
|
+
QUIET_PEAK = 0.1
|
|
29
|
+
QUIET_STREAK = 3
|
|
30
|
+
MIN_SPEECH_SECS = 3.0
|
|
31
|
+
STEP = 0.8
|
|
32
|
+
FLOOR, CEILING = 0.10, 1.0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _WpctlSource:
|
|
36
|
+
def __init__(self, call: _Caller):
|
|
37
|
+
self._call = call
|
|
38
|
+
|
|
39
|
+
def get(self) -> float | None:
|
|
40
|
+
m = re.match(r"Volume: ([\d.]+)", self._call("wpctl", "get-volume", "@DEFAULT_AUDIO_SOURCE@") or "")
|
|
41
|
+
return float(m[1]) if m else None
|
|
42
|
+
|
|
43
|
+
def set(self, volume: float) -> bool:
|
|
44
|
+
return self._call("wpctl", "set-volume", "@DEFAULT_AUDIO_SOURCE@", f"{volume:.2f}") is not None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _PactlSource:
|
|
48
|
+
def __init__(self, call: _Caller):
|
|
49
|
+
self._call = call
|
|
50
|
+
|
|
51
|
+
def get(self) -> float | None:
|
|
52
|
+
m = re.search(r"(\d+)%", self._call("pactl", "get-source-volume", "@DEFAULT_SOURCE@") or "")
|
|
53
|
+
return int(m[1]) / 100 if m else None
|
|
54
|
+
|
|
55
|
+
def set(self, volume: float) -> bool:
|
|
56
|
+
return self._call("pactl", "set-source-volume", "@DEFAULT_SOURCE@",
|
|
57
|
+
f"{round(volume * 100)}%") is not None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class Change:
|
|
62
|
+
old: float
|
|
63
|
+
new: float
|
|
64
|
+
reason: str # "clipping" | "quiet"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class MicGain:
|
|
68
|
+
def __init__(self, enabled: bool = True, device: str = "default", run=None):
|
|
69
|
+
call = _Caller(run)
|
|
70
|
+
self._source = None
|
|
71
|
+
if enabled and device in ("", "default"):
|
|
72
|
+
# Through the module: tests swap quiet_audio._which/_run for guards.
|
|
73
|
+
if quiet_audio._which("wpctl"):
|
|
74
|
+
self._source = _WpctlSource(call)
|
|
75
|
+
elif quiet_audio._which("pactl"):
|
|
76
|
+
self._source = _PactlSource(call)
|
|
77
|
+
self._clipped_at: float | None = None # lowest level known to clip
|
|
78
|
+
self._quiet = 0
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def active(self) -> bool:
|
|
82
|
+
return self._source is not None
|
|
83
|
+
|
|
84
|
+
def after_recording(self, peak: float, clip_ratio: float, secs: float) -> Change | None:
|
|
85
|
+
"""Adjust after one recording; the change made, if any. Never raises."""
|
|
86
|
+
if self._source is None:
|
|
87
|
+
return None
|
|
88
|
+
try:
|
|
89
|
+
return self._adjust(peak, clip_ratio, secs)
|
|
90
|
+
except Exception: # noqa: BLE001 — a gain tweak must never break dictation
|
|
91
|
+
log.debug("mic gain adjustment failed", exc_info=True)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def _adjust(self, peak: float, clip_ratio: float, secs: float) -> Change | None:
|
|
95
|
+
if clip_ratio >= CLIP_RATIO:
|
|
96
|
+
self._quiet = 0
|
|
97
|
+
old = self._source.get()
|
|
98
|
+
if old is None:
|
|
99
|
+
return None
|
|
100
|
+
self._clipped_at = old if self._clipped_at is None else min(self._clipped_at, old)
|
|
101
|
+
new = max(FLOOR, round(old * STEP, 2))
|
|
102
|
+
return self._set(old, new, "clipping")
|
|
103
|
+
if secs < MIN_SPEECH_SECS or peak <= 0:
|
|
104
|
+
return None # too short to judge, or a dead/muted mic: not a gain problem
|
|
105
|
+
if peak >= QUIET_PEAK:
|
|
106
|
+
self._quiet = 0
|
|
107
|
+
return None
|
|
108
|
+
self._quiet += 1
|
|
109
|
+
if self._quiet < QUIET_STREAK:
|
|
110
|
+
return None
|
|
111
|
+
self._quiet = 0
|
|
112
|
+
old = self._source.get()
|
|
113
|
+
if old is None:
|
|
114
|
+
return None
|
|
115
|
+
top = CEILING if self._clipped_at is None else min(CEILING, round(self._clipped_at * STEP, 2))
|
|
116
|
+
new = min(top, round(old / STEP, 2))
|
|
117
|
+
return self._set(old, new, "quiet")
|
|
118
|
+
|
|
119
|
+
def _set(self, old: float, new: float, reason: str) -> Change | None:
|
|
120
|
+
if abs(new - old) < 0.005 or not self._source.set(new):
|
|
121
|
+
return None
|
|
122
|
+
log.info("Mic gain %s: input volume %.0f%% → %.0f%%", reason, old * 100, new * 100)
|
|
123
|
+
return Change(old, new, reason)
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.9.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|