9

I live next to a big road. Having the window open at night is blessedly cool and, intermittently, very loud. How could I adjust the volume automatically, based on the built-in microphone input? If I set the volume so that I can hear speech in a movie while a car passes, it will be very loud at other times, and it feels very obnoxious towards the people nearby (outside and neighbors).

My system is Debian Buster, though I can probably get a generic solution to work. If no package is available that does this, a command to extract the loudness from the default microphone would already be helpful to script this.

Hauke Laging
  • 88,146
  • 18
  • 125
  • 174
Luc
  • 3,418
  • 3
  • 26
  • 37
  • 1
    You can use `sox` to calculate average loudness (IIRC there are other question on stackexchange about this), and `pacmd` to change the volume for Pulseaudio. – dirkt Jul 20 '17 at 21:57
  • 1
    @dirkt Thanks for the pointers! I found this question, I'll try to implement it tomorrow and report back (perhaps I can answer my own question): https://superuser.com/questions/306701/monitoring-the-microphone-level-with-a-command-line-tool-in-linux – Luc Jul 20 '17 at 23:35

1 Answers1

3

I've made a Python script to do the job. A remaining problem is that my laptop's microphone will, of course, also pick up its own speakers. I think 'echo cancellation' might be what I'm looking for, but I'd have no idea how to implement that myself. Using an external microphone might work though.

It is python 2 due to the python-alsaaudio dependency, unfortunately.

#!/usr/bin/env python

''' For noise cancellation:
$ pactl load-module module-echo-cancel
$ PULSE_PROP="filter.want=echo-cancel" ./this-script.py
'''

''' SETTINGS (you might want to keep presets for music and speech) '''
smoothing = 15 # Over how many samples should we compute?
step_size = 1 # maximum volume adjustment in percent points
# scale_xxx = (n, level) # At mic level n, scale to level% audio volume
scale_min = (4, 39)
scale_max = (19, 53)

''' CREDITS
https://stackoverflow.com/a/1937058
How get sound input from microphone in python, and process it on the fly?
Answer by jbochi

https://stackoverflow.com/a/10739764
How to programmatically change volume in Ubuntu
Answer by mata
'''

import alsaaudio, audioop, sys, os

bucket = [None for i in range(smoothing)]

inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE)

inp.setchannels(1)
inp.setrate(8000)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)

inp.setperiodsize(200)

print('Setting volume to minimum ({}%)'.format(scale_min[1]))
os.system('pactl set-sink-volume 0 {}%'.format(scale_min[1]))

i = 1
last_volume = scale_min[1]
while True:
    l, data = inp.read()
    if l:
        val = audioop.max(data, 2)
        bucket[i % smoothing] = val

        if i % smoothing == 0:
            m = min(bucket)
            miclvl = float(m) / 50.0

            if miclvl < scale_min[0]:
                scale = scale_min[1]
            elif miclvl > scale_max[0]:
                scale = scale_max[1]
            else:
                miclvl_range = scale_max[0] - scale_min[0]
                level_range = scale_max[1] - scale_min[1]
                scale = (miclvl - scale_min[0]) / miclvl_range * level_range + scale_min[1]

            scale = int(round(scale))
            step = max(min(scale - last_volume, step_size), -step_size)

            if step != 0:
                last_volume += step
                step = '+' + str(step) if step > 0 else str(step)
                os.system('pactl set-sink-volume 0 {}%'.format(step))

            miclvl = round(miclvl, 1)
            miclvlpacing = ' ' * (4 - len(str(miclvl)))
            stepspacing = ' ' * (2 - len(str(step)))
            sys.stdout.write('mic lvl {}{}  ideal scale {}%  adjust {}{}  now {}  '.format(
                miclvl, miclvlpacing, str(scale), step, stepspacing, last_volume))
            print(int(round(last_volume - scale_min[1])) * 'x')

        i += 1
Luc
  • 3,418
  • 3
  • 26
  • 37
  • 1
    Pulseaudio can do echo cancellation to some degree, see e.g. [here](https://www.reddit.com/r/linux/comments/2yqfqp/just_found_that_pulseaudio_have_noise/) and [here](https://arunraghavan.net/2016/05/improvements-to-pulseaudios-echo-cancellation/), google for more. If you want to do it yourself, you have to subtract the output signal from the input signal, time-delayed and with the right amplitude. Finding those two parameters automatically is the fun part.:-) (Google "correlation") – dirkt Jul 21 '17 at 09:20
  • @dirkt Thanks again! Pulseaudio's doesn't seem to work very well, though; especially at higher volume levels (>50%) it just breaks down, detects itself and keeps going up. I'll just have to go and grab a mic, it's not like they're expensive anyway :) – Luc Jul 22 '17 at 11:16