Skip to content

Instantly share code, notes, and snippets.

@bulletinmybeard
Last active October 26, 2024 17:07
Show Gist options
  • Save bulletinmybeard/b01a87af176821dc3fb995a3b1b7fced to your computer and use it in GitHub Desktop.
Save bulletinmybeard/b01a87af176821dc3fb995a3b1b7fced to your computer and use it in GitHub Desktop.
Automatic Call Detection & Volume Control with Python on macOS

Automatic Call Detection & Volume Control with Python on macOS

A Python script that automatically maintains consistent built-in microphone input volume during calls/meetings on macOS. It detects active audio sessions and adjusts the microphone level accordingly, ensuring the mic volume stays at an optimal level during calls.

Features

  • Automatic call/meeting detection through audio sampling
  • Smart microphone volume management
  • Continuous background monitoring
  • Detailed logging of volume adjustments and call status

Dependencies

Install the required Python packages using pip:

pip install sounddevice numpy

The Python Script

#!/usr/bin/env python3
import subprocess
import time
import logging
from datetime import datetime
import sounddevice as sd
import numpy as np


# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(message)s',
    handlers=[
        logging.FileHandler('mic_control.log'),
        logging.StreamHandler()
    ]
)


def set_mic_volume(volume):
    """Set the microphone input volume (0-100)."""
    try:
        cmd = f"osascript -e 'set volume input volume {volume}'"
        subprocess.run(cmd, shell=True, check=True)
        return True
    except subprocess.CalledProcessError as e:
        logging.error(f"Failed to set volume: {e}")
        return False


def get_mic_volume():
    """Get current microphone input volume."""
    try:
        cmd = "osascript -e 'input volume of (get volume settings)'"
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True)
        return int(result.stdout.strip())
    except subprocess.CalledProcessError as e:
        logging.error(f"Failed to get volume: {e}")
        return None


def is_audio_active(threshold=0.01, duration=1.0):
    """
    Check if there's significant audio activity on the microphone.

    Args:
        threshold: RMS threshold for considering audio as active
        duration: Duration in seconds to sample audio
    """
    try:
        # Get default input device
        device_info = sd.query_devices(kind='input')
        sample_rate = int(device_info['default_samplerate'])

        # Record audio for specified duration
        recording = sd.rec(
            int(duration * sample_rate),
            channels=1,
            dtype='float32',
            samplerate=sample_rate
        )
        sd.wait()

        # Calculate RMS value
        rms = np.sqrt(np.mean(recording**2))

        return rms > threshold

    except Exception as e:
        logging.error(f"Error detecting audio: {e}")
        return False


def detect_call_activity(audio_check_duration=5):
    """
    Detect if we're likely in a call by monitoring audio activity over time.
    Returns True if consistent audio activity is detected.
    """
    audio_samples = []

    # Take several samples over a period
    for _ in range(audio_check_duration):
        audio_samples.append(is_audio_active())
        time.sleep(1)

    # If we detect audio activity in at least 40% of samples,
    # we're probably in a call
    return sum(audio_samples) / len(audio_samples) >= 0.4


def main():
    target_volume = 80
    active_check_interval = 3  # seconds when audio activity detected
    idle_check_interval = 15   # seconds when no audio activity
    call_check_interval = 30   # seconds between full call detection checks

    logging.info("Starting microphone level controller with audio detection")

    last_call_check = 0
    in_call = False

    while True:
        current_time = time.time()

        # Periodically do a full call detection check
        if current_time - last_call_check > call_check_interval:
            in_call = detect_call_activity()
            last_call_check = current_time
            logging.info(f"Call status check: {'in call' if in_call else 'not in call'}")

        if in_call:
            current_volume = get_mic_volume()

            if current_volume is not None and current_volume != target_volume:
                logging.info(f"In call: Adjusting volume from {current_volume} to {target_volume}")
                set_mic_volume(target_volume)

            time.sleep(active_check_interval)
        else:
            time.sleep(idle_check_interval)


if __name__ == "__main__":
    main()

Example Log

2024-10-24 13:15:52,295 - Starting microphone level controller with audio detection
2024-10-24 13:16:03,483 - Call status check: in call
2024-10-24 13:16:07,231 - In call: Adjusting volume from 74 to 80
2024-10-24 13:16:13,948 - In call: Adjusting volume from 61 to 80
2024-10-24 13:16:20,709 - In call: Adjusting volume from 65 to 80
2024-10-24 13:16:35,104 - Call status check: not in call
2024-10-24 13:17:16,222 - Call status check: in call
2024-10-24 13:17:16,449 - In call: Adjusting volume from 38 to 80
2024-10-24 13:17:47,492 - Call status check: in call
2024-10-24 13:17:47,741 - In call: Adjusting volume from 67 to 80
2024-10-24 13:18:18,348 - Call status check: in call
2024-10-24 13:18:18,585 - In call: Adjusting volume from 39 to 80
2024-10-24 13:18:49,137 - Call status check: in call
2024-10-24 13:18:49,368 - In call: Adjusting volume from 65 to 80
2024-10-24 13:18:56,058 - In call: Adjusting volume from 54 to 80
2024-10-24 13:19:02,783 - In call: Adjusting volume from 52 to 80
2024-10-24 13:19:20,354 - Call status check: in call
2024-10-24 13:19:20,590 - In call: Adjusting volume from 42 to 80
2024-10-24 13:19:24,030 - In call: Adjusting volume from 67 to 80
2024-10-24 13:19:51,336 - Call status check: not in call
2024-10-24 13:20:32,464 - Call status check: not in call
2024-10-24 13:21:13,591 - Call status check: not in call
2024-10-24 13:21:54,726 - Call status check: not in call
2024-10-24 13:22:35,877 - Call status check: not in call
2024-10-24 13:23:17,015 - Call status check: not in call
2024-10-24 13:23:58,146 - Call status check: not in call
2024-10-24 13:24:39,274 - Call status check: not in call
2024-10-24 13:25:20,390 - Call status check: not in call
2024-10-24 13:26:01,510 - Call status check: in call
2024-10-24 13:26:01,756 - In call: Adjusting volume from 30 to 80
2024-10-24 13:26:05,225 - In call: Adjusting volume from 61 to 80
2024-10-24 13:26:08,693 - In call: Adjusting volume from 49 to 80
2024-10-24 13:26:18,638 - In call: Adjusting volume from 55 to 80
2024-10-24 13:26:32,981 - Call status check: in call
2024-10-24 13:26:33,217 - In call: Adjusting volume from 26 to 80
2024-10-24 13:26:36,666 - In call: Adjusting volume from 24 to 80
2024-10-24 13:26:40,113 - In call: Adjusting volume from 64 to 80
2024-10-24 13:26:43,556 - In call: Adjusting volume from 65 to 80
2024-10-24 13:26:50,238 - In call: Adjusting volume from 55 to 80
2024-10-24 13:27:04,636 - Call status check: in call
2024-10-24 13:27:04,887 - In call: Adjusting volume from 36 to 80
2024-10-24 13:27:08,326 - In call: Adjusting volume from 64 to 80
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment