talonvoice / talon

Issue Tracker for the main Talon app
85 stars 0 forks source link

Provide a supported callback or method for setting/ensuring microphone volume/gain #624

Open nriley opened 1 year ago

nriley commented 1 year ago

On Windows particularly, there seem to be lots of apps/system services that try to "intelligently" reset the microphone volume despite my best efforts to disable such features. This causes Talon recognition quality to substantially drop and sometimes I don't notice.

Here's what I've written to work around it.

from talon import actions, app, Context

import subprocess

ctx = Context()
ctx.matches = """
os: windows
"""

def fix_volume():
    subprocess.run([r"C:\Users\nriley\Apps\nircmdc.exe", "setsysvolume", "65535", "default_record"])

@ctx.action_class("speech")
class SpeechActions:
    def enable():
        actions.next()
        fix_volume()

app.register("ready", fix_volume)

# XXX this is deprecated, but I'm not sure of a replacement
from talon import microphone
microphone.manager.register("mic_change", lambda mic: fix_volume())

This seems to catch all the common places where I see microphone volume change - when switching speech on/off, when launching Talon and when turning my microphone on. The mic_change callback is deprecated so one option would be to provide a supported version, but if Talon could also give me an option to say "I never want the volume on this microphone to change from X" that'd be fine too.

Thanks!

nriley commented 1 year ago

talon.microphone was removed entirely in Talon 0.4, so the above code no longer works. The recommendation was to switch to polling instead; see below (change the interval to whatever works for you, and of course change the path to nircmdc.exe).

import subprocess

from talon import Context, actions, app, cron

ctx = Context()
ctx.matches = """
os: windows
"""

# Fluency Direct often adjusts the microphone volume (gain); Talon doesn't like that.
def fix_volume():
    subprocess.run(
        [r"C:\Users\nriley\Apps\nircmdc.exe", "setsysvolume", "65535", "default_record"]
    )

@ctx.action_class("speech")
class SpeechActions:
    def enable():
        actions.next()
        fix_volume()

def fix_volume_if_mic_changes():
    previous_microphone = actions.sound.active_microphone()

    def fix_volume_if_mic_changed():
        nonlocal previous_microphone

        if (microphone := actions.sound.active_microphone()) != previous_microphone:
            if microphone != 'None':
                fix_volume()

            previous_microphone = microphone

    fix_volume()
    cron.interval("2s", fix_volume_if_mic_changed)

if app.platform == "windows":
    app.register("ready", fix_volume_if_mic_changes)
mrob95 commented 1 year ago

I've used the pycaw python library to do the same thing:

from talon import cron

try:
    from comtypes import CLSCTX_ALL
    from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
    ENABLED = True
except ImportError:
    ENABLED = False

def main():
    devices = AudioUtilities.GetMicrophone()
    interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
    volume = interface.QueryInterface(IAudioEndpointVolume)
    current_level = volume.GetMasterVolumeLevel()
    _, hi, _ = volume.GetVolumeRange()
    if current_level < hi:
        print("Setting mic volume to max: was %s, now %s" % (current_level, hi))
        volume.SetMasterVolumeLevel(hi, None)
        print("Done setting mic volume to max: was %s, now %s" % (current_level, hi))

if ENABLED:
    cron.interval("1m", main)
phillco commented 3 months ago

Can I use this issue as a standin for a microphone.manager.register("mic_change", ... replacement, for places where we have to poll instead, or should I file a new one?